Skip to content

Commit ccc738a

Browse files
appleboyclaude
andauthored
refactor(examples): improve code quality and safety across examples (#12)
- Add HTTP server read/write/idle timeouts to go-webservice to prevent slowloris attacks - Log exception details in python-cli token validation instead of silently swallowing - Extract duplicated scopes list into SCOPES constant in python-cli - Simplify python-m2m response reading from streaming chunk iterator to simple get - Extract triplicated symlink/ownership guard into validate_cache_file helper in bash-cli Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a532083 commit ccc738a

4 files changed

Lines changed: 29 additions & 34 deletions

File tree

bash-cli/main.sh

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,19 @@ discover_endpoints() {
224224

225225
# --- Token Cache ---
226226

227-
load_cached_token() {
228-
[ -f "$TOKEN_CACHE_FILE" ] || return 1
229-
230-
# Refuse to operate on a symlink or a file not owned by the current user
231-
# to avoid following attacker-controlled links to credential files.
227+
# Refuse to operate on a symlink or a file not owned by the current user
228+
# to avoid following attacker-controlled links to credential files.
229+
validate_cache_file() {
232230
if [ -L "$TOKEN_CACHE_FILE" ] || [ ! -O "$TOKEN_CACHE_FILE" ]; then
233231
echo "Warning: Refusing to use token cache file that is a symlink or not owned by the current user: $TOKEN_CACHE_FILE" >&2
234232
return 1
235233
fi
234+
return 0
235+
}
236+
237+
load_cached_token() {
238+
[ -f "$TOKEN_CACHE_FILE" ] || return 1
239+
validate_cache_file || return 1
236240

237241
chmod 600 "$TOKEN_CACHE_FILE" 2>/dev/null || true
238242

@@ -298,10 +302,7 @@ save_cached_token() {
298302
# If the cache file already exists, refuse to operate on a symlink or
299303
# a file not owned by the current user to prevent credential clobbering.
300304
if [ -e "$TOKEN_CACHE_FILE" ]; then
301-
if [ -L "$TOKEN_CACHE_FILE" ] || [ ! -O "$TOKEN_CACHE_FILE" ]; then
302-
echo "Warning: Refusing to write token cache file that is a symlink or not owned by the current user: $TOKEN_CACHE_FILE" >&2
303-
return 1
304-
fi
305+
validate_cache_file || return 1
305306
fi
306307

307308
# Treat missing or corrupted cache as empty; fall back to {}
@@ -331,11 +332,7 @@ save_cached_token() {
331332
delete_cached_token() {
332333
[ -f "$TOKEN_CACHE_FILE" ] || return 0
333334

334-
# Refuse to operate on a symlink or a file not owned by the current user
335-
if [ -L "$TOKEN_CACHE_FILE" ] || [ ! -O "$TOKEN_CACHE_FILE" ]; then
336-
echo "Warning: Refusing to delete token cache file that is a symlink or not owned by the current user: $TOKEN_CACHE_FILE" >&2
337-
return 0
338-
fi
335+
validate_cache_file || return 0
339336

340337
local tmp
341338
tmp=$(mktemp "${TOKEN_CACHE_FILE}.XXXXXX")

go-webservice/main.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"log"
2222
"net/http"
2323
"os"
24+
"time"
2425

2526
"github.com/go-authgate/sdk-go/discovery"
2627
"github.com/go-authgate/sdk-go/middleware"
@@ -70,8 +71,15 @@ func main() {
7071
mux.Handle("/api/data", authWithScope(http.HandlerFunc(dataHandler)))
7172
mux.HandleFunc("/health", healthHandler)
7273

74+
srv := &http.Server{
75+
Addr: ":8080",
76+
Handler: mux,
77+
ReadTimeout: 15 * time.Second,
78+
WriteTimeout: 15 * time.Second,
79+
IdleTimeout: 60 * time.Second,
80+
}
7381
log.Println("Listening on :8080")
74-
log.Fatal(http.ListenAndServe(":8080", mux))
82+
log.Fatal(srv.ListenAndServe())
7583
}
7684

7785
func profileHandler(w http.ResponseWriter, r *http.Request) {

python-cli/main.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import authgate
2121
from authgate.credstore import default_token_secure_store
2222

23+
SCOPES = ["profile", "email"]
24+
2325

2426
def mask_token(s: str) -> str:
2527
if len(s) <= 8:
@@ -76,20 +78,20 @@ def main():
7678
client, token = authgate.authenticate(
7779
authgate_url,
7880
client_id,
79-
scopes=["profile", "email"],
81+
scopes=SCOPES,
8082
)
8183

8284
# If the cached token is revoked/expired server-side, clear it and re-authenticate.
8385
try:
8486
info = client.userinfo(token.access_token)
85-
except Exception:
86-
print("Cached token is invalid, re-authenticating...")
87+
except Exception as e:
88+
print(f"Cached token is invalid ({e}), re-authenticating...")
8789
store = default_token_secure_store("authgate", ".authgate-tokens.json")
8890
store.delete(client_id)
8991
client, token = authgate.authenticate(
9092
authgate_url,
9193
client_id,
92-
scopes=["profile", "email"],
94+
scopes=SCOPES,
9395
)
9496
info = None
9597

python-m2m/main.py

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,13 @@ def main():
5353
# 4. Use the auto-authenticated HTTP client
5454
auth = BearerAuth(ts)
5555
with httpx.Client(auth=auth) as http:
56-
with http.stream("GET", f"{authgate_url}/oauth/userinfo") as resp:
57-
status_code = resp.status_code
58-
body_bytes = bytearray()
59-
for chunk in resp.iter_bytes():
60-
if not chunk:
61-
continue
62-
remaining = (MAX_BODY_SIZE + 1) - len(body_bytes)
63-
if remaining <= 0:
64-
break
65-
if len(chunk) > remaining:
66-
body_bytes.extend(chunk[:remaining])
67-
break
68-
body_bytes.extend(chunk)
69-
body = bytes(body_bytes)
56+
resp = http.get(f"{authgate_url}/oauth/userinfo")
57+
body = resp.content
7058

7159
truncated = len(body) > MAX_BODY_SIZE
7260
if truncated:
7361
body = body[:MAX_BODY_SIZE]
74-
print(f"Status: {status_code}")
62+
print(f"Status: {resp.status_code}")
7563
print(f"Body: {body.decode(errors='replace')}")
7664
if truncated:
7765
print("(response body truncated to 1 MB)")

0 commit comments

Comments
 (0)