Подписать запрос#

Подпись является частью контракта операции, а секретный ключ принадлежит 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 завершают вызов до отправки. Не выводите ключ, основу подписи с секретными полями или готовую подпись в журнал приложения.

Дальше#