<commit_before>%%
%%   Copyright 2017 <NAME>, All Rights Reserved
%%
%%   Licensed under the Apache License, Version 2.0 (the "License");
%%   you may not use this file except in compliance with the License.
%%   You may obtain a copy of the License at
%%
%%       http://www.apache.org/licenses/LICENSE-2.0
%%
%%   Unless required by applicable law or agreed to in writing, software
%%   distributed under the License is distributed on an "AS IS" BASIS,
%%   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%   See the License for the specific language governing permissions and
%%   limitations under the License.
%%
-module(oauth2).
-compile({parse_transform, category}).
-include("oauth2.hrl").
-include_lib("common_test/include/ct.hrl").


-export([start/0]).
-export([
   signin/2,
   signup/2,
   token/2,
   reset/2,
   recover/2
]).

%%
start() ->
   applib:boot(?MODULE, code:where_is_file("sys.config")).


%%
%%
signin(#{<<"response_type">> := <<"code">>, <<"access">> := Access, <<"secret">> := Secret} = Request, Client) ->
   case 
      oauth2_token:exchange_code(Access, Secret) 
   of
      {ok, Code} ->
         redirect_uri([{code, Code}], Request, Client);
      {error, _} = Error ->
         redirect_uri([Error], Request, Client)
   end;

signin(#{<<"response_type">> := <<"token">>, <<"access">> := Access, <<"secret">> := Secret} = Request, Client) ->
   case 
      oauth2_token:bearer(Access, Secret) 
   of
      {ok, #{<<"access_token">> := Token, <<"expires_in">> := TTL}} ->
         redirect_uri([{access_token, Token}, {expires_in, TTL}], Request, Client);
      {error, _} = Error ->
         redirect_uri([Error], Request, Client)
   end;

signin(Request, Client) ->
   redirect_uri([{error, unsupported_response_type}], Request, Client).


%%
%%
signup(#{<<"response_type">> := <<"code">>, <<"access">> := Access, <<"secret">> := Secret} = Request, Client) ->
   case 
      [either ||
         create_account(Request),
         oauth2_token:exchange_code(Access, Secret)
      ]
   of
      {ok, Code} ->
         redirect_uri([{code, Code}], Request, Client);
      {error, _} = Error ->
         redirect_uri([Error], Request, Client)
   end;

signup(#{<<"response_type">> := <<"token">>, <<"access">> := Access, <<"secret">> := Secret} = Request, Client) ->
   case 
      [either ||
         create_account(Request),
         oauth2_token:bearer(Access, Secret)
      ]
   of
      {ok, #{<<"access_token">> := Token, <<"expires_in">> := TTL}} ->
         redirect_uri([{access_token, Token}, {expires_in, TTL}], Request, Client);
      {error, _} = Error ->
         redirect_uri([Error], Request, Client)
   end;

signup(Request, Client) ->
   redirect_uri([{error, unsupported_response_type}], Request, Client).


%%
%%
token(#{<<"grant_type">> := <<"authorization_code">>, <<"code">> := Code}, Client) ->
   case oauth2_client:is_confidential(Client) of
      {ok, _} ->
         failure(forbidden,
            [either ||
               oauth2_token:is_exchangable(Code),
               oauth2_token:bearer(Code)
            ]
         );
      {error, _} ->
         failure(forbidden,
            [either ||
               oauth2_token:is_exchangable(Code),
               oauth2_token:bearer(Code),
               cats:unit(maps:remove(<<"refresh_token">>, _))
            ]
         )
   end;

token(#{<<"grant_type">> := <<"password">>, <<"username">> := Access, <<"password">> := Secret}, Client) ->
   case oauth2_client:is_confidential(Client) of
      {ok, _} ->
         failure(forbidden,
            [either ||
               oauth2_token:access(Access, Secret),
               oauth2_token:bearer(_)
            ]
         );
      {error, _} ->
         failure(forbidden,
            [either ||
               oauth2_token:access(Access, Secret),
               oauth2_token:bearer(_),
               cats:unit(maps:remove(<<"refresh_token">>, _))
            ]
         )
   end;

token(#{<<"grant_type">> := <<"client_credentials">>}, #{<<"identity">> := Token} = Client) ->
   failure(forbidden,
      [either ||
         oauth2_client:is_confidential(Client),
         oauth2_token:bearer(Token)
      ]
   );

token(#{<<"grant_type">> := <<"refresh_token">>, <<"refresh_token">> := Token}, Client) ->
   failure(forbidden,
      [either ||
         oauth2_token:is_exchangable(Token),
         oauth2_client:is_confidential(Client),
         oauth2_token:bearer(Token)
      ]
   );

token(_, _) ->
   {error, invalid_request}.

%%
%%
reset(#{<<"access">> := Access} = Request, #{<<"access">> := Id} = Client) ->
   case
      [either ||
         oauth2_account:lookup(Access),
         oauth2_account:claims(_),
         permit:update(Access, crypto:strong_rand_bytes(30), _),
         oauth2_token:recovery(_),
         oauth2_email:password_reset(
            Access,
            uri:anchor(<<"recover">>,
               uri:q([{client_id, Id}, {access, Access}, {code, _}],
                  uri:path(<<"/oauth2/authorize">>, 
                     uri:new(opts:val(issuer, permit))
                  )
               )
            )
         )
      ]
   of
      {ok, _} ->
         redirect_uri([{error, recovery}], Request, Client);      
      {error, _} ->
         redirect_uri([{error, forbidden}], Request, Client)
   end.

%%
%%
recover(#{<<"token">> := Token, <<"secret">> := Secret} = Request, Client) ->
   case
      [either ||
         oauth2_token:is_recoverable(Token),
         Access =< lens:get(lens:at(<<"sub">>), _),
         permit:update(Access, Secret),
         oauth2_token:exchange_code(Access, Secret)
      ]
   of
      {ok, Code} ->
         redirect_uri([{code, Code}], Request, Client);
      {error, _} = Error ->
         redirect_uri([Error], Request, Client)
   end.

%%-----------------------------------------------------------------------------
%%
%% private
%%
%%-----------------------------------------------------------------------------<commit_msg>Extra logging for email feature<commit_after>%%
%%   Copyright 2017 <NAME>, All Rights Reserved
%%
%%   Licensed under the Apache License, Version 2.0 (the "License");
%%   you may not use this file except in compliance with the License.
%%   You may obtain a copy of the License at
%%
%%       http://www.apache.org/licenses/LICENSE-2.0
%%
%%   Unless required by applicable law or agreed to in writing, software
%%   distributed under the License is distributed on an "AS IS" BASIS,
%%   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%   See the License for the specific language governing permissions and
%%   limitations under the License.
%%
-module(oauth2).
-compile({parse_transform, category}).
-include("oauth2.hrl").
-include_lib("common_test/include/ct.hrl").


-export([start/0]).
-export([
   signin/2,
   signup/2,
   token/2,
   reset/2,
   recover/2
]).

%%
start() ->
   applib:boot(?MODULE, code:where_is_file("sys.config")).


%%
%%
signin(#{<<"response_type">> := <<"code">>, <<"access">> := Access, <<"secret">> := Secret} = Request, Client) ->
   case 
      oauth2_token:exchange_code(Access, Secret) 
   of
      {ok, Code} ->
         redirect_uri([{code, Code}], Request, Client);
      {error, _} = Error ->
         redirect_uri([Error], Request, Client)
   end;

signin(#{<<"response_type">> := <<"token">>, <<"access">> := Access, <<"secret">> := Secret} = Request, Client) ->
   case 
      oauth2_token:bearer(Access, Secret) 
   of
      {ok, #{<<"access_token">> := Token, <<"expires_in">> := TTL}} ->
         redirect_uri([{access_token, Token}, {expires_in, TTL}], Request, Client);
      {error, _} = Error ->
         redirect_uri([Error], Request, Client)
   end;

signin(Request, Client) ->
   redirect_uri([{error, unsupported_response_type}], Request, Client).


%%
%%
signup(#{<<"response_type">> := <<"code">>, <<"access">> := Access, <<"secret">> := Secret} = Request, Client) ->
   case 
      [either ||
         create_account(Request),
         oauth2_token:exchange_code(Access, Secret)
      ]
   of
      {ok, Code} ->
         redirect_uri([{code, Code}], Request, Client);
      {error, _} = Error ->
         redirect_uri([Error], Request, Client)
   end;

signup(#{<<"response_type">> := <<"token">>, <<"access">> := Access, <<"secret">> := Secret} = Request, Client) ->
   case 
      [either ||
         create_account(Request),
         oauth2_token:bearer(Access, Secret)
      ]
   of
      {ok, #{<<"access_token">> := Token, <<"expires_in">> := TTL}} ->
         redirect_uri([{access_token, Token}, {expires_in, TTL}], Request, Client);
      {error, _} = Error ->
         redirect_uri([Error], Request, Client)
   end;

signup(Request, Client) ->
   redirect_uri([{error, unsupported_response_type}], Request, Client).


%%
%%
token(#{<<"grant_type">> := <<"authorization_code">>, <<"code">> := Code}, Client) ->
   case oauth2_client:is_confidential(Client) of
      {ok, _} ->
         failure(forbidden,
            [either ||
               oauth2_token:is_exchangable(Code),
               oauth2_token:bearer(Code)
            ]
         );
      {error, _} ->
         failure(forbidden,
            [either ||
               oauth2_token:is_exchangable(Code),
               oauth2_token:bearer(Code),
               cats:unit(maps:remove(<<"refresh_token">>, _))
            ]
         )
   end;

token(#{<<"grant_type">> := <<"password">>, <<"username">> := Access, <<"password">> := Secret}, Client) ->
   case oauth2_client:is_confidential(Client) of
      {ok, _} ->
         failure(forbidden,
            [either ||
               oauth2_token:access(Access, Secret),
               oauth2_token:bearer(_)
            ]
         );
      {error, _} ->
         failure(forbidden,
            [either ||
               oauth2_token:access(Access, Secret),
               oauth2_token:bearer(_),
               cats:unit(maps:remove(<<"refresh_token">>, _))
            ]
         )
   end;

token(#{<<"grant_type">> := <<"client_credentials">>}, #{<<"identity">> := Token} = Client) ->
   failure(forbidden,
      [either ||
         oauth2_client:is_confidential(Client),
         oauth2_token:bearer(Token)
      ]
   );

token(#{<<"grant_type">> := <<"refresh_token">>, <<"refresh_token">> := Token}, Client) ->
   failure(forbidden,
      [either ||
         oauth2_token:is_exchangable(Token),
         oauth2_client:is_confidential(Client),
         oauth2_token:bearer(Token)
      ]
   );

token(_, _) ->
   {error, invalid_request}.

%%
%%
reset(#{<<"access">> := Access} = Request, #{<<"access">> := Id} = Client) ->
   case
      [either ||
         oauth2_account:lookup(Access),
         oauth2_account:claims(_),
         permit:update(Access, crypto:strong_rand_bytes(30), _),
         oauth2_token:recovery(_),
         oauth2_email:password_reset(
            Access,
            uri:anchor(<<"recover">>,
               uri:q([{client_id, Id}, {access, Access}, {code, _}],
                  uri:path(<<"/oauth2/authorize">>, 
                     uri:new(opts:val(issuer, permit))
                  )
               )
            )
         )
      ]
   of
      {ok, Message} ->
         lager:notice("[oauth2]: rest account ~s with email ~p", [Access, Request]),
         redirect_uri([{error, recovery}], Request, Client);      
      {error, _} = Error ->
         lager:notice("[oauth2]: failed to rest account ~s with reason ~p", [Access, Error]),
         redirect_uri([{error, forbidden}], Request, Client)
   end.

%%
%%
recover(#{<<"token">> := Token, <<"secret">> := Secret} = Request, Client) ->
   case
      [either ||
         oauth2_token:is_recoverable(Token),
         Access =< lens:get(lens:at(<<"sub">>), _),
         permit:update(Access, Secret),
         oauth2_token:exchange_code(Access, Secret)
      ]
   of
      {ok, Code} ->
         redirect_uri([{code, Code}], Request, Client);
      {error, _} = Error ->
         redirect_uri([Error], Request, Client)
   end.

%%-----------------------------------------------------------------------------
%%
%% private
%%
%%-----------------------------------------------------------------------------
{'fraction_non_alphanumeric': 0.2012140103354934, 'fraction_numerical': 0.007382495283405791, 'mean_word_length': 2.3596031964728574, 'pattern_counts': {'":': 0, '<': 175, '<?xml version=': 0, '>': 233, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1848420', 'n_tokens_mistral': 3911, 'n_tokens_neox': 3848, 'n_words': 732}