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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ 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).

## [0.1.2]

- Redact sensitive information in `log` and `http_logs`

## [0.1.1]

- Update encoding of appHomeRedirectUrl.
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.1"
__version__ = "0.1.2"
16 changes: 9 additions & 7 deletions shopify_app/exchange/client_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
RequestInput,
Res,
)
from ..utils import _get_user_agent
from ..utils import _get_user_agent, redact_http_log
from ..utils.http_client import AsyncHTTPClientContext, HTTPClientContext
from ._response_builders import build_network_error_response
from ._validation import validate_shop
Expand Down Expand Up @@ -171,12 +171,14 @@ def _build_request(client_id, client_secret, shop):
"User-Agent": _get_user_agent(),
}

req_obj = {
"method": "POST",
"url": token_endpoint,
"headers": request_headers,
"body": json.dumps(request_body),
}
req_obj = redact_http_log(
{
"method": "POST",
"url": token_endpoint,
"headers": request_headers,
"body": json.dumps(request_body),
}
)

return token_endpoint, request_body, request_headers, req_obj

Expand Down
31 changes: 18 additions & 13 deletions shopify_app/exchange/refresh_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from __future__ import annotations

import json
from datetime import datetime, timedelta, timezone
from typing import List, Literal, Optional, Tuple, Union, cast

Expand All @@ -21,7 +22,7 @@
TokenExchangeResult,
User,
)
from ..utils import _get_attr, _get_user_agent
from ..utils import _get_attr, _get_user_agent, redact_http_log
from ..utils.http_client import AsyncHTTPClientContext, HTTPClientContext
from ._response_builders import build_network_error_response
from ._validation import validate_client_id, validate_shop
Expand Down Expand Up @@ -96,12 +97,14 @@ def refresh_access_token(
http_logs: List[HttpLog] = []

# Build the request object for logging
req_log: RequestInput = {
"url": token_endpoint,
"method": "POST",
"headers": request_headers,
"body": "", # Don't log sensitive body
}
req_log: RequestInput = redact_http_log(
{
"url": token_endpoint,
"method": "POST",
"headers": request_headers,
"body": json.dumps(request_body),
}
)

with HTTPClientContext(http_client) as client:
while attempt <= max_retries:
Expand Down Expand Up @@ -520,12 +523,14 @@ async def refresh_access_token_async(
http_logs: List[HttpLog] = []

# Build the request object for logging
req_log: RequestInput = {
"url": token_endpoint,
"method": "POST",
"headers": request_headers,
"body": "", # Don't log sensitive body
}
req_log: RequestInput = redact_http_log(
{
"url": token_endpoint,
"method": "POST",
"headers": request_headers,
"body": json.dumps(request_body),
}
)

async with AsyncHTTPClientContext(http_client) as client:
while attempt <= max_retries:
Expand Down
16 changes: 9 additions & 7 deletions shopify_app/exchange/token_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
TokenExchangeResult,
User,
)
from ..utils import _get_attr, _get_user_agent, _to_res
from ..utils import _get_attr, _get_user_agent, _to_res, redact_http_log
from ..utils.http_client import AsyncHTTPClientContext, HTTPClientContext
from ._response_builders import build_network_error_response
from ._validation import validate_client_id
Expand Down Expand Up @@ -355,12 +355,14 @@ def _build_request(client_id, client_secret, jwt_string, access_mode, shop_url):
"User-Agent": _get_user_agent(),
}

req_obj = {
"method": "POST",
"url": token_endpoint,
"headers": request_headers,
"body": json.dumps(request_body),
}
req_obj = redact_http_log(
{
"method": "POST",
"url": token_endpoint,
"headers": request_headers,
"body": json.dumps(request_body),
}
)

return token_endpoint, request_body, request_headers, req_obj

Expand Down
50 changes: 19 additions & 31 deletions shopify_app/graphql/admin_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import httpx

from shopify_app.types import AppConfig, GQLResult, HttpLog, Log, RequestInput, Res
from shopify_app.utils import _get_user_agent, _to_res
from shopify_app.utils import _get_user_agent, _to_res, redact_http_log

