Skip to content

Commit 3f10e38

Browse files
authored
Support IBKR mobile push 2FA config (#33)
1 parent e83bf95 commit 3f10e38

6 files changed

Lines changed: 43 additions & 0 deletions

File tree

.github/workflows/main.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ jobs:
5353
ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY: ${{ vars.IB_GATEWAY_ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY }}
5454
TWS_ACCEPT_INCOMING: ${{ vars.IB_GATEWAY_TWS_ACCEPT_INCOMING }}
5555
READ_ONLY_API: ${{ vars.IB_GATEWAY_READ_ONLY_API }}
56+
TWOFA_DEVICE: ${{ vars.IB_GATEWAY_TWOFA_DEVICE }}
57+
IBKR_2FA_AUTOFILL: ${{ vars.IB_GATEWAY_2FA_AUTOFILL }}
5658
SSH_PRIVATE_KEY_SECRET_NAME: ${{ vars.IB_GATEWAY_SSH_PRIVATE_KEY_SECRET_NAME }}
5759
TWS_USERID_SECRET_NAME: ${{ vars.IB_GATEWAY_TWS_USERID_SECRET_NAME }}
5860
TWS_PASSWORD_SECRET_NAME: ${{ vars.IB_GATEWAY_TWS_PASSWORD_SECRET_NAME }}
@@ -232,6 +234,8 @@ jobs:
232234
"ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY": os.environ["ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY"],
233235
"TWS_ACCEPT_INCOMING": os.environ["TWS_ACCEPT_INCOMING"],
234236
"READ_ONLY_API": os.environ["READ_ONLY_API"],
237+
"TWOFA_DEVICE": os.environ.get("TWOFA_DEVICE", ""),
238+
"IBKR_2FA_AUTOFILL": os.environ.get("IBKR_2FA_AUTOFILL", ""),
235239
}
236240
237241
def quote(value: str) -> str:

2fa_bot.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
# ================= Configuration =================
1212
SECRET_KEY = os.environ.get("TOTP_SECRET")
1313
X11_DISPLAY = os.environ.get("DISPLAY_NUM", ":1")
14+
AUTOFILL_ENABLED = os.environ.get("IBKR_2FA_AUTOFILL", "yes").strip().lower() not in {
15+
"0",
16+
"false",
17+
"no",
18+
"off",
19+
}
1420

1521
# Timing constants (seconds)
1622
CHECK_INTERVAL = 3
@@ -72,6 +78,9 @@ class WindowCandidate:
7278

7379
def validate_config():
7480
"""Validate required config at startup; exit immediately if invalid."""
81+
if not AUTOFILL_ENABLED:
82+
log.info("TOTP auto-fill is disabled; bot will only log auth popup detection")
83+
return
7584
if not SECRET_KEY:
7685
log.error("TOTP_SECRET not found in environment variables")
7786
sys.exit(1)
@@ -188,6 +197,16 @@ def focus_input_area(candidate):
188197

