Files
Cerbero-mcp/scripts/ibkr_oauth_setup.py
2026-05-03 21:40:06 +00:00

133 lines
5.0 KiB
Python

#!/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 <K> \\
--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 <V>
→ 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 <code>\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()}=<paste from portal>")
print(f" IBKR_ACCESS_TOKEN_SECRET_{env.upper()}=<paste from portal>\n")
print("Also set:")
print(f" IBKR_CONSUMER_KEY_{env.upper()}=<the consumer key from step 1>")
print(" IBKR_DH_PRIME=<paste DH prime hex from portal>\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())