from ..utils.http_client import AsyncHTTPClientContext, HTTPClientContext

Expand Down Expand Up @@ -80,6 +80,14 @@ def admin_graphql_request(
# Execute request with retry logic
attempt = 0
logs: List[HttpLog] = []
req: RequestInput = redact_http_log(
{
"url": endpoint,
"method": "POST",
"headers": request_headers,
"body": json.dumps(request_body),
}
)

# Use injected client or create one via context manager
if http_client is not None:
Expand All @@ -102,13 +110,6 @@ def admin_graphql_request(
response_body = response.text
response_headers = dict(response.headers)

req: RequestInput = {
"url": endpoint,
"method": "POST",
"headers": request_headers,
"body": json.dumps(request_body),
}

res = Res(
status=status_code, body=response_body, headers=response_headers
)
Expand Down Expand Up @@ -263,13 +264,6 @@ def admin_graphql_request(

except (httpx.RequestError, httpx.ConnectError, httpx.TimeoutException):
# Network/connection errors - return immediately without retry
# Use the req already defined above, create new res for error
error_req: RequestInput = {
"url": endpoint,
"method": "POST",
"headers": request_headers,
"body": json.dumps(request_body),
}
error_res = Res(status=0, body="", headers={})
return GQLResult(
ok=False,
Expand All @@ -282,7 +276,7 @@ def admin_graphql_request(
HttpLog(
code="network_error",
detail="Network error occurred during GraphQL request",
req=error_req,
req=req,
res=error_res,
)
],
Expand Down Expand Up @@ -691,6 +685,14 @@ async def admin_graphql_request_async(
# Execute request with retry logic
attempt = 0
logs: List[HttpLog] = []
req: RequestInput = redact_http_log(
{
"url": endpoint,
"method": "POST",
"headers": request_headers,
"body": json.dumps(request_body),
}
)

async with AsyncHTTPClientContext(http_client) as client:
while attempt <= max_retries:
Expand All @@ -705,13 +707,6 @@ async def admin_graphql_request_async(
response_body = response.text
response_headers = dict(response.headers)

req: RequestInput = {
"url": endpoint,
"method": "POST",
"headers": request_headers,
"body": json.dumps(request_body),
}

res = Res(
status=status_code, body=response_body, headers=response_headers
)
Expand Down Expand Up @@ -876,13 +871,6 @@ async def admin_graphql_request_async(

except (httpx.RequestError, httpx.ConnectError, httpx.TimeoutException):
# Network/connection errors - return immediately without retry
# Use different variable names to avoid redefinition
error_req: RequestInput = {
"url": endpoint,
"method": "POST",
"headers": request_headers,
"body": json.dumps(request_body),
}
error_res = Res(status=0, body="", headers={})
return GQLResult(
ok=False,
Expand All @@ -895,7 +883,7 @@ async def admin_graphql_request_async(
HttpLog(
code="network_error",
detail="Network error occurred during GraphQL request",
req=error_req,
req=req,
res=error_res,
)
],
Expand Down
14 changes: 8 additions & 6 deletions shopify_app/helpers/app_home_parent_redirect.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from urllib.parse import parse_qs, urlencode, urlparse

from ..types import AppConfig, LogWithReq, RequestInput, Res, ResultForReq
from ..utils import redact_http_log
from ..utils.encoding import _json_encode_for_js
from ..utils.headers import _normalize_headers

Expand Down Expand Up @@ -56,6 +57,7 @@ def app_home_parent_redirect(
client_id = config.get("client_id", "")
shop_domain = f"{shop}.myshopify.com"

req = redact_http_log(request)
# Validate request object
headers = request.get("headers")
if not isinstance(headers, dict):
Expand All @@ -65,7 +67,7 @@ def app_home_parent_redirect(
log=LogWithReq(
code="configuration_error",
detail="Expected request.headers to be an object",
req=request,
req=req,
),
response=Res(status=500, body="", headers={}),
)
Expand All @@ -78,7 +80,7 @@ def app_home_parent_redirect(
log=LogWithReq(
code="configuration_error",
detail="Expected request.url to be a non-empty string",
req=request,
req=req,
),
response=Res(status=500, body="", headers={}),
)
Expand All @@ -95,7 +97,7 @@ def app_home_parent_redirect(
log=LogWithReq(
code="invalid_target",
detail=f"Target must be '_top' or '_blank'. Received {target}. Respond 400 Bad Request using the provided response.",
req=request,
req=req,
),
response=Res(status=400, body="Bad Request", headers={}),
)
Expand All @@ -109,7 +111,7 @@ def app_home_parent_redirect(
log=LogWithReq(
code="configuration_error",
detail="Redirect URL must use http or https scheme",
req=request,
req=req,
),
response=Res(status=500, body="", headers={}),
)
Expand All @@ -132,7 +134,7 @@ def app_home_parent_redirect(
log=LogWithReq(
code="app_home_parent_redirect_success",
detail="App Home Parent Redirect response constructed. Respond with the provided response to redirect outside the app iframe.",
req=request,
req=req,
),
response=Res(
status=401,
Expand All @@ -158,7 +160,7 @@ def app_home_parent_redirect(
log=LogWithReq(
code="app_home_parent_redirect_success",
detail="App Home Parent Redirect response constructed. Respond with the provided response to redirect outside the app iframe.",
req=request,
req=req,
),
response=Res(
status=200,
Expand Down
12 changes: 7 additions & 5 deletions shopify_app/helpers/app_home_patch_id_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from urllib.parse import parse_qs, urlparse

from ..types import AppConfig, LogWithReq, RequestInput, Res, ResultForReq
from ..utils import redact_http_log


def app_home_patch_id_token(request: RequestInput, config: AppConfig) -> ResultForReq:
Expand All @@ -27,6 +28,7 @@ def app_home_patch_id_token(request: RequestInput, config: AppConfig) -> ResultF
ResultForReq: Result with ok, shop, log, and response containing HTML and headers
"""
client_id = config.get("client_id", "")
req = redact_http_log(request)

# Check for missing client ID
if not client_id:
Expand All @@ -36,7 +38,7 @@ def app_home_patch_id_token(request: RequestInput, config: AppConfig) -> ResultF
log=LogWithReq(
code="missing_client_id",
detail="Client ID is required but was not provided. Check configuration and respond 500 Internal Server Error using the provided response.",
req=request,
req=req,
),
response=Res(status=500, body="Internal Server Error", headers={}),
)
Expand All @@ -51,7 +53,7 @@ def app_home_patch_id_token(request: RequestInput, config: AppConfig) -> ResultF
log=LogWithReq(
code="missing_request_url",
detail="Request URL is required but was not provided.",
req=request,
req=req,
),
response=Res(status=400, body="Bad Request", headers={}),
)
Expand All @@ -74,7 +76,7 @@ def app_home_patch_id_token(request: RequestInput, config: AppConfig) -> ResultF
log=LogWithReq(
code="missing_shop",
detail="Shop parameter is required in request URL query string but was not provided. Respond 400 Bad Request using the provided response.",
req=request,
req=req,
),
response=Res(status=400, body="Bad Request", headers={}),
)
Expand All @@ -87,7 +89,7 @@ def app_home_patch_id_token(request: RequestInput, config: AppConfig) -> ResultF
log=LogWithReq(
code="missing_shopify_reload",
detail="shopify-reload parameter is required in request URL query string but was not provided. Respond 400 Bad Request using the provided response.",
req=request,
req=req,
),
response=Res(status=400, body="Bad Request", headers={}),
)
Expand All @@ -101,7 +103,7 @@ def app_home_patch_id_token(request: RequestInput, config: AppConfig) -> ResultF
log=LogWithReq(
code="patch_id_token_page_success",
detail="App Home Patch ID Token page Response constructed. Respond with the provided response and App Bridge will obtain an id token.",
req=request,
req=req,
),
response=Res(
status=200,
Expand Down
Loading
Loading