From e67610c22663ad77063e725d44cc8397ecbc4995 Mon Sep 17 00:00:00 2001 From: Vladimir Dronnikov Date: Wed, 20 Mar 2013 13:31:30 +0400 Subject: [PATCH] Refactor common code --- README.md | 44 +++------ src/cowboy_request.erl | 2 +- src/cowboy_social.erl | 86 ++++++---------- src/cowboy_social_github.erl | 154 ++++++++--------------------- src/cowboy_social_google.erl | 160 ++++++++---------------------- src/cowboy_social_mailru.erl | 169 +++++++++----------------------- src/cowboy_social_paypal.erl | 75 ++++++++++++++ src/cowboy_social_vkontakte.erl | 168 +++++++++---------------------- src/cowboy_social_yandex.erl | 161 +++++++++--------------------- 9 files changed, 343 insertions(+), 676 deletions(-) create mode 100644 src/cowboy_social_paypal.erl diff --git a/README.md b/README.md index 5ef27f0..2b54f59 100644 --- a/README.md +++ b/README.md @@ -18,46 +18,28 @@ Router configuration -------------- ```erlang -{"/auth/google/:action", cowboy_social_google, [ +{"/auth/google/:action", cowboy_social, [ + {provider, cowboy_social_google}, % At the end of the flow this handler will be called as % Mod:Fun({ok, Auth, Profile}, Req) or Mod:Fun({error, Reason}, Req) {handler, {Mod, Fun}}, - {client_id, <<"440647648374.apps.googleusercontent.com">>}, + {client_id, <<"...">>}, {client_secret, <<"...">>}, {scope, <<"https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile">>}, {callback_uri, <<"/auth/google/callback">>} ]}. -{"/auth/github/:action", cowboy_social_github, [ - {handler, {Mod, Fun}}, - {client_id, <<"883b68d607abddc24f77">>}, - {client_secret, <<"...">>}, - {scope, <<>>}, - {callback_uri, <<"/auth/github/callback">>} -]}. -{"/auth/yandex/:action", cowboy_social_yandex, [ - {handler, {Mod, Fun}}, - {client_id, <<"f44bd59ddfbe408ab1d29151126385a6">>}, - {client_secret, <<"...">>}, - {scope, <<>>}, - {callback_uri, <<"/auth/yandex/callback">>} -]}. -{"/auth/vkontakte/:action", cowboy_social_vkontakte, [ - {handler, {Mod, Fun}}, - {client_id, <<"3473116">>}, - {client_secret, <<"...">>}, - {scope, <<"uid,first_name,last_name,sex,photo">>}, - {callback_uri, <<"/auth/vkontakte/callback">>} -]}. -{"/auth/mailru/:action", cowboy_social_mailru, [ - {handler, {Mod, Fun}}, - {client_id, <<"701614">>}, - {client_secret, <<"...">>}, - {secret_key, <<"...">>}, - {scope, <<>>}, - {callback_uri, <<"/auth/mailru/callback">>} -]}. ``` +Supported providers +-------------- +- Github +- Google +- Mail.ru +- PayPal +- Vkontakte +- Yandex +- add more, this very simple + License (MIT) ------- diff --git a/src/cowboy_request.erl b/src/cowboy_request.erl index a78ea6a..7e0a296 100644 --- a/src/cowboy_request.erl +++ b/src/cowboy_request.erl @@ -56,7 +56,7 @@ request(Method, URL, Headers, Body) -> % pecypc_log:info({reqerr, _Else}), {error, failed} end, -% pecypc_log:info({res, _ResHeaders, Result}), +% pecypc_log:info({res, Result}), Result. urlencode(Bin) when is_binary(Bin) -> diff --git a/src/cowboy_social.erl b/src/cowboy_social.erl index 7fd10ea..f9bf2a5 100644 --- a/src/cowboy_social.erl +++ b/src/cowboy_social.erl @@ -15,99 +15,73 @@ %% init(_Transport, Req, Opts) -> - {ok, Req, Opts}. + % compose full redirect URI + case key(callback_uri, Opts) of + << "http://", _/binary >> -> {ok, Req, Opts}; + << "https://", _/binary >> -> {ok, Req, Opts}; + Relative -> + {SelfUri, Req2} = cowboy_req:host_url(Req), + {ok, Req2, lists:keyreplace(callback_uri, 1, Opts, + {callback_uri, << SelfUri/binary, Relative/binary >>})} + end. terminate(_Reason, _Req, _State) -> ok. -%% -%% {"/auth/:provider/:action", cowboy_social, [...]}. -%% handle(Req, Opts) -> - Provider = key(provider, Opts), % extract flow action name {Action, Req2} = cowboy_req:binding(action, Req), - % construct callback URI - {SelfUri, Req3} = cowboy_req:host_url(Req2), - [FullPath] = cowboy_req:get([path], Req3), - CallbackUrl = << SelfUri/binary, FullPath/binary >>, % perform flow action - {ok, Req4} = handle_request(Action, Provider, Opts, CallbackUrl, Req3), - {ok, Req4, undefined}. + {ok, Req3} = handle_request(Action, Req2, Opts), + {ok, Req3, undefined}. %% %% redirect to provider authorization page, expect it to redirect %% to our next handler %% -handle_request(<<"login">>, P, O, U, Req) -> - AuthUrl = << (cowboy_social_providers:authorize_url(P))/binary, $?, - (cowboy_request:urlencode([ - {<<"client_id">>, key(client_id, O)}, - {<<"redirect_uri">>, binary:replace(U, <<"/login">>, <<"/callback">>)}, - {<<"response_type">>, <<"code">>}, - {<<"scope">>, key(scope, O)} - ]))/binary >>, - cowboy_req:reply(303, [{<<"location">>, AuthUrl}], <<>>, Req); +handle_request(<<"login">>, Req, Opts) -> + cowboy_req:reply(302, [ + {<<"location">>, (key(provider, Opts)):get_authorize_url(Opts)} + ], <<>>, Req); %% %% provider redirected back to us with authorization code %% -handle_request(<<"callback">>, P, O, U, Req) -> +handle_request(<<"callback">>, Req, Opts) -> case cowboy_req:qs_val(<<"code">>, Req) of {undefined, Req2} -> - finish({error, nocode}, Req2); + finish({error, nocode}, Req2, Opts); {Code, Req2} -> - get_access_token(P, O, U, Code, Req2) + get_access_token(Code, Req2, Opts) end; %% %% catchall %% -handle_request(_, _, _, _, Req) -> +handle_request(_, Req, _) -> {ok, Req2} = cowboy_req:reply(404, [], <<>>, Req), {ok, Req2, undefined}. %% %% exchange authorization code for auth token %% -get_access_token(P, O, U, Code, Req) -> - case cowboy_request:post_for_json(cowboy_social_providers:token_url(P), [ - {<<"code">>, Code}, - {<<"client_id">>, key(client_id, O)}, - {<<"client_secret">>, key(client_secret, O)}, - {<<"redirect_uri">>, U}, - {<<"grant_type">>, <<"authorization_code">>} - ]) - of - {ok, Auth} -> - get_user_profile(P, O, Auth, Req); - _ -> - finish({error, notoken}, Req) - end. +get_access_token(Code, Req, Opts) -> + {ok, Auth} = (key(provider, Opts)):get_access_token(Code, Opts), + get_user_profile(Auth, Req, Opts). %% -%% use auth tocken to extract info from user profile -%% -get_user_profile(P, O, Auth, Req) -> - AccessToken = key(<<"access_token">>, Auth), - case cowboy_request:get_json(cowboy_social_providers:profile_url(P), [ - {<<"access_token">>, AccessToken} - | cowboy_social_providers:custom_data(P, AccessToken, O) - ]) - of - {ok, Profile} -> - finish({ok, cowboy_social_providers:normalize_profile(P, Auth, Profile)}, - Req); - _ -> - finish({error, noprofile}, Req) - end. +%% use auth token to extract info from user profile +%% +get_user_profile(Auth, Req, Opts) -> + {ok, Profile} = (key(provider, Opts)):get_user_profile(Auth, Opts), + finish({ok, Auth, Profile}, Req, Opts). %% %% finalize application flow by calling callback handler %% -finish(Status, Req) -> - {{M, F}, Req2} = cowboy_req:meta(callback, Req), - M:F(Status, Req2). +finish(Status, Req, Opts) -> + {M, F} = key(handler, Opts), + M:F(Status, Req). %% %%------------------------------------------------------------------------------ diff --git a/src/cowboy_social_github.erl b/src/cowboy_social_github.erl index b2325b7..0d43d98 100644 --- a/src/cowboy_social_github.erl +++ b/src/cowboy_social_github.erl @@ -5,8 +5,11 @@ -module(cowboy_social_github). -author('Vladimir Dronnikov '). --behaviour(cowboy_http_handler). --export([init/3, terminate/3, handle/2]). +-export([ + get_authorize_url/1, + get_access_token/2, + get_user_profile/2 + ]). %% %%------------------------------------------------------------------------------ @@ -14,95 +17,50 @@ %%------------------------------------------------------------------------------ %% -init(_Transport, Req, Opts) -> - % compose full redirect URI - {SelfUri, Req2} = cowboy_req:host_url(Req), - {ok, Req2, lists:keyreplace(callback_uri, 1, Opts, - {callback_uri, << SelfUri/binary, (key(callback_uri, Opts))/binary >>})}. - -terminate(_Reason, _Req, _State) -> - ok. - -handle(Req, Opts) -> - % extract flow action name - {Action, Req2} = cowboy_req:binding(action, Req), - % perform flow action - {ok, Req3} = handle_request(Action, Req2, Opts), - {ok, Req3, undefined}. - -%% -%% redirect to provider authorization page, expect it to redirect -%% to our next handler -%% -handle_request(<<"login">>, Req, Opts) -> - AuthUrl = << (authorize_url())/binary, $?, - (cowboy_request:urlencode([ - {<<"client_id">>, key(client_id, Opts)}, - {<<"redirect_uri">>, key(callback_uri, Opts)}, - {<<"scope">>, key(scope, Opts)} - ]))/binary >>, - cowboy_req:reply(303, [{<<"location">>, AuthUrl}], <<>>, Req); - -%% -%% provider redirected back to us with authorization code -%% -handle_request(<<"callback">>, Req, Opts) -> - case cowboy_req:qs_val(<<"code">>, Req) of - {undefined, Req2} -> - finish({error, nocode}, Req2, Opts); - {Code, Req2} -> - get_access_token(Code, Req2, Opts) - end; - %% -%% catchall +%% get URL of provider authorization page %% -handle_request(_, Req, _) -> - {ok, Req2} = cowboy_req:reply(404, [], <<>>, Req), - {ok, Req2, undefined}. +get_authorize_url(Opts) -> + << "https://github.com/login/oauth/authorize", $?, + (cowboy_request:urlencode([ + {client_id, key(client_id, Opts)}, + {redirect_uri, key(callback_uri, Opts)}, + {scope, key(scope, Opts)} + ]))/binary >>. %% %% exchange authorization code for auth token %% -get_access_token(Code, Req, Opts) -> - try cowboy_request:post_for_json(token_url(), [ - {<<"code">>, Code}, - {<<"client_id">>, key(client_id, Opts)}, - {<<"client_secret">>, key(client_secret, Opts)}, - {<<"redirect_uri">>, key(callback_uri, Opts)} - ]) - of - {ok, Auth} -> - get_user_profile(Auth, Req, Opts); - _ -> - finish({error, notoken}, Req, Opts) - catch _:_ -> - finish({error, notoken}, Req, Opts) - end. - -%% -%% use auth token to extract info from user profile -%% -get_user_profile(Auth, Req, Opts) -> - AccessToken = key(<<"access_token">>, Auth), - try cowboy_request:get_json(profile_url(), [ - {<<"access_token">>, AccessToken} - ]) - of - {ok, Profile} -> - finish({ok, normalize_auth(Auth), normalize_profile(Profile)}, Req, Opts); - _ -> - finish({error, noprofile}, Req, Opts) - catch _:_ -> - finish({error, noprofile}, Req, Opts) - end. +get_access_token(Code, Opts) -> + {ok, Auth} = cowboy_request:post_for_json( + <<"https://github.com/login/oauth/access_token">>, [ + {code, Code}, + {client_id, key(client_id, Opts)}, + {client_secret, key(client_secret, Opts)}, + {redirect_uri, key(callback_uri, Opts)} + ]), + {ok, [ + {access_token, key(<<"access_token">>, Auth)}, + {token_type, key(<<"token_type">>, Auth)}, + {expires_in, 0} + ]}. %% -%% finalize application flow by calling callback handler +%% extract info from user profile %% -finish(Status, Req, Opts) -> - {M, F} = key(handler, Opts), - M:F(Status, Req). +get_user_profile(Auth, _Opts) -> + {ok, Profile} = cowboy_request:get_json( + <<"https://api.github.com/user">>, [ + {access_token, key(access_token, Auth)} + ]), + {ok, [ + {id, << "github:", + (list_to_binary(integer_to_list(key(<<"id">>, Profile))))/binary >>}, + {provider, <<"github">>}, + {email, key(<<"email">>, Profile)}, + {name, key(<<"name">>, Profile)}, + {avatar, key(<<"avatar_url">>, Profile)} + ]}. %% %%------------------------------------------------------------------------------ @@ -113,35 +71,3 @@ finish(Status, Req, Opts) -> key(Key, List) -> {_, Value} = lists:keyfind(Key, 1, List), Value. - -%% -%%------------------------------------------------------------------------------ -%% Provider details -%%------------------------------------------------------------------------------ -%% - -authorize_url() -> - <<"https://github.com/login/oauth/authorize">>. - -token_url() -> - <<"https://github.com/login/oauth/access_token">>. - -profile_url() -> - <<"https://api.github.com/user">>. - -normalize_auth(Auth) -> - [ - {access_token, key(<<"access_token">>, Auth)}, - {token_type, key(<<"token_type">>, Auth)}, - {expires_in, 0} - ]. - -normalize_profile(Raw) -> - [ - {id, << "github:", - (list_to_binary(integer_to_list(key(<<"id">>, Raw))))/binary >>}, - {provider, <<"github">>}, - {email, key(<<"email">>, Raw)}, - {name, key(<<"name">>, Raw)}, - {avatar, key(<<"avatar_url">>, Raw)} - ]. diff --git a/src/cowboy_social_google.erl b/src/cowboy_social_google.erl index 77d061f..97931d7 100644 --- a/src/cowboy_social_google.erl +++ b/src/cowboy_social_google.erl @@ -5,8 +5,11 @@ -module(cowboy_social_google). -author('Vladimir Dronnikov '). --behaviour(cowboy_http_handler). --export([init/3, terminate/3, handle/2]). +-export([ + get_authorize_url/1, + get_access_token/2, + get_user_profile/2 + ]). %% %%------------------------------------------------------------------------------ @@ -14,97 +17,53 @@ %%------------------------------------------------------------------------------ %% -init(_Transport, Req, Opts) -> - % compose full redirect URI - {SelfUri, Req2} = cowboy_req:host_url(Req), - {ok, Req2, lists:keyreplace(callback_uri, 1, Opts, - {callback_uri, << SelfUri/binary, (key(callback_uri, Opts))/binary >>})}. - -terminate(_Reason, _Req, _State) -> - ok. - -handle(Req, Opts) -> - % extract flow action name - {Action, Req2} = cowboy_req:binding(action, Req), - % perform flow action - {ok, Req3} = handle_request(Action, Req2, Opts), - {ok, Req3, undefined}. - -%% -%% redirect to provider authorization page, expect it to redirect -%% to our next handler %% -handle_request(<<"login">>, Req, Opts) -> - AuthUrl = << (authorize_url())/binary, $?, - (cowboy_request:urlencode([ - {<<"client_id">>, key(client_id, Opts)}, - {<<"redirect_uri">>, key(callback_uri, Opts)}, - {<<"response_type">>, <<"code">>}, - {<<"scope">>, key(scope, Opts)} - ]))/binary >>, - cowboy_req:reply(303, [{<<"location">>, AuthUrl}], <<>>, Req); - -%% -%% provider redirected back to us with authorization code +%% get URL of provider authorization page %% -handle_request(<<"callback">>, Req, Opts) -> - case cowboy_req:qs_val(<<"code">>, Req) of - {undefined, Req2} -> - finish({error, nocode}, Req2, Opts); - {Code, Req2} -> - get_access_token(Code, Req2, Opts) - end; - -%% -%% catchall -%% -handle_request(_, Req, _) -> - {ok, Req2} = cowboy_req:reply(404, [], <<>>, Req), - {ok, Req2, undefined}. +get_authorize_url(Opts) -> + << "https://accounts.google.com/o/oauth2/auth", $?, + (cowboy_request:urlencode([ + {client_id, key(client_id, Opts)}, + {redirect_uri, key(callback_uri, Opts)}, + {response_type, <<"code">>}, + {scope, key(scope, Opts)} + ]))/binary >>. %% %% exchange authorization code for auth token %% -get_access_token(Code, Req, Opts) -> - try cowboy_request:post_for_json(token_url(), [ - {<<"code">>, Code}, - {<<"client_id">>, key(client_id, Opts)}, - {<<"client_secret">>, key(client_secret, Opts)}, - {<<"redirect_uri">>, key(callback_uri, Opts)}, - {<<"grant_type">>, <<"authorization_code">>} - ]) - of - {ok, Auth} -> - get_user_profile(Auth, Req, Opts); - _ -> - finish({error, notoken}, Req, Opts) - catch _:_ -> - finish({error, notoken}, Req, Opts) - end. - -%% -%% use auth token to extract info from user profile -%% -get_user_profile(Auth, Req, Opts) -> - AccessToken = key(<<"access_token">>, Auth), - try cowboy_request:get_json(profile_url(), [ - {<<"access_token">>, AccessToken} - ]) - of - {ok, Profile} -> - finish({ok, normalize_auth(Auth), normalize_profile(Profile)}, Req, Opts); - _ -> - finish({error, noprofile}, Req, Opts) - catch _:_ -> - finish({error, noprofile}, Req, Opts) - end. +get_access_token(Code, Opts) -> + {ok, Auth} = cowboy_request:post_for_json( + <<"https://accounts.google.com/o/oauth2/token">>, [ + {code, Code}, + {client_id, key(client_id, Opts)}, + {client_secret, key(client_secret, Opts)}, + {redirect_uri, key(callback_uri, Opts)}, + {grant_type, <<"authorization_code">>} + ]), + {ok, [ + {access_token, key(<<"access_token">>, Auth)}, + {token_type, key(<<"token_type">>, Auth)}, + {expires_in, key(<<"expires_in">>, Auth)} + ]}. %% -%% finalize application flow by calling callback handler +%% extract info from user profile %% -finish(Status, Req, Opts) -> - {M, F} = key(handler, Opts), - M:F(Status, Req). +get_user_profile(Auth, _Opts) -> + {ok, Profile} = cowboy_request:get_json( + <<"https://www.googleapis.com/oauth2/v1/userinfo">>, [ + {access_token, key(access_token, Auth)} + ]), + {ok, [ + {id, << "google:", (key(<<"id">>, Profile))/binary >>}, + {provider, <<"google">>}, + {email, key(<<"email">>, Profile)}, + {name, key(<<"name">>, Profile)}, + {avatar, key(<<"picture">>, Profile)}, + {gender, key(<<"gender">>, Profile)}, + {locale, key(<<"locale">>, Profile)} + ]}. %% %%------------------------------------------------------------------------------ @@ -115,36 +74,3 @@ finish(Status, Req, Opts) -> key(Key, List) -> {_, Value} = lists:keyfind(Key, 1, List), Value. - -%% -%%------------------------------------------------------------------------------ -%% Provider details -%%------------------------------------------------------------------------------ -%% - -authorize_url() -> - <<"https://accounts.google.com/o/oauth2/auth">>. - -token_url() -> - <<"https://accounts.google.com/o/oauth2/token">>. - -profile_url() -> - <<"https://www.googleapis.com/oauth2/v1/userinfo">>. - -normalize_auth(Auth) -> - [ - {access_token, key(<<"access_token">>, Auth)}, - {token_type, key(<<"token_type">>, Auth)}, - {expires_in, key(<<"expires_in">>, Auth)} - ]. - -normalize_profile(Raw) -> - [ - {id, << "google:", (key(<<"id">>, Raw))/binary >>}, - {provider, <<"google">>}, - {email, key(<<"email">>, Raw)}, - {name, key(<<"name">>, Raw)}, - {avatar, key(<<"picture">>, Raw)}, - {gender, key(<<"gender">>, Raw)}, - {locale, key(<<"locale">>, Raw)} - ]. diff --git a/src/cowboy_social_mailru.erl b/src/cowboy_social_mailru.erl index f0a48e4..734970a 100644 --- a/src/cowboy_social_mailru.erl +++ b/src/cowboy_social_mailru.erl @@ -5,8 +5,11 @@ -module(cowboy_social_mailru). -author('Vladimir Dronnikov '). --behaviour(cowboy_http_handler). --export([init/3, terminate/3, handle/2]). +-export([ + get_authorize_url/1, + get_access_token/2, + get_user_profile/2 + ]). %% %%------------------------------------------------------------------------------ @@ -14,104 +17,63 @@ %%------------------------------------------------------------------------------ %% -init(_Transport, Req, Opts) -> - % compose full redirect URI - {SelfUri, Req2} = cowboy_req:host_url(Req), - {ok, Req2, lists:keyreplace(callback_uri, 1, Opts, - {callback_uri, << SelfUri/binary, (key(callback_uri, Opts))/binary >>})}. - -terminate(_Reason, _Req, _State) -> - ok. - -handle(Req, Opts) -> - % extract flow action name - {Action, Req2} = cowboy_req:binding(action, Req), - % perform flow action - {ok, Req3} = handle_request(Action, Req2, Opts), - {ok, Req3, undefined}. - -%% -%% redirect to provider authorization page, expect it to redirect -%% to our next handler %% -handle_request(<<"login">>, Req, Opts) -> - AuthUrl = << (authorize_url())/binary, $?, - (cowboy_request:urlencode([ - {<<"client_id">>, key(client_id, Opts)}, - {<<"redirect_uri">>, key(callback_uri, Opts)}, - {<<"response_type">>, <<"code">>} - ]))/binary >>, - cowboy_req:reply(303, [{<<"location">>, AuthUrl}], <<>>, Req); - -%% -%% provider redirected back to us with authorization code +%% get URL of provider authorization page %% -handle_request(<<"callback">>, Req, Opts) -> - case cowboy_req:qs_val(<<"code">>, Req) of - {undefined, Req2} -> - finish({error, nocode}, Req2, Opts); - {Code, Req2} -> - get_access_token(Code, Req2, Opts) - end; - -%% -%% catchall -%% -handle_request(_, Req, _) -> - {ok, Req2} = cowboy_req:reply(404, [], <<>>, Req), - {ok, Req2, undefined}. +get_authorize_url(Opts) -> + << "https://connect.mail.ru/oauth/authorize", $?, + (cowboy_request:urlencode([ + {client_id, key(client_id, Opts)}, + {redirect_uri, key(callback_uri, Opts)}, + {response_type, <<"code">>} + ]))/binary >>. %% %% exchange authorization code for auth token %% -get_access_token(Code, Req, Opts) -> - try cowboy_request:post_for_json(token_url(), [ - {<<"code">>, Code}, - {<<"client_id">>, key(client_id, Opts)}, - {<<"client_secret">>, key(client_secret, Opts)}, - {<<"redirect_uri">>, key(callback_uri, Opts)}, - {<<"grant_type">>, <<"authorization_code">>} - ]) - of - {ok, Auth} -> - get_user_profile(Auth, Req, Opts); - _ -> - finish({error, notoken}, Req, Opts) - catch _:_ -> - finish({error, notoken}, Req, Opts) - end. +get_access_token(Code, Opts) -> + {ok, Auth} = cowboy_request:post_for_json( + <<"https://connect.mail.ru/oauth/token">>, [ + {code, Code}, + {client_id, key(client_id, Opts)}, + {client_secret, key(client_secret, Opts)}, + {redirect_uri, key(callback_uri, Opts)}, + {grant_type, <<"authorization_code">>} + ]), + {ok, [ + {access_token, key(<<"access_token">>, Auth)}, + {token_type, key(<<"token_type">>, Auth)}, + {expires_in, key(<<"expires_in">>, Auth)} + ]}. %% -%% use auth token to extract info from user profile +%% extract info from user profile %% -get_user_profile(Auth, Req, Opts) -> +get_user_profile(Auth, Opts) -> Sig = md5hex(<< "app_id=", (key(client_id, Opts))/binary, "method=users.getInfosecure=1session_key=", - (key(<<"access_token">>, Auth))/binary, + (key(access_token, Auth))/binary, (key(secret_key, Opts))/binary >>), - try cowboy_request:get_json(profile_url(), [ - {<<"app_id">>, key(client_id, Opts)}, - {<<"method">>, <<"users.getInfo">>}, - {<<"secure">>, <<"1">>}, - {<<"session_key">>, key(<<"access_token">>, Auth)}, - {<<"sig">>, Sig} - ]) - of - {ok, Profile} -> - finish({ok, normalize_auth(Auth), normalize_profile(Profile)}, Req, Opts); - _ -> - finish({error, noprofile}, Req, Opts) - catch _:_ -> - finish({error, noprofile}, Req, Opts) - end. - -%% -%% finalize application flow by calling callback handler -%% -finish(Status, Req, Opts) -> - {M, F} = key(handler, Opts), - M:F(Status, Req). + % NB: provider returns list of data for uids; we need only the first + {ok, [Profile]} = cowboy_request:get_json( + <<"http://www.appsmail.ru/platform/api">>, [ + {app_id, key(client_id, Opts)}, + {method, <<"users.getInfo">>}, + {secure, <<"1">>}, + {session_key, key(access_token, Auth)}, + {sig, Sig} + ]), + {ok, [ + {id, << "mailru:", (key(<<"uid">>, Profile))/binary >>}, + {provider, <<"mailru">>}, + {email, key(<<"email">>, Profile)}, + {name, << (key(<<"first_name">>, Profile))/binary, " ", + (key(<<"last_name">>, Profile))/binary >>}, + {avatar, key(<<"pic">>, Profile)}, + {gender, case key(<<"sex">>, Profile) of + 1 -> <<"female">>; _ -> <<"male">> end} + ]}. %% %%------------------------------------------------------------------------------ @@ -126,36 +88,3 @@ key(Key, List) -> md5hex(Bin) -> list_to_binary(lists:flatten([io_lib:format("~2.16.0b",[N]) || N <- binary_to_list(erlang:md5(binary_to_list(Bin)))])). - -%% -%%------------------------------------------------------------------------------ -%% Provider details -%%------------------------------------------------------------------------------ -%% - -authorize_url() -> - <<"https://connect.mail.ru/oauth/authorize">>. - -token_url() -> - <<"https://connect.mail.ru/oauth/token">>. - -profile_url() -> - <<"http://www.appsmail.ru/platform/api">>. - -normalize_auth(Auth) -> - [ - {access_token, key(<<"access_token">>, Auth)}, - {token_type, key(<<"token_type">>, Auth)}, - {expires_in, key(<<"expires_in">>, Auth)} - ]. - -normalize_profile([Raw]) -> - [ - {id, << "mailru:", (key(<<"uid">>, Raw))/binary >>}, - {provider, <<"mailru">>}, - {email, key(<<"email">>, Raw)}, - {name, << (key(<<"first_name">>, Raw))/binary, " ", - (key(<<"last_name">>, Raw))/binary >>}, - {avatar, key(<<"pic">>, Raw)}, - {gender, case key(<<"sex">>, Raw) of 1 -> <<"female">>; _ -> <<"male">> end} - ]. diff --git a/src/cowboy_social_paypal.erl b/src/cowboy_social_paypal.erl new file mode 100644 index 0000000..568b3c1 --- /dev/null +++ b/src/cowboy_social_paypal.erl @@ -0,0 +1,75 @@ +%% +%% @doc Handler for social login via PayPal. +%% + +-module(cowboy_social_paypal). +-author('Vladimir Dronnikov '). + +-export([ + get_authorize_url/1, + get_access_token/2, + get_user_profile/2 + ]). + +%% +%%------------------------------------------------------------------------------ +%% OAUTH2 Application flow +%%------------------------------------------------------------------------------ +%% + +%% +%% get URL of provider authorization page +%% +get_authorize_url(Opts) -> + << "https://identity.x.com/xidentity/resources/authorize", $?, + (cowboy_request:urlencode([ + {client_id, key(client_id, Opts)}, + {redirect_uri, key(callback_uri, Opts)}, + {response_type, <<"code">>}, + {scope, key(scope, Opts)} + ]))/binary >>. + +%% +%% exchange authorization code for auth token +%% +get_access_token(Code, Opts) -> + {ok, Auth} = cowboy_request:post_for_json( + <<"https://identity.x.com/xidentity/oauthtokenservice">>, [ + {code, Code}, + {client_id, key(client_id, Opts)}, + {client_secret, key(client_secret, Opts)}, + {redirect_uri, key(callback_uri, Opts)}, + {grant_type, <<"authorization_code">>} + ]), + {ok, [ + {access_token, key(<<"access_token">>, Auth)}, + {token_type, <<"Bearer">>}, + {expires_in, key(<<"expires_in">>, Auth)} + ]}. + +%% +%% extract info from user profile +%% +get_user_profile(Auth, _Opts) -> + {ok, Result} = cowboy_request:get_json( + <<"https://identity.x.com/xidentity/resources/profile/me">>, [ + {oauth_token, key(access_token, Auth)} + ]), + % NB: provider returns {status: ..., identity: Profile} + Profile = key(<<"identity">>, Result), + {ok, [ + {id, << "paypal:", (key(<<"userId">>, Profile))/binary >>}, + {provider, <<"paypal">>}, + {email, hd(key(<<"emails">>, Profile))}, + {name, key(<<"fullName">>, Profile)} + ]}. + +%% +%%------------------------------------------------------------------------------ +%% Helpers +%%------------------------------------------------------------------------------ +%% + +key(Key, List) -> + {_, Value} = lists:keyfind(Key, 1, List), + Value. diff --git a/src/cowboy_social_vkontakte.erl b/src/cowboy_social_vkontakte.erl index f789ac0..6b2017c 100644 --- a/src/cowboy_social_vkontakte.erl +++ b/src/cowboy_social_vkontakte.erl @@ -5,8 +5,11 @@ -module(cowboy_social_vkontakte). -author('Vladimir Dronnikov '). --behaviour(cowboy_http_handler). --export([init/3, terminate/3, handle/2]). +-export([ + get_authorize_url/1, + get_access_token/2, + get_user_profile/2 + ]). %% %%------------------------------------------------------------------------------ @@ -14,97 +17,58 @@ %%------------------------------------------------------------------------------ %% -init(_Transport, Req, Opts) -> - % compose full redirect URI - {SelfUri, Req2} = cowboy_req:host_url(Req), - {ok, Req2, lists:keyreplace(callback_uri, 1, Opts, - {callback_uri, << SelfUri/binary, (key(callback_uri, Opts))/binary >>})}. - -terminate(_Reason, _Req, _State) -> - ok. - -handle(Req, Opts) -> - % extract flow action name - {Action, Req2} = cowboy_req:binding(action, Req), - % perform flow action - {ok, Req3} = handle_request(Action, Req2, Opts), - {ok, Req3, undefined}. - -%% -%% redirect to provider authorization page, expect it to redirect -%% to our next handler -%% -handle_request(<<"login">>, Req, Opts) -> - AuthUrl = << (authorize_url())/binary, $?, - (cowboy_request:urlencode([ - {<<"client_id">>, key(client_id, Opts)}, - {<<"redirect_uri">>, key(callback_uri, Opts)}, - {<<"response_type">>, <<"code">>} - ]))/binary >>, - cowboy_req:reply(303, [{<<"location">>, AuthUrl}], <<>>, Req); - -%% -%% provider redirected back to us with authorization code -%% -handle_request(<<"callback">>, Req, Opts) -> - case cowboy_req:qs_val(<<"code">>, Req) of - {undefined, Req2} -> - finish({error, nocode}, Req2, Opts); - {Code, Req2} -> - get_access_token(Code, Req2, Opts) - end; - %% -%% catchall +%% get URL of provider authorization page %% -handle_request(_, Req, _) -> - {ok, Req2} = cowboy_req:reply(404, [], <<>>, Req), - {ok, Req2, undefined}. +get_authorize_url(Opts) -> + << "https://oauth.vk.com/authorize", $?, + (cowboy_request:urlencode([ + {client_id, key(client_id, Opts)}, + {redirect_uri, key(callback_uri, Opts)}, + {response_type, <<"code">>}, + {scope, key(scope, Opts)} + ]))/binary >>. %% %% exchange authorization code for auth token %% -get_access_token(Code, Req, Opts) -> - try cowboy_request:post_for_json(token_url(), [ - {<<"code">>, Code}, - {<<"client_id">>, key(client_id, Opts)}, - {<<"client_secret">>, key(client_secret, Opts)}, - {<<"redirect_uri">>, key(callback_uri, Opts)}, - {<<"grant_type">>, <<"authorization_code">>} - ]) - of - {ok, Auth} -> - get_user_profile(Auth, Req, Opts); - _ -> - finish({error, notoken}, Req, Opts) - catch _:_ -> - finish({error, notoken}, Req, Opts) - end. - -%% -%% use auth token to extract info from user profile -%% -get_user_profile(Auth, Req, Opts) -> - AccessToken = key(<<"access_token">>, Auth), - try cowboy_request:get_json(profile_url(), [ - {<<"access_token">>, AccessToken}, - {<<"fields">>, key(scope, Opts)} - ]) - of - {ok, Profile} -> - finish({ok, normalize_auth(Auth), normalize_profile(Profile)}, Req, Opts); - _ -> - finish({error, noprofile}, Req, Opts) - catch _:_ -> - finish({error, noprofile}, Req, Opts) - end. +get_access_token(Code, Opts) -> + {ok, Auth} = cowboy_request:post_for_json( + <<"https://oauth.vk.com/access_token">>, [ + {code, Code}, + {client_id, key(client_id, Opts)}, + {client_secret, key(client_secret, Opts)}, + {redirect_uri, key(callback_uri, Opts)}, + {grant_type, <<"authorization_code">>} + ]), + {ok, [ + {access_token, key(<<"access_token">>, Auth)}, + {token_type, <<"Bearer">>}, + {expires_in, key(<<"expires_in">>, Auth)} + ]}. %% -%% finalize application flow by calling callback handler +%% extract info from user profile %% -finish(Status, Req, Opts) -> - {M, F} = key(handler, Opts), - M:F(Status, Req). +get_user_profile(Auth, Opts) -> + {ok, Profiles} = cowboy_request:get_json( + <<"https://api.vk.com/method/users.get">>, [ + {access_token, key(access_token, Auth)}, + {fields, key(scope, Opts)} + ]), + % NB: provider returns list of data for uids; we need only the first + [Profile] = key(<<"response">>, Profiles), + {ok, [ + {id, << "vkontakte:", + (list_to_binary(integer_to_list(key(<<"uid">>, Profile))))/binary >>}, + {provider, <<"vkontakte">>}, + % {email, key(<<"email">>, Profile)}, + {name, << (key(<<"first_name">>, Profile))/binary, " ", + (key(<<"last_name">>, Profile))/binary >>}, + {avatar, key(<<"photo">>, Profile)}, + {gender, case key(<<"sex">>, Profile) of + 1 -> <<"female">>; _ -> <<"male">> end} + ]}. %% %%------------------------------------------------------------------------------ @@ -115,39 +79,3 @@ finish(Status, Req, Opts) -> key(Key, List) -> {_, Value} = lists:keyfind(Key, 1, List), Value. - -%% -%%------------------------------------------------------------------------------ -%% Provider details -%%------------------------------------------------------------------------------ -%% - -authorize_url() -> - <<"https://oauth.vk.com/authorize">>. - -token_url() -> - <<"https://oauth.vk.com/access_token">>. - -profile_url() -> - <<"https://api.vk.com/method/users.get">>. - -normalize_auth(Auth) -> - [ - {access_token, key(<<"access_token">>, Auth)}, - {token_type, <<"Bearer">>}, - {expires_in, key(<<"expires_in">>, Auth)} - ]. - -normalize_profile(Raw0) -> - % NB: provider returns list of data for uids; we need only the first - [Raw] = key(<<"response">>, Raw0), - [ - {id, << "vkontakte:", - (list_to_binary(integer_to_list(key(<<"uid">>, Raw))))/binary >>}, - {provider, <<"vkontakte">>}, - % {email, key(<<"email">>, Raw)}, - {name, << (key(<<"first_name">>, Raw))/binary, " ", - (key(<<"last_name">>, Raw))/binary >>}, - {avatar, key(<<"photo">>, Raw)}, - {gender, case key(<<"sex">>, Raw) of 1 -> <<"female">>; _ -> <<"male">> end} - ]. diff --git a/src/cowboy_social_yandex.erl b/src/cowboy_social_yandex.erl index 586fee1..5f24df9 100644 --- a/src/cowboy_social_yandex.erl +++ b/src/cowboy_social_yandex.erl @@ -5,8 +5,11 @@ -module(cowboy_social_yandex). -author('Vladimir Dronnikov '). --behaviour(cowboy_http_handler). --export([init/3, terminate/3, handle/2]). +-export([ + get_authorize_url/1, + get_access_token/2, + get_user_profile/2 + ]). %% %%------------------------------------------------------------------------------ @@ -14,98 +17,54 @@ %%------------------------------------------------------------------------------ %% -init(_Transport, Req, Opts) -> - % compose full redirect URI - {SelfUri, Req2} = cowboy_req:host_url(Req), - {ok, Req2, lists:keyreplace(callback_uri, 1, Opts, - {callback_uri, << SelfUri/binary, (key(callback_uri, Opts))/binary >>})}. - -terminate(_Reason, _Req, _State) -> - ok. - -handle(Req, Opts) -> - % extract flow action name - {Action, Req2} = cowboy_req:binding(action, Req), - % perform flow action - {ok, Req3} = handle_request(Action, Req2, Opts), - {ok, Req3, undefined}. - -%% -%% redirect to provider authorization page, expect it to redirect -%% to our next handler %% -handle_request(<<"login">>, Req, Opts) -> - AuthUrl = << (authorize_url())/binary, $?, - (cowboy_request:urlencode([ - {<<"client_id">>, key(client_id, Opts)}, - {<<"redirect_uri">>, key(callback_uri, Opts)}, - {<<"response_type">>, <<"code">>}, - {<<"scope">>, key(scope, Opts)} - ]))/binary >>, - cowboy_req:reply(303, [{<<"location">>, AuthUrl}], <<>>, Req); - -%% -%% provider redirected back to us with authorization code +%% get URL of provider authorization page %% -handle_request(<<"callback">>, Req, Opts) -> - case cowboy_req:qs_val(<<"code">>, Req) of - {undefined, Req2} -> - finish({error, nocode}, Req2, Opts); - {Code, Req2} -> - get_access_token(Code, Req2, Opts) - end; - -%% -%% catchall -%% -handle_request(_, Req, _) -> - {ok, Req2} = cowboy_req:reply(404, [], <<>>, Req), - {ok, Req2, undefined}. +get_authorize_url(Opts) -> + << "https://oauth.yandex.ru/authorize", $?, + (cowboy_request:urlencode([ + {client_id, key(client_id, Opts)}, + {redirect_uri, key(callback_uri, Opts)}, + {response_type, <<"code">>}, + {scope, key(scope, Opts)} + ]))/binary >>. %% %% exchange authorization code for auth token %% -get_access_token(Code, Req, Opts) -> - try cowboy_request:post_for_json(token_url(), [ - {<<"code">>, Code}, - {<<"client_id">>, key(client_id, Opts)}, - {<<"client_secret">>, key(client_secret, Opts)}, - {<<"redirect_uri">>, key(callback_uri, Opts)}, - {<<"grant_type">>, <<"authorization_code">>} - ]) - of - {ok, Auth} -> - get_user_profile(Auth, Req, Opts); - _ -> - finish({error, notoken}, Req, Opts) - catch _:_ -> - finish({error, notoken}, Req, Opts) - end. - -%% -%% use auth token to extract info from user profile -%% -get_user_profile(Auth, Req, Opts) -> - AccessToken = key(<<"access_token">>, Auth), - try cowboy_request:get_json(profile_url(), [ - {<<"oauth_token">>, AccessToken}, - {<<"format">>, <<"json">>} - ]) - of - {ok, Profile} -> - finish({ok, normalize_auth(Auth), normalize_profile(Profile)}, Req, Opts); - _ -> - finish({error, noprofile}, Req, Opts) - catch _:_ -> - finish({error, noprofile}, Req, Opts) - end. +get_access_token(Code, Opts) -> + {ok, Auth} = cowboy_request:post_for_json( + <<"https://oauth.yandex.ru/token">>, [ + {code, Code}, + {client_id, key(client_id, Opts)}, + {client_secret, key(client_secret, Opts)}, + {redirect_uri, key(callback_uri, Opts)}, + {grant_type, <<"authorization_code">>} + ]), + {ok, [ + {access_token, key(<<"access_token">>, Auth)}, + {token_type, key(<<"token_type">>, Auth)}, + {expires_in, 0} + ]}. %% -%% finalize application flow by calling callback handler +%% extract info from user profile %% -finish(Status, Req, Opts) -> - {M, F} = key(handler, Opts), - M:F(Status, Req). +get_user_profile(Auth, _Opts) -> + {ok, Profile} = cowboy_request:get_json( + <<"https://login.yandex.ru/info">>, [ + {oauth_token, key(access_token, Auth)}, + {format, <<"json">>} + ]), + {ok, [ + {id, << "yandex:", (key(<<"id">>, Profile))/binary >>}, + {provider, <<"yandex">>}, + {email, key(<<"default_email">>, Profile)}, + {name, key(<<"real_name">>, Profile)}, + % {avatar, key(<<"picture">>, Profile)}, + {gender, case key(<<"sex">>, Profile) of + 1 -> <<"female">>; _ -> <<"male">> end} + ]}. %% %%------------------------------------------------------------------------------ @@ -116,35 +75,3 @@ finish(Status, Req, Opts) -> key(Key, List) -> {_, Value} = lists:keyfind(Key, 1, List), Value. - -%% -%%------------------------------------------------------------------------------ -%% Provider details -%%------------------------------------------------------------------------------ -%% - -authorize_url() -> - <<"https://oauth.yandex.ru/authorize">>. - -token_url() -> - <<"https://oauth.yandex.ru/token">>. - -profile_url() -> - <<"https://login.yandex.ru/info">>. - -normalize_auth(Auth) -> - [ - {access_token, key(<<"access_token">>, Auth)}, - {token_type, key(<<"token_type">>, Auth)}, - {expires_in, 0} - ]. - -normalize_profile(Raw) -> - [ - {id, << "yandex:", (key(<<"id">>, Raw))/binary >>}, - {provider, <<"yandex">>}, - {email, key(<<"default_email">>, Raw)}, - {name, key(<<"real_name">>, Raw)}, - % {avatar, key(<<"picture">>, Raw)}, - {gender, case key(<<"sex">>, Raw) of 1 -> <<"female">>; _ -> <<"male">> end} - ].