Skip to content

Commit

Permalink
Enhance HTML display (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
Colin-b authored Jun 18, 2024
1 parent d34bfa5 commit cc90b26
Show file tree
Hide file tree
Showing 13 changed files with 892 additions and 511 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Adding explicit support for Python `3.12`.
- Publicly expose `requests_auth.SupportMultiAuth`, allowing multiple authentication support for every `requests` authentication class that exists.
- Publicly expose `requests_auth.TokenMemoryCache`, allowing to create custom Oauth2 token cache based on this default implementation.
- You can now provide your own HTML success (`success_html`) and failure (`failure_html`) display via the new `OAuth2.display` shared setting. Refer to documentation for more details.
- Thanks to the new `redirect_uri_domain` parameter on Authorization code (with and without PKCE) and Implicit flows, you can now provide the [FQDN](https://en.wikipedia.org/wiki/Fully_qualified_domain_name) to use in the `redirect_uri` when `localhost` (the default) is not allowed.
- `requests_auth.WakaTimeAuthorizationCode` handling access to the [WakaTime API](https://wakatime.com/developers).

Expand All @@ -19,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `requests_auth.JsonTokenFileCache` does not expose `tokens_path` or `last_save_time` attributes anymore and is also allowing `pathlib.Path` instances as cache location.
- `requests_auth.TokenMemoryCache` does not expose `forbid_concurrent_cache_access` or `forbid_concurrent_missing_token_function_call` attributes anymore.
- Browser display settings have been moved to a shared setting, see documentation for more information on `requests_auth.OAuth2.display`.
The failure page will be displayed for 10 seconds by default instead of 5 seconds previously.
As a result the following classes no longer expose `success_display_time` and `failure_display_time` parameters.
- `requests_auth.OAuth2AuthorizationCode`.
- `requests_auth.OktaAuthorizationCode`.
Expand All @@ -30,6 +32,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `requests_auth.AzureActiveDirectoryImplicitIdToken`.
- `requests_auth.OktaImplicit`.
- `requests_auth.OktaImplicitIdToken`.
- The authentication success and failure displayed in the browser were revamped to be more user-friendly. `requests_auth.testing` was modified to accommodate this change:
- `tab.assert_success` `expected_message` parameter was removed.
- `tab.assert_failure` `expected_message` parameter should not be prefixed with `Unable to properly perform authentication: ` anymore and `\n` in the message should be replaced with `<br>`.

### Fixed
- Type information is now provided following [PEP 561](https://www.python.org/dev/peps/pep-0561/).
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<a href="https://github.com/Colin-b/requests_auth/actions"><img alt="Build status" src="https://github.com/Colin-b/requests_auth/workflows/Release/badge.svg"></a>
<a href="https://github.com/Colin-b/requests_auth/actions"><img alt="Coverage" src="https://img.shields.io/badge/coverage-100%25-brightgreen"></a>
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
<a href="https://github.com/Colin-b/requests_auth/actions"><img alt="Number of tests" src="https://img.shields.io/badge/tests-311 passed-blue"></a>
<a href="https://github.com/Colin-b/requests_auth/actions"><img alt="Number of tests" src="https://img.shields.io/badge/tests-363 passed-blue"></a>
<a href="https://pypi.org/project/requests-auth/"><img alt="Number of downloads" src="https://img.shields.io/pypi/dm/requests_auth"></a>
</p>

Expand Down Expand Up @@ -703,7 +703,9 @@ The following parameters can be provided to `DisplaySettings`:
| Name | Description | Default value |
|:-----------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------|
| `success_display_time` | In case a code or token is successfully received, this is the maximum amount of milliseconds the success page will be displayed in your browser. | 1 |
| `failure_display_time` | In case received code or token is not valid, this is the maximum amount of milliseconds the failure page will be displayed in your browser. | 5_000 |
| `success_html` | In case a code or token is successfully received, this is the success page that will be displayed in your browser. `{display_time}` is expected in this content. | |
| `failure_display_time` | In case received code or token is not valid, this is the maximum amount of milliseconds the failure page will be displayed in your browser. | 10_000 |
| `failure_html` | In case received code or token is not valid, this is the failure page that will be displayed in your browser. `{information}` and `{display_time}` are expected in this content. | |

## API key in header

Expand Down Expand Up @@ -911,9 +913,7 @@ def test_something(browser_mock: BrowserMock):

# perform code using authentication

tab.assert_success(
"You are now authenticated on 1234 You may close this tab."
)
tab.assert_success()
```

## Endorsements
Expand Down
40 changes: 12 additions & 28 deletions requests_auth/_oauth2/authentication_responses_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ def do_GET(self):
self.server.request_error = e
logger.exception("Unable to properly perform authentication.")
self.send_html(
self.error_page(f"Unable to properly perform authentication: {e}")
OAuth2.display.failure_html.format(
display_time=OAuth2.display.failure_display_time,
information=str(e).replace("\n", "<br>"),
)
)

def do_POST(self):
Expand All @@ -48,7 +51,10 @@ def do_POST(self):
self.server.request_error = e
logger.exception("Unable to properly perform authentication.")
self.send_html(
self.error_page(f"Unable to properly perform authentication: {e}")
OAuth2.display.failure_html.format(
display_time=OAuth2.display.failure_display_time,
information=str(e).replace("\n", "<br>"),
)
)

def _parse_grant(self, arguments: dict):
Expand All @@ -67,15 +73,15 @@ def _parse_grant(self, arguments: dict):
state = states[0]
self.server.grant = state, grant
self.send_html(
self.success_page(
f"You are now authenticated on {state}. You may close this tab."
OAuth2.display.success_html.format(
display_time=OAuth2.display.success_display_time
)
)

def _get_form(self):
content_length = int(self.headers.get("Content-Length", 0))
body_str = self.rfile.read(content_length).decode("utf-8")
return parse_qs(body_str, keep_blank_values=1)
return parse_qs(body_str, keep_blank_values=True)

def _get_params(self):
return parse_qs(urlparse(self.path).query)
Expand All @@ -87,28 +93,6 @@ def send_html(self, html_content: str):
self.wfile.write(str.encode(html_content))
logger.debug("HTML content sent to client.")

def success_page(self, text: str):
return f"""<body onload="window.open('', '_self', ''); window.setTimeout(close, {OAuth2.display.success_display_time})" style="
color: #4F8A10;
background-color: #DFF2BF;
font-size: xx-large;
display: flex;
align-items: center;
justify-content: center;">
<div style="border: 1px solid;">{text}</div>
</body>"""

def error_page(self, text: str):
return f"""<body onload="window.open('', '_self', ''); window.setTimeout(close, {OAuth2.display.failure_display_time})" style="
color: #D8000C;
background-color: #FFBABA;
font-size: xx-large;
display: flex;
align-items: center;
justify-content: center;">
<div style="border: 1px solid;">{text}</div>
</body>"""

def fragment_redirect_page(self):
"""Return a page with JS that calls back the server on the url
original url: scheme://FQDN/path#fragment
Expand Down Expand Up @@ -180,7 +164,7 @@ def request_new_grant(grant_details: GrantDetails) -> (str, str):
:raises InvalidGrantRequest: If the request was invalid.
:raises TimeoutOccurred: If not retrieved within timeout.
:raises GrantNotProvided: If grant is not provided in response (but no error occurred).
:raises StateNotProvided: If state if not provided in addition to the grant.
:raises StateNotProvided: If state is not provided in addition to the grant.
"""
logger.debug(f"Requesting new {grant_details.name}...")

Expand Down
104 changes: 102 additions & 2 deletions requests_auth/_oauth2/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,122 @@ def __init__(self, kwargs):


class DisplaySettings:
_default_template = """<!DOCTYPE html>
<html lang="en">
<head>
<title>{title}</title>
<style>
body {{
border: none;
box-sizing: border-box;
display: block;
font-family: "Segoe UI";
font-weight: 500;
line-height: 1.5;
padding: 50px 0 76px 0;
text-align: center;
}}
.content {{
padding: 30px 0 50px 0;
}}
h1 {{
color: {color};
font-size: 2.4rem;
margin: 1.7rem auto .5rem auto;
}}
p {{
color: #2f374f;
font-size: 1.2rem;
margin: .75rem 0 0 0;
}}
.btn {{
display: inline-block;
color: {color} !important;
text-decoration: none;
background-color: {background_color};
padding: 14px 24px;
border-radius: 8px;
font-size: 1em;
font-weight: 400;
margin: 50px 0 0 0;
}}
.btn:hover {{
color: {background_color} !important;
background-color: {color};
}}
@keyframes zoomText {{
from {{
opacity: 0;
transform: scale3d(0.9, 0.9, 0.9);
}}
50% {{
opacity: 1;
}}
}}
.content h1 {{
animation-duration: .6s;
animation-fill-mode: both;
animation-name: zoomText;
animation-delay: .2s;
}}
</style>
</head>
<body onload="window.open('', '_self', ''); window.setTimeout(close, {display_time})">
<div class="content">
<h1>{title}</h1>
<p>{information}</p>
</div>
<div class="more">
<a href="https://colin-b.github.io/requests_auth/" class="btn" target="_blank" rel="noreferrer noopener" role="button">Documentation</a>
<a href="https://github.com/Colin-b/requests_auth/blob/develop/CHANGELOG.md" class="btn" target="_blank" rel="noreferrer noopener" role="button">Latest changes</a>
</div>
</body>
</html>"""
_default_success = (
_default_template.replace("{title}", "Authentication success")
.replace("{color}", "#32cd32")
.replace("{background_color}", "#f0fff0")
.replace("{information}", "You can close this tab")
)
_default_failure = (
_default_template.replace("{title}", "Authentication failed")
.replace("{color}", "#dc143c")
.replace("{background_color}", "#fffafa")
)

def __init__(
self,
*,
success_display_time: int = 1,
failure_display_time: int = 5_000,
success_html: str = None,
failure_display_time: int = 10_000,
failure_html: str = None,
):
"""
:param success_display_time: In case a code/token is successfully received,
this is the maximum amount of milliseconds the success page will be displayed in your browser.
Display the page for 1 millisecond by default.
:param success_html: In case a code or token is successfully received,
this is the success page that will be displayed in your browser.
`{display_time}` is expected in this content.
:param failure_display_time: In case received code/token is not valid,
this is the maximum amount of milliseconds the failure page will be displayed in your browser.
Display the page for 5 seconds by default.
Display the page for 10 seconds by default.
:param failure_html: In case received code or token is not valid,
this is the failure page that will be displayed in your browser.
`{information}` and `{display_time}` are expected in this content.
"""
# Time is expressed in milliseconds
self.success_display_time = success_display_time
self.success_html = success_html or self._default_success

# Time is expressed in milliseconds
self.failure_display_time = failure_display_time
self.failure_html = failure_html or self._default_failure
Loading

0 comments on commit cc90b26

Please sign in to comment.