zerobias / telegram-mtproto

Telegram client api (MTProto) library
MIT License
620 stars 136 forks source link

More docs required - handling 2FA #33

Closed leonerd closed 5 years ago

leonerd commented 7 years ago

I've so far been unable to work out how to handle the SESSION_PASSWORD_NEEDED case for 2FA.

Right now, my code is printing

[14.279] Error 401 SESSION_PASSWORD_NEEDED 2 4

but no promises ever seem to get fulfilled or rejected; it's just sitting indefinitely.

From knowing the Telegram API, I know that I have to call account.getPassword then perform the SHA256 operation on the salt + password, before sending that into auth.checkPassword. I just don't see the bits in telegram-mtproto to let me do that.

goodmind commented 7 years ago

Can you enable DEBUG enviroment variable? And send us log file Like this

DEBUG=* node your_app.js > file.log
leonerd commented 7 years ago

OK, I now have a log that contains various bits of application logic plus the debug stuff you were after.

Once the call to auth.sendCode has completed successfully, my program then calls auth.signIn to supply the phone code:

Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:networker [Api call]  [145.741]  auth.signIn { phone_number: '44xxxxxxxxx',
  phone_code: '21709',
  phone_code_hash: 'xxyyzz' } 6406251757556791212 9 { resultType: 'auth.Authorization',
  messageID: '6406251757556791212' }
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.742]  bcd51581 3168081281 auth.signIn[id]:int
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:networker [Container]  [145.743]  6406251757556791212,6406251757561001868 6406251757561001872 12
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  9299359f 2459514271 http_wait[id]:int
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  1f4 500 f http_wait(max_delay):int
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  96 150 f http_wait(wait_after):int
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  bb8 3000 f http_wait(max_wait):int
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  73f1f8dc 1945237724 CONTAINER[id]:int
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  2 2 CONTAINER[count]:int
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  1ee4f7ac 518322092 CONTAINER[0][msg_id]:long[low]
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  58e79408 1491571720 CONTAINER[0][msg_id]:long[high]
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  9 9 CONTAINER[0][seq_no]:int
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  30 48 CONTAINER[0][bytes]:int
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  1f25378c 522532748 CONTAINER[1][msg_id]:long[low]
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  58e79408 1491571720 CONTAINER[1][msg_id]:long[high]
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  b 11 CONTAINER[1][seq_no]:int
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  10 16 CONTAINER[1][bytes]:int
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  1f253790 522532752 message_id:long[low]
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  58e79408 1491571720 message_id:long[high]
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  c 12 seq_no:int
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:type-buffer [writeInt]  [145.744]  68 104 message_data_length:int
[145.962] Error 401 SESSION_PASSWORD_NEEDED 2 4

It then continues via some more HTTP hits apparently. I've trimmed out some of the buffers here as they might contain auth keys:

Fri, 07 Apr 2017 13:28:40 GMT follow-redirects options { maxRedirects: 21,
  protocol: 'http:',
  hostname: '149.154.167.91',
  port: null,
  path: '/apiw1',
  method: 'post',
  headers: { 'User-Agent': 'axios/0.15.3', 'Content-Length': 136 },
  agent: undefined,
  auth: undefined }
Fri, 07 Apr 2017 13:28:40 GMT follow-redirects options { maxRedirects: 21,
  protocol: 'http:',
  hostname: '149.154.167.91',
  port: null,
  path: '/apiw1',
  method: 'post',
  headers: { 'User-Agent': 'axios/0.15.3', 'Content-Length': 120 },
  agent: undefined,
  auth: undefined }
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl [int bytes]  [145.950]  abxxxx auth_key_id:int64
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl [int bytes]  [145.950]  5105xxxx msg_key:int128
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl [raw bytes]  [145.950]  4567d47a4xxxxxxxx encrypted_data
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl [int bytes]  [145.953]  2d7cxxxx salt:int64
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl [int bytes]  [145.953]  170d9705a4e3e553 session_id:int64
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:mediator [read][int]  [145.953]  message_id:long[low] 1248567297
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:mediator [read][int]  [145.953]  message_id:long[high] 1491571720
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:mediator [read][int]  [145.953]  seq_no:int 5
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:mediator [read][int]  [145.953]  message_data[length]:int 44
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl [raw bytes]  [145.954]  016d5cf3acf7e41e0894e75819ca4421910100001753455353494f4e5f50415353574f52445f4e4545444544 message_data
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:mediator [read][int]  [145.954]  INPUT[id] 4082920705
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:mediator [read][int]  [145.954]  INPUT[rpc_result][req_msg_id]:long[low] 518322092
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:mediator [read][int]  [145.955]  INPUT[rpc_result][req_msg_id]:long[high] 1491571720
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:mediator [read][int]  [145.955]  INPUT[rpc_result][result][id] 558156313
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:mediator [read][int]  [145.955]  INPUT[rpc_result][result][rpc_error][error_code]:int 401
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:mediator [read][bytes]  [145.956]  53455353494f4e5f50415353574f52445f4e4545444544 INPUT[rpc_result][result][rpc_error][error_message]:string:bytes
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:tl:mediator [read][string]  [145.956]  SESSION_PASSWORD_NEEDED INPUT[rpc_result][result][rpc_error][error_message]:string
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:networker [Server response]  [145.957]  4 { response: 
   { _: 'rpc_result',
     req_msg_id: '6406251757556791212',
     result: 
      { _: 'rpc_error',
        error_code: 401,
        error_message: 'SESSION_PASSWORD_NEEDED' } },
  messageID: '6406251758287036417',
  sessionID: Uint8Array [ 23, 13, 151, 5, 164, 227, 229, 83 ],
  seqNo: 5 }
