## @flipdish/authorization

This package provides a Typescript/JavaScript client for interacting with Flipdish's Authorization APIs over HTTP.

Internally the package utilizes the [axios](https://github.com/axios/axios) as its HTTP client. 

### Example code

```typescript
import {
  AuthorizationApi,
  Configuration,
  ConfigurationDataApi,
  type ErrorResponse,
  Permissions,
  UserPermissionsApi,
} from "@flipdish/authorization";
import { describe, expect, it, test } from "@jest/globals";
import axios, { isAxiosError } from "axios";

const basePath = "https://api.flipdish.co/auth/";
const bearerConfiguration = new Configuration({
  basePath,
  // to get the API key, you should follow these docs:
  // https://developers.flipdish.com/docs/getting-started
  accessToken: process.env.FLIPDISH_BEARER_TOKEN_PROD,
  // if using in a browser set useDefaultUserAgent
  // to true to prevent errors
  // useDefaultUserAgent: true
});

const authorization = new AuthorizationApi(bearerConfiguration);
const configurationData = new ConfigurationDataApi(bearerConfiguration);

// mimic brower config where cookies will be sent automatically
// you shouldn't need to pass an axios instance as the cookies will be
// sent automatically by the browser
const userPermissions = new UserPermissionsApi(
  new Configuration({ basePath }),
  undefined,
  axios.create({
    headers: {
      Cookie: `FD-Authorization=${process.env.FD_AUTH_COOKIE_PROD_JM_CLIENT};`,
    },
  }),
);

describe("Authorization Tests", () => {
  describe("Authorization", () => {
    test("List Permissions", async () => {
      const permissionsResponse = await configurationData.listPermissions();
      expect(permissionsResponse.status).toBe(200);
      expect(permissionsResponse.data.permissions).toBeDefined();
      expect(permissionsResponse.data.permissions.length).toBeGreaterThan(0);
      expect(permissionsResponse.data.permissions).toContain(
        Permissions.ViewApp,
      );
      expect(permissionsResponse.data.permissions).toContain(
        Permissions.CreateApp,
      );
      expect(permissionsResponse.data.permissions).toContain(
        Permissions.UpdateApp,
      );
      expect(permissionsResponse.data.permissions).toContain(
        Permissions.ViewAppName,
      );
      expect(permissionsResponse.data.permissions).toContain(
        Permissions.EditAppAssets,
      );
    });

    test("List named roles", async () => {
      const namedRolesResponse = await configurationData.listRoles();
      expect(namedRolesResponse.status).toBe(200);
      expect(namedRolesResponse.data.roles).toBeDefined();
      expect(namedRolesResponse.data.roles.length).toBeGreaterThan(0);
      expect(namedRolesResponse.data.roles).toContainEqual("Admin");
    });

    describe("List User Permission Sets", () => {
      it("should list user permission sets", async () => {
        const userPermissionSetsResponse =
          await userPermissions.listOwnPermissions("org42068");
        expect(userPermissionSetsResponse.status).toBe(200);
        expect(userPermissionSetsResponse.data.resources).toBeDefined();
        expect(userPermissionSetsResponse.data.resources).toHaveProperty(
          "org42068",
        );
        expect(
          userPermissionSetsResponse.data.resources.org42068.permissions.length,
        ).toBeGreaterThan(0);
      });
    });

    describe("Authenticate and Authorize", () => {
      it("should authenticate and authorize with a valid FD-Authorization cookie", async () => {
        const authorizationResponse =
          await authorization.authenticateAndAuthorize({
            headers: {
              Cookie: `FD-Authorization=${process.env.FD_AUTH_COOKIE_PROD};`,
            },
            action: Permissions.AnyAuditLogs,
            resource: {
              id: "org12345",
              type: "Org",
            },
          });

        expect(authorizationResponse.status).toBe(200);
        expect(authorizationResponse.data.authentication.authenticated).toBe(
          true,
        );
        expect(authorizationResponse.data.authentication.principal?.type).toBe(
          "User",
        );
        expect(authorizationResponse.data.authentication.principal?.id).toBe(
          "8147747",
        );
      });

      it("should not authenticate and authorize with an invalid FD-Authorization cookie", async () => {
        try {
          await authorization.authenticateAndAuthorize({
            headers: {
              Cookie: `FD-Authorization=not-a-valid-cookie;`,
            },
            action: Permissions.AnyAuditLogs,
            resource: {
              id: "org12345",
              type: "Org",
            },
          });
        } catch (error) {
          if (isAxiosError<ErrorResponse>(error)) {
            expect(error.response?.status).toBe(401);
            expect(error.response?.data.message).toBe("Unauthenticated");
          }
        }
      });

      it("should authenticate and authorize with a valid Bearer token", async () => {
        const authorizationResponse =
          await authorization.authenticateAndAuthorize({
            headers: {
              Authorization: `Bearer ${process.env.FLIPDISH_BEARER_TOKEN_PROD}`,
            },
            action: Permissions.AnyAuditLogs,
            resource: {
              id: "org12345",
              type: "Org",
            },
          });

        expect(authorizationResponse.status).toBe(200);
        expect(authorizationResponse.data.authentication.authenticated).toBe(
          true,
        );
        expect(authorizationResponse.data.authentication.principal?.type).toBe(
          "User",
        );
        expect(authorizationResponse.data.authentication.principal?.id).toBe(
          "8147747",
        );
      });
    });

    test("Authorize", async () => {
      const authorizationResponse = await authorization.authorize({
        principal: {
          id: "12345",
          type: "User",
        },
        action: Permissions.AnyAuditLogs,
        resource: {
          id: "org12345",
          type: "Org",
        },
      });
      expect(authorizationResponse.status).toBe(200);
      expect(authorizationResponse.data.allowed).toBe(false);
      expect(authorizationResponse.data.decision).toBe("DENY");
    });

    describe("Check is in role", () => {
      it("should check if a user is in a role", async () => {
        const isInRoleResponse = await authorization.checkIsInRole({
          principal: {
            id: "12345",
            type: "User",
          },
          roles: ["Admin"],
        });
        expect(isInRoleResponse.status).toBe(200);
        expect(isInRoleResponse.data.authorized).toBe(false);
      });

      it("should authenticate and check if a user is in a role with a valid FD-Authorization cookie", async () => {
        const isInRoleResponse =
          await authorization.authenticateAndCheckIsInRole({
            headers: {
              Cookie: `FD-Authorization=${process.env.FD_AUTH_COOKIE_PROD};`,
            },
            roles: ["Admin"],
          });
        expect(isInRoleResponse.status).toBe(200);
        expect(isInRoleResponse.data.authorized).toBe(true);
      });

      it("should authenticate and check if a user is in a role with a valid Bearer token", async () => {
        const isInRoleResponse =
          await authorization.authenticateAndCheckIsInRole({
            headers: {
              Authorization: `Bearer ${process.env.FLIPDISH_BEARER_TOKEN_PROD}`,
            },
            roles: ["Admin"],
          });
        expect(isInRoleResponse.status).toBe(200);
        expect(isInRoleResponse.data.authorized).toBe(true);
      });
    });
  });
});

```

The generated Node module can be used in the following environments:

Environment
* Node.js
* Webpack
* Browserify

Language level
* ES5 - you must have a Promises/A+ library installed
* ES6

Module system
* CommonJS
* ES6 module system

It can be used in both TypeScript and JavaScript. In TypeScript, the definition will be automatically resolved via `package.json`. ([Reference](https://www.typescriptlang.org/docs/handbook/declaration-files/consumption.html))
