Подписать запрос#
Подпись является частью контракта операции, а секретный ключ принадлежит Identity. Поэтому
вызывающий метод не принимает ключ и не может случайно отправить его как данные запроса.
# examples/mail/http/send.py
MAIL_BEARER = BearerScheme("mail-bearer")
MAIL_KEY = SigningKeyRequirement("mail-send")
MAIL_HMAC = hmac_sha256(
key=MAIL_KEY,
base=body_digest("sha256"),
output=header_output("X-Mail-Signature"),
)
def signing_key(requirement: SigningKeyRequirement) -> SigningKey:
if requirement == MAIL_KEY:
return SigningKey(b"mail-demo-secret")
raise LookupError(requirement.name)
SigningKeyRequirement называет нужный ключ. body_digest("sha256") читает окончательные байты
тела, а header_output(...) записывает результат в разрешённый заголовок.
# examples/mail/http/send.py
@dataclass(frozen=True, slots=True, kw_only=True)
class SendMessage(HttpOperation[SendResult]):
__http__ = Http.post(
"/api/v1/messages/send",
success={200: Json(SendEnvelope)},
errors={200: (Json(RejectedEnvelope), RecipientRejected)},
security=MAIL_BEARER,
signing=MAIL_HMAC,
)
body: Annotated[NewMessage, JsonBody()]
@dataclass(frozen=True, slots=True, kw_only=True)
class FindSent(HttpOperation[SentMessages]):
__http__ = Http.get(
"/api/v1/messages/sent",
success={200: Json(SentMessagesEnvelope)},
security=MAIL_BEARER,
)
recipient: Query[str]
subject: Query[str]
class MailApi(SyncApi):
base_url = "https://mail.example"
send = op(SendMessage)
sent = op(FindSent)
# examples/docs/signing.py
def main() -> None:
identity = Identity(
auth=(MAIL_BEARER.static("session-1"),),
key_provider=signing_key,
)
with mail_client() as client:
result = MailApi(client, identity=identity).send(
body=NewMessage(
recipient="grace@mail.example",
subject="Подписанный запрос",
body="Встречаемся в пятницу.",
)
)
print(f"signed message: {result.message_id}")
Запускаемый файл целиком
"""Send one message signed over the final request body."""
from eazy_sdk import Identity
from examples.mail.http._client import mail_client
from examples.mail.http.send import (
MAIL_BEARER,
MailApi,
NewMessage,
signing_key,
)
# region docs: signing-run
# examples/docs/signing.py
def main() -> None:
identity = Identity(
auth=(MAIL_BEARER.static("session-1"),),
key_provider=signing_key,
)
with mail_client() as client:
result = MailApi(client, identity=identity).send(
body=NewMessage(
recipient="grace@mail.example",
subject="Подписанный запрос",
body="Встречаемся в пятницу.",
)
)
print(f"signed message: {result.message_id}")
# endregion docs: signing-run
if __name__ == "__main__":
main()
signed message: 100
Runtime сначала строит wire-представление, кодирует и при необходимости шифрует его. Только после этого signer получает неизменяемый prepared request. Retry и redirect готовят и подписывают новую попытку заново.
Отсутствующий ключ, неподдерживаемый профиль точных байтов или конфликт двух writers завершают вызов до отправки. Не выводите ключ, основу подписи с секретными полями или готовую подпись в журнал приложения.