Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2FA (response 406) and possible login error 405 fixes #220

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 221 additions & 1 deletion facebook/facebook-api.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ enum
PROP_UID,
PROP_TWEAK,
PROP_WORK,
PROP_MACHINE_ID,
PROP_LOGIN_FIRST_FACTOR,
PROP_TWOFACTOR_CODE,

PROP_N
};
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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" */
Expand Down Expand Up @@ -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)
{
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
5 changes: 4 additions & 1 deletion facebook/facebook-data.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
23 changes: 23 additions & 0 deletions facebook/facebook.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <your code>");
}

static char *
fb_eval_open(struct set *set, char *value)
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down