From 023fcf384bc89b861f51c81a20a5aefea8afe772 Mon Sep 17 00:00:00 2001 From: Jordan Bracco Date: Fri, 3 Sep 2021 22:47:55 +0200 Subject: userinfo endpoint --- apps/ory/src/ory_hydra.erl | 6 +++++- apps/ory/src/ory_kratos.erl | 17 ++++++++++++---- apps/styx_web/src/styx_web_app.erl | 1 + apps/styx_web/src/styx_web_userinfo.erl | 35 +++++++++++++++++++++++++++++++++ config/sys.config | 11 ++++++++++- 5 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 apps/styx_web/src/styx_web_userinfo.erl diff --git a/apps/ory/src/ory_hydra.erl b/apps/ory/src/ory_hydra.erl index 8902c98..47b6601 100644 --- a/apps/ory/src/ory_hydra.erl +++ b/apps/ory/src/ory_hydra.erl @@ -1,5 +1,5 @@ -module(ory_hydra). --export([url/0, admin_url/0, login_request/1, accept_login_request/2, consent_request/1, accept_consent_request/2, reject_consent_request/2]). +-export([url/0, admin_url/0, userinfo/1, login_request/1, accept_login_request/2, consent_request/1, accept_consent_request/2, reject_consent_request/2]). login_request(Challenge) -> Url = [admin_url(), "/oauth2/auth/requests/login?login_challenge=", Challenge], @@ -29,6 +29,10 @@ reject_consent_request(Challenge, Data) -> Json = jsone:encode(Data), api_response(hackney:request(put, Url, Headers, Json, [])). +userinfo(Authorization) -> + Url = [url(), "/userinfo"], + Headers = [{"accept", "application/json"}, {"authorization", Authorization}], + api_response(hackney:request(get, Url, Headers, <<>>, [])). admin_url() -> {ok, Value} = application:get_env(ory, hydra_admin_url), diff --git a/apps/ory/src/ory_kratos.erl b/apps/ory/src/ory_kratos.erl index 02a85a0..1d4c5a1 100644 --- a/apps/ory/src/ory_kratos.erl +++ b/apps/ory/src/ory_kratos.erl @@ -1,7 +1,8 @@ -module(ory_kratos). --export([login_url/1, registration_url/1, settings_url/1, recovery_url/1, verification_url/1, url/0]). +-export([login_url/1, registration_url/1, settings_url/1, recovery_url/1, verification_url/1, url/0, admin_url/0]). -export([registration_flow/2, login_flow/2, settings_flow/2, recovery_flow/2, verification_flow/2, logout_flow/1, whoami/1, error/1]). +-export([get_identity/1]). login_url(browser) -> [url(), "/self-service/login/browser"]. @@ -22,6 +23,10 @@ url() -> {ok, Value} = application:get_env(ory, kratos_url), Value. +admin_url() -> + {ok, Value} = application:get_env(ory, kratos_admin_url), + Value. + registration_flow(Cookie, Id) -> Url = [url(), "/self-service/registration/flows?id=", Id], Headers = [{<<"cookie">>, Cookie}, {"accept", "application/json"}], @@ -59,9 +64,13 @@ whoami(Cookie) -> error(Id) -> Url = [url(), "/self-service/errors?id=", Id], - {ok, 200, _, Client} = hackney:request(get, Url, [], <<>>, []), - {ok, Body} = hackney:body(Client), - {ok, jsone:decode(Body)}. + Headers = [{"accept", "application/json"}], + api_response(hackney:request(get, Url, Headers, <<>>, [])). + +get_identity(Id) -> + Url = [admin_url(), "/identities/", Id], + Headers = [{"accept", "application/json"}], + api_response(hackney:request(get, Url, Headers, <<>>, [])). api_response(Error = {error, Error}) -> logger:error("ory_kratos hackney error: ~p", [Error]), diff --git a/apps/styx_web/src/styx_web_app.erl b/apps/styx_web/src/styx_web_app.erl index 39ce39b..ce2f29f 100644 --- a/apps/styx_web/src/styx_web_app.erl +++ b/apps/styx_web/src/styx_web_app.erl @@ -29,6 +29,7 @@ routes() -> %% App {"/", styx_web_index, undefined}, {"/launchpad", styx_web_launchpad, undefined}, + {"/userinfo", styx_web_userinfo, undefined}, %% Kratos {"/login", styx_web_kratos_flow, #{page_title => "Login", template => login_dtl, getflowmf => {ory_kratos, login_flow}, initflowmf => {ory_kratos, login_url}}}, diff --git a/apps/styx_web/src/styx_web_userinfo.erl b/apps/styx_web/src/styx_web_userinfo.erl new file mode 100644 index 0000000..2875898 --- /dev/null +++ b/apps/styx_web/src/styx_web_userinfo.erl @@ -0,0 +1,35 @@ +-module(styx_web_userinfo). +-behaviour(cowboy_handler). +-export([init/2]). + +init(Req = #{method := <<"GET">>}, State) -> + get(Req, State); +init(Req, _) -> + styx_web_error:init(Req, #{code => 404, status => <<"Not Found">>}). + +get(Req, State) -> + Authorization = cowboy_req:header(<<"authorization">>, Req), + case ory_hydra:userinfo(Authorization) of + {ok, UserInfo} -> + get_identity(Req, UserInfo, State) + end. + +get_identity(Req, UserInfo = #{<<"sub">> := Id}, State) -> + case ory_kratos:get_identity(Id) of + {ok, Identity} -> process(Req, UserInfo, Identity, State) + end. + +process(Req, UserInfo, #{<<"traits">> := Traits, <<"schema_id">> := Schema}, State) -> + Mapper = maps:get(Schema, application:get_env(styx, openid_userinfo_mapper, #{})), + MapFun = fun(Key, Value, Acc) -> maps:put(Key, get_value(Traits, Value), Acc) end, + Data = maps:fold(MapFun, #{}, Mapper), + MergedData = maps:merge(UserInfo, Data), + Headers = #{<<"content-type">> => <<"application/json">>}, + cowboy_req:reply(200, Headers, jsone:encode(MergedData), Req). + +get_value(Traits, Value) when is_binary(Value) -> + maps:get(Value, Traits); +get_value(Traits, [Value | Acc]) -> + get_value(maps:get(Value, Traits), Acc); +get_value(Value, []) -> + Value. diff --git a/config/sys.config b/config/sys.config index 60a32cb..4c51865 100644 --- a/config/sys.config +++ b/config/sys.config @@ -1,6 +1,15 @@ [ {kernel, [{logger_level, debug}]}, - {styx, []}, + {styx, [ + {openid_userinfo_mapper, #{ + <<"default">> => #{ + <<"preferred_username">> => <<"username">>, + <<"email">> => <<"email">>, + <<"given_name">> => [<<"name">>, <<"first">>], + <<"family_name">> => [<<"name">>, <<"last">>] + } + }} + ]}, {ory, [ {kratos_url, <<"http://127.0.0.1:4433">>}, {kratos_admin_url, <<"http://127.0.0.1:4434">>}, -- cgit v1.2.3