Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge commit '4825e'

Conflicts:
	src/cowboy_social_google.erl
	src/cowboy_social_providers.erl
  • Loading branch information...
commit 2016ecc687c7a12d7b0ba7180d05ff073006d5e2 2 parents 7e3905b + 4825ecf
@dvv authored
View
73 README.md
@@ -7,7 +7,7 @@ Usage
--------------
Register your application with callback URI pointing back to your site plus `/auth/:provider/callback`, and supply `ClientID` and `ClientSecret` in provider's configuration proplist.
-Ensure you started erlang ssl appication.
+Ensure you started native erlang 'ssl' appication.
Then in your client-side code:
```html
@@ -18,39 +18,44 @@ Router configuration
--------------
```erlang
-{"/auth/:provider/:action", cowboy_social, [
- % oauth2 parameters
- {oauth2_opts, [
-
- % at the end of the flow this handler will be called as
- % Mod:Fun({ok, Profile}, Req) or Mod:Fun({error, Reason}, Req)
- {callback, {Mod, Fun}},
-
- % allowed providers
- {providers, [
- {google, <<"google">>, [
- {client_id, <<"440647648374.apps.googleusercontent.com">>},
- {client_secret, <<"...">>},
- {scope, <<"https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile">>}
- ]},
- {github, <<"github">>, [
- {client_id, <<"883b68d607abddc24f77">>},
- {client_secret, <<"...">>},
- {scope, <<>>}
- ]},
- {vkontakte, <<"vkontakte">>, [
- {client_id, <<"3473116">>},
- {client_secret, <<"...">>},
- {scope, <<"uid,first_name,last_name,sex,photo">>}
- ]},
- {yandex, <<"yandex">>, [
- {client_id, <<"f44bd59ddfbe408ab1d29151126385a6">>},
- {client_secret, <<"...">>},
- {scope, <<>>}
- ]}
- ]}
- ]}
-]}
+{"/auth/google/:action", 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_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">>}
+]}.
```
License (MIT)
View
140 src/cowboy_social_github.erl
@@ -0,0 +1,140 @@
+%%
+%% @doc Handler for social login via Github.
+%%
+
+-module(cowboy_social_github).
+-author('Vladimir Dronnikov <dronnikov@gmail.com>').
+
+-behaviour(cowboy_http_handler).
+-export([init/3, terminate/3, handle/2]).
+
+%%
+%%------------------------------------------------------------------------------
+%% OAUTH2 Application flow
+%%------------------------------------------------------------------------------
+%%
+
+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
+%%
+handle_request(_, Req, _) ->
+ {ok, Req2} = cowboy_req:reply(404, [], <<>>, Req),
+ {ok, Req2, undefined}.
+
+%%
+%% 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_profile(Auth, 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).
+
+%%
+%%------------------------------------------------------------------------------
+%% Helpers
+%%------------------------------------------------------------------------------
+%%
+
+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_profile(_Auth, 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)}
+ ].
View
40 src/cowboy_social_google.erl
@@ -1,5 +1,5 @@
%%
-%% @doc Handler for social login via OAuth2 providers.
+%% @doc Handler for social login via Google.
%%
-module(cowboy_social_google).
@@ -15,14 +15,14 @@
%%
init(_Transport, Req, Opts) ->
- {ok, 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.
-%%
-%% {"/auth/google/:action", cowboy_social_google, [...]}.
-%%
handle(Req, Opts) ->
% extract flow action name
{Action, Req2} = cowboy_req:binding(action, Req),
@@ -35,16 +35,14 @@ handle(Req, Opts) ->
%% to our next handler
%%
handle_request(<<"login">>, Req, Opts) ->
- {SelfUri, Req2} = cowboy_req:host_url(Req),
- CallbackUrl = << SelfUri/binary, (key(callback_uri, Opts))/binary >>,
AuthUrl = << (authorize_url())/binary, $?,
(cowboy_request:urlencode([
{<<"client_id">>, key(client_id, Opts)},
- {<<"redirect_uri">>, CallbackUrl},
+ {<<"redirect_uri">>, key(callback_uri, Opts)},
{<<"response_type">>, <<"code">>},
{<<"scope">>, key(scope, Opts)}
]))/binary >>,
- cowboy_req:reply(303, [{<<"location">>, AuthUrl}], <<>>, Req2);
+ cowboy_req:reply(303, [{<<"location">>, AuthUrl}], <<>>, Req);
%%
%% provider redirected back to us with authorization code
@@ -68,28 +66,28 @@ handle_request(_, Req, _) ->
%% exchange authorization code for auth token
%%
get_access_token(Code, Req, Opts) ->
- {SelfUri, Req2} = cowboy_req:host_url(Req),
- CallbackUrl = << SelfUri/binary, (key(callback_uri, Opts))/binary >>,
- case cowboy_request:post_for_json(token_url(), [
+ try cowboy_request:post_for_json(token_url(), [
{<<"code">>, Code},
{<<"client_id">>, key(client_id, Opts)},
{<<"client_secret">>, key(client_secret, Opts)},
- {<<"redirect_uri">>, CallbackUrl},
+ {<<"redirect_uri">>, key(callback_uri, Opts)},
{<<"grant_type">>, <<"authorization_code">>}
])
of
{ok, Auth} ->
- get_user_profile(Auth, Req2, Opts);
+ get_user_profile(Auth, Req, Opts);
_ ->
- finish({error, notoken}, Req2, Opts)
+ finish({error, notoken}, Req, Opts)
+ catch _:_ ->
+ finish({error, notoken}, Req, Opts)
end.
%%
-%% use auth tocken to extract info from user profile
+%% use auth token to extract info from user profile
%%
get_user_profile(Auth, Req, Opts) ->
AccessToken = key(<<"access_token">>, Auth),
- case cowboy_request:get_json(profile_url(), [
+ try cowboy_request:get_json(profile_url(), [
{<<"access_token">>, AccessToken}
])
of
@@ -97,6 +95,8 @@ get_user_profile(Auth, Req, Opts) ->
finish({ok, normalize_profile(Auth, Profile)}, Req, Opts);
_ ->
finish({error, noprofile}, Req, Opts)
+ catch _:_ ->
+ finish({error, noprofile}, Req, Opts)
end.
%%
@@ -116,6 +116,12 @@ key(Key, List) ->
{_, Value} = lists:keyfind(Key, 1, List),
Value.
+%%
+%%------------------------------------------------------------------------------
+%% Provider details
+%%------------------------------------------------------------------------------
+%%
+
authorize_url() ->
<<"https://accounts.google.com/o/oauth2/auth">>.
View
155 src/cowboy_social_mailru.erl
@@ -0,0 +1,155 @@
+%%
+%% @doc Handler for social login via Mail.Ru.
+%%
+
+-module(cowboy_social_mailru).
+-author('Vladimir Dronnikov <dronnikov@gmail.com>').
+
+-behaviour(cowboy_http_handler).
+-export([init/3, terminate/3, handle/2]).
+
+%%
+%%------------------------------------------------------------------------------
+%% OAUTH2 Application flow
+%%------------------------------------------------------------------------------
+%%
+
+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
+%%
+handle_request(_, Req, _) ->
+ {ok, Req2} = cowboy_req:reply(404, [], <<>>, Req),
+ {ok, Req2, undefined}.
+
+%%
+%% 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) ->
+ Sig = md5hex(<<
+ "app_id=", (key(client_id, Opts))/binary,
+ "method=users.getInfosecure=1session_key=",
+ (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_profile(Auth, 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).
+
+%%
+%%------------------------------------------------------------------------------
+%% Helpers
+%%------------------------------------------------------------------------------
+%%
+
+key(Key, List) ->
+ {_, Value} = lists:keyfind(Key, 1, List),
+ Value.
+
+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_profile(_Auth, [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}
+ ].
View
106 src/cowboy_social_providers.erl
@@ -1,106 +0,0 @@
--module(cowboy_social_providers).
--author('Vladimir Dronnikov <dronnikov@gmail.com>').
-
--export([authorize_url/1]).
--export([custom_data/3]).
--export([normalize_profile/3]).
--export([profile_url/1]).
--export([token_url/1]).
-
-%%
-%%------------------------------------------------------------------------------
-%% Helpers
-%%------------------------------------------------------------------------------
-%%
-
-key(Key, List) ->
- {_, Value} = lists:keyfind(Key, 1, List),
- Value.
-
-%%
-%%------------------------------------------------------------------------------
-%% Providers
-%%------------------------------------------------------------------------------
-%%
-
-authorize_url(google) -> <<"https://accounts.google.com/o/oauth2/auth">>;
-authorize_url(github) -> <<"https://github.com/login/oauth/authorize">>;
-authorize_url(twitter) -> <<"https://api.twitter.com/oauth/authorize">>;
-authorize_url(vkontakte) -> <<"https://oauth.vk.com/authorize">>;
-authorize_url(yandex) -> <<"https://oauth.yandex.ru/authorize">>.
-
-token_url(google) -> <<"https://accounts.google.com/o/oauth2/token">>;
-token_url(github) -> <<"https://github.com/login/oauth/access_token">>;
-token_url(twitter) -> <<"https://api.twitter.com/oauth/access_token">>;
-token_url(vkontakte) -> <<"https://oauth.vk.com/access_token">>;
-token_url(yandex) -> <<"https://oauth.yandex.ru/token">>.
-
-profile_url(google) -> <<"https://www.googleapis.com/oauth2/v1/userinfo">>;
-profile_url(github) -> <<"https://api.github.com/user">>;
-profile_url(twitter) -> <<"https://api.twitter.com/1/users/lookup.json">>;
-profile_url(vkontakte) -> <<"https://api.vk.com/method/users.get">>;
-profile_url(yandex) -> <<"https://login.yandex.ru/info">>.
-
-custom_data(twitter, AccessToken, _ProviderOpts) ->
- UserId = key(<<"user_id">>, AccessToken),
- [{<<"user_id">>, UserId}];
-custom_data(vkontakte, _AccessToken, ProviderOpts) ->
- Scope = key(scope, ProviderOpts),
- [{<<"fields">>, Scope}];
-custom_data(yandex, AccessToken, _ProviderOpts) ->
- [{<<"oauth_token">>, AccessToken}, {<<"format">>, <<"json">>}];
-custom_data(_, _, _) ->
- [].
-
-normalize_profile(google, _A, P) ->
- [
- {id, << "google:", (key(<<"id">>, P))/binary >>},
- {provider, <<"google">>},
- {email, key(<<"email">>, P)},
- {name, key(<<"name">>, P)},
- {avatar, key(<<"picture">>, P)},
- {gender, key(<<"gender">>, P)},
- {locale, key(<<"locale">>, P)}
- ];
-
-normalize_profile(github, _A, P) ->
- [
- {id, << "github:",
- (list_to_binary(integer_to_list(key(<<"id">>, P))))/binary >>},
- {provider, <<"github">>},
- {email, key(<<"email">>, P)},
- {name, key(<<"name">>, P)},
- {avatar, key(<<"avatar_url">>, P)}
- ];
-
-normalize_profile(twitter, _A, P) ->
- [
- {id, << "twitter:", (key(<<"id">>, P))/binary >>},
- {provider, <<"twitter">>},
- {name, key(<<"name">>, P)},
- {avatar, key(<<"profile_image_url_https">>, P)}
- ];
-
-normalize_profile(vkontakte, _A, P0) ->
- % NB: provider returns list of data for uids; we need only the first
- [P] = key(<<"response">>, P0),
- [
- {id, << "vkontakte:",
- (list_to_binary(integer_to_list(key(<<"uid">>, P))))/binary >>},
- {provider, <<"vkontakte">>},
- % {email, key(<<"email">>, P)},
- {name, << (key(<<"first_name">>, P))/binary, " ",
- (key(<<"last_name">>, P))/binary >>},
- {avatar, key(<<"photo">>, P)},
- {gender, case key(<<"sex">>, P) of 1 -> <<"female">>; _ -> <<"male">> end}
- ];
-
-normalize_profile(yandex, _A, P) ->
- [
- {id, << "yandex:", (key(<<"id">>, P))/binary >>},
- {provider, <<"yandex">>},
- {email, key(<<"default_email">>, P)},
- {name, key(<<"real_name">>, P)},
- % {avatar, key(<<"picture">>, P)},
- {gender, case key(<<"sex">>, P) of 1 -> <<"female">>; _ -> <<"male">> end}
- ].
View
146 src/cowboy_social_vkontakte.erl
@@ -0,0 +1,146 @@
+%%
+%% @doc Handler for social login via VKontakte.
+%%
+
+-module(cowboy_social_vkontakte).
+-author('Vladimir Dronnikov <dronnikov@gmail.com>').
+
+-behaviour(cowboy_http_handler).
+-export([init/3, terminate/3, handle/2]).
+
+%%
+%%------------------------------------------------------------------------------
+%% OAUTH2 Application flow
+%%------------------------------------------------------------------------------
+%%
+
+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
+%%
+handle_request(_, Req, _) ->
+ {ok, Req2} = cowboy_req:reply(404, [], <<>>, Req),
+ {ok, Req2, undefined}.
+
+%%
+%% 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_profile(Auth, 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).
+
+%%
+%%------------------------------------------------------------------------------
+%% Helpers
+%%------------------------------------------------------------------------------
+%%
+
+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_profile(_Auth, 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}
+ ].
View
143 src/cowboy_social_yandex.erl
@@ -0,0 +1,143 @@
+%%
+%% @doc Handler for social login via Yandex.
+%%
+
+-module(cowboy_social_yandex).
+-author('Vladimir Dronnikov <dronnikov@gmail.com>').
+
+-behaviour(cowboy_http_handler).
+-export([init/3, terminate/3, handle/2]).
+
+%%
+%%------------------------------------------------------------------------------
+%% OAUTH2 Application flow
+%%------------------------------------------------------------------------------
+%%
+
+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
+%%
+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}.
+
+%%
+%% 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_profile(Auth, 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).
+
+%%
+%%------------------------------------------------------------------------------
+%% Helpers
+%%------------------------------------------------------------------------------
+%%
+
+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_profile(_Auth, 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}
+ ].
Please sign in to comment.
Something went wrong with that request. Please try again.