LCOV - code coverage report
Current view: top level - src/app - auth_flow.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 30.2 % 106 32
Test Date: 2026-04-20 19:54:22 Functions: 66.7 % 3 2

            Line data    Source code
       1              : /* SPDX-License-Identifier: GPL-3.0-or-later */
       2              : /* Copyright 2026 Peter Csaszar */
       3              : 
       4              : /**
       5              :  * @file app/auth_flow.c
       6              :  * @brief High-level login flow with DC migration.
       7              :  */
       8              : 
       9              : #include "app/auth_flow.h"
      10              : #include "app/dc_config.h"
      11              : #include "app/session_store.h"
      12              : 
      13              : #include "auth_session.h"
      14              : #include "infrastructure/auth_2fa.h"
      15              : #include "mtproto_auth.h"
      16              : #include "mtproto_rpc.h"
      17              : #include "logger.h"
      18              : 
      19              : #include <stdio.h>
      20              : #include <string.h>
      21              : 
      22              : /** Maximum redirects we follow before giving up. Telegram usually needs 1. */
      23              : #define AUTH_MAX_MIGRATIONS 3
      24              : 
      25           12 : int auth_flow_connect_dc(int dc_id, Transport *t, MtProtoSession *s) {
      26           12 :     if (!t || !s) return -1;
      27              : 
      28            8 :     const DcEndpoint *ep = dc_lookup(dc_id);
      29            8 :     if (!ep) {
      30            3 :         logger_log(LOG_ERROR, "auth_flow: unknown DC id %d", dc_id);
      31            3 :         return -1;
      32              :     }
      33              : 
      34            5 :     if (transport_connect(t, ep->host, ep->port) != 0) {
      35            2 :         logger_log(LOG_ERROR, "auth_flow: connect failed for DC%d (%s:%d)",
      36            2 :                    dc_id, ep->host, ep->port);
      37            2 :         return -1;
      38              :     }
      39            3 :     t->dc_id = dc_id;
      40              : 
      41            3 :     if (mtproto_auth_key_gen(t, s) != 0) {
      42            3 :         logger_log(LOG_ERROR, "auth_flow: DH auth key gen failed on DC%d",
      43              :                    dc_id);
      44            3 :         transport_close(t);
      45            3 :         return -1;
      46              :     }
      47            0 :     logger_log(LOG_INFO, "auth_flow: connected to DC%d, auth key ready", dc_id);
      48            0 :     return 0;
      49              : }
      50              : 
      51              : /** Tear down the current session/transport and reconnect to a new DC. */
      52            0 : static int migrate(int new_dc, Transport *t, MtProtoSession *s) {
      53            0 :     logger_log(LOG_INFO, "auth_flow: migrating to DC%d", new_dc);
      54            0 :     transport_close(t);
      55              :     /* Re-init session — auth key is DC-scoped. */
      56            0 :     mtproto_session_init(s);
      57            0 :     return auth_flow_connect_dc(new_dc, t, s);
      58              : }
      59              : 
      60           21 : int auth_flow_login(const ApiConfig *cfg,
      61              :                     const AuthFlowCallbacks *cb,
      62              :                     Transport *t, MtProtoSession *s,
      63              :                     AuthFlowResult *out) {
      64           21 :     if (!cfg || !cb || !t || !s) return -1;
      65           13 :     if (!cb->get_phone || !cb->get_code) {
      66            4 :         logger_log(LOG_ERROR, "auth_flow: get_phone/get_code callbacks required");
      67            4 :         return -1;
      68              :     }
      69              : 
      70            9 :     if (out) memset(out, 0, sizeof(*out));
      71              : 
      72              :     /* Fast path: restore a persisted session. */
      73              :     {
      74            9 :         int saved_dc = 0;
      75            9 :         mtproto_session_init(s);
      76            9 :         if (session_store_load(s, &saved_dc) == 0 && s->has_auth_key) {
      77            7 :             const DcEndpoint *ep = dc_lookup(saved_dc);
      78            7 :             if (ep && transport_connect(t, ep->host, ep->port) == 0) {
      79            7 :                 t->dc_id = saved_dc;
      80            7 :                 logger_log(LOG_INFO,
      81              :                            "auth_flow: reusing persisted session on DC%d",
      82              :                            saved_dc);
      83            7 :                 if (out) { out->dc_id = saved_dc; out->user_id = 0; }
      84            7 :                 return 0;
      85              :             }
      86            0 :             logger_log(LOG_WARN,
      87              :                        "auth_flow: persisted session unusable, re-login");
      88            0 :             if (ep) transport_close(t);
      89            0 :             mtproto_session_init(s);
      90              :         }
      91              :     }
      92              : 
      93            2 :     int current_dc = DEFAULT_DC_ID;
      94            2 :     if (auth_flow_connect_dc(current_dc, t, s) != 0) return -1;
      95              : 
      96              :     char phone[64];
      97            0 :     if (cb->get_phone(cb->user, phone, sizeof(phone)) != 0) {
      98            0 :         logger_log(LOG_ERROR, "auth_flow: phone number input failed");
      99            0 :         return -1;
     100              :     }
     101              : 
     102              :     /* ---- auth.sendCode (with migration) ---- */
     103            0 :     AuthSentCode sent = {0};
     104            0 :     int migrations = 0;
     105            0 :     for (;;) {
     106            0 :         RpcError err = {0};
     107            0 :         err.migrate_dc = -1;
     108            0 :         int rc = auth_send_code(cfg, s, t, phone, &sent, &err);
     109            0 :         if (rc == 0) break;
     110            0 :         if (err.migrate_dc > 0 && migrations < AUTH_MAX_MIGRATIONS) {
     111            0 :             migrations++;
     112            0 :             if (migrate(err.migrate_dc, t, s) != 0) return -1;
     113            0 :             current_dc = err.migrate_dc;
     114            0 :             continue;
     115              :         }
     116            0 :         logger_log(LOG_ERROR, "auth_flow: sendCode failed (%d: %s)",
     117              :                    err.error_code, err.error_msg);
     118            0 :         return -1;
     119              :     }
     120              : 
     121              :     /* ---- user enters code ---- */
     122              :     char code[32];
     123            0 :     if (cb->get_code(cb->user, code, sizeof(code)) != 0) {
     124            0 :         logger_log(LOG_ERROR, "auth_flow: code input failed");
     125            0 :         return -1;
     126              :     }
     127              : 
     128              :     /* ---- auth.signIn (with migration; 2FA detected but unimplemented) ---- */
     129            0 :     int64_t uid = 0;
     130              :     for (;;) {
     131            0 :         RpcError err = {0};
     132            0 :         err.migrate_dc = -1;
     133            0 :         int rc = auth_sign_in(cfg, s, t, phone, sent.phone_code_hash, code,
     134              :                               &uid, &err);
     135            0 :         if (rc == 0) break;
     136            0 :         if (err.migrate_dc > 0 && migrations < AUTH_MAX_MIGRATIONS) {
     137            0 :             migrations++;
     138            0 :             if (migrate(err.migrate_dc, t, s) != 0) return -1;
     139            0 :             current_dc = err.migrate_dc;
     140              :             /* After migration we must re-send the code from scratch because
     141              :              * phone_code_hash is DC-scoped. Loop back via a recursive call
     142              :              * after the outer caller handles the new state. For now we
     143              :              * signal failure with a clear diagnostic. */
     144            0 :             logger_log(LOG_ERROR,
     145              :                        "auth_flow: unexpected migration during signIn — "
     146              :                        "restart login flow");
     147            0 :             return -1;
     148              :         }
     149            0 :         if (strcmp(err.error_msg, "SESSION_PASSWORD_NEEDED") == 0) {
     150            0 :             if (out) out->needs_password = 1;
     151            0 :             if (!cb->get_password) {
     152            0 :                 logger_log(LOG_ERROR,
     153              :                            "auth_flow: 2FA required but no get_password callback");
     154            0 :                 return -1;
     155              :             }
     156            0 :             logger_log(LOG_INFO,
     157              :                        "auth_flow: 2FA password required — running SRP flow");
     158              : 
     159            0 :             Account2faPassword params = {0};
     160            0 :             RpcError gp_err = {0};
     161            0 :             if (auth_2fa_get_password(cfg, s, t, &params, &gp_err) != 0) {
     162            0 :                 logger_log(LOG_ERROR,
     163              :                            "auth_flow: account.getPassword failed (%d: %s)",
     164              :                            gp_err.error_code, gp_err.error_msg);
     165            0 :                 return -1;
     166              :             }
     167            0 :             if (!params.has_password) {
     168            0 :                 logger_log(LOG_ERROR,
     169              :                            "auth_flow: SESSION_PASSWORD_NEEDED but server "
     170              :                            "reports no password configured");
     171            0 :                 return -1;
     172              :             }
     173              :             char pwd[128];
     174            0 :             if (cb->get_password(cb->user, pwd, sizeof(pwd)) != 0) {
     175            0 :                 logger_log(LOG_ERROR, "auth_flow: password input failed");
     176            0 :                 return -1;
     177              :             }
     178            0 :             RpcError cp_err = {0};
     179            0 :             int64_t cp_uid = 0;
     180            0 :             if (auth_2fa_check_password(cfg, s, t, &params, pwd,
     181              :                                            &cp_uid, &cp_err) != 0) {
     182            0 :                 logger_log(LOG_ERROR,
     183              :                            "auth_flow: auth.checkPassword failed (%d: %s)",
     184              :                            cp_err.error_code, cp_err.error_msg);
     185            0 :                 return -1;
     186              :             }
     187            0 :             uid = cp_uid;
     188            0 :             break;
     189              :         }
     190            0 :         logger_log(LOG_ERROR, "auth_flow: signIn failed (%d: %s)",
     191              :                    err.error_code, err.error_msg);
     192            0 :         return -1;
     193              :     }
     194              : 
     195            0 :     if (out) {
     196            0 :         out->dc_id = current_dc;
     197            0 :         out->user_id = uid;
     198              :     }
     199            0 :     logger_log(LOG_INFO, "auth_flow: login complete on DC%d, user_id=%lld",
     200              :                current_dc, (long long)uid);
     201              : 
     202              :     /* Persist so the next run can skip sendCode + signIn. */
     203            0 :     if (session_store_save(s, current_dc) != 0) {
     204            0 :         logger_log(LOG_WARN,
     205              :                    "auth_flow: failed to persist session (non-fatal)");
     206              :     }
     207            0 :     return 0;
     208              : }
        

Generated by: LCOV version 2.0-1