メインコンテンツまでスキップ

Auth API リファレンス

Auth API は、Momento サービスの API キーとトークンを作成および管理します。これらの認証メカニズムは、1つ以上のキャッシュやトピックスへのアクセスを許可するために、1つ以上のパーミッションを持つようにスコープすることができます。

GenerateApiKey API

指定した権限と有効期限を持つ新しい Momento Auth トークンを生成します。

名前タイプ説明
scopePermissionScope新しいトークンに付与する権限。TokenScopeオブジェクトはSDKによって提供されます。
expiresInNumber  |  ExpiresIn objectExpiresIn.never()メソッド、ExpiresIn.minutes()メソッド、ExpiresIn.hours()メソッドを呼び出すことで、トークンが期限切れになるまでの秒数、またはその期間を表すExpiresInオブジェクト。
Method response object
  • Success
    • authToken: string - 新しいAuthトークン
    • refreshToken: string - RefreshApiKey API を使って、トークンの有効期限が切れる前にリフレッシュするためのトークン
    • endpoint: string - Momento クライアントがリクエストを行う際に使用する HTTP エンドポイント
    • expiresAt: Timestamp - トークンの有効期限が切れるタイムスタンプ
  • Error

詳しくはレスポンスオブジェクトを参照してください。

注記

MomentoコントロールプレーンAPIにアクセスするためのトークンは、Momentoコンソールを使用してのみ生成できます。

// Generate a token that allows all data plane APIs on all caches and topics.
const allDataRWTokenResponse = await authClient.generateApiKey(AllDataReadWrite, ExpiresIn.minutes(30));
switch (allDataRWTokenResponse.type) {
case GenerateApiKeyResponse.Success:
console.log('Generated an API key with AllDataReadWrite scope!');
// logging only a substring of the tokens, because logging security credentials is not advisable :)
console.log(`API key starts with: ${allDataRWTokenResponse.apiKey.substring(0, 10)}`);
console.log(`Refresh token starts with: ${allDataRWTokenResponse.refreshToken.substring(0, 10)}`);
console.log(`Expires At: ${allDataRWTokenResponse.expiresAt.epoch()}`);
break;
case GenerateApiKeyResponse.Error:
throw new Error(
`An error occurred while attempting to call generateApiKey with AllDataReadWrite scope: ${allDataRWTokenResponse.errorCode()}: ${allDataRWTokenResponse.toString()}`
);
}

// Generate a token that can only call read-only data plane APIs on a specific cache foo. No topic apis (publish/subscribe) are allowed.
const singleCacheROTokenResponse = await authClient.generateApiKey(
TokenScopes.cacheReadOnly('foo'),
ExpiresIn.minutes(30)
);
switch (singleCacheROTokenResponse.type) {
case GenerateApiKeyResponse.Success:
console.log('Generated an API key with read-only access to cache foo!');
// logging only a substring of the tokens, because logging security credentials is not advisable :)
console.log(`API key starts with: ${singleCacheROTokenResponse.apiKey.substring(0, 10)}`);
console.log(`Refresh token starts with: ${singleCacheROTokenResponse.refreshToken.substring(0, 10)}`);
console.log(`Expires At: ${singleCacheROTokenResponse.expiresAt.epoch()}`);
break;
case GenerateApiKeyResponse.Error:
throw new Error(
`An error occurred while attempting to call generateApiKey with single cache read-only scope: ${singleCacheROTokenResponse.errorCode()}: ${singleCacheROTokenResponse.toString()}`
);
}

// Generate a token that can call all data plane APIs on all caches. No topic apis (publish/subscribe) are allowed.
const allCachesRWTokenResponse = await authClient.generateApiKey(
TokenScopes.cacheReadWrite(AllCaches),
ExpiresIn.minutes(30)
);
switch (allCachesRWTokenResponse.type) {
case GenerateApiKeyResponse.Success:
console.log('Generated an API key with read-write access to all caches!');
// logging only a substring of the tokens, because logging security credentials is not advisable :)
console.log(`API key starts with: ${allCachesRWTokenResponse.apiKey.substring(0, 10)}`);
console.log(`Refresh token starts with: ${allCachesRWTokenResponse.refreshToken.substring(0, 10)}`);
console.log(`Expires At: ${allCachesRWTokenResponse.expiresAt.epoch()}`);
break;
case GenerateApiKeyResponse.Error:
throw new Error(
`An error occurred while attempting to call generateApiKey with all caches read-write scope: ${allCachesRWTokenResponse.errorCode()}: ${allCachesRWTokenResponse.toString()}`
);
}

