1# An invite works until its --ttl is up (7 days unless said) and not a2# second later; one taken back (invites rm) is dead at once. Either way the3# name is free again for another invite or forge user add.4. "$(dirname "$0")/../lib.sh"5user cat6export FORGE_BASE_URL=https://forge.test7ssh-keygen -q -t ed25519 -N '' -C fox -f "$T/keys/fox"8accept() {9 node -e 'console.log(JSON.stringify({ code: process.argv[1], key: process.argv[2] }))' -- "$1" "$(cat "$T/keys/fox.pub")" >"$T/body"10 http -s -w '\n%{http_code}' -H 'Origin: https://forge.test' -H 'Content-Type: application/json' -d @"$T/body" "$PRIV/login/invite/accept"11}12link() { forge user invite "$@" 2>&1 | sed -n 's/^ .*#invite=//p'; }1314fails_with "invite: --ttl is at most 30d" forge user invite fox --ttl 31d15fails_with "invite: --ttl wants" forge user invite fox --ttl soon16fails_with "usage: forge user invite" forge user invite fox --as collaborator17fails_with "usage: forge user invite" forge user invite fox --repo cat/x --as owner18fails_with "no repo cat/x" forge user invite fox --repo cat/x --as reader19fails_with "cat is a user already" forge user invite cat20fails_with "invalid user name: login" forge user invite login2122code=$(link fox --ttl 1h)23ok forge user invites24has "$T_LOG" '^fox +2026-09-01 00:00 +2026-09-01 01:00 +-$'25export FORGE_NOW=$((T_NOW + 3601))26private_server27accept "$code" >"$T/r"28eq "$(tail -n 1 "$T/r")" 40029has "$T/r" 'expired, been used or been taken back'30lacks "$FORGE_ROOT/.forge/keys" '^fox '31ok forge user invites32eq "$(output)" "no invites; forge user invite <name> makes one"3334# Taken back; the name was free for this new invite.35code=$(link fox)36[ -n "$code" ] || fail "no new invite for fox after the first expired"37ok forge user invites rm fox38eq "$(output)" "took back the invite for fox; its link is dead"39fails_with "no invite for fox" forge user invites rm fox40accept "$code" >"$T/r"41eq "$(tail -n 1 "$T/r")" 40042ok forge user add fox "$T/keys/fox.pub"