mcpField
E
edit-embed

edit-embed

github.com/stahura開発24190 回インストール26 週間前に更新
リポジトリ

edit-embed について

edit-embedは、編集可能なDomoコンテンツを埋め込む開発スキルです。Identity BrokerのJWT認証、ロール、インスタンス振り分け、iframe設定を説明します。

主な機能

  1. 01Identity Broker認証用JWTの作成
  2. 02属性に基づくDomoインスタンスへの振り分け
  3. 03Domoロールの割り当て
  4. 04iframeでの編集セッション埋め込み
  5. 05Domoページへのディープリンク

活用シーン

  1. 01外部ユーザーによる埋め込みダッシュボードの編集
  2. 02テナントユーザーのDomoインスタンスへの振り分け
  3. 03ロールに応じた編集権限の付与

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/stahura/domo-ai-vibe-rules --skill edit-embed

インストール後、Claude Code で/edit-embedを入力して読み込まれたか確認します。

同梱ファイルreferences/identity-broker.md

詳細

Domo 埋め込み編集エクスペリエンス

外部ユーザーが、埋め込み iframe を通じて Domo のコンテンツ(ダッシュボード、カード、アラート、レポート、データソース)を作成、編集、保存、共有できるようにします。JWT 認証で Domo Identity Broker を使用します。読み取り専用の埋め込みとは根本的に異なります。

読み取り専用の埋め込みについては、programmatic-filters を参照してください。クライアント側のフィルタリングについては、jsapi-filters を参照してください。

仕組み

  1. サーバーがユーザーを認証し、ユーザーの ID、ロール、ルーティング属性を含む JWT を作成します
  2. JWT は、Domo から提供された共有シークレットで署名されます
  3. サーバーが編集 URL を構築します:{IDP_URL}/jwt?token={jwt_token}
  4. クライアントは、URL を iframe の src として直接描画します(POST フォームは不要です)
  5. Identity Broker が JWT を検証し、ロールに基づいてユーザーを正しい Domo インスタンスにルーティングします

主な違い:読み取り専用=OAuth → 埋め込みトークン → POST フォーム。編集=JWT → Identity Broker URL → iframe の src。

前提条件

Domo の CSM と連携して Identity Broker を設定してください。次の情報を提供します:Domo インスタンスの URL、認証方式(JWT)、ルーティング属性キー、属性とインスタンスのマッピング。

次の情報が提供されます:Identity Broker の URL、JWT 署名シークレット(UUID)、属性キーの設定。

必須の環境変数: IDP_URL(Broker の URL)、JWT_SECRET(署名シークレット)、KEY_ATTRIBUTE(ルーティング属性名)。


Domo Identity Broker

ユーザーを認証(JWT 署名を検証)し、マッピング属性に基づいて正しい Domo インスタンスにルーティングします。SAML2、OIDC、JWT、OAuth2 をサポートしています。埋め込みでは JWT が最も一般的です。

インスタンスのマッピング

各マッピング属性値は、Domo インスタンスに対応します:

属性値Domo インスタンス
acme-corpacme.domo.com
globexglobex.domo.com
initechinitech.domo.com

CSM 経由で設定します(Web フォームのデータセットまたは Excel)。カンマ区切りの値を使用すると、複数のインスタンスにルーティングされます。


JWT トークンの構造

必須フィールド
フィールド型説明
substringユーザーの識別子。通常はメールアドレスです
expnumber有効期限(EPOCH タイムスタンプ)。短く設定してください(5 分を推奨)
jtistring一意のトークン識別子。リプレイ攻撃を防ぐため、UUID v4 を使用してください
{KEY_ATTRIBUTE}string or string[]ユーザーを Domo インスタンスに対応付けるルーティング属性
一般的な任意フィールド
フィールド型説明
namestringDomo でのユーザーの表示名
emailstringユーザーのメールアドレス
rolestringDomo のロール:Admin、Privileged、Editor、または Participant
employee_idstring従業員識別子
titlestring職位
departmentstring部門名
locationstring所在地
phonestring電話番号
localestringロケールの設定
timezonestringタイムゾーンの設定
groupsstring[]Domo 内のグループ割り当て
Domo のロール
ロール権限
Adminフルアクセス — ユーザー、データ、コンテンツ、設定を管理します
Privilegedダッシュボード、カード、データフローの作成・編集、データソースの管理
Editorダッシュボードとカードの作成・編集、データソースへのアクセスは制限されます
Participant共有コンテンツのみの表示と操作(省略時のデフォルト)

外部ユーザーのほとんどには、Editor または Participant を設定してください。


サーバーサイドの実装

ステップ 1:JWT トークンを作成する

Node.js / TypeScript:

import jwt from "jsonwebtoken";
import { v4 as uuidv4 } from "uuid";

function createEditToken(user: {
  username: string;
  email: string;
  domoRole?: string;
  mappingValue?: string | string[];
}) {
  const jwtBody: Record<string, unknown> = {
    sub: user.username,
    name: user.username,
    email: user.email,
    role: user.domoRole || "Participant",
    jti: uuidv4(),
  };

  // Add the routing attribute for instance mapping
  const keyAttribute = process.env.KEY_ATTRIBUTE;
  if (keyAttribute && user.mappingValue) {
    jwtBody[keyAttribute] = user.mappingValue;
  }

  return jwt.sign(jwtBody, process.env.JWT_SECRET!, {
    expiresIn: "5m",
    algorithm: "HS256",
  });
}

Python:

import jwt
import uuid
import time
import os

def create_edit_token(user):
    payload = {
        'sub': user['username'],
        'name': user['username'],
        'email': user['email'],
        'role': user.get('domo_role', 'Participant'),
        'jti': str(uuid.uuid4()),
        'exp': int(time.time()) + 300  # 5 minutes
    }

    key_attribute = os.environ.get('KEY_ATTRIBUTE')
    if key_attribute and user.get('mapping_value'):
        payload[key_attribute] = user['mapping_value']

    return jwt.encode(payload, os.environ['JWT_SECRET'], algorithm='HS256')
ステップ 2:編集 URL を構築する
const editUrl = `${process.env.IDP_URL}/jwt?token=${editToken}`;

特定のページへのディープリンクも設定できます。

const editUrl = `${process.env.IDP_URL}/jwt?token=${editToken}&destination=/page/${pageId}`;
ステップ 3:API ルートを作成する

Next.js App Router:

// app/api/editembed/route.ts
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import jwt from "jsonwebtoken";
import { v4 as uuidv4 } from "uuid";

export async function POST(req: NextRequest) {
  try {
    const { embedID } = await req.json();

    // Authenticate the user against your own auth system
    const token = req.cookies.get("token")?.value;
    if (!token) {
      return NextResponse.json(
        { message: "Unauthorized: Please log in" },
        { status: 401 },
      );
    }

    const user = await verifyAndGetUser(token);
    if (!user) {
      return NextResponse.json({ message: "User not found" }, { status: 404 });
    }

    // Handle comma-separated mapping values
    let mappingValue = user.mappingValue;
    if (typeof mappingValue === "string" && mappingValue.includes(",")) {
      mappingValue = mappingValue.split(",").map((s: string) => s.trim());
    }

    // Build the JWT payload
    const jwtBody: Record<string, unknown> = {
      sub: user.username,
      name: user.username,
      role: user.domoRole || "Participant",
      email: user.email,
      jti: uuidv4(),
    };

    const keyAttr = process.env.KEY_ATTRIBUTE;
    if (keyAttr && mappingValue) {
      jwtBody[keyAttr] = mappingValue;
    }

    // Sign the token
    const editToken = jwt.sign(jwtBody, process.env.JWT_SECRET || "", {
      expiresIn: "5m",
    });

    // Return the Identity Broker URL
    const editUrl = `${process.env.IDP_URL}/jwt?token=${editToken}`;
    return NextResponse.json(editUrl);
  } catch (error) {
    console.error("Error in /api/editembed:", error);
    return NextResponse.json(
      { message: "Server error occurred" },
      { status: 500 },
    );
  }
}

Express の例:

app.post("/api/editembed", authenticateUser, (req, res) => {
  const user = req.user;

  let mappingValue = user.mappingValue;
  if (typeof mappingValue === "string" && mappingValue.includes(",")) {
    mappingValue = mappingValue.split(",").map((s) => s.trim());
  }

  const jwtBody = {
    sub: user.username,
    name: user.username,
    role: user.domoRole || "Participant",
    email: user.email,
    jti: uuidv4(),
  };

  const keyAttr = process.env.KEY_ATTRIBUTE;
  if (keyAttr && mappingValue) {
    jwtBody[keyAttr] = mappingValue;
  }

  const editToken = jwt.sign(jwtBody, process.env.JWT_SECRET, {
    expiresIn: "5m",
  });

  const editUrl = `${process.env.IDP_URL}/jwt?token=${editToken}`;
  res.json(editUrl);
});
ステップ 4:iframe に表示する

編集用埋め込みは iframe の src から直接読み込まれます(読み取り専用埋め込みのような POST フォームは使用しません)。

function EditEmbed({ editUrl }: { editUrl: string }) {
  return (
    <iframe
      src={editUrl}
      style={{ width: "100%", height: "100%", border: "none" }}
      allow="fullscreen"
    />
  );
}