189198
def submit_totp(candidate):
190199
"""Submit a TOTP code to the selected authentication popup."""
200+
if not AUTOFILL_ENABLED:
201+
log.info(
202+
"Authentication window found (id=%s, title=%r, size=%sx%s); auto-fill disabled",
203+
candidate.window_id,
204+
candidate.title,
205+
candidate.width or "?",
206+
candidate.height or "?",
207+
)
208+
return
209+
191210
seconds_remaining = wait_for_fresh_totp_window()
192211
log.info(
193212
"Authentication window found (id=%s, title=%r, size=%sx%s); submitting code with %ss remaining",

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,11 @@ VNC_SERVER_PASSWORD=your_vnc_password
6666
TRADING_MODE=live
6767
TWS_ACCEPT_INCOMING=accept
6868
READ_ONLY_API=no
69+
TWOFA_DEVICE=Mobile Device
6970
TWOFA_TIMEOUT_ACTION=restart
7071
RELOGIN_AFTER_TWOFA_TIMEOUT=yes
7172
EXISTING_SESSION_DETECTED_ACTION=primary
73+
IBKR_2FA_AUTOFILL=no
7274
JAVA_HEAP_SIZE=512
7375

7476
# Recommended: use the exact CIDR used by your Cloud Run egress path
@@ -94,6 +96,8 @@ IB_GATEWAY_DEPLOY_PATH=/home/zwlddx0815/ib-docker
9496
IB_GATEWAY_ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY=no
9597
IB_GATEWAY_TWS_ACCEPT_INCOMING=accept
9698
IB_GATEWAY_READ_ONLY_API=no
99+
IB_GATEWAY_TWOFA_DEVICE=Mobile Device
100+
IB_GATEWAY_2FA_AUTOFILL=no
97101
```
98102

99103
The workflow maps these shared values to the gateway container's `.env`:
@@ -104,6 +108,8 @@ The workflow maps these shared values to the gateway container's `.env`:
104108
- `IB_GATEWAY_ZONE` -> `GCE_ZONE`
105109
- `IB_GATEWAY_GCE_USER` -> `GCE_USER`
106110
- `IB_GATEWAY_DEPLOY_PATH` -> `DEPLOY_PATH`
111+
- `IB_GATEWAY_TWOFA_DEVICE` -> `TWOFA_DEVICE`
112+
- `IB_GATEWAY_2FA_AUTOFILL` -> `IBKR_2FA_AUTOFILL`
107113

108114
`ACCEPT_API_FROM_IP` is intentionally treated as required now. For manual `docker compose` usage, if you forget to set it, Compose will fail fast instead of starting a gateway that Cloud Run can never reach.
109115

@@ -205,6 +211,8 @@ If you temporarily keep the values in GitHub Secrets during migration, you can r
205211
| `IB_GATEWAY_ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY` | Set to `no` for Cloud Run private IP access |
206212
| `IB_GATEWAY_TWS_ACCEPT_INCOMING` | Optional. Recommended `accept`. |
207213
| `IB_GATEWAY_READ_ONLY_API` | Optional. Recommended `no` if this service places trades. |
214+
| `IB_GATEWAY_TWOFA_DEVICE` | Optional. Exact IBC 2FA device name, for example `Mobile Device` for IBKR Mobile push. |
215+
| `IB_GATEWAY_2FA_AUTOFILL` | Optional. Set to `no` when using IBKR Mobile push instead of local TOTP auto-fill. |
208216

209217
The current VM is an `e2-micro`, so the deployment intentionally sets `JAVA_HEAP_SIZE=512`
210218
by default and enables a 2 GiB host swap file during keepalive/deploy. Without this,
@@ -358,9 +366,11 @@ VNC_SERVER_PASSWORD=your_vnc_password
358366
TRADING_MODE=live
359367
TWS_ACCEPT_INCOMING=accept
360368
READ_ONLY_API=no
369+
TWOFA_DEVICE=Mobile Device
361370
TWOFA_TIMEOUT_ACTION=restart
362371
RELOGIN_AFTER_TWOFA_TIMEOUT=yes
363372
EXISTING_SESSION_DETECTED_ACTION=primary
373+
IBKR_2FA_AUTOFILL=no
364374
JAVA_HEAP_SIZE=512
365375

366376
ACCEPT_API_FROM_IP=10.8.0.0/26

docker-compose.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ services:
1313
- READ_ONLY_API=${READ_ONLY_API:-no}
1414
# Keep IBC aligned with the upstream image defaults so paper/live
1515
# sessions recover after missed 2FA or stale-session prompts.
16+
- TWOFA_DEVICE=${TWOFA_DEVICE:-}
1617
- TWOFA_TIMEOUT_ACTION=${TWOFA_TIMEOUT_ACTION:-restart}
1718
- RELOGIN_AFTER_TWOFA_TIMEOUT=${RELOGIN_AFTER_TWOFA_TIMEOUT:-yes}
1819
- EXISTING_SESSION_DETECTED_ACTION=${EXISTING_SESSION_DETECTED_ACTION:-primary}
20+
# Set to no when IBC should use IBKR Mobile push approval instead of
21+
# the local TOTP auto-fill helper.
22+
- IBKR_2FA_AUTOFILL=${IBKR_2FA_AUTOFILL:-yes}
1923
# e2-micro has less than 1 GiB RAM. The upstream image defaults to
2024
# -Xmx768m, which can starve sshd/Docker/guest-agent. Keep this
2125
# configurable, but use a safer default for the current VM.

tests/test_docker_compose_ports.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ grep -Fq ' - "4001:4003"' "$compose_file"
3131
grep -Fq ' - "4002:4004"' "$compose_file"
3232
grep -Fq ' - TWS_ACCEPT_INCOMING=${TWS_ACCEPT_INCOMING:-accept}' "$compose_file"
3333
grep -Fq ' - READ_ONLY_API=${READ_ONLY_API:-no}' "$compose_file"
34+
grep -Fq ' - TWOFA_DEVICE=${TWOFA_DEVICE:-}' "$compose_file"
3435
grep -Fq ' - TWOFA_TIMEOUT_ACTION=${TWOFA_TIMEOUT_ACTION:-restart}' "$compose_file"
3536
grep -Fq ' - RELOGIN_AFTER_TWOFA_TIMEOUT=${RELOGIN_AFTER_TWOFA_TIMEOUT:-yes}' "$compose_file"
3637
grep -Fq ' - EXISTING_SESSION_DETECTED_ACTION=${EXISTING_SESSION_DETECTED_ACTION:-primary}' "$compose_file"
38+
grep -Fq ' - IBKR_2FA_AUTOFILL=${IBKR_2FA_AUTOFILL:-yes}' "$compose_file"
3739
grep -Fq ' - JAVA_HEAP_SIZE=${JAVA_HEAP_SIZE:-512}' "$compose_file"
3840
grep -Fq ' - IB_GATEWAY_PARALLEL_GC_THREADS=${IB_GATEWAY_PARALLEL_GC_THREADS:-2}' "$compose_file"
3941
grep -Fq ' - IB_GATEWAY_CONC_GC_THREADS=${IB_GATEWAY_CONC_GC_THREADS:-1}' "$compose_file"

tests/test_workflow_shared_config.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ grep -Fq 'vars.IB_GATEWAY_CLOUD_RUN_EGRESS_CIDR' "$workflow_file"
2424
grep -Fq 'vars.IB_GATEWAY_ALLOW_CONNECTIONS_FROM_LOCALHOST_ONLY' "$workflow_file"
2525
grep -Fq 'vars.IB_GATEWAY_TWS_ACCEPT_INCOMING' "$workflow_file"
2626
grep -Fq 'vars.IB_GATEWAY_READ_ONLY_API' "$workflow_file"
27+
grep -Fq 'vars.IB_GATEWAY_TWOFA_DEVICE' "$workflow_file"
28+
grep -Fq 'vars.IB_GATEWAY_2FA_AUTOFILL' "$workflow_file"
2729
grep -Fq 'vars.IB_GATEWAY_SSH_PRIVATE_KEY_SECRET_NAME' "$workflow_file"
2830
grep -Fq 'vars.IB_GATEWAY_TWS_USERID_SECRET_NAME' "$workflow_file"
2931
grep -Fq 'vars.IB_GATEWAY_TWS_PASSWORD_SECRET_NAME' "$workflow_file"
@@ -71,6 +73,8 @@ done
7173

7274
grep -Fq '"TRADING_MODE": os.environ["IB_GATEWAY_MODE"]' "$workflow_file"
7375
grep -Fq '"ACCEPT_API_FROM_IP": os.environ["CLOUD_RUN_EGRESS_CIDR"]' "$workflow_file"
76+
grep -Fq '"TWOFA_DEVICE": os.environ.get("TWOFA_DEVICE", "")' "$workflow_file"
77+
grep -Fq '"IBKR_2FA_AUTOFILL": os.environ.get("IBKR_2FA_AUTOFILL", "")' "$workflow_file"
7478
grep -Fq 'REMOTE_DEPLOY_COMMAND=$(cat <<EOF' "$workflow_file"
7579
if grep -Fq 'DEPLOY_SCRIPT=' "$workflow_file"; then
7680
echo "Unexpected DEPLOY_SCRIPT temp upload flow still present" >&2

0 commit comments

Comments
 (0)