データアクセスの保護
Squid が提供する一連のデコレーターを使用して、Squid に接続されているあらゆるデータベース(組み込みの内部データベースを含む)を保護します。
各デコレーターは、データベースの異なる部分を保護するように設計されています。
特定の種類のアクションを保護するには、別の context オブジェクトが関数に渡されます。たとえば、read 操作を保護したい場合、context オブジェクトは QueryContext 型になり、write 操作では MutationContext オブジェクトが使用されます。
適切なデコレーターを使用して、以下の種類のデータアクセスを保護できます。
readinsertupdatedeletewriteall
write デコレーターには insert、update、delete 操作が含まれるため、データベースに対するあらゆる書き込み操作を包括的に保護します。
@secureDatabase
Squid が提供する @secureDatabase デコレーターは、アクセス対象の table や collection に関係なく、すべてのデータベースアクセスを保護するために使用できます。このデコレーターは、データベースにアクセスするすべてのアクションに対して認可(authorization)チェックを強制します。
たとえば、開発者は @secureDatabase デコレーターを使用して、認証済みユーザーのみがデータベースにアクセスできるようにできます。そのためには、ユーザーが認証されているかどうかをチェックする関数を定義します。
import { secureDatabase, SquidService } from '@squidcloud/backend';
export class ExampleService extends SquidService {
@secureDatabase('all', 'usersDatabase')
verifyUserAuthenticated(): boolean {
return this.isAuthenticated();
}
}
admin プロパティを持つユーザーのみが usersDatabase を変更できるようにするには、開発者は @secureDatabase デコレーターを、ユーザーが認証されていて admin プロパティを持つことを確認する verifyUserAuthenticated 関数と組み合わせて使用できます。
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 デコレーターを使用して、データベース内の特定の collection を保護できます。これにより、特定の collection 内のデータにアクセスおよび変更できるのは、認可されたユーザーのみになります。
たとえば、開発者は @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 のみを update、delete、insert できるようにしたい場合もあるでしょう。
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;
}
}
ネイティブクエリの保護
native query を保護するには、@secureNativeQuery() デコレーターを使用し、データベースの integration ID を渡します。次の例では、ユーザーが認証されているかどうかをチェックする関数を定義しています。
import { secureNativeQuery, SquidService } from '@squidcloud/backend';
export class ExampleService extends SquidService {
@secureNativeQuery('YOUR_INTEGRATION_ID')
verifyUserAuthenticated(): boolean {
return this.isAuthenticated();
}
}
次の例に示すように、auth token attributes を使用して、より細かなアクセス制御を追加できます。
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'];
}
}
collection を使用して auth permissions を保存することもできます。その collection が 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
ネイティブな relational または MongoDB クエリを実行する際、Squid backend では、クライアントが実行したい native query のタイプ('relational' または 'mongo')や、クエリのタイプに応じたその他の属性を示す context が利用できます。
ネイティブな relational クエリを実行する場合、次の context type が提供されます。
RelationalNativeQueryContext {
type: 'relational';
query: string;
params: Record<string, any>;
}
この context を使用して、クライアントが実行できる native query のタイプを制限します。たとえば次のコードでは、SQUIDS という collection(または table の行)に対し、YEAR フィールドの値が 1980 より大きいドキュメントを select する種類の native query のみをクライアントに許可します。クライアントが別のクエリを実行しようとしたり、1980 より前の値を対象に table を query しようとしたりすると、そのクエリは失敗します。
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 type が提供されます。
MongoNativeQueryContext {
type: 'mongo';
collectionName: string;
aggregationPipeline: Array<any | undefined>;
}
この context を使用して、クライアントがあなたの Mongo aggregation pipeline に対して実行できる aggregation pipeline クエリの種類を制限します。次の例では、ユーザーが ORDERS collection に対してのみ 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;
}
}