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
10#include "Stroika/Foundation/Common/Common.h"
11#include "Stroika/Foundation/Common/GUID.h"
12#include "Stroika/Foundation/Containers/KeyedCollection.h"
13#include "Stroika/Foundation/Containers/Mapping.h"
14#include "Stroika/Foundation/Containers/Sequence.h"
15#include "Stroika/Foundation/DataExchange/ObjectVariantMapper.h"
18#include "Stroika/Foundation/Execution/VirtualLockable.h"
20
22
23/**
24 * \file
25 *
26 * \note Code-Status: <a href="Code-Status.md#Alpha">Alpha</a>
27 */
28
29namespace Stroika::Frameworks::Auth::OAuth {
30
31 using namespace Stroika::Foundation;
32
34 using Containers::Set;
37 using IO::Network::URI;
38 using Time::DateTime;
39
41
42 /**
43 * @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.
44 *
45 * MEANT to be provider independent, but best docs I've found so far...
46 *
47 * \see https://developers.google.com/identity/protocols/oauth2/web-server#httprest_3
48 */
49 struct TokenRequest {
50 /**
51 * The client ID obtained from the Cloud Console Clients page (https://console.cloud.google.com/auth/clients).
52 */
54
55 /**
56 * The authorization code returned from the request (https://developers.google.com/identity/protocols/oauth2/web-server#httprest_1)
57 *
58 * \note refresh_token and code are mutually exclusive (not using variant cuz same type and no name)
59 *
60 * implies grant_type: authorization_code
61 */
62 optional<String> code;
63
64 /**
65 * \note refresh_token and code are mutually exclusive (not using variant cuz same type and no name)
66 *
67 * implies grant_type: refresh_token
68 */
69 optional<String> refresh_token;
70
71#if 0
72 /**
73 * \brief set to 'authorization_code' when exchanging authorization code for access token
74 *
75 * https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1
76 */
77 String grant_type{"authorization_code"sv};
78#endif
79
80 /**
81 * The client secret obtained from the Cloud Console Clients page (https://console.cloud.google.com/auth/clients).
82 * It is also often found in ClientConfiguration::fClientSecret
83 */
84 optional<String> client_secret;
85
86 /**
87 * 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.
88 * Needed for most flows, but not all.
89 */
90 optional<URI> redirect_uri;
91
92 /**
93 * WAG - FIND DOCS ON THIS---
94 * \note only applies to
95 */
96 optional<String> code_verifier;
97
98 /**
99 */
100 nonvirtual String ToString () const;
101
102 /**
103 */
104 nonvirtual TypedBLOB ToWireFormat () const;
105
106 /**
107 */
108 static TokenRequest FromWireFormat (const TypedBLOB& src);
109
110 /**
111 * @brief Compare by string value of various fields.
112 */
113 auto operator<=> (const TokenRequest& rhs) const = default;
114
115 static const ObjectVariantMapper kMapper;
116 };
117
118 /**
119 * @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.
120 *
121 * MEANT to be provider independent, but best docs I've found so far...
122 *
123 * https://developers.google.com/identity/protocols/oauth2/web-server#exchange-authorization-code
124 */
126 /**
127 * https://datatracker.ietf.org/doc/html/rfc6749#section-1.4
128 */
130
131 /**
132 * OAuth uses expires_in, but we convert to an expires_at since better to track (in UTC)
133 */
134 DateTime expires_at = DateTime::Now ();
135
136 Set<String> scope;
137
138 /**
139 * https://datatracker.ietf.org/doc/html/rfc6749#section-1.5
140 */
141 optional<String> refresh_token;
142
143 /**
144 */
145 optional<String> id_token;
146
147 /**
148 */
149 optional<String> token_type;
150
151 nonvirtual String ToString () const;
152
153 nonvirtual TypedBLOB ToWireFormat () const;
154 static TokenResponse FromWireFormat (const TypedBLOB& src);
155
156 static const ObjectVariantMapper kMapper;
157 };
158
159 /**
160 */
161 struct TokenRevocationRequest {
162 String access_token;
163 optional<String> refresh_token;
164 optional<String> client_id;
165 optional<String> client_secret;
166
167 nonvirtual String ToString () const;
168
169 nonvirtual TypedBLOB ToWireFormat () const;
170
171 static const ObjectVariantMapper kMapper;
172 };
173
174 /**
175 * @brief RFC 7662 compatible API for finding info about a token - https://datatracker.ietf.org/doc/html/rfc7662
176 *
177 * FOR NOW - we only use expires_at field and only support google token_info API
178 */
180
181 /**
182 * OAuth uses expires_in, but we convert to an expires_at since better to track (in UTC)
183 */
184 DateTime expires_at = DateTime::Now ();
185
186 nonvirtual String ToString () const;
187
188 nonvirtual TypedBLOB ToWireFormat () const;
189 static TokenIntrospectionResponse FromWireFormat (const TypedBLOB& src);
190
191 static const ObjectVariantMapper kMapper;
192 };
193
194 /**
195 */
196 struct UserInfo {
197
198 /**
199 */
200 optional<String> name;
201
202 /**
203 */
204 optional<String> given_name;
205
206 /**
207 */
208 optional<String> family_name;
209
210 /**
211 */
212 optional<String> email;
213
214 /**
215 * image of user (thumbnail)
216 */
217 optional<URI> picture;
218
219 nonvirtual String ToString () const;
220
221 static UserInfo FromWireFormat (const TypedBLOB& src);
222
223 static const ObjectVariantMapper kMapper;
224 };
225
226 /**
227 * \brief simple wrapper on IO::Network::Transfer to do fetching (more configurability to do)
228 *
229 * \note often you will want to use CachingFetcher
230 */
231 class Fetcher {
232 public:
233 struct Options {
234 bool fCaching{false};
235 InternallySynchronized fInternallySyncrhonized{InternallySynchronized::eNotKnownInternallySynchronized};
236 };
237
238 public:
239 /**
240 * \par Example Usage
241 * \code
242 * // rarely used, no caching
243 * ProviderConfiguration providerConfiguration{Auth::OAuth::kDefaultProviderConfigurations.LookupChecked (
244 * GetUseProvider_ (wsi), RuntimeErrorException{"Unrecognized provider name"sv})};
245 * if (wsi and wsi->fBearerToken) {
246 * Auth::OAuth::Fetcher f{providerConfiguration};
247 * Auth::OAuth::UserInfo clientUserInfo = f.GetUserInfo (wsi->fBearerToken.value_or (String{}));
248 * return clientUserInfo;
249 * }
250 * \endcode
251 *
252 * \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).
253 * But it will contain all the same OPTIONS settings
254 */
255 Fetcher () = delete;
256 Fetcher (const Fetcher& src);
257 Fetcher (const ProviderConfiguration& providerConfiguration);
258 Fetcher (const ProviderConfiguration& providerConfiguration, const Options& options);
259
260 public:
261 /**
262 * https://developers.google.com/identity/protocols/oauth2/web-server#exchange-authorization-code
263 * https://developers.google.com/identity/protocols/oauth2/web-server#offline
264 *
265 * This can be used to convert EITHER an authorization_code (code parameter) or a refresh_token
266 * to a new access_code (and other TokenResponse info).
267 *
268 * \note - confusingly - despite docs above to the contrary, if you are not getting a refresh_token back it could
269 * be because google doesn't return it except on the first get token call
270 * (https://stackoverflow.com/questions/10827920/not-receiving-google-oauth-refresh-token)
271 */
272 nonvirtual TokenResponse GetToken (const TokenRequest& tr) const;
273
274 public:
275 /**
276 * Try to revoke the given refresh/access tokens. If no URI found in the ProviderConfiguration, its assumed the provider
277 * 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?)
278 * an exception will be reported.
279 *
280 * \see https://datatracker.ietf.org/doc/html/rfc7009 (but this is woefully insufficient/incomplete/NOT what I followed)
281 * \see https://github.com/openid/AppAuth-JS/blob/master/src/revoke_token_request.ts - really got impl from here
282 */
283 nonvirtual void RevokeTokens (const TokenRevocationRequest& tr) const;
284
285 public:
286 /**
287 * curl -v -H "Authorization: Bearer ddd" https://www.googleapis.com/oauth2/v3/userinfo
288 *
289 * @todo FIND DOCS FOR THIS - try docs on https://accounts.google.com/.well-known/openid-configuration
290 *
291 * \note this MAY generate a slightly abbreviated user-info object, if the original access token was retrieved
292 * with an id_token (parsed out of that). To avoid that, if you want the full userInfo from the endpoint,
293 * create a new Fetcher instance.
294 */
295 nonvirtual UserInfo GetUserInfo (const String& accessToken) const;
296
297 private:
298 // Google TokenInfo Endpoint
299 // You can use this endpoint to "introspect" an access token by sending a GET request:
300 // Endpoint: https://oauth2.googleapis.com/tokeninfo
301 // Parameter: access_token
302 // Example Request
303 // http
304 // GET https://oauth2.googleapis.com
305
306 // Expected JSON Response
307 // If the token is valid, Google returns metadata including the expiration time:
308 //
309 // DOES NOT USE CACHE!!!
310 // 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
311 nonvirtual optional<TokenIntrospectionResponse> FetchTokenIntrospection_ (const String& accessToken) const;
312
313 private:
314 /*
315 * NOTE that fMaybeLock_ applies to BOTH cache and ?? - not sure what else there is???
316 * So why not use Syncrhonized? Cuz we dont have maybe-syncrhonized?
317 */
318 const ProviderConfiguration fProviderConfiguration_;
319 const Options fOptions_;
320 mutable Execution::VirtualLockable fMaybeLock_; // either Debug::AssertExternallySyncrhonized or std::recursive_mutex
321 struct Cache_ {
322 static constexpr auto kClearMaxFrequency_{30s};
323 Time::TimePointSeconds fNextClearAt_{Time::GetTickCount () + kClearMaxFrequency_};
324
325 // @todo REIMPLEMENT with new Cache layer code to support this - TTLCacher
327 Containers::Mapping<String, DateTime> fAccessToken2Expiration;
328 Containers::Mapping<String, UserInfo> fAccessToken2UserInfo;
329 };
330 unique_ptr<Cache_> fCache_;
331
332 private:
333 nonvirtual void ClearOldStuffFromCache_ () const;
334 };
335
336}
337
338/*
339 ********************************************************************************
340 ***************************** Implementation Details ***************************
341 ********************************************************************************
342 */
343#include "Client.inl"
344
345#endif /*_Stroika_Frameworks_Auth_OAuth_Client_h_*/
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
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:231
nonvirtual UserInfo GetUserInfo(const String &accessToken) const
Definition Client.cpp:543
nonvirtual TokenResponse GetToken(const TokenRequest &tr) const
Definition Client.cpp:434
nonvirtual void RevokeTokens(const TokenRevocationRequest &tr) const
Definition Client.cpp:487
Track configuration data about stuff that differentiates different OAuth providers - what URLs to use...
RFC 7662 compatible API for finding info about a token - https://datatracker.ietf....
Definition Client.h:179
this is the argument to the Fetcher::GetToken () API. It typically consists of a client_id,...
Definition Client.h:49
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:125