beaucoo / multipassify

Shopify Multipass module for Node.js
92 stars 18 forks source link

Typescript version, or at least typings #6

Open jhalborg opened 5 years ago

jhalborg commented 5 years ago

Hey,

Thanks a lot for the example implementation! I've gone ahead and implemented it pretty much 1:1 in Typescript, using the now built-in version of the crypto library in newer versions of Node instead of using the old dependency - so there is no need to npm install crypto.

I'm just going to leave it here for anyone needing it in the future:

import * as crypto from 'crypto';

export interface ShopifyMultipassTokenData {
  email: string;
  return_to?: string;
}

/**
 * We require only the customer email at the moment as defined by the interface above
 * but we add a created_at to the token to be able to determine when it was issued.
 * The created_at field, along with email, are the two mandatory fields for token generation
 *
 * @export
 * @interface ShopifyMultipassToken
 * @extends {ShopifyMultipassTokenData}
 */
export interface ShopifyMultipassToken extends ShopifyMultipassTokenData {
  created_at: string;
}

const BLOCK_SIZE = 16;
const SHOPIFY_DOMAIN_URL = 'https://yourdomain.myshopify.com';
/**
 * Utility class for generating an access token for Shopify on behalf of a user to help them log in
 *
 * @export
 * @class ShopifyMultipass
 */
export class ShopifyMultipass {
  private encryptionKey: Buffer;
  private signingKey: Buffer;
  constructor(shopifySecret: string) {
    const hash = crypto
      .createHash('sha256')
      .update(shopifySecret)
      .digest();
    this.encryptionKey = hash.slice(0, BLOCK_SIZE);
    // tslint:disable-next-line:no-magic-numbers
    this.signingKey = hash.slice(BLOCK_SIZE, 32);
  }

  public getUrl(customerData: ShopifyMultipassTokenData) {
    const token = this.encodeCustomerData(customerData);
    return `${SHOPIFY_DOMAIN_URL}/account/login/multipass/${token}`;
  }

  private encodeCustomerData(customerData: ShopifyMultipassTokenData) {
    const data: ShopifyMultipassToken = {
      created_at: new Date().toISOString(),
      email: customerData.email,
      return_to: customerData.return_to,
    };

    const cipherText = this.encrypt(JSON.stringify(data));

    // Create a signature (message authentication code) of the ciphertext
    // and encode everything using URL-safe Base64 (RFC 4648)
    let token = Buffer.concat([cipherText, this.sign(cipherText)]).toString('base64');
    token = token
      .replace(/\+/g, '-') // Replace + with -
      .replace(/\//g, '_'); // Replace / with _

    return token;
  }

  private encrypt(text: string) {
    // Use a random IV
    const iv = crypto.randomBytes(BLOCK_SIZE);

    // Shopify uses the aes-128-cbc algorithm. It is a valid option, in spite of the Node typings not recognizing it at the moment
    const cipher = crypto.createCipheriv('aes-128-cbc', this.encryptionKey, iv);

    // Use IV as first block of ciphertext
    return Buffer.concat([iv, cipher.update(text, 'utf8'), cipher.final()]);
  }

  private sign(data: any) {
    return crypto
      .createHmac('SHA256', this.signingKey)
      .update(data)
      .digest();
  }
}

@beaucoo - If you just want to add the typings as a .d.ts file to this repo, this is the resulting definition file from the code above:

export interface ShopifyMultipassTokenData {
    email: string;
    return_to?: string;
}
/**
 * We require only the customer email at the moment as defined by the interface above
 * but we add a created_at to the token to be able to determine when it was issued.
 * The created_at field, along with email, are the two mandatory fields for token generation
 *
 * @export
 * @interface ShopifyMultipassToken
 * @extends {ShopifyMultipassTokenData}
 */
export interface ShopifyMultipassToken extends ShopifyMultipassTokenData {
    created_at: string;
}
/**
 * Utility class for generating an access token for Shopify on behalf of a user to help them log in
 *
 * @export
 * @class ShopifyMultipass
 */
export declare class ShopifyMultipass {
    private encryptionKey;
    private signingKey;
    constructor(shopifySecret: string);
    getUrl(customerData: ShopifyMultipassTokenData): string;
    private encodeCustomerData;
    private encrypt;
    private sign;
}
softmarshmallow commented 4 years ago

also leaving a package so, others can use. https://github.com/softmarshmallow/multipass-js

pure ts based. builder pattern, multipass-js