Add Authentik Git login helper

This commit is contained in:
virgil
2026-06-08 09:26:37 -07:00
parent e1020f0ec7
commit 75479f6a40
2 changed files with 148 additions and 5 deletions

View File

@@ -53,6 +53,7 @@ TARGET_DIR=~/work/rta-handbook \
## Daily Flow ## Daily Flow
```sh ```sh
section0-docs auth login
section0-docs doctor section0-docs doctor
section0-docs pull section0-docs pull
@@ -69,16 +70,20 @@ the lab operator side; collaborators do not need the home-lab repo.
## Contributor Setup ## Contributor Setup
Anonymous clone/read works on the private lab network. Pushing requires an Anonymous clone/read works on the private lab network. Pushing requires
account or access token with write access to `section0/rta-handbook`. Authentik login through the helper.
Run this once per checkout: Run this once per checkout:
```sh ```sh
section0-docs configure section0-docs auth login
``` ```
Then prove write access without changing `main`: The login opens Authentik in your browser, configures the Git author from your
identity, and installs the repo Git credential. After that, normal Git tooling
works too.
Prove write access without changing `main`:
```sh ```sh
section0-docs push-test section0-docs push-test
@@ -87,7 +92,8 @@ section0-docs push-test
`push-test` does not commit files and does not change `main`. It pushes the `push-test` does not commit files and does not change `main`. It pushes the
current commit to a temporary scratch branch, then deletes that branch current commit to a temporary scratch branch, then deletes that branch
immediately. If Git prompts for a password, use a Gitea access token rather immediately. If Git prompts for a password, use a Gitea access token rather
than your normal account password. than your normal account password. If login is healthy, Git should usually get
the credential from your local credential store.
## End-To-End Smoke Test ## End-To-End Smoke Test

View File

