データアクセスの保護
Squid に接続されている、組み込みの内部データベースを含む任意のデータベースを保護するために、Squid が提供するさまざまなデコレーターを使用してください。
各デコレーターは、データベースの異なる部分を保護するために設計されています。
特定のタイプの操作を保護するために、異なる context オブジェクトが関数に渡されます。たとえば、read 操作を保護したい場合、context オブジェクトは QueryContext タイプになり、一方で write 操作の場合は MutationContext オブジェクトが使用されます。
適切なデコレーターを使用することで、以下の任意の種類のデータアクセスを保護できます:
- read
- insert
- update
- delete
- write
- all
なお、write デコレーターは insert、update、および delete 操作を含むため、データベース上のすべての種類の書き込み操作に対して包括的な保護を提供します。
@secureDatabase
Squid によって提供される @secureDatabase デコレーターを使用すると、アクセスされるテーブルやコレクションに関係なく、すべてのデータベースアクセスを保護できます。このデコレーターは、データベースにアクセスするすべての操作に対して認可チェックを強制します。
例えば、開発者は @secureDatabase デコレーターを使用して、認証済みのユーザーのみがデータベースにアクセスできるようにすることができます。そのために、ユーザーが認証されているかどうかをチェックする関数を定義することができます:
import { secureDatabase, SquidService } from '@squidcloud/backend';
export class ExampleService extends SquidService {
  @secureDatabase('all', 'usersDatabase')
  verifyUserAuthenticated(): boolean {
    return this.isAuthenticated();
  }
}
usersDatabase を変更できるのは admin プロパティを持つユーザーのみとするには、開発者はユーザーが認証され、かつ admin プロパティを持つかどうかを確認する verifyUserAuthenticated 関数と組み合わせて @secureDatabase デコレーターを使用できます:
import { secureDatabase, SquidService, MutationContext } from '@squidcloud/backend';
export class ExampleService extends SquidService {
  @secureDatabase('write', 'usersDatabase')
  verifyUserAuthenticated(context: MutationContext): boolean {
    const userAuth = this.getUserAuth();
    if (!userAuth) return false;
    return !!userAuth.attributes['admin'];
  }
}
場合によっては、クライアントが認可されているかどうかを判断するために、操作の context にアクセスする必要があります。例えば、以下の関数は、ユーザーが認証されているかどうかおよび試みている書き込みが insert(update ではなく)であるかどうかを確認します:
import { secureDatabase, SquidService, MutationContext } from '@squidcloud/backend';
export class ExampleService extends SquidService {
  @secureDatabase('write', 'usersDatabase')
  allowOnlyInsertsAndAuthenticated(context: MutationContext): boolean {
    return this.isAuthenticated() && context.getMutationType() === 'insert';
  }
}
@secureCollection
開発者は、@secureCollection デコレーターを使用して、データベース内の特定のコレクションを保護できます。これにより、認可されたユーザーのみが特定のコレクション内のデータにアクセスおよび変更できるようになります。
例えば、開発者は @secureCollection デコレーターを使用して、ユーザーが owner カラムに自分の userId があるドキュメントのみを読み取ることを許可できます。以下はサンプルコードです:
import { secureCollection, SquidService, QueryContext } from '@squidcloud/backend';
export class ExampleService extends SquidService {
  @secureCollection('Items', 'read')
  secureItemsRead(context: QueryContext<Item>): boolean {
    const userId = this.getUserAuth()?.userId;
    if (!userId) return false;
    return context.isSubqueryOf('owner', '==', userId);
  }
}
また、ユーザーが自分の所有する Items のみを更新、削除、または挿入できるようにする必要がある場合もあります:
import { secureCollection, SquidService, MutationContext } from '@squidcloud/backend';
export class ExampleService extends SquidService {
  @secureCollection('Items', 'write')
  secureItemWrite(context: MutationContext<Item>): boolean {
    const userId = this.getUserAuth()?.userId;
    if (!userId) return false;
    const { before, after } = context.beforeAndAfterDocs;
    if (before && before.owner !== userId) return false;
    if (after && after.owner !== userId) return false;
    return true;
  }
}
ネイティブクエリのセキュリティ
ネイティブクエリ を保護するには、@secureNativeQuery() デコレーターを使用し、データベースの統合 ID を渡します。以下の例は、ユーザーが認証されているかどうかを確認する関数を定義しています:
import { secureNativeQuery, SquidService } from '@squidcloud/backend';
export class ExampleService extends SquidService {
  @secureNativeQuery('YOUR_INTEGRATION_ID')
  verifyUserAuthenticated(): boolean {
    return this.isAuthenticated();
  }
}
この例のように、auth token の属性を使用してより細かいアクセス権を追加することもできます:
import { secureNativeQuery, SquidService } from '@squidcloud/backend';
export class ExampleService extends SquidService {
  @secureNativeQuery('YOUR_INTEGRATION_ID')
  verifyAdminUser(): boolean {
    // Get the authenticated user's details
    const userAuth = this.getUserAuth();
    if (!userAuth) {
      return false;
    }
    // Check for the admin attribute
    return !!userAuth.attributes['admin'];
  }
}
auth パーミッションを保存するためにコレクションを使用することもできます。コレクションが Security Service function で保護されていることを確認してください。
import { secureNativeQuery, SquidService } from '@squidcloud/backend';
export class ExampleService extends SquidService {
  @secureNativeQuery('YOUR_INTEGRATION_ID')
  verifyNativeQueryAccess(): Promise<boolean> {
    // Get the authenticated user's ID
    const userId = this.getUserAuth()?.userId;
    if (!userId) {
      return false;
    }
    // Check if the user's ID is in the collection listing users permitted to access native query
    const userTableAccess = await this.squid.collection('table_access').doc(userId).snapshot();
    return !!userTableAccess;
  }
}
ネイティブクエリの context
ネイティブなリレーショナルまたは MongoDB クエリを実行する際、Squid バックエンドには、クライアントが実行しようとしているネイティブクエリの種類('relational' または 'mongo')や、クエリの種類に基づくその他の属性を示す context が利用可能です。
以下は、ネイティブなリレーショナルクエリを実行する際に提供される context のタイプです:
RelationalNativeQueryContext  {
  type: 'relational';
  query: string;
  params: Record<string, any>;
}
この context を使用して、クライアントが行えるネイティブクエリの種類を制限できます。たとえば、以下のコードは、SQUIDS というコレクション(またはテーブルの行)から、YEAR フィールドの値が 1980 より大きいドキュメントを選択するという一種類のネイティブクエリのみをクライアントに許可します。もしクライアントが異なるクエリを実行しようとしたり、1980 より前の値を照会した場合、クエリは失敗します。
import { secureNativeQuery, SquidService, RelationalNativeQueryContext } from '@squidcloud/backend';
export class ExampleService extends SquidService {
  @secureNativeQuery('YOUR_INTEGRATION_ID')
  verifyNativeQueryAccess(context: RelationalNativeQueryContext): boolean {
    if (context.query !== 'SELECT * FROM SQUIDS WHERE YEAR = ${year}' || context.params.year < 1980) {
      return false;
    }
    return true;
  }
}
以下は、ネイティブな MongoDB クエリを実行する際に提供される context のタイプです:
MongoNativeQueryContext {
  type: 'mongo';
  collectionName: string;
  aggregationPipeline: Array<any | undefined>;
}
この context を使用して、クライアントが Mongo aggregation pipeline に対して実行できる aggregation pipeline クエリの種類を制限できます。以下の例では、ユーザーが ORDERS コレクション内でのみ MongoDB aggregation pipeline を実行することを許可しています:
import { secureNativeQuery, SquidService, MongoNativeQueryContext } from '@squidcloud/backend';
export class ExampleService extends SquidService {
  @secureNativeQuery('YOUR_INTEGRATION_ID')
  verifyNativeMongoQueryAccess(context: MongoNativeQueryContext): boolean {
    if (context.collectionName !== 'ORDERS') {
      return false;
    }
    return true;
  }
}