0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Zoho WidgetとLambdaの連携 第3回:PDFプレビューと直送保存の分離

0
Last updated at Posted at 2026-07-15

大きいPDFをZoho関数に載せるのをやめ、画面で確認したbytesをそのまま保存できるようにしました。方針は2点です。Delugeに巨大Base64を載せないことと、保存正本をサーバ再生成結果ではなく利用者が確認したbytesにすることです。

シリーズ「Zoho WidgetとLambdaの連携」の第3回です。

前回: Zoho WidgetとLambdaの連携 第2回:HMAC署名とDelugeのJSON運搬

次回: Zoho WidgetとLambdaの連携 第4回:外部送信の受付判定と自動retry抑止

この記事の内容

  1. 許可はHMAC、本体は直送に分けた理由
  2. プレビューと保存で認可方式が異なること
  3. Base64・bytes・ハッシュの役割と、直送にCORSが必要な理由

構成要素

要素 役割
Widget プレビュー表示と直送の起点
Deluge/入口Lambda 保存許可などメタデータのHMAC経路
副作用Lambda PDF生成。プレビューGET用の公開面を持ちうる
直送入口 入口Function URL配下。PDF本体のみ受け取る

単一経路にしない理由

プレビューと保存を同じ経路にした方が単純に見えます。ただしZoho関数やDelugeに巨大PDFを載せると、サイズ制限・破損・遅延が先に問題になります。サーバで再生成して保存すると、画面表示と保存内容の一致保証が弱くなります。URLだけ渡してサーバ取得すると、参照差し替えの余地が残ります。

そのため経路を分けました。許可とメタデータはHMAC経路、PDF本体だけ入口Function URL配下へブラウザ直送します。

Base64・bytes・ハッシュ

用語 意味
bytes PDFの実体バイト列
Base64 バイト列の可逆なテキスト符号化
ハッシュ(SHA-256) バイト列の固定長ダイジェスト。表示内容と保存内容の一致確認に使う

上限の定義は次のとおりです。

上限 意味
PDF本体 6MiB Base64デコード後のbytes
直送body 8MiB HTTPリクエスト全体
許可token 300秒 発行からの有効期間

直送先のURLとCORS

Same-Origin Policyにより、ブラウザはページのoriginと異なる先への応答読取を、サーバ許可なしでは制限します。別originへの読取許可を示す仕組みがCORSです。CORSは認証ではありません。

直送は入口Function URL配下で行います。CORS許可と、Widgetが知る入口の基底URLが必要です。Widget配信も入口に載せている構成では同一originなので、/api/direct のような相対パスで足りる場合があります。配信ホストとAPIホストが分かれる場合は、配布時設定で入口の基底URLを渡し、DelugeのHMAC先と同系統に揃えます。

許可originはWidgetが動作するexactなHTTPSのみです(具体値は記載しません)。未許可だとiframeからの直送はブラウザ段階で失敗します。

プレビューと保存

プレビュー 保存
目的 bytesを画面表示する 確認したbytesを履歴正本にする
主な入口 副作用側の短命URLでGET 入口側の短命token+直送POST
漏洩時のリスク 有効期限内のPDF読取 期限内・claim一致なら保存が実行されうる

プレビューURL

プレビューGETは通常APIのHMAC経路ではありません。副作用LambdaのFunction URL上で、期限付きの署名付きURLを発行してbytesを取得します。業務の保存・送信POSTは受けません。

漏洩時のリスクは、有効期限内のPDF読取です。送信や保存は開始しません。防衛の主軸はURL秘匿ではなく、短命化と、業務副作用を入口側のtoken/HMACに閉じることです。パラメータ名は記載しません。

PDF保存の流れ

  1. Widgetがプレビューを要求し、副作用LambdaがPDFを生成する
  2. Widgetが短命URLでGETして表示する
  3. 表示bytesのSHA-256を計算する
  4. HMAC経路で保存許可を取り、履歴を用意する
  5. 短命tokenを受け取り、本体を直送する
  6. サーバはtokenとハッシュが一致するときだけ保存する

メタデータは関数経由、本体は直送です。

ZOHO.CRM.FUNCTIONS.execute(fnName, {
  arguments: JSON.stringify(saveRequest), // hashやモード。PDF本体は載せない
});

await fetch(entryBaseUrl + "/api/direct", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    dealId,
    historyId,
    idempotencyKey,
    directSaveToken,
    previewPdfBase64,
  }),
});

entryBaseUrl は空(同一origin)または設定で渡す絶対URLです。SHA-256は crypto.subtle.digest("SHA-256", bytes) です。tokenの存在だけでは保存せず、確認済みbytesのハッシュを許可条件に含めます。

許可tokenの内容

短命の署名付き許可として、中のclaim(条件フィールド)を検証します。形式名(JWT等)には依存しません。含める条件は商談ID、履歴ID、操作者ID(body申告ではなく署名境界内で解決した値)、確認済みPDFのSHA-256、期限、nonceです。tokenと直送内容が一致しない場合は拒否します。

使用済みtokenは、warmコンテナ上でbest-effortに拒否します。同一画面での保存連打では、2回目以降がここで失敗することがあります。インスタンス横断の厳密なone-timeではありません。失敗や期限切れ後の正規手順は、プレビュー再作成からのやり直しです。

直送入口はPDF保存専用です。previewPdfUrl は受け付けません。入力保存、履歴作成、外部送信開始も扱いません。

よくある失敗

症状 確認/次の手順
PDF保存の認証失敗 token期限、hash不一致、claim不一致。プレビュー再作成
保存連打で2回目だけ失敗 使用済みtoken。再作成して再保存
PDFが大きすぎる デコード後6MiB超
リクエストが大きすぎる body 8MiB超
iframeから直送だけ失敗 CORS/origin許可
保存成功後に送信だけ失敗 保存と送信は別操作(第4回

画面にはclaim名を出しません。

送信との分離と限界

正式フロー以外の保存フォールバックは事故回避用に残し、通常画面では使いません。

PDF保存のあとに外部送信します。送信時に巨大PDFを再送しません。正本は履歴上の保存済みPDFです。

保証する範囲 保証しない範囲
画面で計算したbytesと直送bytesの一致 画面表示自体が改ざん済みPDFであること
tokenの期限・claim一致 インスタンス横断の厳密one-time
直送入口の専用化 プレビューURL漏洩時の読取ゼロ

巨大PDFをDeluge経由で運ぶ構成より、制約と事故面が小さい、というのが採用理由です。

次回: Zoho WidgetとLambdaの連携 第4回:外部送信の受付判定と自動retry抑止

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?