Role Serialization and Deserialization

Learn how to serialize and deserialize Role instances for data storage, transmission, and interoperability.

The Role class in iamjs provides powerful methods for converting role instances to various formats and recreating them. These methods enhance interoperability and flexibility in managing role data.

Core Serialization Methods

toObject(): GetRoleConfig<T>

Converts the Role instance to a plain JavaScript object.

const role = new Role({
  name: 'editor',
  description: 'Can edit and publish content',
  config: {
    article: { base: 'crudl', custom: { publish: true } },
    user: { base: '-r---', custom: { viewProfile: true } }
  }
});

const roleObject = role.toObject();
console.log(roleObject);
// Output:
// {
//   name: 'editor',
//   description: 'Can edit and publish content',
//   config: {
//     article: { base: 'crudl', custom: { publish: true } },
//     user: { base: '-r---', custom: { viewProfile: true } }
//   }
// }

toJSON(): string

Serializes the Role instance to a JSON string.

const roleJSON = role.toJSON();
console.log(roleJSON);
// Output: '{"name":"editor","description":"Can edit and publish content","config":{"article":{"base":"crudl","custom":{"publish":true}},"user":{"base":"r---","custom":{"viewProfile":true}}}}'

Core Deserialization Methods

static fromObject(obj: GetRoleConfig<T>): Role<T>

Creates a new Role instance from a plain object.

const newRole = Role.fromObject(roleObject);
console.log(newRole.can('article', 'publish')); // Output: true

static fromJSON(json: string): Role<T>

Creates a new Role instance from a JSON string.

const recreatedRole = Role.fromJSON(roleJSON);
console.log(recreatedRole.can('user', 'update')); // Output: false

Advanced Usage: Custom Transformations

Both toObject and toJSON methods accept an optional transform function for custom processing:

import { encrypt, decrypt } from './cryptoUtils';

// Encrypting role data
const encryptedRoleObject = role.toObject(obj => encrypt(JSON.stringify(obj)));

// Decrypting and creating a role
const decryptedRole = Role.fromObject(encryptedRoleObject, obj => JSON.parse(decrypt(obj)));

Flexible Role Creation: from Method

The from method allows creating a Role instance from any data source:

const externalRoleData = fetchRoleDataFromExternalAPI();

const transformedRole = Role.from(externalRoleData, (data) => {
  // Transform external data to Role config format which satisfies the GetRoleConfig<T> type
  return {
    name: data.roleName,
    description: data.roleDescription,
    config: data.permissions.reduce((acc, perm) => {
      acc[perm.resource] = { base: perm.baseActions, custom: perm.customActions };
      return acc;
    }, {})
  } as GetRoleConfig<typeof role>;
});

This method is particularly useful for integrating with external systems or handling complex data transformations.

Best Practices

  1. Data Integrity: Always validate the structure of deserialized data before creating Role instances.
  2. Security: When dealing with sensitive role data, use encryption in conjunction with these methods.
  3. Version Control: Consider including a version field in your serialized data to handle future schema changes.
  4. Error Handling: Implement robust error handling when deserializing data from external sources.

By leveraging these serialization and deserialization methods, you can efficiently store, transmit, and recreate role instances, enabling seamless integration with various data storage systems and external APIs.

Detailed Examples of Role Serialization and Deserialization

import { GetRoleConfig, Role, TRoleOptions } from '@iamjs/core';
import crypto from 'crypto';

// Create a complex role for our examples
const complexRole = new Role({
  name: 'senior_editor',
  description: 'Senior editor with advanced permissions',
  meta: {
    department: 'content',
    level: 'senior',
  },
  config: {
    article: {
      base: 'crudl',
      custom: {
        publish: true,
        featureOnHomepage: true,
        addComment: true,
      },
    },
    user: {
      base: '-r---',
      custom: {
        viewProfile: true,
        sendMessage: true,
      },
    },
    analytics: {
      base: '-r--l',
      custom: {
        exportData: true,
      },
    },
    review: {
      base: 'crud-',
      custom: {
        submitForApproval: true,
      },
    },
  },
});