// Generate a token that can call publish and subscribe on all topics within cache bar
const singleCacheAllTopicsRWTokenResponse = await authClient.generateApiKey(
TokenScopes.topicPublishSubscribe({name: 'bar'}, AllTopics),
ExpiresIn.minutes(30)
);
switch (singleCacheAllTopicsRWTokenResponse.type) {
case GenerateApiKeyResponse.Success:
console.log('Generated an API key with publish-subscribe access to all topics within cache bar!');
// logging only a substring of the tokens, because logging security credentials is not advisable :)
console.log(`API key starts with: ${singleCacheAllTopicsRWTokenResponse.apiKey.substring(0, 10)}`);
console.log(`Refresh token starts with: ${singleCacheAllTopicsRWTokenResponse.refreshToken.substring(0, 10)}`);
console.log(`Expires At: ${singleCacheAllTopicsRWTokenResponse.expiresAt.epoch()}`);
break;
case GenerateApiKeyResponse.Error:
throw new Error(
`An error occurred while attempting to call generateApiKey with read-write scope for all topics in a single cache: ${singleCacheAllTopicsRWTokenResponse.errorCode()}: ${singleCacheAllTopicsRWTokenResponse.toString()}`
);
}

// Generate a token that can only call subscribe on topic where_is_mo within cache mo_nuts
const oneCacheOneTopicRWTokenResponse = await authClient.generateApiKey(
TokenScopes.topicSubscribeOnly('mo_nuts', 'where_is_mo'),
ExpiresIn.minutes(30)
);
switch (oneCacheOneTopicRWTokenResponse.type) {
case GenerateApiKeyResponse.Success:
console.log('Generated an API key with subscribe-only access to topic where_is_mo within cache mo_nuts!');
// logging only a substring of the tokens, because logging security credentials is not advisable :)
console.log(`API key starts with: ${oneCacheOneTopicRWTokenResponse.apiKey.substring(0, 10)}`);
console.log(`Refresh token starts with: ${oneCacheOneTopicRWTokenResponse.refreshToken.substring(0, 10)}`);
console.log(`Expires At: ${oneCacheOneTopicRWTokenResponse.expiresAt.epoch()}`);
break;
case GenerateApiKeyResponse.Error:
throw new Error(
`An error occurred while attempting to call generateApiKey with read-write scope for single topic in a single cache: ${oneCacheOneTopicRWTokenResponse.errorCode()}: ${oneCacheOneTopicRWTokenResponse.toString()}`
);
}

// Generate a token with multiple permissions
const cachePermission1 = {
role: CacheRole.ReadWrite, // Managed role that grants access to read as well as write apis on caches
cache: 'acorns', // Scopes the access to a single cache named 'acorns'
};
const cachePermission2 = {
role: CacheRole.ReadOnly, // Managed role that grants access to only read data apis on caches
cache: AllCaches, // Built-in value for access to all caches in the account
};
const topicPermission1 = {
role: TopicRole.PublishSubscribe, // Managed role that grants access to subscribe as well as publish apis
cache: 'walnuts', // Scopes the access to a single cache named 'walnuts'
topic: 'mo_favorites', // Scopes the access to a single topic named 'mo_favorites' within cache 'walnuts'
};
const topicPermission2 = {
role: TopicRole.SubscribeOnly, // Managed role that grants access to only subscribe api
cache: AllCaches, // Built-in value for all cache(s) in the account.
topic: AllTopics, // Built-in value for access to all topics in the listed cache(s).
};

const permissions = {
permissions: [cachePermission1, cachePermission2, topicPermission1, topicPermission2],
};

