# Firestore PoLP (Principle of Least Privilege)

This library provides a simple and secure way to interact with Firestore, adhering to the Principle of Least Privilege (PoLP). It allows you to read and write data to Firestore without exposing your Firebase service
token credentials.

## Why?

Firebase service tokens give complete access to your Firebase project and Firestore database. They should be avoided where possible, especially in untrusted environments (like Electron apps or CI/CD
pipelines that may be used by untrusted parties).

These are the official alternatives and the reasons why they might be unsuitable for an untrusted environment in Node.JS/Electron:

- [Firebase Admin SDK](https://www.npmjs.com/package/firebase-admin) - This library requires a service account token which is not suitable for untrusted environments.
- [Firebase Web SDK](https://www.npmjs.com/package/firebase) - This library is advertised as working in Node.js but auth is not persisted correctly which makes it unsuitable.

Unfortunately, the Firebase Node.js client library requires a service token and the firebase Web SDK doesn't work in a Node.js environment.

## Features

- Secure interaction with Firestore
- Read and write operations
- Support for document updates with merge
- Automatic serialization and deserialization of Firestore data types
- Retry mechanism for failed requests

## Installation

```bash
npm install firestore-polp
```

## Usage

First, sign in using a custom token:

```typescript
import { signInWithCustomToken } from 'firestore-polp'
const credentials = await signInWithCustomToken(
  'your-public-api-key',
  'users-custom-token'
)
```

Then, initialize the library with your project details:

```typescript
import { initialize } from 'firestore-polp'
const app = initialize('your-project-id', credentials.idToken, {
  databaseId: 'your-database-id' // Optional, defaults to '(default)'
})
```

Expired id tokens can also be refreshed using the initial `credentials`:

```typescript
import { refreshIdToken } from 'firestore-polp'
const updatedCredentials = await refreshIdToken(
  'your-public-api-key',
  credentials.refreshToken
)

// re-initialize the library
const app = initialize('your-project-id', updatedCredentials.idToken, {
  databaseId: 'your-database-id' // Optional, defaults to '(default)'
})
```

### Reading a document

```typescript
import { getDocument } from 'firestore-polp'
const document = await getDocument(app, ['collection', 'document-id'])
console.log(document)
```

### Updating a Document with Merge

```typescript
import { updateDocumentWithMerge } from 'firestore-polp'
const result = await updateDocumentWithMerge(
  app,
  ['collection', 'document-id'],
  { field1: 'new value', field2: 42 }
)
console.log(result)
```

### Setting a document

```typescript
import { setDocument } from 'firestore-polp'
const result = await setDocument(app, ['collection', 'document-id'], {
  field1: 'value',
  field2: true
})
console.log(result)
```

## API Reference

### `signInWithCustomToken(apiKey: string, customToken: string): Promise<SignInResponse>`

Signs in with a custom token.

### `refreshIdToken(apiKey: string, refreshToken: string ): Promise<RefreshTokenResponse>`

Refreshes an id token using an existing refresh token.

### `initialize(projectId: string, accessToken: string, options?: { databaseId: string }): AppConfig`

Initializes the library with your project details.

### `getDocument<T>(config: AppConfig, documentPath: string[]): Promise<T>`

Retrieves a document from Firestore.

### `updateDocumentWithMerge(config: AppConfig, documentPath: string[], fields: PlainData): Promise<UpdateDocumentResponse>`

Updates a document with a merge operation.

### `setDocument(config: AppConfig, documentPath: string[], fields: PlainData): Promise<UpdateDocumentResponse>`

Sets the contents of a document.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.
