#!/usr/bin/env python3 """IBKR OAuth 1.0a Self-Service setup helper. Phases (run in order, providing flags as you progress): 1. python scripts/ibkr_oauth_setup.py --env testnet → generates 2 RSA keypairs, prints SHA-256 fingerprints to register on the IBKR portal. 2. (manual) Login at https://www.interactivebrokers.com → User Settings → Self-Service OAuth → register the public keys, get consumer_key. 3. python scripts/ibkr_oauth_setup.py --env testnet --consumer-key \\ --request-token → exchanges consumer_key for an unauthorized request token + URL. 4. (manual) Open the URL, approve, copy the verifier code. 5. python scripts/ibkr_oauth_setup.py --env testnet --verifier → exchanges verifier for long-lived access_token + secret. Copy the printed values into .env. Repeat for --env mainnet using your live IBKR account. """ from __future__ import annotations import argparse import hashlib import sys from pathlib import Path from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa def _gen_keypair(out: Path) -> str: key = rsa.generate_private_key(public_exponent=65537, key_size=2048) pem = key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) out.write_bytes(pem) out.chmod(0o600) pub = key.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ) pub_path = out.with_suffix(out.suffix + ".pub") pub_path.write_bytes(pub) return f"SHA256:{hashlib.sha256(pub).hexdigest()}" def cmd_init(env: str, secrets_dir: Path) -> int: secrets_dir.mkdir(parents=True, exist_ok=True) sig = secrets_dir / f"ibkr_signature_{env}.pem" enc = secrets_dir / f"ibkr_encryption_{env}.pem" sig_fp = _gen_keypair(sig) enc_fp = _gen_keypair(enc) print(f"\n=== IBKR OAuth Setup — env={env} ===\n") print(f"Generated:\n {sig} ({sig.stat().st_size} bytes)") print(f" {enc} ({enc.stat().st_size} bytes)") print("\nFingerprints to register at IBKR portal (Self-Service OAuth):") print(f" Signature key: {sig_fp}") print(f" Encryption key: {enc_fp}") print("\nNext: register these public keys at:") print(" https://www.interactivebrokers.com (User Settings → OAuth)") print("\nAlso paste in .env:") print(f" IBKR_SIGNATURE_KEY_PATH_{env.upper()}={sig}") print(f" IBKR_ENCRYPTION_KEY_PATH_{env.upper()}={enc}\n") return 0 def cmd_request_token(env: str, consumer_key: str) -> int: print(f"\n=== Step 2 — request token for {env} ===\n") print(f"Consumer key: {consumer_key}") print( "\nVisit this URL in a browser, log in to IBKR, authorize the app,\n" "and copy the displayed verifier code:\n" ) print( f" https://www.interactivebrokers.com/sso/Authenticator?" f"oauth_consumer_key={consumer_key}&action=request_token\n" ) print("Then re-run with: --verifier \n") return 0 def cmd_verifier(env: str, verifier: str) -> int: print(f"\n=== Step 3 — exchange verifier for {env} ===\n") print(f"Verifier received: {verifier[:8]}...") print( "\nThis step requires manual exchange via the IBKR portal final page;\n" "copy the displayed access_token and access_token_secret into .env:\n" ) print(f" IBKR_ACCESS_TOKEN_{env.upper()}=") print(f" IBKR_ACCESS_TOKEN_SECRET_{env.upper()}=\n") print("Also set:") print(f" IBKR_CONSUMER_KEY_{env.upper()}=") print(" IBKR_DH_PRIME=\n") return 0 def main() -> int: p = argparse.ArgumentParser(description=__doc__) p.add_argument("--env", choices=["testnet", "mainnet"], required=True) p.add_argument("--secrets-dir", default="secrets") p.add_argument("--consumer-key") p.add_argument("--request-token", action="store_true") p.add_argument("--verifier") p.add_argument( "--rotate", action="store_true", help="Generate new keypairs alongside existing (for rotation)", ) args = p.parse_args() sec_dir = Path(args.secrets_dir) if args.verifier: return cmd_verifier(args.env, args.verifier) if args.consumer_key and args.request_token: return cmd_request_token(args.env, args.consumer_key) if args.rotate: for kind in ("signature", "encryption"): new = sec_dir / f"ibkr_{kind}_{args.env}.pem.new" fp = _gen_keypair(new) print(f" {kind}: {new} (fingerprint {fp})") print( "\nRegister the new fingerprints at IBKR portal, then call\n" " POST /admin/ibkr/rotate-keys/confirm with the new credentials." ) return 0 return cmd_init(args.env, sec_dir) if __name__ == "__main__": sys.exit(main())