Packages

Erlang + Google + Auth = egoth

Current section

Files

Jump to
egoth src egoth.erl
Raw

src/egoth.erl

-module(egoth).
% new API
-export([for_scope/1]).
-export([get_access_token/1]).
-export([get_access_token/2]).
-export ([retrieve_metadata_project/0]).
% -define(OAUTH_URL, <<"https://www.googleapis.com/oauth2/v4/token">>).
-define(OAUTH_URL, <<"https://www.googleapis.com/oauth2/v4/token">>).
-define(OAUTH_REFRESH_URL(ClientId),
lists:concat(["https://www.googleapis.com/oauth2/v4/token?client_id=", ClientId, ",access_type=offline,nonc=foobar,response_type=code,"])).
% -define(CLOUD_TRACE_URL(Project, TraceId, SpanId),
% lists:concat(["https://cloudtrace.googleapis.com/v2/projects/", Project, "/traces/", TraceId, "/spans/", SpanId])).
-include("egoth.hrl").
%%% API ========================================================================
% is there a way for this method to live in token.erl but still be consumable in
% downstream modules?
- spec for_scope(Scope) -> Return when
Scope :: binary(),
Return :: {ok, #token{}}.
for_scope(Scope) ->
token:for_scope(Scope).
- spec get_access_token(Scope) -> Return when
Scope :: #config{},
Return :: {ok, #token{}} | {'error', _}.
get_access_token(#config{}=Scope) ->
Config = config:get(token_source),
{_, TokenSource} = Config#config.token_source,
get_access_token({TokenSource}, Scope).
- spec get_access_token({atom()}, Scope) -> Return when
Scope :: #config{},
Return :: {ok, #token{}} | {'error', _}.
get_access_token({oauth_jwt}, #config{}=Scope) ->
Headers = [{
<<"Content-Type">>, <<"application/x-www-form-urlencoded">>
}],
Assertion = jwt(Scope),
Body = [{
<<"grant_type">>, <<"urn:ietf:params:oauth:grant-type:jwt-bearer">>},
{<<"assertion">>, Assertion
}],
Options = [],
Return = case hackney:post(?OAUTH_URL, Headers, {form, Body}, Options)
of
{ok, _, _, ClientRef} ->
{ok, ResponseBody} = hackney:body(ClientRef),
% should be Scope => Config
Token = token:from_response_json(Scope, ResponseBody),
{ok, Token};
{error, Reason} ->
{error, Reason}
end,
Return;
get_access_token({oauth_refresh}, #config{}= _Scope) ->
Config = config:get(refresh_token),
{_, RefreshToken} = Config#config.refresh_token,
{_, ClientId} = Config#config.client_id,
{_, ClientSecret} = Config#config.client_secret,
Headers = [{
<<"Content-Type">>, <<"application/x-www-form-urlencoded">>
}],
Body = [#{
grant_type => authorization_code,
refresh_token => RefreshToken,
client_id => ClientId,
client_secret => ClientSecret,
access_type => offline
}],
Payload = jiffy:encode(Body),
Options = [],
URL = list_to_binary(?OAUTH_REFRESH_URL(binary_to_list(ClientId))),
Return = case hackney:post(URL, Headers, Payload, Options)
of
{ok, _, _, ClientRef} ->
{ok, ResponseBody} = hackney:body(ClientRef),
{token = Token} = jiffy:decode(ResponseBody, [ return_maps ]),
{ok, Token};
{error, Reason} ->
{error, Reason}
end,
Return;
get_access_token({metadata}, _Scope) ->
io:fwrite("get_access_token metata"),
% TODO: account needs to be specificed in URL
URL = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default",
Headers = [
{
<<"Metadata-Flavor">>, <<"Google">>
}
],
Payload = <<>>,
Options = [],
% https://github.com/benoitc/hackney
case hackney:get(URL, Headers, Payload, Options)
of
{ok, _, _, ClientRef} ->
{ok, ResponseBody} = hackney:body(ClientRef),
{token = Token} = jiffy:decode(ResponseBody, [ return_maps ]),
{ok, Token};
{error, Reason} ->
{error, Reason}
end.
retrieve_metadata_project() ->
URL = "http://metadata.google.internal/computeMetadata/v1/project/project-id",
Headers = [{
<<"Metadata-Flavor">>, <<"Google">>
}],
Options = [],
{ok, _, _, ClientRef} = hackney:get(URL, Headers, Options),
% probably returns json, need to parse
hackney:body(ClientRef).
%%% INTERNAL ===================================================================
- spec claims(Scope) -> Return when
Scope :: #config{},
Return :: map().
claims(Scope) ->
Config = config:get(client_email),
{_, Value} = Config#config.client_email,
{MegaSecs, Secs, _} = erlang:timestamp(),
UnixTime = MegaSecs * 1000000 + Secs,
#{
<<"iss">> => Value,
<<"scope">> => Scope#config.scope,
<<"aud">> => <<"https://www.googleapis.com/oauth2/v4/token">>,
<<"iat">> => UnixTime,
<<"exp">> => UnixTime + 3600
}.
- spec jwt(Scope) -> Return when
Scope :: #config{},
Return :: binary().
jwt(Scope) ->
Claims = claims(Scope),
Config = config:get(private_key),
{_, PrivateKey} = Config#config.private_key,
{ok, Token} = jwt:encode(<<"RS256">>, Claims, PrivateKey),
Token.
%%%_ * Tests -------------------------------------------------------
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
%%%_* Emacs ============================================================
%%% Local Variables:
%%% allout-layout: t
%%% erlang-indent-level: 2
%%% End: