まえがき
GAEでセッション管理にFirestoreを使うという内容のガイドがGoogleから公開されています。
Firestore でのセッション処理 | Node.js | Google Cloud
非常にわかりやすいのですが気になる事が一つ
Connect-firestore は、古いセッションや期限切れのセッションを削除しません。Google Cloud Console でセッション データを削除するか、自動削除戦略を実装できます。セッションに、Memcache や Redis などのストレージ ソリューションを使用すると、期限切れのセッションが自動的に削除されます。
流石にコンソールから手作業で削除するのは面倒くさいので、古いセッションを自動で削除するようにしたい所。
要するにCloud MemorystoreやCloud SQLを使ってほしいということらしい。
しかしConnect-firestoreはデータをJSON文字列として保存する仕様のため、古いセッションの削除を行うにもクエリが使えない。なら保存時に作成時刻のフィールドを書き込むようにしてしまおうというのがこの記事の内容。
そもそもなんで作成時刻がないのか
実はissueが作成されたが特に何かあったわけでもなく終了している。
Session data cleanup · Issue #63 · googleapis/nodejs-firestore-session
曰く
「セッションデータの中に有効期限が入っているので別途作成時刻を含める必要はない。Firestore自体に自動削除機能は無いから必要性が薄い。」とのこと
やること
FirestoreStoreを継承したクラスを作り、setメソッドをオーバーライドする。
まずFirestoreStoreを継承してsetメソッドをオーバーライド
Firestoreに書き込む際に作成時刻も含めるようにして、それをexportすればおk。
import { FirestoreStore, StoreOptions } from "@google-cloud/connect-firestore";
export class FirestoreStoreMod extends FirestoreStore {
constructor(storeOption: StoreOptions) {
super(storeOption);
}
set = async (
sid: string,
session: unknown,
callback?: ((err?: Error | undefined) => void) | undefined
): Promise<void> => {
let sessJson;
try {
sessJson = JSON.stringify(session);
} catch (err) {
if (typeof callback === "function") {
return callback();
}
}
const username: string | null =
(session as any)?.passport?.user?.username || null;
const createdDate = new Date(); // 本当はサーバータイムスタンプの方が良い。
await this.db
.collection(this.kind)
.doc(sid)
.set({
data: sessJson,
createdDate,
username
})
.then(() => {
if (typeof callback === "function") {
callback();
}
});
};
}
importして使う。
あとはガイドと同じようにsessionモジュールを読み込む際にimportして呼び出せばok。
import express from "express";
import session from "express-session";
import { Firestore } from "@google-cloud/firestore";
import { FirestoreStoreMod } from "./components/sessionStore";
const app = express();
// jsonの取り扱い用
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const store = new FirestoreStoreMod({
dataset: new Firestore({
projectId: "ebikaniuni",
keyFilename:"opabinia.json",
}),
kind: "sessions",
});
// セッション
app.use(
session({
secret: "same", // dotenv等で別ファイル化+gitignoreに含める
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1 * 60 * 60 * 100, // ミリ秒で指定
sameSite: "strict", // ドメインが違うなら送信しない。
httpOnly: true, //httpリクエスト以外で送信されない
secure: false, // 本番時は有効にする
},
store,// 上で宣言したstore
})
);
app.listen(3000, () => {
console.log("server is started.");
});
あとはcronなどで定期的に古いセッションを一括で削除する処理を追加すれば良いはず。