4#include "Stroika/Frameworks/StroikaPreComp.h"
7#include "Stroika/Foundation/Containers/Association.h"
14#include "Stroika/Foundation/IO/Network/Transfer/Connection.h"
27using namespace Stroika::Frameworks;
28using namespace Stroika::Frameworks::Auth::OAuth;
40String TokenRequest::ToString ()
const
46 sb <<
", code: "sv <<
code;
47 sb <<
", grant_type: authorization_code"sv;
51 sb <<
", grant_type: refresh_token"sv;
82TypedBLOB TokenRequest::ToWireFormat ()
const
89 static const auto kExcept_ =
RuntimeErrorException{
"Cannot combine authorization code/refresh_token"sv};
96 BLOB reqBody = [&] () {
100 params.Add ({
"code"sv, *
code});
101 params.Add ({
"grant_type"sv,
"authorization_code"sv});
105 params.Add ({
"grant_type"sv,
"refresh_token"sv});
118 return TypedBLOB{reqBody, InternetMediaTypes::kWWWFormURLEncoded};
124 static const auto kExcept_ =
RuntimeErrorException{
"Expected {}"_f(InternetMediaTypes::kWWWFormURLEncoded)};
138 static const auto kExcept_ =
RuntimeErrorException{
"Cannot combine authorization code/refresh_token"sv};
144 .client_secret = params.
LookupOne (
"client_secret"sv),
145 .redirect_uri = params.
LookupOne (
"redirect_uri"sv)};
153String TokenResponse::ToString ()
const
159 sb <<
", scope: "sv << scope;
164 sb <<
", id_token: "sv << id_token;
167 sb <<
", token_type: "sv << token_type;
186 return VariantValue{(objOfType->AsUTC () - DateTime::NowUTC ()).
As<
int> ()};
190 *into = DateTime::NowUTC ().AddSeconds (d.As<
int> ());
193 {
"scope"sv, &TokenResponse::scope,
196 return objOfType->Join (
" "sv);
203 {
"id_token"sv, &TokenResponse::id_token},
204 {
"token_type"sv, &TokenResponse::token_type},
209TypedBLOB TokenResponse::ToWireFormat ()
const
228String TokenRevocationRequest::ToString ()
const
232 sb <<
"access_token: "sv << access_token;
234 sb <<
", refresh_token: "sv << *refresh_token;
237 sb <<
", client_id: "sv << *client_id;
240 sb <<
", client_secret: "sv << *client_secret;
250 mapper.
AddClass<TokenRevocationRequest> ({
251 {
"access_token"sv, &TokenRevocationRequest::access_token},
252 {
"refresh_token"sv, &TokenRevocationRequest::refresh_token},
253 {
"client_id"sv, &TokenRevocationRequest::client_id},
254 {
"client_secret"sv, &TokenRevocationRequest::client_secret},
259TypedBLOB TokenRevocationRequest::ToWireFormat ()
const
261 if (access_token.empty ()) {
265 BLOB reqBody = [&] () {
268 params.
Add ({
"token_type_hint"sv,
"refresh_token"sv});
269 params.
Add ({
"token"sv, *refresh_token});
272 params.
Add ({
"token_type_hint"sv,
"access_token"sv});
273 params.
Add ({
"token"sv, access_token});
276 params.
Add ({
"client_id"sv, *client_id});
279 params.
Add ({
"client_secret"sv, *client_secret});
283 return TypedBLOB{reqBody, InternetMediaTypes::kWWWFormURLEncoded};
291String TokenIntrospectionResponse::ToString ()
const
308 Google TokenInfo Endpoint
309 +You can use
this endpoint to
"introspect" an access token by sending a GET request:
311 +Parameter: access_token
315 +Expected JSON Response
316 +If the token is valid, Google returns metadata including the expiration time:
319 +
"azp":
"123456789-example.apps.googleusercontent.com",
320 +
"aud":
"123456789-example.apps.googleusercontent.com",
321 +
"sub":
"111222333444555",
322 +
"scope":
"https://www.googleapis.com/auth/userinfo.email openid",
323 +
"exp":
"1710275200",
324 +
"expires_in":
"3599",
325 +
"email":
"user@example.com",
326 +
"email_verified":
"true"
333 return VariantValue{(objOfType->AsUTC () - DateTime::NowUTC ()).
As<
int> ()};
337 *into = DateTime::NowUTC ().AddSeconds (d.As<
int> ());
343TypedBLOB TokenIntrospectionResponse::ToWireFormat ()
const
362String UserInfo::ToString ()
const
367 sb <<
"name: "sv << name;
370 sb <<
", given_name: "sv << given_name;
373 sb <<
", family_name: "sv << family_name;
376 sb <<
", email: "sv << email;
379 sb <<
", picture: "sv << picture;
392 {
"name"sv, &UserInfo::name},
393 {
"given_name"sv, &UserInfo::given_name},
394 {
"family_name"sv, &UserInfo::family_name},
395 {
"email"sv, &UserInfo::email},
396 {
"picture"sv, &UserInfo::picture},
401UserInfo UserInfo::FromWireFormat (
const TypedBLOB& src)
416 : fProviderConfiguration_{providerConfiguration}
418 , fMaybeLock_{options.fInternallySyncrhonized == eInternallySynchronized ?
VirtualLockable::Make<recursive_mutex> ()
420 , fCache_{options.fCaching ? make_unique<Cache_> () : nullptr}
425 : fProviderConfiguration_{src.fProviderConfiguration_}
426 , fOptions_{src.fOptions_}
427 , fMaybeLock_{src.fOptions_.fInternallySyncrhonized == eInternallySynchronized
430 , fCache_{src.fCache_ ? make_unique<Cache_> () : nullptr}
436#if USE_NOISY_TRACE_IN_THIS_MODULE_
446 return TokenResponse::FromWireFormat (r.
GetTypedData ());
449 DbgTrace (
"Fetcher::Token: exception={}"_f, current_exception ());
454 scoped_lock critSec{fMaybeLock_};
455 if (optional<TokenResponse> o = fCache_->fTokens.Lookup (tr)) {
456 auto now = DateTime::Now ();
457 if (o->expires_at <= now) {
462 auto r = nonCachingFetcher ();
464 scoped_lock critSec{fMaybeLock_};
465 fCache_->fTokens.Add (tr, r);
466 fCache_->fAccessToken2Expiration.Add (r.access_token, r.expires_at);
480 ClearOldStuffFromCache_ ();
481#if USE_NOISY_TRACE_IN_THIS_MODULE_
489#if USE_NOISY_TRACE_IN_THIS_MODULE_
493 scoped_lock critSec{fMaybeLock_};
495 fCache_->fTokens.RemoveAll (
497 fCache_->fAccessToken2Expiration.RemoveIf (tr.access_token);
498 fCache_->fAccessToken2UserInfo.RemoveIf (tr.access_token);
507 DbgTrace (
"Fetcher::RevokeTokens: exception={}"_f, current_exception ());
512 DbgTrace (
"Fetcher::RevokeTokens: skipping due to missing revocation_endpoint"_f);
517Google TokenInfo Endpoint
518You can use
this endpoint to
"introspect" an access token by sending a GET request:
520Parameter: access_token
524Use code with caution.
526Expected JSON Response
527If the token is valid, Google returns metadata including the expiration time:
528Google Cloud Documentation
529Google Cloud Documentation
532 "azp":
"123456789-example.apps.googleusercontent.com",
533 "aud":
"123456789-example.apps.googleusercontent.com",
534 "sub":
"111222333444555",
535 "scope":
"https://www.googleapis.com/auth/userinfo.email openid",
537 "expires_in":
"3599",
538 "email":
"user@example.com",
539 "email_verified":
"true"
545#if USE_NOISY_TRACE_IN_THIS_MODULE_
548 auto nonCachingFetcher = [&] () -> UserInfo {
550 auto authInfo = IO::Network::Transfer::Connection::Options::Authentication{
"Bearer "sv + accessToken};
558 DbgTrace (
"Fetcher::UserInfo: exception={}"_f, current_exception ());
563 scoped_lock critSec{fMaybeLock_};
564 if (optional<DateTime> od = fCache_->fAccessToken2Expiration.Lookup (accessToken)) {
565 Time::DateTime now = Time::DateTime::Now ();
567 fCache_->fAccessToken2UserInfo.RemoveIf (accessToken);
570 if (optional<UserInfo> ou = fCache_->fAccessToken2UserInfo.Lookup (accessToken)) {
576 UserInfo userInfo = nonCachingFetcher ();
581 unique_lock tmpLock{fMaybeLock_};
582 if (not fCache_->fAccessToken2Expiration.ContainsKey (accessToken)) {
584 if (optional<TokenIntrospectionResponse> o = FetchTokenIntrospection_ (accessToken)) {
586 fCache_->fAccessToken2Expiration.Add (accessToken, o->expires_at);
590 static constexpr auto kWAG_ = 30s;
591 fCache_->fAccessToken2Expiration.Add (accessToken, Time::DateTime::NowUTC () + kWAG_);
595 scoped_lock critSec{fMaybeLock_};
596 fCache_->fAccessToken2UserInfo.Add (accessToken, userInfo);
598 ClearOldStuffFromCache_ ();
599#if USE_NOISY_TRACE_IN_THIS_MODULE_
600 DbgTrace (
"returning: {}"_f, userInfo);
605optional<TokenIntrospectionResponse> Fetcher::FetchTokenIntrospection_ (
const String& accessToken)
const
613 auto authInfo = IO::Network::Transfer::Connection::Options::Authentication{
"Bearer "sv + accessToken};
629 return TokenIntrospectionResponse::FromWireFormat (r.
GetTypedData ());
632 DbgTrace (
"Fetcher::FetchTokenInfo_: exception={}"_f, current_exception ());
639void Fetcher::ClearOldStuffFromCache_ ()
const
641#if USE_NOISY_TRACE_IN_THIS_MODULE_
645 scoped_lock critSec{fMaybeLock_};
647 Time::DateTime now = Time::DateTime::Now ();
648 if (Time::GetTickCount () > fCache_->fNextClearAt_) {
650 auto keys2Keep = fCache_->fAccessToken2UserInfo.Keys ();
651 fCache_->fAccessToken2Expiration.RetainAll (keys2Keep);
652 fCache_->fAccessToken2UserInfo.RetainAll (keys2Keep);
653 fCache_->fNextClearAt_ = Time::GetTickCount () + Cache_::kClearMaxFrequency_;
#define AssertNotImplemented()
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
An Association pairs key values with (possibly multiple or none) mapped_type values....
nonvirtual optional< mapped_type > LookupOne(ArgByValueType< key_type > key) const
Lookup and return the first (maybe arbitrarily chosen which is first) value with this key,...
nonvirtual void Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt)
nonvirtual mapped_type LookupOneChecked(ArgByValueType< key_type > key, const THROW_IF_MISSING &throwIfMissing) const
Lookup and return the first (maybe arbitrarily chosen which is first) value with this key,...
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
ObjectVariantMapper can be used to map C++ types to and from variant-union types, which can be transp...
nonvirtual void AddClass(const Traversal::Iterable< StructFieldInfo > &fieldDescriptions, const ClassMapperOptions< CLASS > &mapperOptions={})
function< VariantValue(const ObjectVariantMapper &mapper, const T *objOfType)> FromObjectMapperType
nonvirtual void AddCommonType(ARGS &&... args)
nonvirtual VariantValue FromObject(const T &from) const
nonvirtual T ToObject(const VariantValue &v) const
function< void(const ObjectVariantMapper &mapper, const VariantValue &d, T *into)> ToObjectMapperType
TypedBLOB is a named tuple<Memory::BLOB, optional<InternetMediaType>> - with friendlier names,...
nonvirtual Memory::BLOB WriteAsBLOB(const VariantValue &v) const
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
nonvirtual RETURNTYPE As() const
nonvirtual TypedBLOB GetTypedData() const
combine the 'body' data with a content-type indicator into a combined structure.
nonvirtual CONTAINER_OF_T As(CONTAINER_OF_T_CONSTRUCTOR_ARGS... args) const
simple wrapper on IO::Network::Transfer to do fetching (more configurability to do)
nonvirtual UserInfo GetUserInfo(const String &accessToken) const
nonvirtual TokenResponse GetToken(const TokenRequest &tr) const
nonvirtual void RevokeTokens(const TokenRevocationRequest &tr) const
Track configuration data about stuff that differentiates different OAuth providers - what URLs to use...
optional< URI > token_uri
optional< URI > tokeninfo_endpoint
logically similar to introspection_endpoint, but googles incompatible way
optional< URI > revocation_endpoint
optional< URI > introspection_endpoint
RFC 7662 compatible API for finding info about a token - https://datatracker.ietf....
optional< URI > userinfo_endpoint
this represents a HTTP request object for the WebServer module
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
RFC 7662 compatible API for finding info about a token - https://datatracker.ietf....
this is the argument to the Fetcher::GetToken () API. It typically consists of a client_id,...
optional< String > code_verifier
optional< String > refresh_token
optional< String > client_secret
optional< URI > redirect_uri
this is the response to the Fetcher::GetToken () API. It typically provides an 'access token' with a ...
optional< String > refresh_token