// 1. toObject() Example
console.log('1. toObject() Example:');
const roleObject = complexRole.toObject();
console.log(JSON.stringify(roleObject, null, 2));

// 2. toJSON() Example
console.log('2. toJSON() Example:');
const roleJSON = complexRole.toJSON();
console.log(roleJSON);

// 3. fromObject() Example
console.log('3. fromObject() Example:');
const recreatedFromObject = Role.fromObject(roleObject);
console.log(
  'Can publish article:',
  recreatedFromObject.can('article', 'publish'),
);
console.log('Can update user:', recreatedFromObject.can('user', 'update'));

// 4. fromJSON() Example
console.log('4. fromJSON() Example:');
const recreatedFromJSON = Role.fromJSON<typeof complexRole>(roleJSON);
console.log(
  'Can export analytics data:',
  recreatedFromJSON.can('analytics', 'exportData'),
);
console.log(
  'Can delete analytics:',
  recreatedFromJSON.can('analytics', 'delete'),
);

// 5. Custom Transformation with toObject()
console.log('5. Custom Transformation with toObject():');
const customTransform = (obj: any) => ({
  ...obj,
  meta: {
    ...obj.meta,
    lastModified: new Date().toISOString(),
  },
});
const transformedObject = complexRole.toObject(customTransform);
console.log(JSON.stringify(transformedObject, null, 2));

// 6. Encryption with toJSON()
console.log('6. Encryption with toJSON():');
const encryptData = (data: string) => {
  const algorithm = 'aes-256-cbc';
  const key = crypto.randomBytes(32);
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(algorithm, key, iv);
  let encrypted = cipher.update(data, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  return { encrypted, key: key.toString('hex'), iv: iv.toString('hex') };
};

const { encrypted, key, iv } = encryptData(complexRole.toJSON());
console.log('Encrypted:', encrypted);
console.log('Key:', key);
console.log('IV:', iv);

// 7. Decryption and fromJSON()
console.log('7. Decryption and fromJSON():');
const decryptData = (encryptedData: string, key: string, iv: string) => {
  const algorithm = 'aes-256-cbc';
  const decipher = crypto.createDecipheriv(
    algorithm,
    Buffer.from(key, 'hex'),
    Buffer.from(iv, 'hex'),
  );
  let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  return decrypted;
};

const decryptedJSON = decryptData(encrypted, key, iv);
const decryptedRole = Role.fromJSON<typeof complexRole>(decryptedJSON);
console.log(
  'Decrypted role can feature article on homepage:',
  decryptedRole.can('article', 'featureOnHomepage'),
);

// 8. from() Method with External Data
console.log('8. from() Method with External Data:');
const externalRoleData = {
  roleName: 'external_reviewer',
  roleDescription: 'External content reviewer',
  permissions: [
    {
      resource: 'article',
      baseActions: 'r--l',
      customActions: { addComment: true },
    },
    {
      resource: 'review',
      baseActions: 'crud-',
      customActions: { submitForApproval: true },
    },
  ],
};

const transformedRole = Role.from(
  externalRoleData,
  (data) =>
    ({
      name: data.roleName,
      description: data.roleDescription,
      meta: {},
      config: data.permissions.reduce((acc: any, perm: any) => {
        acc[perm.resource] = {
          base: perm.baseActions,
          custom: perm.customActions,
        };
        return acc;
      }, {}),
    } as GetRoleConfig<typeof complexRole>),
);

console.log('Transformed external role:');
console.log('Can read article:', transformedRole.can('article', 'read'));
console.log(
  'Can add comment to article:',
  transformedRole.can('article', 'addComment'),
);
console.log(
  'Can submit review for approval:',
  transformedRole.can('review', 'submitForApproval'),
);