From 31b56ec07d8b1682a15e1dde114810140dddcb4e Mon Sep 17 00:00:00 2001 From: Janne Paalijarvi Date: Fri, 15 Jul 2022 06:24:42 +0300 Subject: [PATCH] * machine_id props and automatic requesting (Probably helps in other non-stable auth cases also as a side effect.) * 2FA first_factor and the twofactor access code props, storing and use. * Automatic use of the 2FA when user has set the twofactor ccess code. * Twofactor access code automatic invalidation if dependant data has changed or on critical error. * Prompt about how to set the twofactor code. --- facebook/facebook-api.c | 222 ++++++++++++++++++++++++++++++++++++++- facebook/facebook-data.c | 5 +- facebook/facebook.c | 23 ++++ 3 files changed, 248 insertions(+), 2 deletions(-) diff --git a/facebook/facebook-api.c b/facebook/facebook-api.c index b452ccd..6c8392f 100644 --- a/facebook/facebook-api.c +++ b/facebook/facebook-api.c @@ -41,6 +41,9 @@ enum PROP_UID, PROP_TWEAK, PROP_WORK, + PROP_MACHINE_ID, + PROP_LOGIN_FIRST_FACTOR, + PROP_TWOFACTOR_CODE, PROP_N }; @@ -70,6 +73,9 @@ struct _FbApiPrivate gboolean need_work_switch; gchar *sso_verifier; FbId work_community_id; + gchar *machine_id; + gchar *login_first_factor; + gchar *twofactor_code; }; struct _FbApiData @@ -159,6 +165,18 @@ fb_api_set_property(GObject *obj, guint prop, const GValue *val, case PROP_WORK: priv->is_work = g_value_get_boolean(val); break; + case PROP_MACHINE_ID: + g_free(priv->machine_id); + priv->machine_id = g_value_dup_string(val); + break; + case PROP_LOGIN_FIRST_FACTOR: + g_free(priv->login_first_factor); + priv->login_first_factor = g_value_dup_string(val); + break; + case PROP_TWOFACTOR_CODE: + g_free(priv->twofactor_code); + priv->twofactor_code = g_value_dup_string(val); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); @@ -196,6 +214,15 @@ fb_api_get_property(GObject *obj, guint prop, GValue *val, GParamSpec *pspec) case PROP_WORK: g_value_set_boolean(val, priv->is_work); break; + case PROP_MACHINE_ID: + g_value_set_string(val, priv->machine_id); + break; + case PROP_LOGIN_FIRST_FACTOR: + g_value_set_string(val, priv->login_first_factor); + break; + case PROP_TWOFACTOR_CODE: + g_value_set_string(val, priv->twofactor_code); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); @@ -229,6 +256,9 @@ fb_api_dispose(GObject *obj) g_free(priv->token); g_free(priv->contacts_delta); g_free(priv->sso_verifier); + g_free(priv->machine_id); + g_free(priv->login_first_factor); + g_free(priv->twofactor_code); } static void @@ -337,6 +367,48 @@ fb_api_class_init(FbApiClass *klass) "", FALSE, G_PARAM_READWRITE); + + /** + * FbApi:machine_id: + * + * The machine id we ask facebook to generate for us. + * Saved automatically for persistence. + * + * NOT TO BE CONFUSED WITH mid! + */ + props[PROP_MACHINE_ID] = g_param_spec_string( + "machine_id", + "Machine Id", + "Machine Id generated by facebook", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:login_first_factor: + * + * The first factor challenge code fo 2FA. + * Saved automatically for persistence. + */ + props[PROP_LOGIN_FIRST_FACTOR] = g_param_spec_string( + "login_first_factor", + "Login First Factor", + "Login First Factor challenge code for 2FA", + NULL, + G_PARAM_READWRITE); + + /** + * FbApi:twofactor_code: + * + * The 2FA code the user receives via external means. + * User needs to set this manually to the account. + */ + props[PROP_TWOFACTOR_CODE] = g_param_spec_string( + "twofactor_code", + "Twofactor Code", + "Twofactor Code externally received, for 2FA", + NULL, + G_PARAM_READWRITE); + g_object_class_install_properties(gklass, PROP_N, props); /** @@ -591,6 +663,23 @@ fb_api_class_init(FbApiClass *klass) fb_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * FbApi::twofactor-code-prompt: + * @api: The #FbApi. + * + * Emitted when we want to instruct the user about how to input + * new twofactor code. + */ + + g_signal_new("twofactor-code-prompt", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); } static void @@ -652,6 +741,90 @@ fb_api_data_take(FbApi *api, gconstpointer handle) return data; } +static void +fb_api_json_update_from_error_data(FbApi *api, gconstpointer data, gssize size, JsonNode **node) +{ + gchar *str; + FbApiPrivate *priv; + gboolean found_data = FALSE; + GError *err = NULL; + gint64 code; + gint64 uid; + JsonNode *root; + gchar *error_data; + JsonNode *error_data_root; + + g_return_if_fail(FB_IS_API(api)); + priv = api->priv; + + if (G_UNLIKELY(size == 0)) { + return; + } + root = fb_json_node_new(data, size, &err); + if (err != NULL) { + g_clear_error(&err); + return; + } + // We handle all error stuff for info, not just 406 + + error_data = fb_json_node_get_str(root, "$.error_data", &err); + if (err != NULL) { + // No error data. This is ok. + json_node_free(root); + g_clear_error(&err); + return; + } + + error_data_root = fb_json_node_new(error_data, -1, &err); + if (err != NULL) { + g_free(error_data); + json_node_free(root); + return; + } + + + // Finally, parse individual datas + // uid, special case + uid = fb_json_node_get_int(error_data_root, "$.uid", &err); + if (err != NULL) { g_clear_error(&err) ; } else { + if (uid != priv->uid) { + priv->uid = uid; + found_data = TRUE; + } + } + // machine_id + str = fb_json_node_get_str(error_data_root, "$.machine_id", &err); + if (err != NULL) { g_clear_error(&err) ; } else { + if (g_strcmp0(str, priv->machine_id) != 0) { + g_free(priv->machine_id); + priv->machine_id = g_strdup(str); + found_data = TRUE; + } + g_free(str); + } + // login_first_factor + str = fb_json_node_get_str(error_data_root, "$.login_first_factor", &err); + if (err != NULL) { g_clear_error(&err) ; } else { + if (g_strcmp0(str, priv->login_first_factor) != 0) { + g_free(priv->login_first_factor); + priv->login_first_factor = g_strdup(str); + found_data = TRUE; + } + g_free(str); + } + + json_node_free(error_data_root); + g_free(error_data); + json_node_free(root); + + // If the data changed, with high probability we need new code and invalidate old + if (found_data) { + g_free(priv->twofactor_code); + priv->twofactor_code = NULL; + } +} + + static gboolean fb_api_json_chk(FbApi *api, gconstpointer data, gssize size, JsonNode **node) { @@ -681,7 +854,6 @@ fb_api_json_chk(FbApi *api, gconstpointer data, gssize size, JsonNode **node) fb_api_error(api, FB_API_ERROR_GENERAL, "Empty JSON data"); return FALSE; } - fb_util_debug_info("Parsing JSON: %.*s", (gint) size, (const gchar *) data); root = fb_json_node_new(data, size, &err); @@ -711,6 +883,13 @@ fb_api_json_chk(FbApi *api, gconstpointer data, gssize size, JsonNode **node) g_free(priv->token); priv->token = NULL; + + g_free(priv->twofactor_code); + priv->twofactor_code = NULL; + } + + if (code == 406) { + g_signal_emit_by_name(api, "twofactor-code-prompt"); } /* 509 is used for "invalid attachment id" */ @@ -762,6 +941,7 @@ fb_api_json_chk(FbApi *api, gconstpointer data, gssize size, JsonNode **node) return TRUE; } + static gboolean fb_api_http_chk(FbApi *api, FbHttpRequest *req, JsonNode **root) { @@ -781,6 +961,10 @@ fb_api_http_chk(FbApi *api, FbHttpRequest *req, JsonNode **root) FB_API_ERROR_EMIT(api, err, return FALSE); } + // Scan possible error data for important auth state info changes + fb_api_json_update_from_error_data(api, data, size, root); + + // Actual, propagable errors still handled here: if (!fb_api_json_chk(api, data, size, root)) { if (G_UNLIKELY(err != NULL)) { g_error_free(err); @@ -2306,6 +2490,42 @@ fb_api_auth(FbApi *api, const gchar *user, const gchar *pass, const gchar *crede fb_http_values_set_str(prms, "access_token", priv->token); } + if (!(priv->machine_id) || strlen(priv->machine_id) == 0) { + fb_http_values_set_str(prms, "generate_machine_id", "1"); + } else { + fb_http_values_set_str(prms, "machine_id", priv->machine_id); + } + + if (priv->uid && + priv->machine_id && strlen(priv->machine_id) > 0 && + priv->login_first_factor && strlen(priv->login_first_factor) > 0 && + priv->twofactor_code && strlen(priv->twofactor_code) > 0) { + + // Everything ready for the whole 2fa auth set + + // uid, set_int is fine here + fb_http_values_set_int(prms, "uid", priv->uid); + + // device_id comes from somewhere else + + // credentials_type magic + fb_http_values_set_str(prms, "credentials_type", "two_factor"); + + // first_factor, yes cleverly wants it with different name than gives + fb_http_values_set_str(prms, "first_factor", priv->login_first_factor); + + // twofactor_code + fb_http_values_set_str(prms, "twofactor_code", priv->twofactor_code); + + // password actually now same as twofactor_code + fb_http_values_set_str(prms, "password", priv->twofactor_code); + + // userid , same as uid. needs to be here for 2FA to work. + fb_http_values_set_int(prms, "userid", priv->uid); + + // machine_id already there (otherwise we would not be in this loop) + } + fb_api_http_req(api, FB_API_URL_AUTH, "authenticate", "auth.login", prms, fb_api_cb_auth); } diff --git a/facebook/facebook-data.c b/facebook/facebook-data.c index 84dc270..660ac7d 100644 --- a/facebook/facebook-data.c +++ b/facebook/facebook-data.c @@ -34,7 +34,10 @@ static const gchar *fb_props_strs[] = { "cid", "did", "stoken", - "token" + "token", + "machine_id", + "login_first_factor", + "twofactor_code" }; G_DEFINE_TYPE_WITH_PRIVATE(FbData, fb_data, G_TYPE_OBJECT); diff --git a/facebook/facebook.c b/facebook/facebook.c index 9e6d9e3..6bbb2a9 100644 --- a/facebook/facebook.c +++ b/facebook/facebook.c @@ -724,6 +724,16 @@ fb_cb_api_work_sso_login(FbApi *api, gpointer data) g_free(url); } +static void +fb_cb_api_twofactor_code_prompt(FbApi *api, gpointer data) +{ + FbData *fata = data; + struct im_connection *ic; + + ic = fb_data_get_connection(fata); + imcb_log(ic, "If you receive new 2FA code, do like this: acc facebook set twofactor_code "); +} + static char * fb_eval_open(struct set *set, char *value) { @@ -770,6 +780,15 @@ fb_init(account_t *acct) s = set_add(&acct->set, "tweak", NULL, NULL, acct); s->flags = SET_NULL_OK | SET_HIDDEN; + s = set_add(&acct->set, "machine_id", NULL, NULL, acct); + s->flags = SET_NULL_OK | SET_HIDDEN; + + s = set_add(&acct->set, "login_first_factor", NULL, NULL, acct); + s->flags = SET_NULL_OK | SET_HIDDEN; + + s = set_add(&acct->set, "twofactor_code", NULL, NULL, acct); + s->flags = SET_NULL_OK | SET_HIDDEN; + set_add(&acct->set, "group_chat_open", "false", fb_eval_open, acct); set_add(&acct->set, "mark_read", "false", fb_eval_mark_read, acct); set_add(&acct->set, "mark_read_reply", "false", set_eval_bool, acct); @@ -850,6 +869,10 @@ fb_login(account_t *acc) "work-sso-login", G_CALLBACK(fb_cb_api_work_sso_login), fata); + g_signal_connect(api, + "twofactor-code-prompt", + G_CALLBACK(fb_cb_api_twofactor_code_prompt), + fata); if (!fb_data_load(fata)) { imcb_log(ic, "Authenticating");