diff --git a/sim/cluster0/lab-seeds/backend/scripts/ensure_auth_email_bearer.php b/sim/cluster0/lab-seeds/backend/scripts/ensure_auth_email_bearer.php new file mode 100755 index 0000000..b29228e --- /dev/null +++ b/sim/cluster0/lab-seeds/backend/scripts/ensure_auth_email_bearer.php @@ -0,0 +1,28 @@ +#!/usr/bin/env php83 +execute([$event, $bearerId, $slug, $ip, $detail]); } + + /** Outbound mail/SMS — shorten our URLs; fall back to long URL on failure. */ + public static function shortenForOutbound(string $longUrl, int $ttlSeconds = 86400): string { + $longUrl = trim($longUrl); + if ($longUrl === '' || !UrlShortenerDatabase::enabled() || !UrlShortenerDatabase::ping()) { + return $longUrl; + } + $bearerId = self::resolveOutboundBearerId(); + if ($bearerId <= 0) { + return $longUrl; + } + $mint = self::createLink($bearerId, $longUrl, $ttlSeconds); + if (empty($mint['ok']) || empty($mint['short_url'])) { + error_log('ShortLinksRepository: outbound shorten failed'); + return $longUrl; + } + return (string) $mint['short_url']; + } + + private static function resolveOutboundBearerId(): int { + $cfgId = (int) cfg('url_shortener.auth_bearer_id', 0); + if ($cfgId > 0 && self::findBearerById($cfgId) !== null) { + return $cfgId; + } + foreach (self::listBearers() as $b) { + if (!empty($b['revoked_at'])) { + continue; + } + $label = strtolower((string) ($b['label'] ?? '')); + if (str_contains($label, 'auth-email') || str_contains($label, 'auth_email')) { + return (int) ($b['id'] ?? 0); + } + } + foreach (self::listBearers() as $b) { + if (empty($b['revoked_at']) && !empty($b['can_temporary'])) { + return (int) ($b['id'] ?? 0); + } + } + return 0; + } } diff --git a/sim/cluster0/populate_lab_setup.sh b/sim/cluster0/populate_lab_setup.sh index 1ae730b..c4ac86b 100644 --- a/sim/cluster0/populate_lab_setup.sh +++ b/sim/cluster0/populate_lab_setup.sh @@ -73,6 +73,10 @@ run_phase_app() { run_script "$ROOT/scripts/deploy-ac-url-shortener.sh" if [ "${COMPOSE_RUNTIME:-0}" = "1" ]; then run_script "$ROOT/scripts/compose-lab-backend.sh" + if is_primary_node; then + run_script "$ROOT/scripts/ensure-auth-email-bearer.sh" || journal_warn auth_bearer "mint failed" + run_script "$ROOT/scripts/compose-lab-backend.sh" + fi fi if [ "${POSTGRES_SEMI_ENABLE:-0}" = "1" ] && is_primary_node; then run_script "$ROOT/scripts/deploy-postgres-semi.sh" || journal_warn postgres_semi "install failed (non-fatal)" diff --git a/sim/cluster0/scripts/compose-lab-backend.sh b/sim/cluster0/scripts/compose-lab-backend.sh index 8b76555..4470db2 100755 --- a/sim/cluster0/scripts/compose-lab-backend.sh +++ b/sim/cluster0/scripts/compose-lab-backend.sh @@ -122,6 +122,8 @@ if [ -n "$_MSMTP_PASS" ]; then MAIL_FROM="${MAIL_FROM:-Android Cast }" fi unset _MSMTP_PASS +AUTH_BEARER_ID="$(read_secret URL_SHORTENER_AUTH_BEARER_ID 2>/dev/null || true)" +[ -n "$AUTH_BEARER_ID" ] || AUTH_BEARER_ID="${URL_SHORTENER_AUTH_BEARER_ID:-0}" mkdir -p "${COMPOSED}/config" log "write composed config.php base_path=${ISSUES_BASE}" cat > "${COMPOSED}/config/config.php" < [ 'enabled' => true, 'public_base' => '${SHORT_LINKS_PUBLIC_BASE}', + 'auth_bearer_id' => ${AUTH_BEARER_ID:-0}, 'db' => [ 'mysql' => [ 'host' => '${DB_HOST}', diff --git a/sim/cluster0/scripts/ensure-auth-email-bearer.sh b/sim/cluster0/scripts/ensure-auth-email-bearer.sh new file mode 100755 index 0000000..3482ad4 --- /dev/null +++ b/sim/cluster0/scripts/ensure-auth-email-bearer.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Mint auth-email bearer for outbound mail short links; store id in secrets + config. +set -eu +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +# shellcheck source=/dev/null +. "$ROOT/scripts/lib/common.sh" +load_cluster_env + +COMPOSED="${COMPOSE_BACKEND:-/var/www/ac/composed/backend}" +SCRIPT="${COMPOSED}/scripts/ensure_auth_email_bearer.php" +[ -f "$SCRIPT" ] || SCRIPT="${ROOT}/lab-seeds/backend/scripts/ensure_auth_email_bearer.php" +[ -f "$SCRIPT" ] || die "ensure_auth_email_bearer.php missing" + +BEARER_ID="$(php83 "$SCRIPT" 2>/dev/null | tail -1)" +[ -n "$BEARER_ID" ] && [ "$BEARER_ID" -gt 0 ] 2>/dev/null || die "could not mint auth-email bearer" + +SECRETS="${CAST_CLUSTER_ROOT}/secrets.lab.env" +if [ -f "$SECRETS" ]; then + grep -v '^URL_SHORTENER_AUTH_BEARER_ID=' "$SECRETS" > /tmp/secrets.new || true + echo "URL_SHORTENER_AUTH_BEARER_ID=${BEARER_ID}" >> /tmp/secrets.new + mv /tmp/secrets.new "$SECRETS" + chmod 600 "$SECRETS" +fi +log "ensure-auth-email-bearer_ok id=${BEARER_ID}" diff --git a/sim/cluster0/scripts/lib/common.sh b/sim/cluster0/scripts/lib/common.sh index 76bf46d..b4ef57d 100644 --- a/sim/cluster0/scripts/lib/common.sh +++ b/sim/cluster0/scripts/lib/common.sh @@ -197,7 +197,7 @@ load_secrets_lab() { _k="${_line%%=*}" _v="${_line#*=}" case "$_k" in - MAIL_*|MSMTP_*|SMTP_*|AUTH_*|GITEA_*|WG_*|RSSH_*) + MAIL_*|MSMTP_*|SMTP_*|AUTH_*|GITEA_*|WG_*|RSSH_*|URL_SHORTENER_*) export "$_k=$_v" ;; esac diff --git a/sim/cluster0/scripts/verify-mail-lab.sh b/sim/cluster0/scripts/verify-mail-lab.sh index 67281ba..2e7df8b 100755 --- a/sim/cluster0/scripts/verify-mail-lab.sh +++ b/sim/cluster0/scripts/verify-mail-lab.sh @@ -16,16 +16,19 @@ COMPOSED="${COMPOSE_BACKEND:-/var/www/ac/composed/backend}" export MAIL_TEST_TO="$TO" PHP="${PHP_BIN:-php83}" command -v "$PHP" >/dev/null 2>&1 || PHP=php +_origin="${LAB_PUBLIC_ORIGIN:-https://acl0.f0xx.org}" +_token="lab-smoke-$(date +%s)" +_long="${_origin%/}/app/androidcast_project/issues/verify-email?token=${_token}" +export MAIL_TEST_LONG="$_long" _out="$("$PHP" -r ' require "'"${COMPOSED}"'/src/bootstrap.php"; $to = getenv("MAIL_TEST_TO") ?: ""; -$origin = rtrim((string) cfg("public_origin", "https://acl0.f0xx.org"), "/"); -$base = rtrim((string) cfg("base_path", ""), "/"); -$ok = AuthMailer::sendVerifyEmail($to, $origin . $base . "/verify-email?token=lab-smoke"); +$long = getenv("MAIL_TEST_LONG") ?: ""; +$ok = AuthMailer::sendVerifyEmail($to, $long); echo $ok ? "mail_ok" : "mail_fail"; ' 2>&1)" || _out="mail_fail" -log "$_out to=$TO" +log "$_out to=$TO long=$_long" case "$_out" in mail_ok) exit 0 ;; *) exit 1 ;;