Fri, 07 Apr 2017 13:28:40 GMT telegram-mtproto:networker [ERROR][Rpc error]  [145.959]  { code: 401,
  type: 'SESSION_PASSWORD_NEEDED',
  description: 'CODE#401 SESSION_PASSWORD_NEEDED',
  originalError: 
   { _: 'rpc_error',
     error_code: 401,
     error_message: 'SESSION_PASSWORD_NEEDED' } }
goodmind commented 7 years ago

@leonerd can you add third argument to your api calls for auth.* methods

api('...', {...}, { createNetworker: true })

And see if it works?

leonerd commented 7 years ago

No change I'm afraid. This promise is just sitting around never making progress. I'll try adding more logging my end and see what happens.

leonerd commented 7 years ago

I've now put the debug logging right up in the actual call to MTProto itself:

    return client("auth.signIn", {
        phone_number:    phone_number,
        phone_code:      phone_code,
        phone_code_hash: phone_code_hash
    }, { createNetworker: true }).then(
        (result) => { console.log("auth.signIn resolved", result); return result },
        (err)    => { console.log("auth.signIn rejected", err); throw err }
    );

Neither of those log lines ever gets printed.

goodmind commented 7 years ago

Weird, can you try develop branch of telegram-mtproto? I just tested authorization in goodmind/treact and it worked

leonerd commented 7 years ago

Lets go with "maybe". Is that something I can just make npm install do for me?

npm install telegram-mtproto/develop

doesn't immediately appear to DTRT

goodmind commented 7 years ago

It looks like npm can't do it

leonerd commented 7 years ago

I think that made it a lot worse. Now my code doesn't even start:

$ npm install 'zerobias/telegram-mtproto#develop'
matrix-appservice-tg@0.0.0 /home/leo/src/matrix/matrix-appservice-tg
└─┬ telegram-mtproto@2.2.2  (git://github.com/zerobias/telegram-mtproto.git#8ec1902a9cce3316a5f78b06a135d29e152f832c)
  ├── bluebird@3.5.0 
  ├── debug@2.6.3 
  ├── pako@1.0.5 
  └── UNMET PEER DEPENDENCY webpack@>=0.9 <2 || ^2.1.0-beta || ^2.2.0

npm WARN worker-loader@0.8.0 requires a peer of webpack@>=0.9 <2 || ^2.1.0-beta || ^2.2.0 but none was installed.
npm WARN matrix-appservice-tg@0.0.0 No repository field.

and then

Error: Cannot find module 'telegram-mtproto'
    at Function.Module._resolveFilename (module.js:469:15)
    at Function.Module._load (module.js:417:25)
...

Perhaps this UNMET PEER DEPENDENCY is to blame?

$ ls node_modules/telegram-mtproto/
CHANGELOG.md  index.d.ts  node_modules  README.md  src
examples      LICENSE     package.json  schema     test

appears to be missing a lib directory or an index.js.

goodmind commented 7 years ago

@leonerd can you do this like this:

git clone https://github.com/zerobias/telegram-mtproto
npm run prepublish
npm link
cd ~/path/to/your-project/
npm link telegram-mtproto

and then start your app. We should definitely publish develop branch to npm as well (maybe with another tag like telegram-mtproto@develop)

leonerd commented 7 years ago

OK well that made it start up at least. ((For future posterity, you missed the npm install step after git clone but I inferred that))

But still exactly the same result as before - the last output printed is

[62.608] Error 401 SESSION_PASSWORD_NEEDED 2 4

with no sign of my resolve or reject debug print.

(BTW no need to /cc me - I see the replies :) )

goodmind commented 7 years ago

