Skip to content

Instantly share code, notes, and snippets.

@omarkj
Created March 18, 2011 23:42
Show Gist options
  • Save omarkj/877038 to your computer and use it in GitHub Desktop.
Save omarkj/877038 to your computer and use it in GitHub Desktop.
%% Getting an access token using the Authorization Request and Response from oAuth2 - Draft 13
%% Important to notice; in this document a the remote service is referred to as 'the client' and the user as 'the user'.
%% NOTICE: I'm not implementing scope here but I might.
fun(Doc, {[Info|Request]}) ->
%% Functions used all over
OpenDb = fun (Database) ->
{ok, Db} = couch_db:open(Database, [{user_ctx, {user_ctx, undefined, [<<"_admin">>], undefined}}]),
Db
end,
GetDocumentRev = fun
(Db, DocumentId) when is_binary(Db) ->
Db0 = OpenDb(Db),
{ok, {doc_info, DocumentId, _, RevInfos}} = couch_db:get_doc_info(Db0, DocumentId),
{rev_info, RevInfo, _, _, _} = hd(RevInfos),
StrRev = couch_doc:rev_to_str(RevInfo);
(Db, DocumentId) ->
{ok, {doc_info, DocumentId, _, RevInfos}} = couch_db:get_doc_info(Db, DocumentId),
{rev_info, RevInfo, _, _, _} = hd(RevInfos),
StrRev = couch_doc:rev_to_str(RevInfo)
end,
ActualSave = fun(Document, Database) ->
CouchDoc = couch_doc:from_json_obj({Document}),
Db = OpenDb(Database),
try couch_db:update_doc(Db, CouchDoc, []) of
Ok -> ok
catch
conflict ->
conflict
end
end,
SaveDoc = fun(Document, Database) ->
case ActualSave(Document, Database) of
ok -> saved;
conflict ->
DocumentId = proplists:get_value(<<"_id">>, Document),
OldRev = GetDocumentRev(Database, DocumentId),
Document0 = Document ++ [{<<"_rev">>, OldRev}],
ActualSave(Document0, Database)
end
end,
%% Check functions etc
ValidUser = fun(ExpectedHash, CleanPassword, Salt) ->
EncryptedInput = binary:list_to_bin(couch_util:to_hex(crypto:sha(<<CleanPassword/binary, Salt/binary>>))),
case EncryptedInput of
ExpectedHash ->
true;
_ ->
false
end
end,
CreateError = fun
(ErrorType) ->
{[{<<"code">>, 400},
{<<"headers">>, {[
{<<"Content-Type">>, <<"application/json; chatset=utf-8">>},
{<<"Cache-Control">>, <<"no-store">>}
]}},
{<<"body">>, "{\"error\":\""++atom_to_list(ErrorType)++"\"}"}]}
end,
CreateToken0 = fun({UserId, ClientId, GeneratedAt, Token1}) ->
TokenId = couch_uuids:new(),
Token2 = Token1 ++ [{<<"token_id">>, TokenId},
{<<"user_id">>, UserId},
{<<"generated_at">>, GeneratedAt},
{<<"client_id">>, ClientId}],
%MaybeDeleteDocument(proplists:get_value(<<"_id">>, Token2)),
SaveDoc(Token2, <<"authorization">>),
TokenId
end,
CreateToken = fun
%% Access token
(access_token, {UserId, ClientId, TokenTime, GeneratedAt}) ->
TimeoutAt = GeneratedAt + TokenTime,
Token = [{<<"_id">>, <<UserId/binary, "/", ClientId/binary, "/access_token">>},
{<<"type">>, <<"access_token">>},
{<<"timeout">>, TimeoutAt}],
CreateToken0({UserId, ClientId, GeneratedAt, Token});
(refresh_token, {UserId, ClientId, GeneratedAt}) ->
Token = [{<<"_id">>, <<UserId/binary, "/", ClientId/binary, "/refresh_token">>},
{<<"type">>, <<"refresh_token">>}],
CreateToken0({UserId, ClientId, GeneratedAt, Token})
end,
%% Create the tokens, one access token and a refresh token.
%% These tokens are inserted into the database
%% The existance of these tokens also mean access has been granted
CreateResponse = fun(UserId, ClientDoc1) ->
TokenTime = proplists:get_value(<<"token_time">>, ClientDoc1),
ClientId = proplists:get_value(<<"id">>, ClientDoc1),
GeneratedAt = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
AccessToken = CreateToken(access_token, {UserId, ClientId, TokenTime, GeneratedAt}),
Body = "{\"access_token\":\""++binary_to_list(AccessToken)++"\", \"token_type\":\"BEARER\", \"expires_in\":"++integer_to_list(TokenTime),
Body0 = case proplists:get_value(<<"refresh_token">>, ClientDoc1) of
true ->
RefreshToken = CreateToken(refresh_token, {UserId, ClientId, GeneratedAt}),
Body ++ ", \"refresh_token\":\""++binary_to_list(RefreshToken)++"\"}";
_ ->
Body ++ "}"
end,
{[{<<"headers">>, {[
{<<"Content-Type">>, <<"application/json; chatset=utf-8">>},
{<<"Cache-Control">>, <<"no-store">>}
]}},
{<<"body">>, Body0}]}
end,
%% Handle different grant types
HandleGrantType = fun
%% Username and password flow
(<<"password">>, RequestDetails0, UserDoc0, ClientDoc0) ->
ExpectedHash = couch_util:get_value(<<"password">>, UserDoc0),
Salt = couch_util:get_value(<<"salt">>, UserDoc0),
InputPassword = proplists:get_value(<<"password">>, RequestDetails0),
case ValidUser(ExpectedHash, InputPassword, Salt) of
true ->
% This is a valid user, let's create a response
CreateResponse(couch_util:get_value(<<"_id">>, UserDoc0), ClientDoc0);
false ->
CreateError(invalid_client)
end;
%% No more grant types supported
(_GrantType, _UserDoc, _UserDetails, _ClientDocument) ->
CreateError(unsupported_grant_type)
end,
%% Get grant document from the database
GetGrantDocument = fun (RequestDetails1, UserDoc0) ->
% TODO Get this with a view to filter invalid clients from active clients
Db = OpenDb(<<"authorization">>),
ClientId = proplists:get_value(<<"client_id">>, RequestDetails1),
ClientId0 = <<"client:", ClientId/binary>>,
try couch_httpd_db:couch_doc_open(Db, ClientId0, nil, []) of
{doc, _, _, {ClientDocument0}, _, _, _} ->
ClientDocument1 = ClientDocument0 ++ [{<<"id">>, ClientId0}],
GrantType = proplists:get_value(<<"grant_type">>, RequestDetails1),
AllowedGrantTypes = proplists:get_value(<<"grant_types">>, ClientDocument1),
case lists:member(GrantType, AllowedGrantTypes) of
true ->
HandleGrantType(GrantType, RequestDetails1, UserDoc0, ClientDocument1);
_ ->
CreateError(invalid_grant)
end
catch
_Else0 ->
CreateError(invalid_client)
end
end,
%%
case Doc of
null ->
CreateError(invalid_client);
{UserDoc} ->
% A user exist
{Headers} = proplists:get_value(<<"headers">>, Request),
ContentType = proplists:get_value(<<"Content-Type">>, Headers),
RequestDetails = case ContentType of
<<"application/x-www-form-urlencoded">> ->
{UserDetails0} = proplists:get_value(<<"form">>, Request),
UserDetails0;
<<"application/json">> ->
{struct, UserDetails0} = mochijson2:decode(proplists:get_value(<<"body">>, Request)),
UserDetails0;
_Else ->
invalid_request
end,
case RequestDetails of
invalid_request ->
CreateError(invalid_request);
_Else0 ->
GetGrantDocument(RequestDetails, UserDoc)
end
end
end.
@omarkj
Copy link
Author

omarkj commented Mar 18, 2011

This is saved as a Erlang show in CouchDB.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment