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

データアクセスの保護

Squid が提供するさまざまな decorator を使用して、組み込みの internal database を含む、Squid に接続されている任意の database を保護します。

各 decorator は、database の異なる部分を保護するように設計されています。security function は boolean を返します。true はリクエストを許可し、false は拒否します。

特定の種類の action を保護するために、異なる context object が関数に渡されます。たとえば、read 操作を保護したい場合、context object は QueryContext 型になります。一方、write 操作では MutationContext object が使用されます。

適切な decorator を使用することで、以下の種類の data access を保護できます。

  • read
  • insert
  • update
  • delete
  • write
  • all

write decorator には insertupdatedelete 操作が含まれるため、database 上のすべての種類の write 操作に対して包括的な保護を提供します。

@secureDatabase

Squid が提供する @secureDatabase decorator は、どの table や collection にアクセスしているかに関係なく、すべての database access を保護するために使用できます。この decorator は、database にアクセスするすべての action に対して authorization check を適用します。

たとえば、開発者は @secureDatabase decorator を使用して、認証済みユーザーのみが database にアクセスできるようにできます。そのためには、ユーザーが認証されているかどうかを確認する関数を定義します。

Backend code
import { secureDatabase, SquidService } from '@squidcloud/backend';

export class ExampleService extends SquidService {
@secureDatabase('all', 'usersDatabase')
verifyUserAuthenticated(): boolean {
return this.isAuthenticated();
}
}

admin property を持つユーザーのみが usersDatabase を変更できるようにするには、開発者は @secureDatabase decorator と、ユーザーが認証されており admin property を持っているかどうかを確認する verifyUserAuthenticated 関数を組み合わせて使用できます。

Backend code
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'];
}
}

場合によっては、client が authorized かどうかを判断するために、action の context にアクセスする必要があります。たとえば、次の関数はユーザーが認証されているかどうか、および試行している write が insertupdate ではない)かどうかを確認します。

Backend code
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 decorator を使用して、database 内の特定の collection を保護できます。これにより、authorized users のみが特定の collection 内の data にアクセスし、変更できるようになります。

たとえば、開発者は @secureCollection decorator を使用して、ユーザーが owner column に自分の userId を持つ document のみを read できるようにできます。以下は code snippet の例です。

Backend code
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 できるようにしたい場合もあります。

Backend code
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;
}
}

@publicCollection

デフォルトでは、collection の read は、@secureCollection または @secureDatabase rule が許可するまで拒否されます。認証されていない client を含め、誰でも安全に read できる data を collection が保持している場合は、@publicCollection decorator を使用して、その read が意図的に public であることを宣言します。public read は security-rule evaluation を完全に bypass します。Squid は backend を呼び出さずにそれらを提供するため、read rule は実行されません。誰にでも公開して安全な data にのみ使用してください。

このページの他の decorator とは異なり、@publicCollection は method ではなく class に適用されます。public にできるのは read のみです。write は常に security rule を通るため、public-read collection でも @secureCollection を使用して insertupdatedeletewrite を制限できます。

Backend code
import { publicCollection, secureCollection, SquidService, MutationContext } from '@squidcloud/backend';

// Anyone can read the 'articles' collection, but only authenticated users can write to it.
@publicCollection('articles', 'read')
export class ArticleService extends SquidService {
@secureCollection('articles', 'write')
secureArticlesWrite(context: MutationContext): boolean {
return this.isAuthenticated();
}
}

この decorator は、collection name、action type(read のみがサポートされています)、および collection を保持する connector の任意の ID を受け取ります。connector ID が省略された場合、デフォルトで組み込み database が使用されます。

Backend code
// Public read on a collection in a specific connector.
@publicCollection('articles', 'read', 'YOUR_CONNECTOR_ID')
export class ArticleService extends SquidService {}

競合がある場合は secure rule が優先されます。同じ collection に @secureCollection または @secureDatabaseread(または all)rule も適用される場合、その rule が runtime で優先され、public 宣言は無視されます。

native query の保護

native query を保護するには、database の connector ID を渡して @secureNativeQuery() decorator を使用します。次の例では、ユーザーが認証されているかどうかを確認する関数を定義しています。

Backend code
import { secureNativeQuery, SquidService } from '@squidcloud/backend';

export class ExampleService extends SquidService {
@secureNativeQuery('YOUR_CONNECTOR_ID')
verifyUserAuthenticated(): boolean {
return this.isAuthenticated();
}
}

この例に示すように、auth token attributes を使用して、より細かい access control を追加できます。

Backend code
import { secureNativeQuery, SquidService } from '@squidcloud/backend';

export class ExampleService extends SquidService {
@secureNativeQuery('YOUR_CONNECTOR_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 permissions を保存するために collection を使用することもできます。collection が Security Service function で保護されていることを確認してください。

Backend code
import { secureNativeQuery, SquidService } from '@squidcloud/backend';

export class ExampleService extends SquidService {
@secureNativeQuery('YOUR_CONNECTOR_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;
}
}

Native query context

native relational query または MongoDB query を実行するとき、Squid backend では、client が実行したい native query の種類('relational' または 'mongo')と、query の種類に基づくその他の attributes を示す context を利用できます。

native relational query を実行するときは、次の context type が提供されます。

Backend code
RelationalNativeQueryContext  {
type: 'relational';
query: string;
params: Record<string, any>;
}

この context を使用して、client が実行できる native query の種類を制限します。たとえば、次の code では、client が SQUIDS という collection 内の documents(または table 内の rows)のうち、YEAR field の値が 1980 より大きいものを選択する 1 種類の native query だけを実行できるようにします。client が別の query を実行しようとしたり、1980 より前の値について table に query しようとしたりすると、query は失敗します。

Backend code
import { secureNativeQuery, SquidService, RelationalNativeQueryContext } from '@squidcloud/backend';

export class ExampleService extends SquidService {
@secureNativeQuery('YOUR_CONNECTOR_ID')
verifyNativeQueryAccess(context: RelationalNativeQueryContext): boolean {
if (context.query !== 'SELECT * FROM SQUIDS WHERE YEAR = ${year}' || context.params.year < 1980) {
return false;
}
return true;
}
}

native MongoDB query を実行するときは、次の context type が提供されます。

Backend code
MongoNativeQueryContext {
type: 'mongo';
collectionName: string;
aggregationPipeline: Array<any | undefined>;
}

この context を使用して、client が Mongo aggregation pipeline に対して実行できる aggregation pipeline queries の種類を制限します。次の例では、ユーザーが ORDERS collection でのみ MongoDB aggregation pipeline を実行できるようにしています。

Backend code
import { secureNativeQuery, SquidService, MongoNativeQueryContext } from '@squidcloud/backend';

export class ExampleService extends SquidService {
@secureNativeQuery('YOUR_CONNECTOR_ID')
verifyNativeMongoQueryAccess(context: MongoNativeQueryContext): boolean {
if (context.collectionName !== 'ORDERS') {
return false;
}
return true;
}
}