diff --git a/CLAUDE.md b/CLAUDE.md index e9e76f7..b852a9d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -422,22 +422,49 @@ existing login page. Reuse `_authelia_provision_oidc_client()` (guarded by instead of duplicating Authelia's client-secret-generation/config-patching logic again. -**Scoping a domain to specific users instead of every Authelia user.** -By default, any domain with an `access_control` rule at all is reachable by -every Authelia user (the existing catch-all `*.${AUTHELIA_DOMAIN}` rule). -`services/authelia.sh`'s `_authelia_scope_access(SERVICE_ID, DOMAIN)` is a -generic, reusable opt-in on top of that — call it right after *any* service -finishes being protected by Authelia, forward_auth gate or native OIDC -alike (it only cares about the domain, not the gating mechanism; see -`_gitea_offer_authelia_sso()` for the reference caller). Asks whether -access should stay universal or be scoped to specific usernames; if scoped, -creates a dedicated `-only` group, adds every listed username -to it (creating accounts on the fly via +**Internal vs. outside access — named, reusable groups, not one group per +service.** By default, any domain with an `access_control` rule at all is +"internal": reachable by every Authelia user (the existing catch-all +`*.${AUTHELIA_DOMAIN}` rule) — admins included automatically, since the +admin-bypass rule (below) always outranks it anyway. `services/authelia.sh`'s +`_authelia_scope_access(SERVICE_ID, DOMAIN)` is the generic, reusable opt-in +on top of that for "outside access" — call it right after *any* service +finishes being protected by Authelia, forward_auth gate or native OIDC alike +(it only cares about the domain, not the gating mechanism; see +`_gitea_offer_authelia_sso()` for the reference caller). Asks "Internal +(default) or Outside access", and if outside access, lets the admin pick an +*existing* named group (by number, so e.g. "customer1" can be attached to a +second, third, unrelated site later) or type a new one — `service_id` is +only the suggested default name, never forced. Creates the group if new +(adding every listed username to it, creating accounts on the fly via `_authelia_create_user_noninteractive()` for names that don't exist yet, -printing their temp password), and inserts two rules *above* the general -catch-all — allow that group on this domain, deny that group on every -other protected domain. Idempotent: reruns against an already-scoped -domain just report the existing group instead of duplicating rules. +printing their temp password) and inserts two rules *above* the general +catch-all but *below* the admin-bypass rule — allow that group on this +domain, deny that group on every other protected domain. The already-scoped +check is keyed to the (domain, group) pair, not the group name alone, so +reusing a group on a second site correctly adds that site's own rule instead +of a false "already scoped" no-op (a real bug in an earlier version of this +function, since fixed). + +Three more menu options round this out: **13** backfills the admin-bypass +rule (below) onto any apex domain missing it; **14** renames a group +everywhere it's referenced (rules + every member); **15** lists every +group's sites and members in one place; **16** is the reverse of the +scoping prompt's own member-picker — pick a group first, then toggle which +users are in it, for adding members without re-touching a site. + +**Admins always match first, on every domain — old sites and new.** +`_authelia_ensure_admin_bypass(config_file, domain)` inserts +`- domain: "*.${domain}" / subject: "group:admins" / policy: two_factor` as +literally the first rule under `rules:`, and every insertion point that +adds new rules (`add_authelia_domain`, `_authelia_scope_access`) inserts +*after* this block rather than at the literal top of `rules:`, so a later +scoping action can never accidentally outrank it. Without this, a group's +deny-elsewhere rule (above) would deny an admin who's ever added to that +group on every OTHER domain — this rule exists specifically so that can't +happen. `install_authelia()`/`add_authelia_domain()` bake it in for +anything created from here on; menu option 13 backfills it onto an +instance that predates the feature. Guard every cross-file call with `declare -F`, same convention as the OIDC helper above — a service can run standalone with authelia.sh never sourced. diff --git a/services/authelia.sh b/services/authelia.sh index c6aa117..79b832d 100644 --- a/services/authelia.sh +++ b/services/authelia.sh @@ -1689,11 +1689,11 @@ _authelia_scope_access() { echo "" echo " Who should be able to reach $domain via Authelia?" - echo " 0) Native — your own users, no extra restriction (default — same" + echo " 0) Internal — your own users, no extra restriction (default — same" echo " access as everything else)" echo " 1) Outside access — a named group of specific users only" local scope_choice="" - prompt_text " Choice [0 for native, 1 for outside access]:" "0" scope_choice + prompt_text " Choice [0 for internal, 1 for outside access]:" "0" scope_choice [ "$scope_choice" = "1" ] || return 0 local -a existing_groups @@ -1936,7 +1936,7 @@ _authelia_report_groups() { local -a groups mapfile -t groups < <(_authelia_list_scoped_groups "$users_file") if [ "${#groups[@]}" -eq 0 ]; then - log_info "No outside-access groups exist yet — every site is native (open to any Authelia user, admins always included)." + log_info "No outside-access groups exist yet — every site is internal (open to any Authelia user, admins always included)." return 0 fi @@ -1995,7 +1995,7 @@ _authelia_manage_group_membership() { if [ "${#groups[@]}" -eq 0 ]; then log_info "No outside-access groups exist yet. A group is created the first time you" log_info "protect a site (option 10, or a service's own \"Add Sign in with Authelia\"" - log_info "offer) and choose \"Outside access\" instead of \"Native\" — name it there" + log_info "offer) and choose \"Outside access\" instead of \"Internal\" — name it there" log_info "(e.g. \"customer1\"), and it'll show up here afterward to manage its members." return 0 fi @@ -2147,7 +2147,7 @@ _authelia_report_access_scope() { fi echo "" - echo " Native (universal — every protected domain, no outside-access group):" + echo " Internal (universal — every protected domain, no outside-access group):" local -a universal=() restricted=() local u start_end start end groups_in_range for u in "${all_users[@]}"; do