I think this is because you're on 4th datacenter. And library only works on 2, because we can't test it on another datacenters :(.

Can you add to third param dcID: 4 or dcID: 2 and see if it works?

leonerd commented 7 years ago

No effect; code now looks

    return client("auth.signIn", {
        phone_number:    phone_number,
        phone_code:      phone_code,
        phone_code_hash: phone_code_hash
    }, { createNetworker: true, dcID: 4 }).then(
        (result) => { console.log("auth.signIn resolved", result); return result },
        (err)    => { console.log("auth.signIn rejected", err); throw err }
    );

I'm not sure that DC is the thing here. This code is clearly receiving the error - my earlier debug paste shows the error structure arriving in the MTProto stream layer. It just never gets around to rejecting the toplevel client() promise (or resolving it with some sort of error object, I don't mind which as long as I know).

leonerd commented 7 years ago

(I don't mind which as long as I know).

This was more the thrust of my original bug title - the fact that very little documentation on how to use this library actually exists; and especially there's nothing written about how to handle errors that come back from these promises. What even will they look like? I'm familiar with the steps at the Telegram API level that I need to take for 2FA, what I don't know is how to interact with this library to make them happen.

leonerd commented 7 years ago

If you need to test the DC redirection logic, is it possible to do so in reverse? I.e. make your initial connection to, say, DC4 and have the redirect push you the other way to 2, when you get to the auth stage? That way you can test with your account in DC2.

Also this in particular is a 2FA problem - the login flow all works fine if I disable 2FA. So you'll need to test with a 2FA-enabled account.

goodmind commented 7 years ago

this is weird, because I have working authorization in my app here

goodmind commented 7 years ago

/cc @zerobias

goodmind commented 7 years ago

I'm sure that it is related to datacenters somehow (not redirection)

goodmind commented 7 years ago

@leonerd what about dcID: 2 ?

zerobias commented 7 years ago

It seems like an issue with dc redirection handling; I'm working on it right now.

Valid usage examples are here https://github.com/zerobias/telegram-mtproto/tree/develop/examples

leonerd commented 7 years ago

Valid usage examples are here https://github.com/zerobias/telegram-mtproto/tree/develop/examples

I'm aware of those examples. None of them involve 2FA at all.

In particular, the API flow I'm looking for is the one wherein auth.signIn fails with SESSION_PASSWORD_NEEDED, so you

  1. call account.getPassword to obtain the password hint and hash
  2. ask the user for their 2FA password by printing this hint
  3. calculate the SHA256 hash by concatenating the salt, password, and salt again
  4. send this hash back via auth.checkPassword

In your examples dir, I don't see any that call account.getPassword nor auth.checkPassword, not any use of SHA256. I'm sure I can work out how to make those various calls from the general API structure here; my concern is more that I'm having trouble getting the SESSION_PASSWORD_NEEDED error to appear out of the API, so I wonder if it's known working or not yet.

leonerd commented 7 years ago

Ahh... https://github.com/goodmind/treact/blob/development/src/app/redux/api/auth.ts#L47 seems to be indicative. I'll see if I can get something like that to work.

leonerd commented 7 years ago

@leonerd what about dcID: 2 ?

Ahah! That seems to be the missing killer feature - that has caused my code to behave differently anyway. It turns out that

{ dcID: 2 }

is sufficient; it doesn't also need to set createNetworker

zerobias commented 7 years ago

@leonerd I added separated plugin to make proper password hash. Usage example also in its jsDoc https://github.com/zerobias/telegram-mtproto/blob/develop/src/plugins/math-help.js#L10

Develop branch can be installed via npm install --save telegram-mtproto@alpha

goodmind commented 7 years ago

Killer feature is that this library only works on second datacenter :P

leonerd commented 7 years ago

OK, well DC redirection issues aside, I have now at least worked out the code required for doing 2FA. Would you like me to send you a PR adding a new example, showing the bits of API I've now learned? Such an example would no-doubt help the next person with this question.

zerobias commented 7 years ago

@leonerd Yes, of course, new examples are always welcome)

leonerd commented 7 years ago

https://github.com/zerobias/telegram-mtproto/pull/39

wfjsw commented 7 years ago

I am wondering whether Buffer or Uint8Array should be used in hash calculating(i thought we should to UTF-8 first in this) & checkPassword. (Same question in other bytes operations) Also from the debug log, it stopped after auth.signIn

madtaras commented 6 years ago

@zerobias any updates on this problem? Is there any ETA when auth.signIn Promise will reject error, so code can handle SESSION_PASSWORD_NEEDED?

madtaras commented 6 years ago

@leonerd did you find a solution on how to get SESSION_PASSWORD_NEEDED error from signIn call?

lluiscab commented 6 years ago

Anyone actually found a solution for SESSION_PASSWORD_NEEDED?