@@ -6,6 +6,7 @@ TARGET_DIR="${TARGET_DIR:-$HOME/Developer/Section0/rta-handbook}"
COMMAND_DIR="${COMMAND_DIR:-$HOME/.local/bin}" COMMAND_DIR="${COMMAND_DIR:-$HOME/.local/bin}"
COMMAND_NAME="${COMMAND_NAME:-section0-docs}" COMMAND_NAME="${COMMAND_NAME:-section0-docs}"
COMMAND_PATH="$COMMAND_DIR/$COMMAND_NAME" COMMAND_PATH="$COMMAND_DIR/$COMMAND_NAME"
SERVER_URL="${SECTION0_SERVER_URL:-https://ops.virgil.info/md-to-section0-api}"
step() { step() {
printf "\n==> %s\n" "$*" printf "\n==> %s\n" "$*"
@@ -37,12 +38,17 @@ cat > "$COMMAND_PATH" <<EOF
set -eu set -eu
REPO_DIR="$TARGET_DIR" REPO_DIR="$TARGET_DIR"
SERVER_URL="$SERVER_URL"
SESSION_DIR="\${SECTION0_SESSION_DIR:-\$HOME/.config/section0-docs}"
SESSION_PATH="\$SESSION_DIR/session.json"
usage() { usage() {
cat <<USAGE cat <<USAGE
section0-docs - helper for Section 0 shared Markdown docs section0-docs - helper for Section 0 shared Markdown docs
Commands: Commands:
auth login open Authentik and install Git credentials
auth status show current saved login
configure set Git author name/email for this repo configure set Git author name/email for this repo
doctor check clone, author, remote, and read access doctor check clone, author, remote, and read access
open print the repo path open print the repo path
@@ -54,6 +60,8 @@ Commands:
help show this help help show this help
Examples: Examples:
section0-docs auth login
section0-docs auth status
section0-docs doctor section0-docs doctor
section0-docs configure section0-docs configure
section0-docs pull section0-docs pull
@@ -67,7 +75,135 @@ AFFiNE copies are refreshed from Git.
USAGE USAGE
} }
need_python() {
command -v python3 >/dev/null 2>&1 || {
echo "python3 is required for Authentik login JSON handling" >&2
exit 1
}
}
json_get() {
need_python
python3 -c 'import json,sys; data=json.load(sys.stdin); cur=data
for part in sys.argv[1].split("."):
cur = cur.get(part, "") if isinstance(cur, dict) else ""
print(cur if cur is not None else "")' "\$1"
}
open_url() {
if command -v open >/dev/null 2>&1; then
open "\$1" >/dev/null 2>&1 || true
elif command -v xdg-open >/dev/null 2>&1; then
xdg-open "\$1" >/dev/null 2>&1 || true
fi
}
credential_host() {
need_python
python3 -c 'from urllib.parse import urlparse; import sys
url=urlparse(sys.argv[1])
print(url.netloc)' "\$1"
}
install_git_credential() {
remote="\$1"
username="\$2"
password="\$3"
host="\$(credential_host "\$remote")"
protocol="\$(printf "%s" "\$remote" | sed -n "s#^\\([^:/]*\\)://.*#\\1#p")"
[ -n "\$protocol" ] || protocol="http"
if [ "\$(uname -s 2>/dev/null || true)" = "Darwin" ]; then
git -C "\$REPO_DIR" config credential.helper osxkeychain
fi
printf "protocol=%s\nhost=%s\nusername=%s\npassword=%s\n\n" "\$protocol" "\$host" "\$username" "\$password" \
| git -C "\$REPO_DIR" credential approve
}
auth_login() {
need_python
mkdir -p "\$SESSION_DIR"
device_json="\$(curl -fsS -X POST "\$SERVER_URL/device")"
code="\$(printf "%s" "\$device_json" | json_get code)"
auth_url="\$(printf "%s" "\$device_json" | json_get authUrl)"
[ -n "\$code" ] && [ -n "\$auth_url" ] || {
echo "login server did not return a device code" >&2
exit 1
}
echo "Opening Authentik login:"
echo " \$auth_url"
open_url "\$auth_url"
echo "Waiting for login..."
token_json=""
i=0
while [ "\$i" -lt 90 ]; do
token_json="\$(curl -fsS "\$SERVER_URL/device/\$code/token" 2>/dev/null || true)"
access_token="\$(printf "%s" "\${token_json:-{}}" | json_get accessToken 2>/dev/null || true)"
[ -n "\$access_token" ] && break
i=\$((i + 1))
sleep 2
done
[ -n "\${access_token:-}" ] || {
echo "timed out waiting for Authentik login" >&2
exit 1
}
access_json="\$(curl -fsS -X POST -H "Authorization: Bearer \$access_token" "\$SERVER_URL/section0/git/access")"
ok="\$(printf "%s" "\$access_json" | json_get ok)"
[ "\$ok" = "True" ] || [ "\$ok" = "true" ] || {
echo "Git access broker did not return credentials:" >&2
printf "%s\n" "\$access_json" >&2
exit 1
}
remote="\$(printf "%s" "\$access_json" | json_get remote)"
git_username="\$(printf "%s" "\$access_json" | json_get git.username)"
git_password="\$(printf "%s" "\$access_json" | json_get git.password)"
author_name="\$(printf "%s" "\$access_json" | json_get author.name)"
author_email="\$(printf "%s" "\$access_json" | json_get author.email)"
[ -n "\$remote" ] && [ -n "\$git_username" ] && [ -n "\$git_password" ] || {
echo "Git access broker response was incomplete" >&2
exit 1
}
git -C "\$REPO_DIR" remote set-url origin "\$remote"
git -C "\$REPO_DIR" config user.name "\$author_name"
git -C "\$REPO_DIR" config user.email "\$author_email"
install_git_credential "\$remote" "\$git_username" "\$git_password"
python3 -c 'import json,sys
token=json.loads(sys.argv[1])
access=json.loads(sys.argv[2])
print(json.dumps({
"authenticated": True,
"serverUrl": sys.argv[3],
"authentik": token.get("authentik", {}),
"author": access.get("author", {}),
"remote": access.get("remote", ""),
"credentialUser": access.get("git", {}).get("username", "")
}, indent=2))' "\$token_json" "\$access_json" "\$SERVER_URL" > "\$SESSION_PATH"
chmod 600 "\$SESSION_PATH"
echo "Authenticated: \$author_email"
echo "Git remote: \$remote"
echo "Git user: \$git_username"
echo "Session: \$SESSION_PATH"
}
case "\${1:-help}" in case "\${1:-help}" in
auth)
case "\${2:-}" in
login)
auth_login
;;
status)
if [ -f "\$SESSION_PATH" ]; then
cat "\$SESSION_PATH"
else
echo "not authenticated; run: section0-docs auth login" >&2
exit 1
fi
;;
*)
echo "usage: section0-docs auth <login|status>" >&2
exit 1
;;
esac
;;
configure) configure)
current_name="\$(git -C "\$REPO_DIR" config user.name || true)" current_name="\$(git -C "\$REPO_DIR" config user.name || true)"
current_email="\$(git -C "\$REPO_DIR" config user.email || true)" current_email="\$(git -C "\$REPO_DIR" config user.email || true)"
@@ -146,6 +282,7 @@ Remote: $REPO_URL
Helper: $COMMAND_PATH Helper: $COMMAND_PATH
Daily flow: Daily flow:
$COMMAND_NAME auth login
$COMMAND_NAME doctor $COMMAND_NAME doctor
$COMMAND_NAME pull $COMMAND_NAME pull
# edit Markdown # edit Markdown