diff --git a/ansible/roles/noble_authentik/README.md b/ansible/roles/noble_authentik/README.md index 16a72ab..9aee536 100644 --- a/ansible/roles/noble_authentik/README.md +++ b/ansible/roles/noble_authentik/README.md @@ -41,8 +41,8 @@ Network isolation is enforced at **DNS and the tunnel**, not inside Authentik. O **Two-Brand model:** -- **Lab Brand** — domain equals **`noble_authentik_host`**: use a **restricted authentication flow** so only operator groups (defaults: **`noble-admins`**, **`authentik Admins`**) can complete sign-in on that hostname. Your bootstrap / break-glass account must remain in one of those groups (see **`noble_authentik_ensure_admin_ui_access`** and **`configure_authentik.py`** group membership). -- **Public Brand(s)** — one Brand per FQDN in **`noble_authentik_ingress_extra_hosts`**: use the stock **`default-authentication-flow`** (or replace with your own flow slug via a forked blueprint later). Assign people to **`noble_authentik_blueprint_public_groups`** and/or **`noble_authentik_blueprint_nikflix_groups`** (defaults include **`nikflix-users`** / **`nikflix-admins`** for the Nikflix hostname); **`noble-admins`** / **`noble-editors`** remain for cluster / Argo / Grafana as today. +- **Lab Brand** — domain equals **`noble_authentik_host`**: restricted authentication flow (**`noble_authentik_blueprint_lab_flow_slug`**) so only operator groups (defaults: **`noble-admins`**, **`authentik Admins`**) can continue past identification; dedicated password stage with **`noble_authentik_blueprint_lab_password_failed_attempts`**, expression-based password strength (**length / character classes / optional zxcvbn**), and MFA stage **`noble-lab-authenticator-validate-strict`** with **`noble_authentik_blueprint_lab_mfa_not_configured_action`** (**`configure`** injects default TOTP setup when no device; **`deny`** blocks; **`skip`** matches stock). WebAuthn passwordless does **not** skip MFA on this flow. Your bootstrap / break-glass account must remain in one of those groups (see **`noble_authentik_ensure_admin_ui_access`** and **`configure_authentik.py`** group membership). +- **Public Brand(s)** — one Brand per FQDN in **`noble_authentik_ingress_extra_hosts`**: authentication flow **`noble_authentik_blueprint_public_auth_flow_slug`** (default **`noble-public-authentication-flow`**) mirrors stock **`default-authentication-flow`** (optional password / MFA skip for WebAuthn passwordless) so you can tune lab vs public independently. Assign people to **`noble_authentik_blueprint_public_groups`** and/or **`noble_authentik_blueprint_nikflix_groups`** (defaults include **`nikflix-users`** / **`nikflix-admins`** for the Nikflix hostname); **`noble-admins`** / **`noble-editors`** remain for cluster / Argo / Grafana as today. **OAuth note:** Redirect URIs and **`iss`** must stay consistent with the hostname clients use (internal issuer for in-cluster apps vs public issuer is a deliberate choice — avoid mixing both for the same app). @@ -51,11 +51,26 @@ Network isolation is enforced at **DNS and the tunnel**, not inside Authentik. O | Key | Purpose | | --- | --- | | **`10-noble-public-groups.yaml.j2`** | **`noble_authentik_blueprint_public_groups`** ∪ **`noble_authentik_blueprint_extra_directory_groups`** ∪ **`noble_authentik_blueprint_nikflix_groups`** → **Group** objects (see **Blueprint: directory groups**). | -| **`20-noble-lab-operator-authentication-flow.yaml.j2`** | Flow **`noble_authentik_blueprint_lab_flow_slug`** + expression policy **`noble_authentik_blueprint_operator_policy_name`** (allowed groups **`noble_authentik_blueprint_lab_operator_groups`**). | -| **`30-noble-brands-domain-split.yaml.j2`** | Brand for **`noble_authentik_host`** → lab flow; one Brand per **`noble_authentik_ingress_extra_hosts`** → default authentication. | +| **`20-noble-lab-operator-authentication-flow.yaml.j2`** | Flow **`noble_authentik_blueprint_lab_flow_slug`**: operator policy **`noble_authentik_blueprint_operator_policy_name`**, lab password/MFA tunables (see **`defaults/main.yml`**). | +| **`21-noble-public-authentication-flow.yaml.j2`** | Flow **`noble_authentik_blueprint_public_auth_flow_slug`** — public sign-in (same optional policies as stock default authentication). | +| **`22-noble-invitation-enrollment-flows.yaml.j2`** | Two **enrollment** flows + **Invitation** stages: public (**`noble_authentik_blueprint_public_invitation_enrollment_flow_slug`**) vs lab (**`noble_authentik_blueprint_lab_invitation_enrollment_flow_slug`**); see **Invitations** below. | +| **`30-noble-brands-domain-split.yaml.j2`** | Brand for **`noble_authentik_host`** → lab flow; one Brand per **`noble_authentik_ingress_extra_hosts`** → public flow slug above. | Tune titles via **`noble_authentik_blueprint_lab_brand_title`** and **`noble_authentik_blueprint_public_brand_title_prefix`**. After the worker applies blueprints, confirm **System → Brands** and **Flows** in the admin UI; fix any **`!Find`** failures if upstream default stage **names** change between Authentik versions. +#### Invitations (public vs lab) + +[Brands](https://docs.goauthentik.io/brands/) do **not** expose a default “enrollment” or “invitation” flow: onboarding is driven by **Directory → Invitations**, where each invitation row selects an **enrollment** flow. The link Authentik shows is: + +`https:///if/flow//?itoken=` + +Use **``** that matches the experience you want: + +- **Public / Nikflix** — an FQDN from **`noble_authentik_ingress_extra_hosts`**: use flow slug **`noble_authentik_blueprint_public_invitation_enrollment_flow_slug`** (default **`noble-public-invitation-enrollment`**). New users are added to **`noble_authentik_blueprint_public_invitation_user_group`** (default **`noble-public-users`**; override to **`nikflix-users`** if that is your only audience group). Tune **`noble_authentik_blueprint_public_invitation_user_type`** (**`external`** / **`internal`**) and **`noble_authentik_blueprint_public_invitation_user_path`** as needed. +- **Lab** — **`noble_authentik_host`** only when you intend to onboard someone who will later get **`noble_authentik_blueprint_lab_operator_groups`** access: use **`noble_authentik_blueprint_lab_invitation_enrollment_flow_slug`** (default **`noble-lab-invitation-enrollment`**). The blueprint creates **`noble_authentik_blueprint_lab_invitee_group_name`** (default **`noble-lab-invited`**) and assigns new enrollments there; **promote** people to **`noble-admins`** / **`authentik Admins`** (or your configured operator groups) in the admin UI when they should sign in on the lab URL. + +Blueprint **22** does **not** create sample **Invitation** rows (no placeholder emails). Create invitations in the UI after blueprints apply. For richer patterns (prefilled attributes, extra policies), see [Invitations](https://docs.goauthentik.io/users-sources/user/invitations/) and the upstream example blueprint **`flows-invitation-enrollment.yaml`** ([download](https://goauthentik.io/blueprints/example/flows-invitation-enrollment.yaml)). Password strength for enrollment prompts is **not** duplicated from the lab **authentication** flow here; add **Prompt** validation policies or a dedicated policy if you need parity. + #### Blueprint: directory groups Three inventory lists are concatenated **in this order** into **`10-noble-public-groups.yaml.j2`**: diff --git a/ansible/roles/noble_authentik/defaults/main.yml b/ansible/roles/noble_authentik/defaults/main.yml index 440086c..313d07f 100644 --- a/ansible/roles/noble_authentik/defaults/main.yml +++ b/ansible/roles/noble_authentik/defaults/main.yml @@ -68,6 +68,37 @@ noble_authentik_blueprint_lab_operator_groups: - authentik Admins noble_authentik_blueprint_lab_brand_title: Noble lab (operators) noble_authentik_blueprint_public_brand_title_prefix: Noble public +# Public hostname Brand(s) → dedicated authentication flow (**21-noble-public-…** blueprint). +noble_authentik_blueprint_public_auth_flow_slug: noble-public-authentication-flow +# Lab flow: password stage (**failed_attempts_before_cancel**) and strength checks (expression policy; skips when **password** not yet in request context). +noble_authentik_blueprint_lab_password_failed_attempts: 3 +noble_authentik_blueprint_lab_password_policy_length_min: 16 +noble_authentik_blueprint_lab_password_policy_amount_uppercase: 1 +noble_authentik_blueprint_lab_password_policy_amount_lowercase: 1 +noble_authentik_blueprint_lab_password_policy_amount_digits: 1 +noble_authentik_blueprint_lab_password_policy_amount_symbols: 1 +noble_authentik_blueprint_lab_password_policy_check_zxcvbn: true +noble_authentik_blueprint_lab_password_policy_zxcvbn_score_threshold: 3 +noble_authentik_blueprint_lab_password_policy_error_message: >- + Lab password policy: at least 16 characters with upper, lower, digit, symbol, and sufficient strength. +# Lab MFA when user has no compatible device: **skip** (like stock), **deny** (block), **configure** (TOTP setup via default stage). +noble_authentik_blueprint_lab_mfa_not_configured_action: configure +# Invitation-based **enrollment** flows (blueprint **22**). Brands do not select enrollment; each **Invitation** picks a flow. +# Link shape: **`https:///if/flow//?itoken=`** — use the **public** hostname for **`noble_authentik_blueprint_public_invitation_enrollment_flow_slug`** invites. +noble_authentik_blueprint_public_invitation_enrollment_flow_slug: noble-public-invitation-enrollment +noble_authentik_blueprint_lab_invitation_enrollment_flow_slug: noble-lab-invitation-enrollment +noble_authentik_blueprint_public_invitation_flow_name: Noble public invitation enrollment +noble_authentik_blueprint_public_invitation_flow_title: Complete your signup +noble_authentik_blueprint_lab_invitation_flow_name: Noble lab invitation enrollment +noble_authentik_blueprint_lab_invitation_flow_title: Lab access — complete enrollment +# **User write** for public invites: must match an existing **Group** name from **`10-noble-public-groups`** (default **`noble-public-users`**; use **`nikflix-users`** if you only maintain Nikflix groups). +noble_authentik_blueprint_public_invitation_user_group: noble-public-users +noble_authentik_blueprint_public_invitation_user_type: external +noble_authentik_blueprint_public_invitation_user_path: users/noble/public +# Lab invites: blueprint creates **`noble_authentik_blueprint_lab_invitee_group_name`**; add members to **`noble_authentik_blueprint_lab_operator_groups`** manually when they should use the lab URL. +noble_authentik_blueprint_lab_invitee_group_name: noble-lab-invited +noble_authentik_blueprint_lab_invitation_user_type: internal +noble_authentik_blueprint_lab_invitation_user_path: users/noble/lab noble_authentik_oauth2_proxy_host: oauth2.apps.noble.lab.pcenicni.dev diff --git a/ansible/roles/noble_authentik/tasks/main.yml b/ansible/roles/noble_authentik/tasks/main.yml index 1211dbf..c1e0758 100644 --- a/ansible/roles/noble_authentik/tasks/main.yml +++ b/ansible/roles/noble_authentik/tasks/main.yml @@ -90,11 +90,23 @@ + (noble_authentik_blueprint_nikflix_groups | default([])) | length) > 0 - noble_authentik_blueprint_lab_operator_groups | default([]) | length > 0 - noble_authentik_blueprint_lab_flow_slug | default('') | trim | length > 0 + - noble_authentik_blueprint_public_auth_flow_slug | default('') | trim | length > 0 + - (noble_authentik_blueprint_lab_mfa_not_configured_action | default('configure') | trim | lower) + in ['skip', 'deny', 'configure'] + - noble_authentik_blueprint_public_invitation_enrollment_flow_slug | default('') | trim | length > 0 + - noble_authentik_blueprint_lab_invitation_enrollment_flow_slug | default('') | trim | length > 0 + - noble_authentik_blueprint_public_invitation_user_group | default('') | trim | length > 0 + - noble_authentik_blueprint_lab_invitee_group_name | default('') | trim | length > 0 + - (noble_authentik_blueprint_public_invitation_user_type | default('external') | trim | lower) in ['external', 'internal'] + - (noble_authentik_blueprint_lab_invitation_user_type | default('internal') | trim | lower) in ['external', 'internal'] fail_msg: >- When noble_authentik_blueprints_enabled is true, set at least one entry across noble_authentik_blueprint_public_groups, noble_authentik_blueprint_extra_directory_groups, and/or noble_authentik_blueprint_nikflix_groups, - plus noble_authentik_blueprint_lab_operator_groups (non-empty) and noble_authentik_blueprint_lab_flow_slug. + plus noble_authentik_blueprint_lab_operator_groups (non-empty), noble_authentik_blueprint_lab_flow_slug, + noble_authentik_blueprint_public_auth_flow_slug, noble_authentik_blueprint_lab_mfa_not_configured_action + (skip, deny, or configure), invitation enrollment flow slugs, noble_authentik_blueprint_public_invitation_user_group, + noble_authentik_blueprint_lab_invitee_group_name, and invitation user_type values (external or internal). See ansible/roles/noble_authentik/defaults/main.yml and README. when: noble_authentik_blueprints_enabled | default(false) | bool @@ -118,6 +130,8 @@ loop: - 10-noble-public-groups.yaml - 20-noble-lab-operator-authentication-flow.yaml + - 21-noble-public-authentication-flow.yaml + - 22-noble-invitation-enrollment-flows.yaml - 30-noble-brands-domain-split.yaml when: noble_authentik_blueprints_enabled | default(false) | bool diff --git a/ansible/roles/noble_authentik/templates/blueprints/20-noble-lab-operator-authentication-flow.yaml.j2 b/ansible/roles/noble_authentik/templates/blueprints/20-noble-lab-operator-authentication-flow.yaml.j2 index 7d96c8b..3646db7 100644 --- a/ansible/roles/noble_authentik/templates/blueprints/20-noble-lab-operator-authentication-flow.yaml.j2 +++ b/ansible/roles/noble_authentik/templates/blueprints/20-noble-lab-operator-authentication-flow.yaml.j2 @@ -1,5 +1,4 @@ -# Noble — authentication flow for the **lab** hostname Brand: only members of operator groups may continue. -# Reuses default identification / password / MFA / login stages; adds a policy on the password stage binding. +# Noble — **lab** hostname authentication: operator-only, stricter password checks, MFA required (no WebAuthn-PWL skip). version: 1 metadata: name: noble-lab-operator-authentication @@ -11,6 +10,28 @@ entries: identifiers: name: Default - Password change flow required: false + - model: authentik_stages_password.passwordstage + id: noble-lab-password-stage + identifiers: + name: noble-lab-authentication-password + attrs: + backends: + - authentik.core.auth.InbuiltBackend + - authentik.sources.kerberos.auth.KerberosBackend + - authentik.sources.ldap.auth.LDAPBackend + - authentik.core.auth.TokenBackend + configure_flow: !Find [authentik_flows.flow, [slug, default-password-change]] + failed_attempts_before_cancel: {{ noble_authentik_blueprint_lab_password_failed_attempts | int }} + - model: authentik_stages_authenticator_validate.authenticatorvalidatestage + id: noble-lab-authenticator-validate-strict + identifiers: + name: noble-lab-authenticator-validate-strict + attrs: + not_configured_action: {{ noble_authentik_blueprint_lab_mfa_not_configured_action | trim | lower | to_json }} +{% if noble_authentik_blueprint_lab_mfa_not_configured_action | trim | lower == 'configure' %} + configuration_stages: + - !Find [authentik_stages_authenticator_totp.authenticatortotpstage, [name, default-authenticator-totp-setup]] +{% endif %} - model: authentik_flows.flow id: flow identifiers: @@ -30,7 +51,7 @@ entries: model: authentik_flows.flowstagebinding identifiers: order: 20 - stage: !Find [authentik_stages_password.passwordstage, [name, default-authentication-password]] + stage: !KeyOf noble-lab-password-stage target: !KeyOf flow attrs: re_evaluate_policies: true @@ -38,7 +59,7 @@ entries: model: authentik_flows.flowstagebinding identifiers: order: 30 - stage: !Find [authentik_stages_authenticator_validate.authenticatorvalidatestage, [name, default-authentication-mfa-validation]] + stage: !KeyOf noble-lab-authenticator-validate-strict target: !KeyOf flow - model: authentik_flows.flowstagebinding identifiers: @@ -56,15 +77,43 @@ entries: return True return not hasattr(flow_plan.context.get("pending_user"), "backend") - model: authentik_policies_expression.expressionpolicy - id: noble-lab-authenticator-validate-optional + id: noble-lab-password-strength identifiers: - name: noble-lab-authenticator-validate-optional + name: noble-lab-password-strength attrs: expression: | - flow_plan = request.context.get("flow_plan") - if not flow_plan: + import re + _zxc = {{ noble_authentik_blueprint_lab_password_policy_check_zxcvbn | bool }} + _zxc_thr = {{ noble_authentik_blueprint_lab_password_policy_zxcvbn_score_threshold | int }} + pwd = request.context.get("password") + if not pwd: return True - return not (flow_plan.context.get("auth_method") == "auth_webauthn_pwl") + msg = {{ noble_authentik_blueprint_lab_password_policy_error_message | trim | to_json }} + if len(pwd) < {{ noble_authentik_blueprint_lab_password_policy_length_min | int }}: + ak_message(msg) + return False + if len(re.findall(r"[A-Z]", pwd)) < {{ noble_authentik_blueprint_lab_password_policy_amount_uppercase | int }}: + ak_message(msg) + return False + if len(re.findall(r"[a-z]", pwd)) < {{ noble_authentik_blueprint_lab_password_policy_amount_lowercase | int }}: + ak_message(msg) + return False + if len(re.findall(r"[0-9]", pwd)) < {{ noble_authentik_blueprint_lab_password_policy_amount_digits | int }}: + ak_message(msg) + return False + sym = sum(1 for c in pwd if (not c.isalnum()) and (not c.isspace())) + if sym < {{ noble_authentik_blueprint_lab_password_policy_amount_symbols | int }}: + ak_message(msg) + return False + if _zxc: + try: + from zxcvbn import zxcvbn + if zxcvbn(pwd[:72])["score"] <= _zxc_thr: + ak_message("Password is too weak for the lab policy.") + return False + except Exception: + pass + return True - model: authentik_policies_expression.expressionpolicy id: noble-lab-operators-only identifiers: @@ -94,8 +143,6 @@ entries: failure_result: true - model: authentik_policies.policybinding identifiers: - order: 10 - target: !KeyOf noble-lab-authenticator-binding - policy: !KeyOf noble-lab-authenticator-validate-optional - attrs: - failure_result: true + order: 15 + target: !KeyOf noble-lab-password-binding + policy: !KeyOf noble-lab-password-strength diff --git a/ansible/roles/noble_authentik/templates/blueprints/21-noble-public-authentication-flow.yaml.j2 b/ansible/roles/noble_authentik/templates/blueprints/21-noble-public-authentication-flow.yaml.j2 new file mode 100644 index 0000000..9360f61 --- /dev/null +++ b/ansible/roles/noble_authentik/templates/blueprints/21-noble-public-authentication-flow.yaml.j2 @@ -0,0 +1,80 @@ +# Noble — **public** hostname(s): same behaviour as stock **default-authentication-flow** (optional password / MFA skip for WebAuthn PWL), isolated slug for Brand binding. +version: 1 +metadata: + name: noble-public-authentication-flow + labels: + blueprints.goauthentik.io/instantiate: "true" +entries: + - model: authentik_blueprints.metaapplyblueprint + attrs: + identifiers: + name: Default - Password change flow + required: false + - model: authentik_flows.flow + id: flow + identifiers: + slug: {{ noble_authentik_blueprint_public_auth_flow_slug | trim | to_json }} + attrs: + name: Noble public sign-in + title: Sign in + designation: authentication + authentication: none + - id: noble-public-identification-binding + model: authentik_flows.flowstagebinding + identifiers: + order: 10 + stage: !Find [authentik_stages_identification.identificationstage, [name, default-authentication-identification]] + target: !KeyOf flow + - id: noble-public-password-binding + model: authentik_flows.flowstagebinding + identifiers: + order: 20 + stage: !Find [authentik_stages_password.passwordstage, [name, default-authentication-password]] + target: !KeyOf flow + attrs: + re_evaluate_policies: true + - id: noble-public-authenticator-binding + model: authentik_flows.flowstagebinding + identifiers: + order: 30 + stage: !Find [authentik_stages_authenticator_validate.authenticatorvalidatestage, [name, default-authentication-mfa-validation]] + target: !KeyOf flow + - model: authentik_flows.flowstagebinding + identifiers: + order: 100 + stage: !Find [authentik_stages_user_login.userloginstage, [name, default-authentication-login]] + target: !KeyOf flow + - model: authentik_policies_expression.expressionpolicy + id: noble-public-password-optional + identifiers: + name: noble-public-password-optional + attrs: + expression: | + flow_plan = request.context.get("flow_plan") + if not flow_plan: + return True + return not hasattr(flow_plan.context.get("pending_user"), "backend") + - model: authentik_policies_expression.expressionpolicy + id: noble-public-authenticator-validate-optional + identifiers: + name: noble-public-authenticator-validate-optional + attrs: + expression: | + flow_plan = request.context.get("flow_plan") + if not flow_plan: + return True + return not (flow_plan.context.get("auth_method") == "auth_webauthn_pwl") + - model: authentik_policies.policybinding + identifiers: + order: 10 + target: !KeyOf noble-public-password-binding + policy: !KeyOf noble-public-password-optional + attrs: + failure_result: true + - model: authentik_policies.policybinding + identifiers: + order: 10 + target: !KeyOf noble-public-authenticator-binding + policy: !KeyOf noble-public-authenticator-validate-optional + attrs: + failure_result: true diff --git a/ansible/roles/noble_authentik/templates/blueprints/22-noble-invitation-enrollment-flows.yaml.j2 b/ansible/roles/noble_authentik/templates/blueprints/22-noble-invitation-enrollment-flows.yaml.j2 new file mode 100644 index 0000000..5f77ee4 --- /dev/null +++ b/ansible/roles/noble_authentik/templates/blueprints/22-noble-invitation-enrollment-flows.yaml.j2 @@ -0,0 +1,227 @@ +# Noble — two **enrollment** flows (public vs lab) with separate **Invitation** stages (invitation token required). +# Create rows under **Directory → Invitations** in the admin UI and pick the matching flow; share links with the +# correct **Host** so the right Brand applies. Does **not** ship example **Invitation** objects (no prefilled emails). +version: 1 +metadata: + name: noble-invitation-enrollment-flows + labels: + blueprints.goauthentik.io/instantiate: "true" +entries: + - model: authentik_core.group + id: noble-lab-invited-group + identifiers: + name: {{ noble_authentik_blueprint_lab_invitee_group_name | trim | to_json }} + attrs: + is_superuser: false + attributes: + "noble.ak/audience": lab + "noble.ak/role": lab-invited + + - model: authentik_flows.flow + id: noble-inv-flow-public + identifiers: + slug: {{ noble_authentik_blueprint_public_invitation_enrollment_flow_slug | trim | to_json }} + attrs: + name: {{ noble_authentik_blueprint_public_invitation_flow_name | trim | to_json }} + title: {{ noble_authentik_blueprint_public_invitation_flow_title | trim | to_json }} + designation: enrollment + authentication: require_unauthenticated + + - model: authentik_flows.flow + id: noble-inv-flow-lab + identifiers: + slug: {{ noble_authentik_blueprint_lab_invitation_enrollment_flow_slug | trim | to_json }} + attrs: + name: {{ noble_authentik_blueprint_lab_invitation_flow_name | trim | to_json }} + title: {{ noble_authentik_blueprint_lab_invitation_flow_title | trim | to_json }} + designation: enrollment + authentication: require_unauthenticated + + - model: authentik_stages_invitation.invitationstage + id: noble-inv-stage-public + identifiers: + name: noble-invitation-enrollment-invitation-public + attrs: + continue_flow_without_invitation: false + + - model: authentik_stages_invitation.invitationstage + id: noble-inv-stage-lab + identifiers: + name: noble-invitation-enrollment-invitation-lab + attrs: + continue_flow_without_invitation: false + + - id: noble-inv-prompt-field-username + model: authentik_stages_prompt.prompt + identifiers: + name: noble-inv-enroll-field-username + attrs: + field_key: username + label: Username + type: username + required: true + placeholder: Username + placeholder_expression: false + order: 0 + + - id: noble-inv-prompt-field-password + model: authentik_stages_prompt.prompt + identifiers: + name: noble-inv-enroll-field-password + attrs: + field_key: password + label: Password + type: password + required: true + placeholder: Password + placeholder_expression: false + order: 1 + + - id: noble-inv-prompt-field-password-repeat + model: authentik_stages_prompt.prompt + identifiers: + name: noble-inv-enroll-field-password-repeat + attrs: + field_key: password_repeat + label: Password (repeat) + type: password + required: true + placeholder: Password (repeat) + placeholder_expression: false + order: 2 + + - id: noble-inv-prompt-field-name + model: authentik_stages_prompt.prompt + identifiers: + name: noble-inv-enroll-field-name + attrs: + field_key: name + label: Name + type: text + required: true + placeholder: Name + placeholder_expression: false + order: 0 + + - id: noble-inv-prompt-field-email + model: authentik_stages_prompt.prompt + identifiers: + name: noble-inv-enroll-field-email + attrs: + field_key: email + label: Email + type: email + required: true + placeholder: Email + placeholder_expression: false + order: 1 + + - id: noble-inv-prompt-stage-credentials + model: authentik_stages_prompt.promptstage + identifiers: + name: noble-inv-enroll-prompt-credentials + attrs: + fields: + - !KeyOf noble-inv-prompt-field-username + - !KeyOf noble-inv-prompt-field-password + - !KeyOf noble-inv-prompt-field-password-repeat + + - id: noble-inv-prompt-stage-details + model: authentik_stages_prompt.promptstage + identifiers: + name: noble-inv-enroll-prompt-details + attrs: + fields: + - !KeyOf noble-inv-prompt-field-name + - !KeyOf noble-inv-prompt-field-email + + - id: noble-inv-user-write-public + model: authentik_stages_user_write.userwritestage + identifiers: + name: noble-inv-enroll-user-write-public + attrs: + user_creation_mode: always_create + user_type: {{ noble_authentik_blueprint_public_invitation_user_type | trim | lower | to_json }} + user_path_template: {{ noble_authentik_blueprint_public_invitation_user_path | trim | to_json }} + create_users_group: !Find [authentik_core.group, [name, {{ noble_authentik_blueprint_public_invitation_user_group | trim | to_json }}]] + + - id: noble-inv-user-write-lab + model: authentik_stages_user_write.userwritestage + identifiers: + name: noble-inv-enroll-user-write-lab + attrs: + user_creation_mode: always_create + user_type: {{ noble_authentik_blueprint_lab_invitation_user_type | trim | lower | to_json }} + user_path_template: {{ noble_authentik_blueprint_lab_invitation_user_path | trim | to_json }} + create_users_group: !KeyOf noble-lab-invited-group + + - id: noble-inv-user-login + model: authentik_stages_user_login.userloginstage + identifiers: + name: noble-inv-enroll-user-login + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-public + stage: !KeyOf noble-inv-stage-public + order: 5 + attrs: + evaluate_on_plan: true + re_evaluate_policies: true + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-public + stage: !KeyOf noble-inv-prompt-stage-credentials + order: 10 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-public + stage: !KeyOf noble-inv-prompt-stage-details + order: 15 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-public + stage: !KeyOf noble-inv-user-write-public + order: 20 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-public + stage: !KeyOf noble-inv-user-login + order: 100 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-lab + stage: !KeyOf noble-inv-stage-lab + order: 5 + attrs: + evaluate_on_plan: true + re_evaluate_policies: true + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-lab + stage: !KeyOf noble-inv-prompt-stage-credentials + order: 10 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-lab + stage: !KeyOf noble-inv-prompt-stage-details + order: 15 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-lab + stage: !KeyOf noble-inv-user-write-lab + order: 20 + + - model: authentik_flows.flowstagebinding + identifiers: + target: !KeyOf noble-inv-flow-lab + stage: !KeyOf noble-inv-user-login + order: 100 diff --git a/ansible/roles/noble_authentik/templates/blueprints/30-noble-brands-domain-split.yaml.j2 b/ansible/roles/noble_authentik/templates/blueprints/30-noble-brands-domain-split.yaml.j2 index 8c995f7..c308dbe 100644 --- a/ansible/roles/noble_authentik/templates/blueprints/30-noble-brands-domain-split.yaml.j2 +++ b/ansible/roles/noble_authentik/templates/blueprints/30-noble-brands-domain-split.yaml.j2 @@ -1,4 +1,4 @@ -# Noble — Brands so **Host** selects authentication flow: lab hostname → operator-only flow; extra hosts → default login. +# Noble — Brands so **Host** selects authentication flow: lab hostname → operator-only hardened flow; extra hosts → public flow (**21**). version: 1 metadata: name: noble-brands-domain-split @@ -21,7 +21,7 @@ entries: attrs: default: false title: {{ ((noble_authentik_blueprint_public_brand_title_prefix | default('Noble public')) ~ ' (' ~ (host | trim) ~ ')') | to_json }} - flow_authentication: !Find [authentik_flows.flow, [slug, default-authentication-flow]] + flow_authentication: !Find [authentik_flows.flow, [slug, {{ noble_authentik_blueprint_public_auth_flow_slug | trim | to_json }}]] flow_invalidation: !Find [authentik_flows.flow, [slug, default-invalidation-flow]] flow_user_settings: !Find [authentik_flows.flow, [slug, default-user-settings-flow]] {% endfor %}