Firebase Databaseでパスにuidを含めたユーザ権限周りをFirestoreでやるには


あきらめましょう

通常のKVSのようにドキュメントにオーナーのユーザIDを含めてセキュリティルールでオーナー以外を弾く設定をしましょう

おわり

.

.

.

.

.

.

っとしても良いのだけどどうせならexampleでも

まずベースとなるモデルを定義します.

このモデルをベースにモデルを作ります.

// BaseModel.ts

export default abstract class BaseModel {
    // ドキュメントオーナーのID
    readonly userID: string;

    // ドキュメントのID
    id: string | null;

    // ドキュメントの作成・更新日時
    timestamp: Date;

    constructor(userID: string, timestamp: Date) {
        this.userID = userID;
        this.timestamp = timestamp;
    }

    // Firestoreに保存するとき用
    abstract toJSON(): Object;
}

toJSONはデータベースに保存する際に特定のプロパティのみを保存するためにあります.

これを定義すればidを保存対象から無視できます.

このBaseModelを継承したToDoModelを実装します.

// ToDoModel.ts

import BaseModel from './BaseModel';

export default class ToDoModel extends BaseModel {
    // タイトル
    title: string;
    
    // ToDoが完了したかどうか
    completed: boolean = false;

    constructor(userID: string, title: string, timestamp: Date) {
        super(userID, timestamp);

        this.title = title;
    }

    toJSON(): Object {
        // NOTICE: idはfirestore上で自動で割り振られるのでわざわざデータとして格納しなくてよい
        return {
            userID: this.userID,
            title: this.title,
            completed: this.completed,
            timestamp: this.timestamp,
        };
    }
}

ここまで実装して重要なのはuserIDを定義しておくことです.

このuserIDセキュリティルールでドキュメントの所有者かどうかチェックを行うのに必要になります.

さて次にセキュリティルールを定義してあげます.

RTDBの場合リファレンスパスは/todos/$uid/<todoDoc.id>なので

// database.rules.json

{
    "rules": {
        "todos": {
            "$uid": {
                ".read": "$uid === auth.uid",
                ".write": "$uid === auth.uid"
            }
        }
    }
}

でドキュメントの所有者のみが閲覧・更新が可能になります.

つづいて,

Firestoreの場合はコレクションがtodosとなり,

// firestore.rules

service cloud.firestore {
    match /databases/{database}/documents {
        match /todos/{todoID} {
            allow read, write: if get(/databases/$(database)/documents/todos/$(todoID)).userID == request.auth.uid;
        }
    }
}

でRTDBどうよう所有者のみが閲覧・更新が可能になります.

走らせる擬似コードも載せておきます. ( ブラウザで走らせるためにはやらなくてはいけないことがそれなりにあるが省略)

// index.ts

import * as firebase from "firebase";
import 'firebase/firestore';

import ToDoModel from './ToDoModel';

const frConfig = {
    apiKey: "<API_KEY>",
    authDomain: "<PROJECT_ID>.firebaseapp.com",
    databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
    storageBucket: "<BUCKET>.appspot.com",
};

const firebaseApp = firebase.initializeApp(frConfig);

let rtdb: firebase.database.Database;
let frStore: firebase.firestore.Firestore;
let uid: string;

async function setup():Promise<void> {
    try {
        const user = await firebaseApp.auth().signInAnonymously();
        uid = user.uid;
        rtdb = firebaseApp.database();
        frStore = firebase.firestore(firebaseApp);
        return Promise.resolve();
    } catch (e) {
        return Promise.reject(e);
    }
}

async function rtdbCreate(): Promise<ToDoModel> {
    const key = rtdb.ref(`todos/${uid}`).push().key;

    const todoDoc = new ToDoModel(uid, '🍣をたくさんたべる (๑•̀ㅂ•́)و✧', new Date());
    todoDoc.id = key;

    const updates: { [key: string]: any } = {};
    updates[`todos/${uid}/${key}`] = todoDoc.toJSON();

    try {
        await rtdb.ref().update(updates);
    } catch (e) {
        return Promise.reject(e);
    }
    
    return Promise.resolve(todoDoc);
}

async function firestoreCreate(): Promise<ToDoModel> {
    const todoDoc = new ToDoModel(uid, '🍣をたくさんたべる (๑•̀ㅂ•́)و✧', new Date());

    try {
        const todoDocRef = await frStore.collection('todos').add(todoDoc.toJSON());
        todoDoc.id = todoDocRef.id;
    } catch (e) {
        return Promise.reject(e);
    }

    return Promise.resolve(todoDoc);
}

async function main() {
    await setup();
    
    console.log('uid: ', uid);

    const result1 = await rtdbCreate();
    console.log(result1);

    const result2 = await firestoreCreate();
    console.log(result2);
}

main();

Firestoreのセキュリティルールに関数を定義できるの便利だけど, これテストしたいときどうするんですかね…

製品版に期待してFirestoreでどんどん実装していきたいと思います(๑•̀ㅂ•́)و✧