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;
}
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:
@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: