Skip to content
Merged
2 changes: 1 addition & 1 deletion packages/firebase/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@

## Module Augmentations

26. **`FastifyInstance.verifySession`** — declares `verifySession` (from `supertokens-node`) on the Fastify instance interface.
26. **`FastifyInstance.verifySession`** — declares a host-provided `verifySession` preHandler factory on the Fastify instance (no SuperTokens dependency); the host auth plugin (typically `@prefabs.tech/fastify-user`) must decorate it and populate `request.user`.

27. **`FastifyRequest.user`** — declares an optional `user: User` property on all Fastify requests.

Expand Down
37 changes: 16 additions & 21 deletions packages/firebase/GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ pnpm add @prefabs.tech/fastify-config \
@prefabs.tech/fastify-graphql \
@prefabs.tech/fastify-slonik \
mercurius \
slonik \
supertokens-node
slonik
```

### For monorepo development (pnpm install / test / build)
Expand Down Expand Up @@ -84,16 +83,14 @@ What we add on top:
- `firebaseAdmin` — re-export of the `firebase-admin` default export, so consumers can reach the initialized app without declaring the dependency themselves.
- `verifyFirebaseAppCheck` — a Fastify hook over `getAppCheck().verifyToken` (from `firebase-admin/app-check`) that adds per-route opt-in and a uniform `403` response shape.

### `supertokens-node` — Partial Passthrough
### Auth prerequisite

**Their docs:** https://www.npmjs.com/package/supertokens-node

We use `verifySession` from `supertokens-node/recipe/session/framework/fastify` as a preHandler on every route. We do not wrap or re-export the supertokens initialization; you must configure SuperTokens in your application before registering this plugin.
All REST routes in this package call `fastify.verifySession()` as a preHandler. This package does not depend on SuperTokens directly; the host application must register an auth plugin that decorates `verifySession` and populates `request.user` before this plugin is registered. In this monorepo, that is typically `@prefabs.tech/fastify-user`.

What we add on top:

- `FastifyInstance.verifySession` module augmentation so the decorator is typed everywhere.
- `FastifyRequest.user` module augmentation (`{ id: string }`) populated by your application's session middleware.
- `FastifyInstance.verifySession` module augmentation so the host-provided decorator is typed (shape only — this package does not implement it).
- `FastifyRequest.user` module augmentation (`{ id: string }`) populated by your application's auth middleware.

### `fastify-plugin` — Full Passthrough

Expand Down Expand Up @@ -219,10 +216,9 @@ config.firebase.notification = {
Replace any default route handler with your own implementation:

```typescript
import type { FastifyReply } from "fastify";
import type { SessionRequest } from "supertokens-node/framework/fastify";
import type { FastifyReply, FastifyRequest } from "fastify";

const myAddHandler = async (request: SessionRequest, reply: FastifyReply) => {
const myAddHandler = async (request: FastifyRequest, reply: FastifyReply) => {
// custom logic
reply.send({ ok: true });
};
Expand Down Expand Up @@ -294,7 +290,7 @@ Requires a valid SuperTokens session. Associates the authenticated user's ID wit

```typescript
// POST /user-device
// Headers: Cookie: sAccessToken=...
// Headers: authenticated session (via host auth plugin)
// Body:
{ "deviceToken": "fcm-token-abc123" }

Expand All @@ -315,7 +311,7 @@ Requires authentication. Validates that the device token belongs to the requesti

```typescript
// DELETE /user-device
// Headers: Cookie: sAccessToken=...
// Headers: authenticated session (via host auth plugin)
// Body:
{ "deviceToken": "fcm-token-abc123" }

Expand All @@ -332,12 +328,12 @@ Only registered when `config.firebase.notification.test.enabled = true`. Sends a

```typescript
// POST /send-notification (or your configured test path)
// Headers: Cookie: sAccessToken=...
// Headers: authenticated session (via host auth plugin)
// Body:
{
"userId": "target-user-uuid",
"title": "Hello",
"message": "World",
"body": "World",
}

// 200: { "message": "Notification sent successfully" }
Expand Down Expand Up @@ -504,7 +500,7 @@ The package extends four interfaces automatically on import. No action needed
```typescript
import "@prefabs.tech/fastify-firebase"; // augmentations applied on import

// fastify.verifySession is now typed
// fastify.verifySession is now typed (host auth plugin must decorate it)
// request.user is now typed as User | undefined
// MercuriusContext.user is now typed as User
// ApiConfig.firebase is now typed with all config options
Expand Down Expand Up @@ -652,18 +648,17 @@ async function notifyUser(
```typescript
// Disable device routes; use only GraphQL for device management.
// Override the notification handler with custom logic.
import type { FastifyReply } from "fastify";
import type { SessionRequest } from "supertokens-node/framework/fastify";
import type { FastifyReply, FastifyRequest } from "fastify";

const customSendNotification = async (
request: SessionRequest,
request: FastifyRequest,
reply: FastifyReply,
) => {
// custom auditing, rate limiting, etc.
const { userId, title, message } = request.body as {
const { body, userId, title } = request.body as {
body: string;
userId: string;
title: string;
message: string;
};
// ... custom logic ...
reply.send({ success: true, message: "sent" });
Expand Down
9 changes: 4 additions & 5 deletions packages/firebase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,19 @@ Peer dependencies (install compatible versions — see [package.json](./package.
- [`fastify-plugin`](https://www.npmjs.com/package/fastify-plugin)
- [`mercurius`](https://www.npmjs.com/package/mercurius)
- [`slonik`](https://www.npmjs.com/package/slonik)
- [`supertokens-node`](https://www.npmjs.com/package/supertokens-node)

## Installation

Install with npm:

```bash
npm install @prefabs.tech/fastify-config @prefabs.tech/fastify-error-handler @prefabs.tech/fastify-graphql @prefabs.tech/fastify-slonik @prefabs.tech/fastify-firebase fastify fastify-plugin mercurius slonik supertokens-node
npm install @prefabs.tech/fastify-config @prefabs.tech/fastify-error-handler @prefabs.tech/fastify-graphql @prefabs.tech/fastify-slonik @prefabs.tech/fastify-firebase fastify fastify-plugin mercurius slonik
```

Install with pnpm:

```bash
pnpm add --filter "@scope/project" @prefabs.tech/fastify-config @prefabs.tech/fastify-error-handler @prefabs.tech/fastify-graphql @prefabs.tech/fastify-slonik @prefabs.tech/fastify-firebase fastify fastify-plugin mercurius slonik supertokens-node
pnpm add --filter "@scope/project" @prefabs.tech/fastify-config @prefabs.tech/fastify-error-handler @prefabs.tech/fastify-graphql @prefabs.tech/fastify-slonik @prefabs.tech/fastify-firebase fastify fastify-plugin mercurius slonik
```

## Usage
Expand Down Expand Up @@ -105,10 +104,10 @@ const config: ApiConfig = {
};
handlers: {
userDevice?: {
addUserDevice: (request: SessionRequest, reply: FastifyReply) => Promise<void>
addUserDevice: (request: FastifyRequest, reply: FastifyReply) => Promise<void>
},
notification: {
sendNotification: (request: SessionRequest, reply: FastifyReply) => Promise<void>
sendNotification: (request: FastifyRequest, reply: FastifyReply) => Promise<void>
},
};
}
Expand Down
4 changes: 1 addition & 3 deletions packages/firebase/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
"pg-mem": "3.0.14",
"prettier": "3.9.5",
"slonik": "46.8.0",
"supertokens-node": "14.1.4",
"typescript": "5.9.3",
"vite": "8.1.5",
"vitest": "4.1.10"
Expand All @@ -64,8 +63,7 @@
"fastify": ">=5.10.0",
"fastify-plugin": ">=5.1.0",
"mercurius": ">=16.10.0",
"slonik": ">=46.8.0",
"supertokens-node": ">=14.1.4"
"slonik": ">=46.8.0"
},
"engines": {
"node": ">=20"
Expand Down
2 changes: 1 addition & 1 deletion packages/firebase/src/__test__/controllers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe("notification controller — custom handler overrides", async () => {

await fastify.inject({
method: "POST",
payload: { message: "Hello", title: "Test", userId: "user-1" },
payload: { body: "Hello", title: "Test", userId: "user-1" },
url: ROUTE_SEND_NOTIFICATION,
});

Expand Down
3 changes: 1 addition & 2 deletions packages/firebase/src/__test__/handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ describe("firebase route handlers", () => {
const response = await fastify.inject({
headers: { "x-user-id": "sender-1" },
method: "POST",
payload: { message: "Body", title: "Title", userId: "receiver-1" },
payload: { body: "Body", title: "Title", userId: "receiver-1" },
url: ROUTE_SEND_NOTIFICATION,
});

Expand Down Expand Up @@ -257,7 +257,6 @@ describe("firebase route handlers", () => {
payload: {
body: "Body",
data: { orderId: "42" },
message: "Body",
title: "Title",
userId: "receiver-1",
},
Expand Down
6 changes: 4 additions & 2 deletions packages/firebase/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { verifySession } from "supertokens-node/recipe/session/framework/fastify";
import type { FastifyReply } from "fastify";

import type { User } from "./types";

Expand All @@ -7,7 +7,9 @@ import deviceHandlers from "./model/userDevice/handlers";

declare module "fastify" {
interface FastifyInstance {
verifySession: typeof verifySession;
verifySession: (options?: {
sessionRequired?: boolean;
}) => (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
}

interface FastifyRequest {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import type { FastifyReply } from "fastify";
import type { FastifyReply, FastifyRequest } from "fastify";
import type { MulticastMessage } from "firebase-admin/lib/messaging/messaging-api";
import type { SessionRequest } from "supertokens-node/framework/fastify";

import type { TestNotificationInput } from "../../../types";

import { sendPushNotification } from "../../../lib";
import DeviceService from "../../userDevice/service";

const testPushNotification = async (
request: SessionRequest,
request: FastifyRequest,
reply: FastifyReply,
) => {
const user = request.user;
Expand Down
4 changes: 2 additions & 2 deletions packages/firebase/src/model/notification/schema.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export const sendNotificationSchema = {
body: {
properties: {
message: { type: "string" },
body: { type: "string" },
title: { type: "string" },
userId: { type: "string" },
},
required: ["title", "message", "userId"],
required: ["title", "body", "userId"],
type: "object",
},
description: "Send a notification to a specific user",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { FastifyReply } from "fastify";
import type { SessionRequest } from "supertokens-node/framework/fastify";
import type { FastifyReply, FastifyRequest } from "fastify";

import type { UserDeviceCreateInput } from "../../../types";

import Service from "../service";

const addUserDevice = async (request: SessionRequest, reply: FastifyReply) => {
const addUserDevice = async (request: FastifyRequest, reply: FastifyReply) => {
const { body, config, dbSchema, slonik, user } = request;

if (!user) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { FastifyReply } from "fastify";
import type { SessionRequest } from "supertokens-node/framework/fastify";
import type { FastifyReply, FastifyRequest } from "fastify";

import Service from "../service";

const removeUserDevice = async (
request: SessionRequest,
request: FastifyRequest,
reply: FastifyReply,
) => {
const user = request.user;
Expand Down
1 change: 0 additions & 1 deletion packages/firebase/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export default defineConfig(({ mode }) => {
// String externals match exact ids only; subpath imports
// (firebase-admin/app-check) would otherwise be bundled.
/^firebase-admin\//,
/^supertokens-node\//,
],
output: {
exports: "named",
Expand Down
3 changes: 0 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.