const multiplePermsTokenResponse = await authClient.generateApiKey(permissions, ExpiresIn.minutes(30));
switch (multiplePermsTokenResponse.type) {
case GenerateApiKeyResponse.Success:
console.log('Generated an API key with multiple cache and topic permissions!');
// logging only a substring of the tokens, because logging security credentials is not advisable :)
console.log(`API key starts with: ${multiplePermsTokenResponse.apiKey.substring(0, 10)}`);
console.log(`Refresh token starts with: ${multiplePermsTokenResponse.refreshToken.substring(0, 10)}`);
console.log(`Expires At: ${multiplePermsTokenResponse.expiresAt.epoch()}`);
break;
case GenerateApiKeyResponse.Error:
throw new Error(
`An error occurred while attempting to call generateApiKey with multiple permissions: ${multiplePermsTokenResponse.errorCode()}: ${multiplePermsTokenResponse.toString()}`
);
}

RefreshApiKey API

既存の有効期限が切れていない Momento Authトークンをリフレッシュします。 元のトークンと同じ権限と有効期限を持つ新しいトークンを生成します。

名前タイプ説明
refreshTokenStringGenerateApiKey をコールした際に取得した、現在のAuthトークンの refreshToken。
Method response object
  • Success
    • apiKey: string - 新しいAuthトークン
    • refreshToken: string - RefreshApiKey APIで使用するリフレッシュトークン。
    • endpoint: string - Momentoクライアントがリクエストを行う際に使用する HTTP エンドポイント。
    • expiresAt: Timestamp - トークンの有効期限が切れるタイムスタンプ
  • Error

詳しくはレスポンスオブジェクトを参照してください。

const generateTokenResponse = await authClient.generateApiKey(AllDataReadWrite, ExpiresIn.minutes(30));

let successResponse: GenerateApiKey.Success;
switch (generateTokenResponse.type) {
case GenerateApiKeyResponse.Success: {
successResponse = generateTokenResponse;
break;
}
case GenerateApiKeyResponse.Error:
throw new Error(
`An error occurred while attempting to call generateApiKey: ${generateTokenResponse.errorCode()}: ${generateTokenResponse.toString()}`
);
}

console.log('Generated API key; refreshing!');
const refreshAuthClient = new AuthClient({
credentialProvider: CredentialProvider.fromString({apiKey: successResponse.apiKey}),
});
const refreshTokenResponse = await refreshAuthClient.refreshApiKey(successResponse.refreshToken);
switch (refreshTokenResponse.type) {
case RefreshApiKeyResponse.Success:
console.log('API key refreshed!');
// logging only a substring of the tokens, because logging security credentials is not advisable :)
console.log(`Refreshed API key starts with: ${refreshTokenResponse.apiKey.substring(0, 10)}`);
console.log(`New refresh token starts with: ${refreshTokenResponse.refreshToken.substring(0, 10)}`);
console.log(`Refreshed API key expires At: ${refreshTokenResponse.expiresAt.epoch()}`);
break;
case RefreshApiKeyResponse.Error:
throw new Error(
`An error occurred while attempting to call refreshApiKey: ${refreshTokenResponse.errorCode()}: ${refreshTokenResponse.toString()}`
);
}

PermissionScope objects

名前タイプ説明
permissionsList <Permission>新しいトークンに付与するパーミッション

TokenScopeは、パーミッションオブジェクトのリストです。このリストには、CachePermission 型または TopicPermission 型のパーミッションを含めることができ、最大 10 個のパーミッションオブジェクトを含めることができます。パーミッションは Momento データプレーン API (getset など) へのアクセスのみを許可します。 複数のパーミッションオブジェクトを持つAuthトークンが作成された場合、一致するパーミッションがアクセスを許可します。たとえば、1 つのトークンに 2 つのパーミッションオブジェクトを設定した場合、次のようになります:

  1. アカウントのすべてのキャッシュへの ReadWrite アクセスを許可するパーミッションオブジェクト
  2. キャッシュ foo に対する ReadOnly アクセスを許可するパーミッションオブジェクト

この場合でも、トークンはキャッシュ foo に対してデータ操作 API (setdeleteDictionarySetFields など) を使用することができます。

Permission objects

これらのオブジェクトはキャッシュやトピック情報を持つ特定のロールを定義し、PermissionScopeに割り当てられます。

CachePermission

キャッシュのパーミッションを定義する PermissionScope オブジェクトのコンポーネント

名前タイプ説明
roleReadOnly  |  ReadWrite  |  WriteOnlyパーミッションによって許可されたアクセスのタイプ
cacheAllCaches  |  CacheNameパーミッションは CacheName オブジェクトまたは AllCaches 組み込みの値を使用して、セレクトキャッシュの名前で制限することができます。

