Stroika Library 3.0d23
 
Loading...
Searching...
No Matches
Client.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Frameworks_Auth_OAuth_Client_h_
5#define _Stroika_Frameworks_Auth_OAuth_Client_h_ 1
6
7#include "Stroika/Frameworks/StroikaPreComp.h"
8
9#include "Stroika/Foundation/Cache/TimedCache.h"
11#include "Stroika/Foundation/Common/Common.h"
12#include "Stroika/Foundation/Common/GUID.h"
13#include "Stroika/Foundation/Containers/KeyedCollection.h"
14#include "Stroika/Foundation/Containers/Mapping.h"
15#include "Stroika/Foundation/Containers/Sequence.h"
16#include "Stroika/Foundation/DataExchange/ObjectVariantMapper.h"
19#include "Stroika/Foundation/Execution/VirtualLockable.h"
21
23
24/**
25 * \file
26 *
27 * \note Code-Status: <a href="Code-Status.md#Alpha">Alpha</a>
28 */
29
30namespace Stroika::Frameworks::Auth::OAuth {
31
32 using namespace Stroika::Foundation;
33
35 using Containers::Set;
38 using IO::Network::URI;
39 using Time::DateTime;
40
42
43 /**
44 * @brief this is the argument to the Fetcher::GetToken () API. It typically consists of a client_id, client_secret, authorization 'code' and other things in OAUTH client auto token request.
45 *
46 * MEANT to be provider independent, but best docs I've found so far...
47 *
48 * \see https://developers.google.com/identity/protocols/oauth2/web-server#httprest_3
49 */
50 struct TokenRequest {
51 /**
52 * The client ID obtained from the Cloud Console Clients page (https://console.cloud.google.com/auth/clients).
53 */
55
56 /**
57 * The authorization code returned from the request (https://developers.google.com/identity/protocols/oauth2/web-server#httprest_1)
58 *
59 * \note refresh_token and code are mutually exclusive (not using variant cuz same type and no name)
60 *
61 * implies grant_type: authorization_code
62 */
63 optional<String> code;
64
65 /**
66 * \note refresh_token and code are mutually exclusive (not using variant cuz same type and no name)
67 *
68 * implies grant_type: refresh_token
69 */
70 optional<String> refresh_token;
71
72#if 0
73 /**
74 * \brief set to 'authorization_code' when exchanging authorization code for access token
75 *
76 * https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1
77 */
78 String grant_type{"authorization_code"sv};
79#endif
80
81 /**
82 * The client secret obtained from the Cloud Console Clients page (https://console.cloud.google.com/auth/clients).
83 * It is also often found in ClientConfiguration::fClientSecret
84 */
85 optional<String> client_secret;
86
87 /**
88 * One of the redirect URIs listed for your project in the Cloud Console Clients page (https://console.cloud.google.com/auth/clients) for the given client_id.
89 * Needed for most flows, but not all.
90 */
91 optional<URI> redirect_uri;
92
93 /**
94 * WAG - FIND DOCS ON THIS---
95 * \note only applies to
96 */
97 optional<String> code_verifier;
98
99 /**
100 */
101 nonvirtual String ToString () const;
102
103 /**
104 */
105 nonvirtual TypedBLOB ToWireFormat () const;
106
107 /**
108 */
109 static TokenRequest FromWireFormat (const TypedBLOB& src);
110
111#if qCompilerAndStdLib_explicitly_defaulted_threeway_warning_Buggy
112 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wdefaulted-function-deleted\"")
113#endif
114 /**
115 * @brief Compare by string value of various fields.
116 */
117 auto operator<=> (const TokenRequest& rhs) const = default;
118#if qCompilerAndStdLib_explicitly_defaulted_threeway_warning_Buggy
119 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wdefaulted-function-deleted\"")
120#endif
121
122 static const ObjectVariantMapper kMapper;
123 };
124
125 /**
126 * @brief this is the response to the Fetcher::GetToken () API. It typically provides an 'access token' with a set of scopes, and other things about the provided access.
127 *
128 * MEANT to be provider independent, but best docs I've found so far...
129 *
130 * https://developers.google.com/identity/protocols/oauth2/web-server#exchange-authorization-code
131 */
133 /**
134 * https://datatracker.ietf.org/doc/html/rfc6749#section-1.4
135 */
137
138 /**
139 * OAuth uses expires_in, but we convert to an expires_at since better to track (in UTC)
140 */
141 DateTime expires_at = DateTime::Now ();
142
143 Set<String> scope;
144
145 /**
146 * https://datatracker.ietf.org/doc/html/rfc6749#section-1.5
147 */
148 optional<String> refresh_token;
149
150 /**
151 */
152 optional<String> id_token;
153
154 /**
155 */
156 optional<String> token_type;
157
158 nonvirtual String ToString () const;
159
160 nonvirtual TypedBLOB ToWireFormat () const;
161 static TokenResponse FromWireFormat (const TypedBLOB& src);
162
163 static const ObjectVariantMapper kMapper;
164 };
165
166 /**
167 */
168 struct TokenRevocationRequest {
169 String access_token;
170 optional<String> refresh_token;
171 optional<String> client_id;
172 optional<String> client_secret;
173
174 nonvirtual String ToString () const;
175
176 nonvirtual TypedBLOB ToWireFormat () const;
177
178 static const ObjectVariantMapper kMapper;
179 };
180
181 /**
182 * @brief RFC 7662 compatible API for finding info about a token - https://datatracker.ietf.org/doc/html/rfc7662
183 *
184 * FOR NOW - we only use expires_at field and only support google token_info API
185 */
187
188 /**
189 * OAuth uses expires_in, but we convert to an expires_at since better to track (in UTC)
190 */
191 DateTime expires_at = DateTime::Now ();
192
193 nonvirtual String ToString () const;
194
195 nonvirtual TypedBLOB ToWireFormat () const;
196 static TokenIntrospectionResponse FromWireFormat (const TypedBLOB& src);
197
198 static const ObjectVariantMapper kMapper;
199 };
200
201 /**
202 */
203 struct UserInfo {
204
205 /**
206 */
207 optional<String> name;
208
209 /**
210 */
211 optional<String> given_name;
212
213 /**
214 */
215 optional<String> family_name;
216
217 /**
218 */
219 optional<String> email;
220
221 /**
222 * image of user (thumbnail)
223 */
224 optional<URI> picture;
225
226 nonvirtual String ToString () const;
227
228 static UserInfo FromWireFormat (const TypedBLOB& src);
229
230 static const ObjectVariantMapper kMapper;
231 };
232
233 /**
234 * \brief simple wrapper on IO::Network::Transfer to do fetching (more configurability to do)
235 *
236 * \note often you will want to use CachingFetcher
237 */
238 class Fetcher {
239 public:
240 struct Options {
241 bool fCaching{false};
242 InternallySynchronized fInternallySyncrhonized{InternallySynchronized::eNotKnownInternallySynchronized};
243 };
244
245 public:
246 /**
247 * \par Example Usage
248 * \code
249 * // rarely used, no caching
250 * ProviderConfiguration providerConfiguration{Auth::OAuth::kDefaultProviderConfigurations.LookupChecked (
251 * GetUseProvider_ (wsi), RuntimeErrorException{"Unrecognized provider name"sv})};
252 * if (wsi and wsi->fBearerToken) {
253 * Auth::OAuth::Fetcher f{providerConfiguration};
254 * Auth::OAuth::UserInfo clientUserInfo = f.GetUserInfo (wsi->fBearerToken.value_or (String{}));
255 * return clientUserInfo;
256 * }
257 * \endcode
258 *
259 * \note if you create a COPY of a Fetcher, it will NOT contain the same CACHE (so you can use this to clear/lose the cache).
260 * But it will contain all the same OPTIONS settings
261 */
262 Fetcher () = delete;
263 Fetcher (const Fetcher& src);
264 Fetcher (const ProviderConfiguration& providerConfiguration);
265 Fetcher (const ProviderConfiguration& providerConfiguration, const Options& options);
266
267 public:
268 /**
269 * https://developers.google.com/identity/protocols/oauth2/web-server#exchange-authorization-code
270 * https://developers.google.com/identity/protocols/oauth2/web-server#offline
271 *
272 * This can be used to convert EITHER an authorization_code (code parameter) or a refresh_token
273 * to a new access_code (and other TokenResponse info).
274 *
275 * \note - confusingly - despite docs above to the contrary, if you are not getting a refresh_token back it could
276 * be because google doesn't return it except on the first get token call
277 * (https://stackoverflow.com/questions/10827920/not-receiving-google-oauth-refresh-token)
278 *
279 * \note this doesn't use the cache (if present) - and makes a remote WS call each time.
280 */
281 nonvirtual TokenResponse GetToken (const TokenRequest& tr) const;
282
283 public:
284 /**
285 * Try to revoke the given refresh/access tokens. If no URI found in the ProviderConfiguration, its assumed the provider
286 * doesn't support this, and nothing todo. If an error occurs (say because of bad or missing client_id, or client_secret or already revoked?)
287 * an exception will be reported.
288 *
289 * \see https://datatracker.ietf.org/doc/html/rfc7009 (but this is woefully insufficient/incomplete/NOT what I followed)
290 * \see https://github.com/openid/AppAuth-JS/blob/master/src/revoke_token_request.ts - really got impl from here
291 */
292 nonvirtual void RevokeTokens (const TokenRevocationRequest& tr) const;
293
294 public:
295 /**
296 * curl -v -H "Authorization: Bearer ddd" https://www.googleapis.com/oauth2/v3/userinfo
297 *
298 * @todo FIND DOCS FOR THIS - try docs on https://accounts.google.com/.well-known/openid-configuration
299 *
300 * \note this MAY generate a slightly abbreviated user-info object, if the original access token was retrieved
301 * with an id_token (parsed out of that). To avoid that, if you want the full userInfo from the endpoint,
302 * create a new Fetcher instance (copying the Fetcher loses the cache info in the copy).
303 *
304 * @todo maybe too quirky - just add flag saying ignoreCache?
305 */
306 nonvirtual UserInfo GetUserInfo (const String& accessToken) const;
307
308 private:
309 //Google TokenInfo Endpoint:
310 // You can use this endpoint to "introspect" an access token by sending a GET request:
311 // Endpoint: https://oauth2.googleapis.com/tokeninfo
312 // Parameter: access_token
313 // Example Request
314 // http GET https://oauth2.googleapis.com
315 // Expected JSON Response
316 // If the token is valid, Google returns metadata including the expiration time:
317 //
318 // DOES NOT USE CACHE!!!
319 // Only used so that when we are given an accessCode to get userInfo for - so we can know how long it remains active in the cache
320 // NOTE: DONT throw on failure, just GUESS (maybe this should be a configurable option if we throw or not on introspection failure?)
321 nonvirtual optional<TokenIntrospectionResponse> FetchTokenIntrospectionQueitly_ (const String& accessToken) const;
322
323 private:
324 const ProviderConfiguration fProviderConfiguration_;
325 const Options fOptions_;
326
327 struct Cache_ {
328 // user-info CAN be missing, and just validity and expiration date on access token can be the only info...
331 Cache::TimedCache<String, optional<UserInfo>, UICACHETRAITS_> fAccessToken2UserInfo{30s};
332 };
333 unique_ptr<Cache_> fCache_;
334 };
335
336}
337
338/*
339 ********************************************************************************
340 ***************************** Implementation Details ***************************
341 ********************************************************************************
342 */
343#include "Client.inl"
344
345#endif /*_Stroika_Frameworks_Auth_OAuth_Client_h_*/
Keep track of a bunch of objects, each with an associated time used to allow data to 'expire'.
Definition TimedCache.h:572
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
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...
TypedBLOB is a named tuple<Memory::BLOB, optional<InternetMediaType>> - with friendlier names,...
Definition TypedBLOB.h:48
simple wrapper on IO::Network::Transfer to do fetching (more configurability to do)
Definition Client.h:238
nonvirtual UserInfo GetUserInfo(const String &accessToken) const
Definition Client.cpp:479
nonvirtual TokenResponse GetToken(const TokenRequest &tr) const
Definition Client.cpp:410
nonvirtual void RevokeTokens(const TokenRevocationRequest &tr) const
Definition Client.cpp:453
Track configuration data about stuff that differentiates different OAuth providers - what URLs to use...
InternallySynchronizedTraits same as argument traits, but resetting the kInternallySynchronized to eI...
Definition TimedCache.h:284
take argument TRAITS, and set to track-expires-at mode.
Definition TimedCache.h:316
RFC 7662 compatible API for finding info about a token - https://datatracker.ietf....
Definition Client.h:186
this is the argument to the Fetcher::GetToken () API. It typically consists of a client_id,...
Definition Client.h:50
auto operator<=>(const TokenRequest &rhs) const =default
Compare by string value of various fields.
this is the response to the Fetcher::GetToken () API. It typically provides an 'access token' with a ...
Definition Client.h:132