From b41b30934d01ef605c6261e4f26f56de94b060d7 Mon Sep 17 00:00:00 2001
From: frankie567 HTTPX OAuth
@@ -648,6 +648,7 @@
Async OAuth client using HTTPX
Documentation: https://frankie567.github.io/httpx-oauth/
Source Code: https://github.com/frankie567/httpx-oauth
"},{"location":"#installation","title":"Installation","text":"pip install httpx-oauth\n
"},{"location":"#contributors","title":"Contributors \u2728","text":"Thanks goes to these wonderful people (emoji key):
Fran\u00e7ois Voron\ud83d\udea7 Xavi Torell\u00f3\ud83d\udcbb dbf\ud83d\udcbb Kenton Parton\ud83d\udcbb stepan-chatalyan\ud83d\udcbb Foster Snowhill\ud83d\udcbb William Hatcher\ud83d\udcbb Matt Chan\ud83d\udce6 Goran Meki\u0107\ud83d\udce6 Joona Yoon\ud83d\udcbb LindezaGrey\ud83d\udcbb R. Singh\ud83d\udc1bThis project follows the all-contributors specification. Contributions of any kind welcome!
"},{"location":"#development","title":"Development","text":""},{"location":"#setup-environment","title":"Setup environment","text":"We use Hatch to manage the development environment and production build. Ensure it's installed on your system.
"},{"location":"#run-unit-tests","title":"Run unit tests","text":"You can run all the tests with:
hatch run test\n
"},{"location":"#format-the-code","title":"Format the code","text":"Execute the following command to apply isort
and black
formatting:
hatch run lint\n
"},{"location":"#serve-the-documentation","title":"Serve the documentation","text":"You can serve the documentation locally with the following command:
hatch run docs\n
The documentation will be available on http://localhost:8000.
"},{"location":"#license","title":"License","text":"This project is licensed under the terms of the MIT license.
"},{"location":"fastapi/","title":"FastAPI","text":"Utilities are provided to ease the integration of an OAuth2 process in FastAPI.
"},{"location":"fastapi/#oauth2authorizecallback","title":"OAuth2AuthorizeCallback
","text":"Dependency callable to handle the authorization callback. It reads the query parameters and returns the access token and the state.
Parameters
client: OAuth2
: The OAuth2 client.route_name: Optional[str]
: Name of the callback route, as defined in the name
parameter of the route decorator.redirect_url: Optional[str]
: Full URL to the callback route.Tip
You should either set route_name
, which will automatically reverse the URL, or redirect_url
, which is an arbitrary URL you set.
from httpx_oauth.integrations.fastapi import OAuth2AuthorizeCallback\nfrom httpx_oauth.oauth2 import OAuth2\n\nclient = OAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\", \"AUTHORIZE_ENDPOINT\", \"ACCESS_TOKEN_ENDPOINT\")\noauth2_authorize_callback = OAuth2AuthorizeCallback(client, \"oauth-callback\")\napp = FastAPI()\n\n@app.get(\"/oauth-callback\", name=\"oauth-callback\")\nasync def oauth_callback(access_token_state=Depends(oauth2_authorize_callback)):\n token, state = access_token_state\n # Do something useful\n
"},{"location":"oauth2/","title":"OAuth2","text":""},{"location":"oauth2/#generic-client","title":"Generic client","text":"A generic OAuth2 class is provided to adapt to any OAuth2-compliant service. You can instantiate it like this:
from httpx_oauth.oauth2 import OAuth2\n\nclient = OAuth2(\n \"CLIENT_ID\",\n \"CLIENT_SECRET\",\n \"AUTHORIZE_ENDPOINT\",\n \"ACCESS_TOKEN_ENDPOINT\",\n refresh_token_endpoint=\"REFRESH_TOKEN_ENDPOINT\",\n revoke_token_endpoint=\"REVOKE_TOKEN_ENDPOINT\",\n)\n
Note that refresh_token_endpoint
and revoke_token_endpoint
are optional since not every services propose to refresh and revoke tokens.
get_authorization_url
","text":"Returns the authorization URL where you should redirect the user to ask for their approval.
Parameters
redirect_uri: str
: Your callback URI where the user will be redirected after the service prompt.state: str = None
: Optional string that will be returned back in the callback parameters to allow you to retrieve state information.scope: Optional[List[str]] = None
: Optional list of scopes to ask for.extras_params: Optional[Dict[str, Any]] = None
: Optional dictionary containing parameters specific to the service.Example
authorization_url = await client.get_authorization_url(\n \"https://www.tintagel.bt/oauth-callback\", scope=[\"SCOPE1\", \"SCOPE2\", \"SCOPE3\"],\n)\n
"},{"location":"oauth2/#get_access_token","title":"get_access_token
","text":"Returns an OAuth2Token
object for the service given the authorization code passed in the redirection callback.
Raises a GetAccessTokenError
if an error occurs.
Parameters
code: str
: The authorization code passed in the redirection callback.redirect_uri: str
: The exact same redirect_uri
you passed to the authorization URL.code_verifier: Optional[str]
: Optional code verifier in a PKCE context.Example
access_token = await client.get_access_token(\"CODE\", \"https://www.tintagel.bt/oauth-callback\")\n
"},{"location":"oauth2/#refresh_token","title":"refresh_token
","text":"Returns a fresh OAuth2Token
object for the service given a refresh token.
Raises a RefreshTokenNotSupportedError
if no refresh_token_endpoint
was provided.
Parameters
refresh_token: str
: A valid refresh token for the service.Example
access_token = await client.refresh_token(\"REFRESH_TOKEN\")\n
"},{"location":"oauth2/#revoke_token","title":"revoke_token
","text":"Revokes a token.
Raises a RevokeTokenNotSupportedError
if no revoke_token_endpoint
was provided.
Parameters
token: str
: A token or refresh token to revoke.token_type_hint: str = None
: Optional hint for the service to help it determine if it's a token or refresh token. Usually either token
or refresh_token
.Example
await client.revoke_token(\"TOKEN\")\n
"},{"location":"oauth2/#get_id_email","title":"get_id_email
","text":"Returns the id and the email (if available) of the authenticated user from the API provider. It assumes you have asked for the required scopes.
Raises a GetIdEmailError
if an error occurs.
Parameters
token: str
: A valid access token.Example
user_id, user_email = await client.get_id_email(\"TOKEN\")\n
"},{"location":"oauth2/#oauth2token-class","title":"OAuth2Token
class","text":"This class is a wrapper around a standard Dict[str, Any]
that bears the response of get_access_token
. Properties can vary greatly from a service to another but, usually, you can get access token like this:
access_token = token[\"access_token\"]\n
"},{"location":"oauth2/#is_expired","title":"is_expired
","text":"A utility method is provided to quickly determine if the token is still valid or needs to be refreshed.
Example
if token.is_expired():\n token = await client.refresh_token(token[\"refresh_token\"])\n # Save token to DB\n\naccess_token = token[\"access_token\"]\n# Do something useful with this access token\n
"},{"location":"oauth2/#provided-clients","title":"Provided clients","text":"We provide several ready-to-use clients for widely used services with configured endpoints and specificites took into account.
"},{"location":"oauth2/#openid","title":"OpenID","text":"Generic client for providers following the OpenID Connect protocol. Besides the Client ID and the Client Secret, you'll have to provide the OpenID configuration endpoint, allowing the client to discover the required endpoints automatically. By convention, it's usually served under the path .well-known/openid-configuration
.
from httpx_oauth.clients.openid import OpenID\n\nclient = OpenID(\"CLIENT_ID\", \"CLIENT_SECRET\", \"https://example.fief.dev/.well-known/openid-configuration\")\n
refresh_token
: depends if the OpenID provider supports itrevoke_token
: depends if the OpenID provider supports itfrom httpx_oauth.clients.discord import DiscordOAuth2\n\nclient = DiscordOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
Warning about get_id_email
Email is optional for Discord accounts, so the email might be None
.
from httpx_oauth.clients.facebook import FacebookOAuth2\n\nclient = FacebookOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
get_long_lived_access_token
","text":"Returns an OAuth2Token
object with a long-lived access token given a short-lived access token.
Raises a GetLongLivedAccessTokenError
if an error occurs.
Parameters
token: str
: A short-lived access token given by get_access_token
.Example
long_lived_access_token = await client.get_long_lived_access_token(\"TOKEN\")\n
"},{"location":"oauth2/#github","title":"GitHub","text":"from httpx_oauth.clients.github import GitHubOAuth2\n\nclient = GitHubOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
Tip
You should enable Email addresses permission in the Permissions & events section of your GitHub app parameters. You can find it at https://github.com/settings/apps/{YOUR_APP}/permissions.
"},{"location":"oauth2/#google","title":"Google","text":"from httpx_oauth.clients.google import GoogleOAuth2\n\nclient = GoogleOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
from httpx_oauth.clients.kakao import KakaoOAuth2\n\nclient = KakaoOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
from httpx_oauth.clients.linkedin import LinkedInOAuth2\n\nclient = LinkedInOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
(only for selected partners)revoke_token
from httpx_oauth.clients.naver import NaverOAuth2\n\nclient = NaverOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
Based on the OpenID client. You need to provide the domain of your Okta domain for automatically discovering the required endpoints.
from httpx_oauth.clients.okta import OktaOAuth2\n\nclient = OktaOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\", \"example.okta.com\")\n
refresh_token
revoke_token
from httpx_oauth.clients.reddit import RedditOAuth2\n\nclient = RedditOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
Warning about get_id_email
Reddit API never return email addresses. Thus, e-mail will always be None
.
from httpx_oauth.clients.franceconnect import FranceConnectOAuth2\n\nclient = FranceConnectOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
Integration server
Since you need to go through a heavy validation process before getting your client ID and secret, you can use during development the integration server with demo credentials. You can enable this mode by setting the integration
flag to True
.
from httpx_oauth.clients.franceconnect import FranceConnectOAuth2\n\nclient = FranceConnectOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\", integration=True)\n
"},{"location":"oauth2/#shopify","title":"Shopify","text":"The OAuth2 client for Shopify allows you to authenticate shop owners so your app can make calls to the Shopify Admin API. Besides the Client ID and Secret, you'll need the shop subdomain of the shop you need to access.
from httpx_oauth.clients.shopify import ShopifyOAuth2\n\nclient = ShopifyOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\", \"my-shop\")\n
refresh_token
revoke_token
get_id_email
is based on the Shop
resource
The implementation of get_id_email
calls the Get Shop endpoint of the Shopify Admin API. It means that it'll return you the ID of the shop and the email of the shop owner.
By default, requests are made using httpx.AsyncClient
with default parameters. If you wish to customize settings, like setting timeout or proxies, you can do this by overloading the get_httpx_client
method.
from typing import AsyncContextManager\n\nimport httpx\nfrom httpx_oauth.oauth2 import OAuth2\n\n\nclass OAuth2CustomTimeout(OAuth2):\n def get_httpx_client(self) -> AsyncContextManager[httpx.AsyncClient]:\n return httpx.AsyncClient(timeout=10.0) # Use a default 10s timeout everywhere.\n\n\nclient = OAuth2CustomTimeout(\n \"CLIENT_ID\",\n \"CLIENT_SECRET\",\n \"AUTHORIZE_ENDPOINT\",\n \"ACCESS_TOKEN_ENDPOINT\",\n refresh_token_endpoint=\"REFRESH_TOKEN_ENDPOINT\",\n revoke_token_endpoint=\"REVOKE_TOKEN_ENDPOINT\",\n)\n
"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"HTTPX OAuth","text":"Async OAuth client using HTTPX
Documentation: https://frankie567.github.io/httpx-oauth/
Source Code: https://github.com/frankie567/httpx-oauth
"},{"location":"#installation","title":"Installation","text":"pip install httpx-oauth\n
"},{"location":"#contributors","title":"Contributors \u2728","text":"Thanks goes to these wonderful people (emoji key):
Fran\u00e7ois Voron\ud83d\udea7 Xavi Torell\u00f3\ud83d\udcbb dbf\ud83d\udcbb Kenton Parton\ud83d\udcbb stepan-chatalyan\ud83d\udcbb Foster Snowhill\ud83d\udcbb William Hatcher\ud83d\udcbb Matt Chan\ud83d\udce6 Goran Meki\u0107\ud83d\udce6 Joona Yoon\ud83d\udcbb LindezaGrey\ud83d\udcbb R. Singh\ud83d\udc1b Lukas L\u00f6sche\ud83d\udc1b \ud83d\udcbbThis project follows the all-contributors specification. Contributions of any kind welcome!
"},{"location":"#development","title":"Development","text":""},{"location":"#setup-environment","title":"Setup environment","text":"We use Hatch to manage the development environment and production build. Ensure it's installed on your system.
"},{"location":"#run-unit-tests","title":"Run unit tests","text":"You can run all the tests with:
hatch run test\n
"},{"location":"#format-the-code","title":"Format the code","text":"Execute the following command to apply isort
and black
formatting:
hatch run lint\n
"},{"location":"#serve-the-documentation","title":"Serve the documentation","text":"You can serve the documentation locally with the following command:
hatch run docs\n
The documentation will be available on http://localhost:8000.
"},{"location":"#license","title":"License","text":"This project is licensed under the terms of the MIT license.
"},{"location":"fastapi/","title":"FastAPI","text":"Utilities are provided to ease the integration of an OAuth2 process in FastAPI.
"},{"location":"fastapi/#oauth2authorizecallback","title":"OAuth2AuthorizeCallback
","text":"Dependency callable to handle the authorization callback. It reads the query parameters and returns the access token and the state.
Parameters
client: OAuth2
: The OAuth2 client.route_name: Optional[str]
: Name of the callback route, as defined in the name
parameter of the route decorator.redirect_url: Optional[str]
: Full URL to the callback route.Tip
You should either set route_name
, which will automatically reverse the URL, or redirect_url
, which is an arbitrary URL you set.
from httpx_oauth.integrations.fastapi import OAuth2AuthorizeCallback\nfrom httpx_oauth.oauth2 import OAuth2\n\nclient = OAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\", \"AUTHORIZE_ENDPOINT\", \"ACCESS_TOKEN_ENDPOINT\")\noauth2_authorize_callback = OAuth2AuthorizeCallback(client, \"oauth-callback\")\napp = FastAPI()\n\n@app.get(\"/oauth-callback\", name=\"oauth-callback\")\nasync def oauth_callback(access_token_state=Depends(oauth2_authorize_callback)):\n token, state = access_token_state\n # Do something useful\n
"},{"location":"oauth2/","title":"OAuth2","text":""},{"location":"oauth2/#generic-client","title":"Generic client","text":"A generic OAuth2 class is provided to adapt to any OAuth2-compliant service. You can instantiate it like this:
from httpx_oauth.oauth2 import OAuth2\n\nclient = OAuth2(\n \"CLIENT_ID\",\n \"CLIENT_SECRET\",\n \"AUTHORIZE_ENDPOINT\",\n \"ACCESS_TOKEN_ENDPOINT\",\n refresh_token_endpoint=\"REFRESH_TOKEN_ENDPOINT\",\n revoke_token_endpoint=\"REVOKE_TOKEN_ENDPOINT\",\n)\n
Note that refresh_token_endpoint
and revoke_token_endpoint
are optional since not every services propose to refresh and revoke tokens.
get_authorization_url
","text":"Returns the authorization URL where you should redirect the user to ask for their approval.
Parameters
redirect_uri: str
: Your callback URI where the user will be redirected after the service prompt.state: str = None
: Optional string that will be returned back in the callback parameters to allow you to retrieve state information.scope: Optional[List[str]] = None
: Optional list of scopes to ask for.extras_params: Optional[Dict[str, Any]] = None
: Optional dictionary containing parameters specific to the service.Example
authorization_url = await client.get_authorization_url(\n \"https://www.tintagel.bt/oauth-callback\", scope=[\"SCOPE1\", \"SCOPE2\", \"SCOPE3\"],\n)\n
"},{"location":"oauth2/#get_access_token","title":"get_access_token
","text":"Returns an OAuth2Token
object for the service given the authorization code passed in the redirection callback.
Raises a GetAccessTokenError
if an error occurs.
Parameters
code: str
: The authorization code passed in the redirection callback.redirect_uri: str
: The exact same redirect_uri
you passed to the authorization URL.code_verifier: Optional[str]
: Optional code verifier in a PKCE context.Example
access_token = await client.get_access_token(\"CODE\", \"https://www.tintagel.bt/oauth-callback\")\n
"},{"location":"oauth2/#refresh_token","title":"refresh_token
","text":"Returns a fresh OAuth2Token
object for the service given a refresh token.
Raises a RefreshTokenNotSupportedError
if no refresh_token_endpoint
was provided.
Parameters
refresh_token: str
: A valid refresh token for the service.Example
access_token = await client.refresh_token(\"REFRESH_TOKEN\")\n
"},{"location":"oauth2/#revoke_token","title":"revoke_token
","text":"Revokes a token.
Raises a RevokeTokenNotSupportedError
if no revoke_token_endpoint
was provided.
Parameters
token: str
: A token or refresh token to revoke.token_type_hint: str = None
: Optional hint for the service to help it determine if it's a token or refresh token. Usually either token
or refresh_token
.Example
await client.revoke_token(\"TOKEN\")\n
"},{"location":"oauth2/#get_id_email","title":"get_id_email
","text":"Returns the id and the email (if available) of the authenticated user from the API provider. It assumes you have asked for the required scopes.
Raises a GetIdEmailError
if an error occurs.
Parameters
token: str
: A valid access token.Example
user_id, user_email = await client.get_id_email(\"TOKEN\")\n
"},{"location":"oauth2/#oauth2token-class","title":"OAuth2Token
class","text":"This class is a wrapper around a standard Dict[str, Any]
that bears the response of get_access_token
. Properties can vary greatly from a service to another but, usually, you can get access token like this:
access_token = token[\"access_token\"]\n
"},{"location":"oauth2/#is_expired","title":"is_expired
","text":"A utility method is provided to quickly determine if the token is still valid or needs to be refreshed.
Example
if token.is_expired():\n token = await client.refresh_token(token[\"refresh_token\"])\n # Save token to DB\n\naccess_token = token[\"access_token\"]\n# Do something useful with this access token\n
"},{"location":"oauth2/#provided-clients","title":"Provided clients","text":"We provide several ready-to-use clients for widely used services with configured endpoints and specificites took into account.
"},{"location":"oauth2/#openid","title":"OpenID","text":"Generic client for providers following the OpenID Connect protocol. Besides the Client ID and the Client Secret, you'll have to provide the OpenID configuration endpoint, allowing the client to discover the required endpoints automatically. By convention, it's usually served under the path .well-known/openid-configuration
.
from httpx_oauth.clients.openid import OpenID\n\nclient = OpenID(\"CLIENT_ID\", \"CLIENT_SECRET\", \"https://example.fief.dev/.well-known/openid-configuration\")\n
refresh_token
: depends if the OpenID provider supports itrevoke_token
: depends if the OpenID provider supports itfrom httpx_oauth.clients.discord import DiscordOAuth2\n\nclient = DiscordOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
Warning about get_id_email
Email is optional for Discord accounts, so the email might be None
.
from httpx_oauth.clients.facebook import FacebookOAuth2\n\nclient = FacebookOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
get_long_lived_access_token
","text":"Returns an OAuth2Token
object with a long-lived access token given a short-lived access token.
Raises a GetLongLivedAccessTokenError
if an error occurs.
Parameters
token: str
: A short-lived access token given by get_access_token
.Example
long_lived_access_token = await client.get_long_lived_access_token(\"TOKEN\")\n
"},{"location":"oauth2/#github","title":"GitHub","text":"from httpx_oauth.clients.github import GitHubOAuth2\n\nclient = GitHubOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
Tip
You should enable Email addresses permission in the Permissions & events section of your GitHub app parameters. You can find it at https://github.com/settings/apps/{YOUR_APP}/permissions.
"},{"location":"oauth2/#google","title":"Google","text":"from httpx_oauth.clients.google import GoogleOAuth2\n\nclient = GoogleOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
from httpx_oauth.clients.kakao import KakaoOAuth2\n\nclient = KakaoOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
from httpx_oauth.clients.linkedin import LinkedInOAuth2\n\nclient = LinkedInOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
(only for selected partners)revoke_token
from httpx_oauth.clients.naver import NaverOAuth2\n\nclient = NaverOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
Based on the OpenID client. You need to provide the domain of your Okta domain for automatically discovering the required endpoints.
from httpx_oauth.clients.okta import OktaOAuth2\n\nclient = OktaOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\", \"example.okta.com\")\n
refresh_token
revoke_token
from httpx_oauth.clients.reddit import RedditOAuth2\n\nclient = RedditOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
Warning about get_id_email
Reddit API never return email addresses. Thus, e-mail will always be None
.
from httpx_oauth.clients.franceconnect import FranceConnectOAuth2\n\nclient = FranceConnectOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\")\n
refresh_token
revoke_token
Integration server
Since you need to go through a heavy validation process before getting your client ID and secret, you can use during development the integration server with demo credentials. You can enable this mode by setting the integration
flag to True
.
from httpx_oauth.clients.franceconnect import FranceConnectOAuth2\n\nclient = FranceConnectOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\", integration=True)\n
"},{"location":"oauth2/#shopify","title":"Shopify","text":"The OAuth2 client for Shopify allows you to authenticate shop owners so your app can make calls to the Shopify Admin API. Besides the Client ID and Secret, you'll need the shop subdomain of the shop you need to access.
from httpx_oauth.clients.shopify import ShopifyOAuth2\n\nclient = ShopifyOAuth2(\"CLIENT_ID\", \"CLIENT_SECRET\", \"my-shop\")\n
refresh_token
revoke_token
get_id_email
is based on the Shop
resource
The implementation of get_id_email
calls the Get Shop endpoint of the Shopify Admin API. It means that it'll return you the ID of the shop and the email of the shop owner.
By default, requests are made using httpx.AsyncClient
with default parameters. If you wish to customize settings, like setting timeout or proxies, you can do this by overloading the get_httpx_client
method.
from typing import AsyncContextManager\n\nimport httpx\nfrom httpx_oauth.oauth2 import OAuth2\n\n\nclass OAuth2CustomTimeout(OAuth2):\n def get_httpx_client(self) -> AsyncContextManager[httpx.AsyncClient]:\n return httpx.AsyncClient(timeout=10.0) # Use a default 10s timeout everywhere.\n\n\nclient = OAuth2CustomTimeout(\n \"CLIENT_ID\",\n \"CLIENT_SECRET\",\n \"AUTHORIZE_ENDPOINT\",\n \"ACCESS_TOKEN_ENDPOINT\",\n refresh_token_endpoint=\"REFRESH_TOKEN_ENDPOINT\",\n revoke_token_endpoint=\"REVOKE_TOKEN_ENDPOINT\",\n)\n
"}]}
\ No newline at end of file
diff --git a/sitemap.xml.gz b/sitemap.xml.gz
index 865723d8fd289e63544c608fd1401782b6cd4044..a6c388046cb877ee5343a12c75b068fcee213da6 100644
GIT binary patch
delta 12
Tcmb=gXOr*d;J6h$k*yK{8BPQI
delta 12
Tcmb=gXOr*d;CLN0k*yK{8J`3U