ロールの場合、CacheRole.ReadOnly を使用すると、CacheSelector で定義されたキャッシュ上のすべての読み取りデータプレーン API (getDictionaryGetField など) にアクセスできるようになります。 CacheRole.ReadWrite を使用すると、CacheSelector で定義されたキャッシュ上のすべての読み取りデータプレーン API および書き込みデータプレーン API にアクセスできるようになります。 CacheRole.WriteOnly を使用すると、CacheSelector で定義されたキャッシュのすべての書き込みデータプレーン API にアクセスできるようになります。 CacheRole.WriteOnly は、SetIf* のような条件付きの書き込みを意味する API や、ListPushBack が新しい長さを返すなど、コレクションの更新状態に関する情報を返す API には使用できません。カスタムロールはサポートされていません。

キャッシュの場合、値は組み込みの AllCaches か、このパーミッションのキャッシュ名を含む文字列となります。

CachePermission オブジェクトの PermissionScope の例

これは、CachePermissions だけで PermissionScope を作成する例です。

const CachePermissions = {
permissions: [
{
role: CacheRole.ReadWrite, // Managed role
cache: "MyCache" // grants access to a specific cache
},
{
role: CacheRole.ReadOnly, // Managed role
cache: AllCaches // Built-in value for access to all caches in the account.
},
],
};

TopicPermission

トークンのパーミッションを定義するPermissionScopeオブジェクトのコンポーネント

名前タイプ説明
roleSubscribeOnly | PublishSubscribe  |  PublishOnlyパーミッションによって許可されたアクセスのタイプ。
cacheAllCaches  |  CacheNameパーミッションは CacheName オブジェクトを使用して選択したキャッシュに制限することも、AllCaches 組み込み値を使用してアカウント内のすべてのキャッシュに制限することもできます。
topicAllTopics  |  TopicNameパーミッションは TopicName オブジェクトを使用して選択したトピックに制限することも、 AllTopics 組み込み値を使用して上記のキャッシュ内のすべてのトピックに制限することもできます。

ロールには、TopicRole.PublishSubscribeTopicRole.SubscribeOnlyTopicRole.PublishOnlyの3つの管理ロールがあります。カスタムロールはサポートされていません。SubscribeOnlyロールを使用するとトピックの購読のみが可能になり、PublishSubscribeロールを使用するとトピックの発行と購読が可能になり、PublishOnlyロールを使用するとトピックの発行のみが可能になります。

キャッシュでは、そのキャッシュの名前空間内のトピックのみがパーミッションによって許可されます。 これは組み込みの AllCaches 値またはキャッシュを指定する文字列に設定することができます。

トピックには、組み込みの AllTopics 値を設定することができます。 これは、cache で定義されているキャッシュ内のすべてのトピックにアクセスできるようにするもので、特定のトピック名を文字列で指定することもできます。

TopicPermissionオブジェクトのTokenScopeの例

これは、TopicPermissions だけで PermissionScope を作成する例です。

const TopicsPermissions = {
permissions: [
{
role: TopicRole.PublishSubscribe, // Managed role
cache: 'the-great-wall', // grants access to a specific cache
topic: 'highlights', // grants access to a specific topic
},
{
role: TopicRole.SubscribeOnly, // Managed role
cache: AllCaches, // This is a built-in value for access to all caches in the account
topic: AllTopics, // This is a built-in value for access to all topic in the listed cache(s).
},
],
};

GenerateDisposableToken API

指定した権限と有効期限を持つ、使い捨ての Momento Authトークンを生成します。