読み取り専用モードと編集モードの処理

アプリが読み取り専用モードと編集モードの両方に対応している場合、埋め込みコンポーネントで両方のフローを処理する必要があります。

function EmbedDashboard({ embedID }: { embedID: string }) {
  const [embedURL, setEmbedURL] = useState<string | null>(null);
  const [embedToken, setEmbedToken] = useState<string | null>(null);
  const isEditMode = embedID === "edit";

  useEffect(() => {
    const endpoint = isEditMode ? "/api/editembed" : "/api/getembedtoken";

    fetch(endpoint, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ embedID }),
      credentials: "include",
    })
      .then((res) => res.json())
      .then((data) => {
        if (isEditMode) {
          setEmbedURL(data); // Edit returns a direct URL string
          setEmbedToken(null);
        } else {
          setEmbedURL(data.embedUrl); // Read-only returns { embedUrl, embedToken }
          setEmbedToken(data.embedToken);
        }
      });
  }, [embedID, isEditMode]);

  // Edit mode: direct iframe src
  if (isEditMode && embedURL) {
    return (
      <iframe
        src={embedURL}
        style={{ width: "100%", height: "100%", border: "none" }}
      />
    );
  }

  // Read-only mode: POST form submission to iframe
  // (handle embedToken + form submission as in programmatic-filters skill)
}

編集用埋め込みのユーザー管理

主なユーザープロパティ
プロパティ用途例
domoRole編集権限を制御します'Editor'、'Participant'
mappingValueユーザーを適切な Domo インスタンスにルーティングします'acme-corp' または ['acme-corp', 'globex']
emailDomo のユーザー ID に必要です'[email protected]'
複数インスタンスを利用するユーザー

ユーザーが複数の Domo インスタンスにアクセスする必要がある場合は、mappingValue をカンマ区切りの文字列または配列として保存してください。

// Single instance
user.mappingValue = "acme-corp";

// Multiple instances
user.mappingValue = "acme-corp, globex";
// or
user.mappingValue = ["acme-corp", "globex"];

JWT を作成する際は、両方の形式を処理してください。

let mappingValue = user.mappingValue;
if (typeof mappingValue === "string" && mappingValue.includes(",")) {
  mappingValue = mappingValue.split(",").map((s) => s.trim());
}

ディープリンク

const editUrl = `${IDP_URL}/jwt?token=${editToken}&destination=/page/${pageId}`;
const editUrl = `${IDP_URL}/jwt?token=${editToken}&destination=/kpicard/${cardId}`;

注意点とベストプラクティス

  • トークンの有効期限: JWT の有効期間は短く設定してください(5 分を推奨)。JWT は初回認証にのみ使用され、その後もセッションは継続します。
  • 署名アルゴリズム: HS256 を使用してください。Domo の Identity Broker ではこれが必要です。
  • JTI の一意性: 常に UUID v4 を使用してください。Domo は、再利用された JTI 値を拒否する場合があります(リプレイ攻撃対策)。
  • マッピング値: KEY_ATTRIBUTE の名前は、CSM と合意した値(customer、keyAttribute、tenant)を使用します。値は Domo のインスタンスマッピングと完全に一致している必要があります。
  • シークレット管理: JWT_SECRET をクライアント側に公開しないでください。環境変数またはシークレットマネージャーを使用してください。
  • セキュリティモデル: 読み取り専用埋め込みでは、フィルターを使って特定のダッシュボードに範囲を限定します。編集用埋め込みでは、ロールに応じた完全な Domo セッションが付与されるため、ロールの割り当ては慎重に行ってください。
  • インスタンスマッピングの変更: 安定した値(変更される可能性のある会社名ではなく、テナント ID など)を使用してください。

環境変数リファレンス

変数説明例
IDP_URLIdentity Broker の URLhttps://yourcompany.identity.domo.com
JWT_SECRET共有署名シークレット(UUID 形式)aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeee
KEY_ATTRIBUTEインスタンスのルーティングに使う属性キーkeyAttribute、customer、tenant

TypeScript 型定義

type DomoRole = "Admin" | "Privileged" | "Editor" | "Participant";

interface EditEmbedUser {
  username: string;
  email: string;
  domoRole?: DomoRole;
  mappingValue?: string | string[];
}

interface EditJwtPayload {
  sub: string;
  name?: string;
  email?: string;
  role?: DomoRole;
  jti: string;
  exp?: number;
  [keyAttribute: string]: unknown; // dynamic routing attribute
}

クイックリファレンス

Identity Broker の設定とインスタンスマッピング管理の詳細については、references/identity-broker.md を参照してください。

コメント