mirror of
git://f0xx.org/ac/ac-deploy
synced 2026-07-29 05:19:12 +03:00
cluster0: auth-email bearer + shorten verify URLs in outbound mail
ShortLinksRepository::shortenForOutbound; ensure-auth-email-bearer.sh; verify-mail-lab sends shortened s.f0xx.org link in verify email body. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
28
sim/cluster0/lab-seeds/backend/scripts/ensure_auth_email_bearer.php
Executable file
28
sim/cluster0/lab-seeds/backend/scripts/ensure_auth_email_bearer.php
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env php83
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
/** Ensure system bearer for auth-email outbound short links. Prints bearer_id on stdout. */
|
||||||
|
require __DIR__ . '/../src/bootstrap.php';
|
||||||
|
|
||||||
|
if (!UrlShortenerDatabase::enabled() || !UrlShortenerDatabase::ping()) {
|
||||||
|
fwrite(STDERR, "url_shortener unavailable\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ShortLinksRepository::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')) {
|
||||||
|
echo (int) ($b['id'] ?? 0), "\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$mint = ShortLinksRepository::mintBearer('auth-email-system', true, false, 5000);
|
||||||
|
if (empty($mint['ok'])) {
|
||||||
|
fwrite(STDERR, 'mint failed: ' . ($mint['error'] ?? '?') . "\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
echo (int) $mint['bearer_id'], "\n";
|
||||||
@@ -18,7 +18,10 @@ final class AuthMailer {
|
|||||||
|
|
||||||
public static function sendVerifyEmail(string $to, string $verifyUrl): bool {
|
public static function sendVerifyEmail(string $to, string $verifyUrl): bool {
|
||||||
$subject = 'Verify your Android Cast account';
|
$subject = 'Verify your Android Cast account';
|
||||||
$body = "Open this link to verify your email (valid 24h):\n\n" . $verifyUrl . "\n";
|
$link = class_exists('ShortLinksRepository', false)
|
||||||
|
? ShortLinksRepository::shortenForOutbound($verifyUrl, 86400)
|
||||||
|
: $verifyUrl;
|
||||||
|
$body = "Open this link to verify your email (valid 24h):\n\n" . $link . "\n";
|
||||||
return self::send($to, $subject, $body);
|
return self::send($to, $subject, $body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -478,4 +478,44 @@ final class ShortLinksRepository {
|
|||||||
);
|
);
|
||||||
$stmt->execute([$event, $bearerId, $slug, $ip, $detail]);
|
$stmt->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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ run_phase_app() {
|
|||||||
run_script "$ROOT/scripts/deploy-ac-url-shortener.sh"
|
run_script "$ROOT/scripts/deploy-ac-url-shortener.sh"
|
||||||
if [ "${COMPOSE_RUNTIME:-0}" = "1" ]; then
|
if [ "${COMPOSE_RUNTIME:-0}" = "1" ]; then
|
||||||
run_script "$ROOT/scripts/compose-lab-backend.sh"
|
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
|
fi
|
||||||
if [ "${POSTGRES_SEMI_ENABLE:-0}" = "1" ] && is_primary_node; then
|
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)"
|
run_script "$ROOT/scripts/deploy-postgres-semi.sh" || journal_warn postgres_semi "install failed (non-fatal)"
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ if [ -n "$_MSMTP_PASS" ]; then
|
|||||||
MAIL_FROM="${MAIL_FROM:-Android Cast <bestcastr@gmail.com>}"
|
MAIL_FROM="${MAIL_FROM:-Android Cast <bestcastr@gmail.com>}"
|
||||||
fi
|
fi
|
||||||
unset _MSMTP_PASS
|
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"
|
mkdir -p "${COMPOSED}/config"
|
||||||
log "write composed config.php base_path=${ISSUES_BASE}"
|
log "write composed config.php base_path=${ISSUES_BASE}"
|
||||||
cat > "${COMPOSED}/config/config.php" <<PHP
|
cat > "${COMPOSED}/config/config.php" <<PHP
|
||||||
@@ -174,6 +176,7 @@ return [
|
|||||||
'url_shortener' => [
|
'url_shortener' => [
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
'public_base' => '${SHORT_LINKS_PUBLIC_BASE}',
|
'public_base' => '${SHORT_LINKS_PUBLIC_BASE}',
|
||||||
|
'auth_bearer_id' => ${AUTH_BEARER_ID:-0},
|
||||||
'db' => [
|
'db' => [
|
||||||
'mysql' => [
|
'mysql' => [
|
||||||
'host' => '${DB_HOST}',
|
'host' => '${DB_HOST}',
|
||||||
|
|||||||
24
sim/cluster0/scripts/ensure-auth-email-bearer.sh
Executable file
24
sim/cluster0/scripts/ensure-auth-email-bearer.sh
Executable file
@@ -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}"
|
||||||
@@ -197,7 +197,7 @@ load_secrets_lab() {
|
|||||||
_k="${_line%%=*}"
|
_k="${_line%%=*}"
|
||||||
_v="${_line#*=}"
|
_v="${_line#*=}"
|
||||||
case "$_k" in
|
case "$_k" in
|
||||||
MAIL_*|MSMTP_*|SMTP_*|AUTH_*|GITEA_*|WG_*|RSSH_*)
|
MAIL_*|MSMTP_*|SMTP_*|AUTH_*|GITEA_*|WG_*|RSSH_*|URL_SHORTENER_*)
|
||||||
export "$_k=$_v"
|
export "$_k=$_v"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -16,16 +16,19 @@ COMPOSED="${COMPOSE_BACKEND:-/var/www/ac/composed/backend}"
|
|||||||
export MAIL_TEST_TO="$TO"
|
export MAIL_TEST_TO="$TO"
|
||||||
PHP="${PHP_BIN:-php83}"
|
PHP="${PHP_BIN:-php83}"
|
||||||
command -v "$PHP" >/dev/null 2>&1 || PHP=php
|
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 '
|
_out="$("$PHP" -r '
|
||||||
require "'"${COMPOSED}"'/src/bootstrap.php";
|
require "'"${COMPOSED}"'/src/bootstrap.php";
|
||||||
$to = getenv("MAIL_TEST_TO") ?: "";
|
$to = getenv("MAIL_TEST_TO") ?: "";
|
||||||
$origin = rtrim((string) cfg("public_origin", "https://acl0.f0xx.org"), "/");
|
$long = getenv("MAIL_TEST_LONG") ?: "";
|
||||||
$base = rtrim((string) cfg("base_path", ""), "/");
|
$ok = AuthMailer::sendVerifyEmail($to, $long);
|
||||||
$ok = AuthMailer::sendVerifyEmail($to, $origin . $base . "/verify-email?token=lab-smoke");
|
|
||||||
echo $ok ? "mail_ok" : "mail_fail";
|
echo $ok ? "mail_ok" : "mail_fail";
|
||||||
' 2>&1)" || _out="mail_fail"
|
' 2>&1)" || _out="mail_fail"
|
||||||
|
|
||||||
log "$_out to=$TO"
|
log "$_out to=$TO long=$_long"
|
||||||
case "$_out" in
|
case "$_out" in
|
||||||
mail_ok) exit 0 ;;
|
mail_ok) exit 0 ;;
|
||||||
*) exit 1 ;;
|
*) exit 1 ;;
|
||||||
|
|||||||
Reference in New Issue
Block a user