使い捨てトークンは、通常の Momento 認証トークンとはいくつかの点で異なります:

  • 使い捨てトークンはコンソールで生成することはできません。generateDisposableToken`APIコールに使用するトークンは、Momentoコンソールから生成したスーパーユーザースコープのトークンでなければなりません。
  • トークンの有効期限は1時間です。
  • リフレッシュはできないので、リフレッシュトークンは付属しません。
  • パーミッションは DisposableTokenScope オブジェクトで指定します。
名前タイプ説明
scopeDisposableTokenScope新しい使い捨てトークンに付与する権限。SDK は、あらかじめ DisposableTokenScope オブジェクトを用意しています。
expiresInNumber  |  ExpiresIn objectトークンが失効するまでの秒数、または ExpiresIn.minutes() メソッドや ExpiresIn.hours(1) メソッドを呼び出して期間を指定した ExpiresIn オブジェクト。使い捨てトークンは1時間以内に失効しなければなりません。
Method response object
  • Success
    • authToken: string - 新しい使い捨てAuthトークン
    • endpoint: string - Momento クライアントがリクエストを行う際に使用する HTTP エンドポイント
    • expiresAt: Timestamp - トークンの有効期限が切れるタイムスタンプ
  • Error

詳しくはレスポンスオブジェクトを参照してください。

// Generate a disposable token with read-write access to a specific key in one cache
const oneKeyOneCacheToken = await authClient.generateDisposableToken(
DisposableTokenScopes.cacheKeyReadWrite('squirrels', 'mo'),
ExpiresIn.minutes(30)
);
switch (oneKeyOneCacheToken.type) {
case GenerateDisposableTokenResponse.Success:
console.log('Generated a disposable API key with access to the "mo" key in the "squirrels" cache!');
// logging only a substring of the tokens, because logging security credentials is not advisable :)
console.log(`API key starts with: ${oneKeyOneCacheToken.authToken.substring(0, 10)}`);
console.log(`Expires At: ${oneKeyOneCacheToken.expiresAt.epoch()}`);
break;
case GenerateDisposableTokenResponse.Error:
throw new Error(
`An error occurred while attempting to call generateApiKey with disposable token scope: ${oneKeyOneCacheToken.errorCode()}: ${oneKeyOneCacheToken.toString()}`
);
}

// Generate a disposable token with read-write access to a specific key prefix in all caches
const keyPrefixAllCachesToken = await authClient.generateDisposableToken(
DisposableTokenScopes.cacheKeyPrefixReadWrite(AllCaches, 'squirrel'),
ExpiresIn.minutes(30)
);
switch (keyPrefixAllCachesToken.type) {
case GenerateDisposableTokenResponse.Success:
console.log('Generated a disposable API key with access to keys prefixed with "squirrel" in all caches!');
// logging only a substring of the tokens, because logging security credentials is not advisable :)
console.log(`API key starts with: ${keyPrefixAllCachesToken.authToken.substring(0, 10)}`);
console.log(`Expires At: ${keyPrefixAllCachesToken.expiresAt.epoch()}`);
break;
case GenerateDisposableTokenResponse.Error:
throw new Error(
`An error occurred while attempting to call generateApiKey with disposable token scope: ${keyPrefixAllCachesToken.errorCode()}: ${keyPrefixAllCachesToken.toString()}`
);
}

// Generate a disposable token with read-only access to all topics in one cache
const allTopicsOneCacheToken = await authClient.generateDisposableToken(
TokenScopes.topicSubscribeOnly('squirrel', AllTopics),
ExpiresIn.minutes(30)
);
switch (allTopicsOneCacheToken.type) {
case GenerateDisposableTokenResponse.Success:
console.log('Generated a disposable API key with access to all topics in the "squirrel" cache!');
// logging only a substring of the tokens, because logging security credentials is not advisable :)
console.log(`API key starts with: ${allTopicsOneCacheToken.authToken.substring(0, 10)}`);
console.log(`Expires At: ${allTopicsOneCacheToken.expiresAt.epoch()}`);
break;
case GenerateDisposableTokenResponse.Error:
throw new Error(
`An error occurred while attempting to call generateApiKey with disposable token scope: ${allTopicsOneCacheToken.errorCode()}: ${allTopicsOneCacheToken.toString()}`
);
}

// Generate a disposable token with write-only access to a single topic in all caches
const oneTopicAllCachesToken = await authClient.generateDisposableToken(
TokenScopes.topicPublishOnly(AllCaches, 'acorn'),
ExpiresIn.minutes(30)
);
switch (oneTopicAllCachesToken.type) {
case GenerateDisposableTokenResponse.Success:
console.log('Generated a disposable API key with access to all topics in the "squirrel" cache!');
// logging only a substring of the tokens, because logging security credentials is not advisable :)
console.log(`API key starts with: ${oneTopicAllCachesToken.authToken.substring(0, 10)}`);
console.log(`Expires At: ${oneTopicAllCachesToken.expiresAt.epoch()}`);
break;
case GenerateDisposableTokenResponse.Error:
throw new Error(
`An error occurred while attempting to call generateApiKey with disposable token scope: ${oneTopicAllCachesToken.errorCode()}: ${oneTopicAllCachesToken.toString()}`
);
}

DisposableTokenScope objects

名前タイプ説明
permissionsList <DisposableTokenCachePermission  |  Permission>新しいトークンに付与するパーミッション

DisposableTokenScope オブジェクトは、CachePermissionTopicPermission、または DisposableTokenCachePermission タイプのパーミッション・オブジェクトを受け入れます。

DisposableTokenCachePermissions

DisposableTokenCachePermission は CachePermission を拡張したもので、CachePermission と同じフィールドを持ちますが、item フィールドが追加されています。

例えば、キーまたはキープレフィックスを表す文字列に item を設定すると、特定のキーまたはプレフィックスで始まるキーのセットのみにアクセスを制限できます。別の方法として、item を AllCacheItems に設定すると、通常の CachePermission と同じパーミッションのセットが作成されます。

名前タイプ説明
roleReadOnly  |  ReadWrite  |  WriteOnlyパーミッションによって許可されるアクセスのタイプ
cacheAllCaches  |  CacheNameパーミッションは CacheName オブジェクトまたは AllCaches 組み込みの値を使用して、セレクトキャッシュの名前で制限することができます。
itemAllCacheItems  |  Key  |  KeyPrefixパーミッションは、Key または KeyPrefix オブジェクト、あるいは AllCachesItems 組み込みの値を使用して、名前によるキャッシュアイテムの選択を制限することができます。

ロールの場合、CacheRole.ReadOnly を使用すると、CacheSelector で定義されたキャッシュ上のすべての読み取りデータプレーン API (getDictionaryGetField など) にアクセスできるようになります。 CacheRole.ReadWrite を使用すると、CacheSelector で定義されたキャッシュ上のすべての読み取りデータプレーン API および書き込みデータプレーン API にアクセスできるようになります。 CacheRole.WriteOnly を使用すると、CacheSelector で定義されたキャッシュのすべての書き込みデータプレーン API にアクセスできるようになります。 CacheRole.WriteOnly は、SetIf* のような条件付きの書き込みを意味する API や、ListPushBack が新しい長さを返すなど、コレクションの更新状態に関する情報を返す API には使用できません。カスタムロールはサポートされていません。

キャッシュの場合、値は組み込みの AllCaches か、このパーミッションのキャッシュ名を含む文字列となります。

item の場合、値は組み込みの AllCacheItems か、このパーミッションが対象とするキャッシュアイテムのキーまたはキープレフィックスを含む文字列となります。

DisposableTokenScope オブジェクトの例

これは、3 種類のパーミッション・オブジェクトをすべて持つ DisposableTokenScope を作成する例です: CachePermission、TopicPermission、DisposableTokenCachePermission です。

const exampleDisposableTokenPermission: DisposableTokenCachePermission = {
role: CacheRole.WriteOnly,
cache: "WriteCache",
item: {
keyPrefix: "WriteKey"
}
};

const exampleCachePermission: CachePermission = {
role: CacheRole.ReadOnly,
cache: "ReadCache"
};

const exampleTopicPermission: TopicPermission = {
role: TopicRole.PublishSubscribe,
cache: "ReadWriteCache",
topic: "MyTopic"
}

const exampleScope: DisposableTokenScope = {
permissions: [
exampleDisposableTokenPermission,
exampleCachePermission,
exampleTopicPermission,
],
};

// Then pass in the entire DisposableTokenScope object when
// you call generateDisposableToken
const tokenResponse = await authClient.generateDisposableToken(
exampleScope,
ExpiresIn.minutes(30)
);

FAQ

キャッシュやトピックのパーミッションにカスタムロールを作成できますか?

各権限について、上記の管理された役割のみをサポートしています。

これらのトークンは、MomentoのコントロールプレーンAPIへのアクセスを制御しますか?

GenerateApiKeyAPIで生成されたアクセストークンは、MomentoのデータプレーンAPIへのアクセスのみを制御します。Momento のコントロールプレーン API にアクセスするためのトークンは、Momento console を使用して生成する必要があります。

ヒント

ここで答えられない質問があれば、私たちのDiscordサーバーに飛び、サポートチャンネルで私たちに質問してください。