Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0]

- **Breaking:** rename the verify result field `new_id_token_response` to `invalid_token_response`, matching the `exchange_using_token_exchange` and `admin_graphql_request` parameters. Update any code that reads this field:

```diff
- result.new_id_token_response
+ result.invalid_token_response
```

## [0.1.4]

- Verify the dest property is not a malicious URL before making a token exchange request
- Reject App Proxy requests with multiple `shop` query parameters with a 401 response.
- Refreshing a non-expiring token now returns a no-refresh-needed result instead of an error
- Checkout UI and Customer Account UI Extension requests now return `shop` without the `https://` prefix
- Update the README for the package

## [0.1.3]

Expand Down
335 changes: 242 additions & 93 deletions README.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions shopify_app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def verify_pos_ui_ext_req(
request (RequestInput): A RequestInput dict with method, headers, url, and body fields

Returns:
ResultWithExchangeableIdToken: Verification result with ok, shop, user_id, id_token, log, response, and new_id_token_response fields
ResultWithExchangeableIdToken: Verification result with ok, shop, user_id, id_token, log, response, and invalid_token_response fields
"""
return verify_pos_ui_ext_req(request, self.config)

Expand Down Expand Up @@ -158,7 +158,7 @@ def verify_admin_ui_ext_req(
request (RequestInput): A RequestInput dict with method, headers, url, and body fields

Returns:
ResultWithExchangeableIdToken: Verification result with ok, shop, user_id, id_token, log, response, and new_id_token_response fields
ResultWithExchangeableIdToken: Verification result with ok, shop, user_id, id_token, log, response, and invalid_token_response fields
"""
return verify_admin_ui_ext_req(request, self.config)

Expand All @@ -173,7 +173,7 @@ def verify_app_home_req(
app_home_patch_id_token_path (str): Path to the patch ID token page

Returns:
ResultWithExchangeableIdToken: Verification result with ok, shop, user_id, id_token, log, response, and new_id_token_response fields
ResultWithExchangeableIdToken: Verification result with ok, shop, user_id, id_token, log, response, and invalid_token_response fields
"""
return verify_app_home_req(request, self.config, app_home_patch_id_token_path)

Expand Down
2 changes: 1 addition & 1 deletion shopify_app/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

from __future__ import annotations

__version__ = "0.1.4"
__version__ = "1.0.0"
4 changes: 2 additions & 2 deletions shopify_app/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ class ResultWithExchangeableIdToken:
response: Suggested HTTP response to return
user_id: The user ID from the token's sub claim, or None on failure
id_token: ID token details (exchangeable), or None on failure
new_id_token_response: Pre-built response for token refresh scenarios
invalid_token_response: Pre-built response for token refresh scenarios
"""

ok: bool
Expand All @@ -281,7 +281,7 @@ class ResultWithExchangeableIdToken:
response: Res
user_id: Optional[str]
id_token: Optional[IdTokenDetails]
new_id_token_response: Optional[Res]
invalid_token_response: Optional[Res]


@dataclass(frozen=True)
Expand Down
2 changes: 1 addition & 1 deletion shopify_app/verify/_non_exchangeable_id_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def _verify_non_exchangeable_id_token(

# Extract shop from dest claim
dest = payload.get("dest", "")
shop = dest.replace(".myshopify.com", "") if dest else ""
shop = dest.replace("https://", "").replace(".myshopify.com", "") if dest else ""

return ResultWithNonExchangeableIdToken(
ok=True,
Expand Down
18 changes: 9 additions & 9 deletions shopify_app/verify/admin_ui_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def verify_admin_ui_ext_req(
response=Res(status=500, body="", headers={}),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

headers = request.get("headers")
Expand All @@ -65,7 +65,7 @@ def verify_admin_ui_ext_req(
response=Res(status=500, body="", headers={}),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

url = request.get("url")
Expand All @@ -81,7 +81,7 @@ def verify_admin_ui_ext_req(
response=Res(status=500, body="", headers={}),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

client_secret = config.get("client_secret", "")
Expand Down Expand Up @@ -116,7 +116,7 @@ def verify_admin_ui_ext_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

# Check for Authorization header
Expand All @@ -132,7 +132,7 @@ def verify_admin_ui_ext_req(
response=Res(status=401, body="Unauthorized", headers={}),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

# Extract the Bearer token
Expand All @@ -153,7 +153,7 @@ def verify_admin_ui_ext_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

id_token = auth_header[7:] # Remove "Bearer " prefix
Expand Down Expand Up @@ -204,7 +204,7 @@ def verify_admin_ui_ext_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

# Verify the audience (aud) matches the clientId
Expand All @@ -225,7 +225,7 @@ def verify_admin_ui_ext_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

# Extract shop from dest claim
Expand All @@ -250,7 +250,7 @@ def verify_admin_ui_ext_req(
token=id_token,
claims=payload,
),
new_id_token_response=Res(
invalid_token_response=Res(
status=401,
body="",
headers={"X-Shopify-Retry-Invalid-Session-Request": "1"},
Expand Down
28 changes: 14 additions & 14 deletions shopify_app/verify/app_home_req.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def _build_patch_id_token_redirect(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)


Expand Down Expand Up @@ -129,7 +129,7 @@ def verify_app_home_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

if app_home_patch_id_token_path == "":
Expand All @@ -148,7 +148,7 @@ def verify_app_home_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

# Validate request object
Expand All @@ -169,7 +169,7 @@ def verify_app_home_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

headers = request.get("headers")
Expand All @@ -189,7 +189,7 @@ def verify_app_home_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

client_secret = config.get("client_secret", "")
Expand Down Expand Up @@ -249,7 +249,7 @@ def verify_app_home_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)
id_token = auth_header[7:] # Remove "Bearer " prefix

Expand All @@ -269,7 +269,7 @@ def verify_app_home_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

payload = None
Expand Down Expand Up @@ -332,7 +332,7 @@ def verify_app_home_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

# Verify the audience (aud) matches the clientId
Expand Down Expand Up @@ -360,7 +360,7 @@ def verify_app_home_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

# Extract shop from dest claim (parse as URL and get hostname)
Expand All @@ -380,8 +380,8 @@ def verify_app_home_req(
"Link": '<https://cdn.shopify.com>; rel="preconnect", <https://cdn.shopify.com/shopifycloud/app-bridge.js>; rel="preload"; as="script", <https://cdn.shopify.com/shopifycloud/polaris.js>; rel="preload"; as="script"',
}

# Build new_id_token_response
new_id_token_response = None
# Build invalid_token_response
invalid_token_response = None
if not has_authorization_header:
# Document request - build patch ID token URL
clean_query = _remove_query_param(parsed_url.query, "id_token")
Expand All @@ -394,7 +394,7 @@ def verify_app_home_req(

patch_id_token_location = f"{parsed_url.scheme}://{parsed_url.netloc}{app_home_patch_id_token_path}?{patch_id_token_query}"

new_id_token_response = Res(
invalid_token_response = Res(
status=302,
body="",
headers={
Expand All @@ -403,7 +403,7 @@ def verify_app_home_req(
)
else:
# Fetch request
new_id_token_response = Res(
invalid_token_response = Res(
status=401,
body="",
headers={
Expand Down Expand Up @@ -435,5 +435,5 @@ def verify_app_home_req(
token=id_token,
claims=payload,
),
new_id_token_response=new_id_token_response,
invalid_token_response=invalid_token_response,
)
20 changes: 10 additions & 10 deletions shopify_app/verify/pos_ui_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def verify_pos_ui_ext_req(
config (dict): The app configuration with client_id, client_secret and optional old_client_secret

Returns:
ResultWithExchangeableIdToken: Verification result with ok, shop, log, response, user_id, id_token, and new_id_token_response fields
ResultWithExchangeableIdToken: Verification result with ok, shop, log, response, user_id, id_token, and invalid_token_response fields
"""
req = redact_http_log(request)
# Validate request object
Expand All @@ -53,7 +53,7 @@ def verify_pos_ui_ext_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

headers = request.get("headers")
Expand All @@ -73,7 +73,7 @@ def verify_pos_ui_ext_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

url = request.get("url")
Expand All @@ -93,7 +93,7 @@ def verify_pos_ui_ext_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

client_id = config.get("client_id", "")
Expand Down Expand Up @@ -128,7 +128,7 @@ def verify_pos_ui_ext_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

# Check for Authorization header
Expand All @@ -148,7 +148,7 @@ def verify_pos_ui_ext_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

# Extract the Bearer token
Expand All @@ -169,7 +169,7 @@ def verify_pos_ui_ext_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

id_token = auth_header[7:] # Remove "Bearer " prefix
Expand Down Expand Up @@ -223,7 +223,7 @@ def verify_pos_ui_ext_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

# Verify the audience (aud) matches clientId
Expand All @@ -244,7 +244,7 @@ def verify_pos_ui_ext_req(
),
user_id=None,
id_token=None,
new_id_token_response=None,
invalid_token_response=None,
)

# Extract shop from dest claim (format: https://shop-name.myshopify.com)
Expand Down Expand Up @@ -273,5 +273,5 @@ def verify_pos_ui_ext_req(
token=id_token,
claims=payload,
),
new_id_token_response=None,
invalid_token_response=None,
)