npm package: @fireaudit/functions
Description: FireAudit client functions library
Author: David Hardy
Latest version: 1.1.2

[![pipeline status](https://gitlab.com/codecentric/fireaudit/clients/functions/badges/master/pipeline.svg)](https://gitlab.com/codecentric/fireaudit/clients/functions/-/commits/master)
[![coverage report](https://gitlab.com/codecentric/fireaudit/clients/functions/badges/master/coverage.svg)](https://gitlab.com/codecentric/fireaudit/clients/functions/-/commits/master)
[![npm version](https://badge.fury.io/js/%40fireaudit%2Ffunctions.svg)](https://badge.fury.io/js/%40fireaudit%2Ffunctions)

# FireAudit Functions Client

Add Audit Monitoring to any paid Firebase Plan. Track all changes to every document in time, and find any issue regarding data quality, find relations, and many many more. See https://fireaudit.studio. 

## Installation

```sh
npm install @fireaudit/functions --save
```

or

```sh
yarn add @fireaudit/functions
```

## Basic Usage

Add this code to your `index.ts` file to get started. Be sure to grab your API key with your free account at https://fireaudit.studio.

```typescript
import {FireAudit} from '@fireaudit/functions/client/fireaudit';

exports.fireAudit = new FireAudit().build({
  apiKey: 'EXAMPLE_API_KEY', // Get your key at https://fireaudit.studio
  levels: 2, // How many levels of nesting does your Firestore database have
  domain: 'EU' // Either EU or US
});
```

Done, that's it. Now deploy your functions as regular, and within minutes you should see FireAudit working for your at https://fireaudit.studio.

## Advanced Usage

FireAudit can do more than just track history. It can audit changes in your data to see if there are any anomalies. For this many _sensors_ can be configured, some of this sensors need metadata. 
To get the most value out of FireAudit there is a `fireAudit` property that should be used on all documents. FireAudit uses this property to track **which** users does **what**, **when** and **why**.

### Metadata

- `uid: string`      - Strongly recommended, The UID of the user causing this change, use `FUNCTION` when a function causes a change and no specific user must be tagged.
- `event: string`    - Strongly recommended, The reason why the changed occurs, this typically is the name of the class that caused the change, or the name of the trigger.
- `serverTime: Date` - Strongly recommended, The time of receiving in firestore, use FieldValue.serverTimestamp()
- `userTime: Date`   - Optional, a field to indicate the time on the users device.
- `delete: boolean`  - Optional, a flag to indicate that this document is marked for deletion. Use this when you want to delete a file, but want to keep the UID information. When set to true this document will be deleted after it has been logged by FireAudit.
 
Note that none of these fields is mandatory, but it's highly recommended to use these fields. FireAudit can do advanced processing on the data when these fields are present.
There is a convenience interface - `FireAuditMetadataContainer` - which can be used as an interface for any model type. It defined the `fireAudit` property used by FireAudit. 
See further down for the exact definition of this property.

### Security rules

To enforce that all changes to the database have the correct metadata, this check can be used in the security rules.  

```javascript
function assertFireAudit() {
    return request.resource.data.fireAudit.uid == request.auth.uid
        && request.resource.data.fireAudit.serverTime == request.time
        && request.resource.data.fireAudit.event != null
        && request.resource.data.fireAudit.userTime != null
}
```
 
### Multi Firebase

If you want to use FireAudit in an environment where you have custom settings for `firebase-admin`, or have multiple instances of `firestore`, you can use the `FireAudit` constructor to configure such. 
Below snippet is an example of a custom instance of `admin`.
 
```typescript
admin.initializeApp();
new FireAudit(admin)...
```
 
### Functions

FireAudit exposes a convenience method to set the `fireAudit` property. Any interface can extend `FireAuditMetadataContainer`, and when writing to FireStore the `fireAudit` convenience method can be used: 

```typescript
export interface MyType extends FireAuditMetadataContainer {
  someKey: string,
  complex: {
      anotherKey: string
  },
}

async saveMyType(data: MyType) {
  await firestore.doc('my/path').set({
    ...data,
    fireAudit: FireAudit.fireAudit('ExampleEvent')
  });
}
```

### Angular

Angular clients can draw inspiration from the examples below.

```typescript
const data = {
    someKey: 'This could be any data',
    complex: {
        anotherKey: 'Any format or structure needed by the application'
    },
    fireAudit: fireAuditService.fireAudit('ExampleEvent')
}

// FireAuditService
import {Injectable} from '@angular/core';
import {AngularFireAuth} from '@angular/fire/auth';
import * as firebase from 'firebase/app';

@Injectable({providedIn: 'root'})
export class FireAuditService {

  constructor(private fireAuth: AngularFireAuth) {}

  fireAudit(event: string) {
    return {
      uid: this.fireAuth.auth.currentUser ? this.fireAuth.auth.currentUser.uid : null,
      event: event || 'Unknown',
      serverTime: firebase.firestore.FieldValue.serverTimestamp(),
      userTime: new Date()
    };
  }
}
```

## Heuristics

FireAudit needs to make certain assumptions about the data to make sense of the data, without understanding the data. The following assumptions are made:

- There is a collection containing users documents: `users/${uid}`. This document contains a field named `displayName` or `name`.
- Any field that ends on `Id` or `Ids` (basically matches regex `.+Ids?$`) is considered to be a possible relation identifier.

## Export

To make your audit history start at a sensible place, when you already have data in FireStore before connecting FireAudit, you can export the current state of FireStore and import it into FireAudit. To make an export go to [Pub/Sub](https://console.cloud.google.com/cloudpubsub) in GCP, select your project and the topic `FIRE_AUDIT_EXPORT`. Now Publish a message, their must be message body, it can be anything, it isn't used by FireAudit. Also, some additional attributes can be set, all optional:
- `firestorePath`   - The root of the export. Can be any collection, default to the root of FireStore. 
- `bucketName`      - The name of the storage bucket, defaults to the firebase default bucket.
- `storagePath`     - Folder to store the export file within the bucket, defaults to `fireAudit`.
- `timestamp`       - Timestamp, default to now.   

## Options

The FireAudit functions have some advanced features, like hashing sensitive data, or in the near future encryption.

```typescript
export interface FireAuditOptions {
    /**
     * Mandatory: How many levels of collection/document nesting does your deepest firestore path have.
     */
    levels: number;
    
    /**
     * Mandatory: The domain of FireAudit, either EU or US, or possible a custom domain for enterprise usage.
     */
    domain: 'EU' | 'US' | string;

    /**
     * Optional: The highest collection that FireAudit must monitor. When omitted FireAudit will monitor everything.
     */
    root?: string;

    /**
     * Optional: The region the FireAudit function is deployed to. Use multiple configurations when you must deploy into multiple regions.
     * Defaults to europe-west1.
     */
    region?: 'us-central1' | 'us-east1' | 'us-east4' | 'europe-west1' | 'europe-west2' | 'asia-east2' | 'asia-northeast1';

    /**
     * Mandatory: The API key generated by FireAudit for your project, see https://console.fireaudit.studio for more.
     */
    apiKey: string;

    /**
     * Optional: A transformer function to transform any data before it is processed by FireAudit. Possible use-cases are
     * data anonymization, or exclusion of certain paths. When omitted all data is processed.
     * @param data: The data which could be anonymized, or pre processed,
     * @param path: The path of the data.
     * @return Processed data, or null when FireAudit should ignore this change completely.
     */
    transformer?: (data: any, path: string, util: FireAuditUtil) => FireAuditMetadataContainer;

    /**
     * Optional: The following settings are only mandatory when using client-side hashing.
     */
    hashing?: {
        /**
         * Mandatory: Salt for hashing strings.
         */
        sha512Salt: string;
    };

    /**
     * Optional: The following settings are only mandatory when using client-side symmetric encryption.
     */
    encryption?: {
        /**
         * Mandatory: Key for encryption. Please use a lengthy, random string, instead of a short word.
         */
        encryptionPassword: string;

        /**
         * Optional: Keys for decryption. Can be used to update the encryption password, but still allow decrypting old data.
         * By default the encryptionPassword is added to the list of decryption passwords.
         */
        decryptionPasswords?: string[];
    };
    
    /**
     * Optional: Disables deleting documents when fireAudit.delete is true.
     */
    disableDelete?: boolean;
}
```

## Metadata

```typescript
/**
 * Convenience interface which could be used as a interface for any model type.
 */
export interface FireAuditMetadataContainer {
    fireAudit?: FireAuditMetadata;
}

/**
 * FireAudit can do more than just track history. It can audit changes in your data to see if there are any anomalies. 
 * For this many _sensors_ can be configured, some of this sensors need metadata.
 * To get the most value out of FireAudit there is a `fireAudit` property that should be used on all documents. 
 * FireAudit uses this property to track **which** users does **what**, **when** and **why**.
 */
export interface FireAuditMetadata {
    /**
     * Strongly recommended, The UID of the user causing this change, use `FUNCTION` when a function causes a change and no specific user must be tagged.
     */
    uid?: string;

    /**
     * Strongly recommended, The reason why the changed occurs, this typically is the name of the class that caused the change, or the name of the trigger.
     */
    event?: string;

    /**
     * Strongly recommended, The time of receiving in firestore, use FieldValue.serverTimestamp().
     */
    serverTime?: Date;
    
    /**
     * Optional, a field to indicate the time on the users device.
     */
    userTime?: Date;

    /**
     * Optional, a flag to indicate that this document is marked for deletion. Use this when you want to delete a file, but want to keep the UID information. When set to true this document will be deleted after it has been logged by FireAudit.
     */
    delete?: boolean;

    /**
     * Optional, user defined properties.
     */
    [prop: string]: any;

    /**
     * Internally used by FireAudit, will be set automatically, can be ignored.
     */
    path?: string;
}
```

## License

```text
Copyright 2020 codecentric nl / David Hardy

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```