Browse Source

Basic Telegram Login Widget (redirect URL type) integration.

Andrii Zadorozhnii 7 years ago
parent
commit
7912d1d73c
3 changed files with 87 additions and 1 deletions
  1. 19 1
      README.md
  2. 6 0
      priv/telegram_login.dtl
  3. 62 0
      src/telegram.erl

+ 19 - 1
README.md

@@ -13,6 +13,7 @@ Supported Methods
 * Facebook
 * Github
 * Microsoft
+* Telegram
 
 API
 ---
@@ -60,6 +61,7 @@ Authentication endpoints can be configured in your `sys.config` under avz applic
 Available settings listed below with test applications configured for each provider and will
 call you back on `http://localhost:8000/login`.
 
+
 ```erlang
 {
   ...
@@ -92,12 +94,28 @@ call you back on `http://localhost:8000/login`.
         % Microsoft Account Login
         {ms_client_id, "54385d15-f1e0-4fcf-9bf4-042d740e0df4"},
         {ms_client_secret, "jU0tStEzRdDPFwL9NdVGYxo"},
-        {ms_redirect_uri, "http://localhost:8000/login"}
+        {ms_redirect_uri, "http://localhost:8000/login"},
+        
+        % Telegram Login Widget
+        {tl_bot, "NYNJA_bot"},
+        {tl_bot_token, "548231922:AAHmXMMr38XGtH0tJMDUdiByheT2mZ7qkVI"}
+        {tl_auth_url, "http://127.0.0.1/login"},
+        {tl_request_access, "write"}
+        {tl_btn_size, "large"},
+        {tl_btn_radius, "20"},
   ]}
   ...
 }
 ```
 
+Telegram Notes
+---------------
+Login widget is displayed within the iframe, so the battle of `CPS` and `x-frame-options` is expected in different browsers.
+
+When setting a domain in BotFather with `/setdomain`, please note that telegram will cut the port part of your domain in the `X-Frame-Options` and `Content-Security-Policy` response headers. 
+
+So in fact you are limited to use 80 and 443 ports only.
+
 Credits
 -------
 

+ 6 - 0
priv/telegram_login.dtl

@@ -0,0 +1,6 @@
+<script async src="https://telegram.org/js/telegram-widget.js?4" 
+    data-telegram-login = {{bot}}
+    data-size           = {{size}}
+    data-size           = {{radius}}
+    data-auth-url       = {{auth_url}}
+    data-request-access = {{request_access}}></script>

+ 62 - 0
src/telegram.erl

@@ -0,0 +1,62 @@
+-module(telegram).
+-include_lib("avz/include/avz.hrl").
+-include_lib("nitro/include/nitro.hrl").
+-include_lib("kvs/include/user.hrl").
+-compile(export_all).
+-export(?API).
+
+-define(TL_BOT_NAME,    application:get_env(avz, tl_bot, [])).
+-define(TL_BOT_TOKEN,   application:get_env(avz, tl_bot_token, [])).
+-define(TL_AUTH_URL,    application:get_env(avz, tl_auth_url, [])).
+-define(TL_ACCESS,      application:get_env(avz, tl_request_access, "write")).
+-define(TL_BTN_SIZE,    application:get_env(avz, tl_btn_size, "large")).
+-define(TL_BTN_RADIUS,  application:get_env(avz, tl_btn_radius, "20")).
+
+-define(TL_USER, [<<"id">>, <<"first_name">>, <<"last_name">>,<<"username">>,<<"auth_date">>, <<"photo_url">>]).
+-define(ATTS, #{email => <<"id">>}).
+
+api_event(_,_,_) -> ok.
+
+registration_data(Props, telegram, Ori) -> 
+    Id = proplists:get_value(<<"id">>, Props),
+    UserName = binary_to_list(proplists:get_value(<<"username">>, Props)),
+    Email = email_prop(Props,telegram),
+    Ori#user{   username = re:replace(UserName, "\\.", "_", [{return, list}]),
+                display_name = proplists:get_value(<<"username">>, Props),
+                images = avz:update({tl_avatar,proplists:get_value(<<"photo_url">>, Props)},Ori#user.images),
+                names = proplists:get_value(<<"first_name">>, Props),
+                email = Email,
+                surnames = proplists:get_value(<<"last_name">>, Props),
+                tokens = avz:update({telegram,Id},Ori#user.tokens),
+                register_date = os:timestamp(),
+                status = ok }.
+
+index(K) -> maps:get(K, ?ATTS, wf:to_binary(K)).
+email_prop(Props, telegram) -> proplists:get_value(maps:get(email,?ATTS), Props).
+
+login_button() ->
+    #dtl{bind_script=false, file="telegram_login", ext="dtl", bindings=[
+        {bot,             ?TL_BOT_NAME},
+        {size,            ?TL_BTN_SIZE}, 
+        {radius,          ?TL_BTN_RADIUS},
+        {auth_url,        ?TL_AUTH_URL},
+        {request_access,  ?TL_ACCESS} ]}.
+
+event(E) -> ok.
+sdk() -> [].
+
+% HMAC-SHA-256 signature of the data-check-string with the SHA256 hash of the bot's token used as a secret key
+callback() ->
+    Hash = wf:q(<<"hash">>),
+
+    Rec  = lists:filter(fun({_, undefined}) -> false; (_) -> true end, [ {T, wf:q(T)} || T <- lists:sort(?TL_USER) ]),
+    Data = lists:join(<<"\n">>, [unicode:characters_to_nfkc_binary([K, <<"=">>, V]) || {K, V} <- Rec]),
+
+    case crypto:hmac(sha256, crypto:hash(sha256, ?TL_BOT_TOKEN), Data) of <<X:256/big-unsigned-integer>> ->
+        case list_to_binary(lists:flatten(io_lib:format("~64.16.0b", [X]))) of 
+          Hash ->
+              avz:login(telegram, Rec);
+          _ -> skip
+        end;
+        _ -> skip
+    end.