Skip to content

Commit

Permalink
clean up multipass errors
Browse files Browse the repository at this point in the history
  • Loading branch information
mshick committed Mar 19, 2024
1 parent afc5447 commit 40c367f
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 48 deletions.
7 changes: 4 additions & 3 deletions src/features/Auth/AuthSignIn/AuthSignIn.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { errors } from '@/lib/auth/errors';
import { Meta, StoryObj } from '@storybook/react';
import { AuthSignIn, errors } from './AuthSignIn';
import { AuthSignIn } from './AuthSignIn';

const meta: Meta<typeof AuthSignIn> = {
title: 'Features / Auth / Sign In',
Expand All @@ -8,7 +9,7 @@ const meta: Meta<typeof AuthSignIn> = {
error: {
control: {
type: 'select',
options: Object.keys(errors)
options: Object.keys(errors as Record<string, string>)
}
}
}
Expand All @@ -22,7 +23,7 @@ export const Success: Story = {};

export const Error: Story = {
args: {
error: { code: 'CredentialsSignin' }
error: { type: 'CredentialsSignin', code: 'credentials' }
}
};

Expand Down
17 changes: 12 additions & 5 deletions src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { AuthCustomerQuery } from '@/features/Auth/queries.storefront';
import { getStorefrontClient } from '@/lib/apollo/rsc';
import { withAllAccess } from '@/lib/auth-all-access';
import { NoAccessTokenError, NoEmailError } from '@/lib/auth/errors';
import { getMultipassCustomerAccessToken } from '@/lib/auth/multipass-helper';
import ShopifyCredentialsProvider from '@/lib/auth/shopify-credentials-provider';
import logger from '@/lib/logger';
Expand Down Expand Up @@ -45,8 +46,13 @@ const nextAuthConfig: NextAuthConfig = withAllAccess({
const { email } = user;

if (!email) {
logger.error('Signin missing email');
throw new Error('MISSING_EMAIL');
logger.error(
{
errors: [{ message: 'Missing email' }]
},
'Signin callback failure'
);
throw new NoEmailError('No email address');
}

let shopifyCustomerAccessToken = user.shopifyCustomerAccessToken;
Expand All @@ -58,11 +64,12 @@ const nextAuthConfig: NextAuthConfig = withAllAccess({
if (!shopifyCustomerAccessToken) {
logger.error(
{
email
email,
errors: [{ message: 'No access token' }]
},
'Signin failure'
'Signin callback failure'
);
throw new Error('NO_ACCESS_TOKEN');
throw new NoAccessTokenError('No access token');
}

// Fetch the customer data to enhance the token
Expand Down
20 changes: 18 additions & 2 deletions src/lib/auth/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ export class NoAccountError extends CredentialsSignin {
code = 'no-account';
}

export class NoEmailError extends CredentialsSignin {
code = 'no-email';
}

export class NoAccessTokenError extends CredentialsSignin {
code = 'no-access-token';
}

export class MultipassError extends CredentialsSignin {
code = 'multipass';
}

export type SigninError = {
type: string;
code: string;
Expand Down Expand Up @@ -91,7 +103,7 @@ export function parseSigninError(searchParams: ServerProps['searchParams']): Sig

const defaultSigninErrorMessage = 'Unable to sign in.';

const errors: Record<ErrorType, string> = {
export const errors: Record<ErrorType, string> = {
AccessDenied: 'Please sign in to access this page.',
OAuthAccountNotLinked: 'To confirm your identity, sign in with the same account you used originally.',
AccountNotLinked: 'To confirm your identity, sign in with the same account you used originally.',
Expand All @@ -111,7 +123,11 @@ const credentialSignInErrorCodes: Record<string, string> = {
credentials: 'Email address or password are incorrect.',
'missing-credentials': 'Email address or password not provided.',
'no-account': 'Email address or password are incorrect.',
'email-in-use': 'Email address already in use. Sign in instead.'
'email-in-use': 'Email address already in use. Sign in instead.',
// Not actually CredentialsSignin errors, but that's the only way to throw custom data
'no-email': 'No email address found on the linked account.',
'no-access-token': 'Unable to get an access token from Shopify.',
multipass: 'Multipass error connecting your account to Shopify.'
};

export function getSigninErrorMessage(error: SigninError) {
Expand Down
41 changes: 38 additions & 3 deletions src/lib/auth/multipass-helper.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,50 @@
import { shopifyMultipassSecret } from '@/config';
import { AuthCustomerAccessTokenCreateWithMultipassMutation } from '@/features/Auth/queries.storefront';
import { getStorefrontClient } from '@/lib/apollo/rsc';
import { MultipassError } from '@/lib/auth/errors';
import logger from '@/lib/logger';
import { createMultipassToken } from '@/lib/multipass';
import {
AuthCustomerAccessTokenCreateWithMultipassMutationResponse,
AuthCustomerAccessTokenCreateWithMultipassMutationVariables
} from '@/types/storefront';
import { Account, Profile } from 'next-auth';
import crypto from 'node:crypto';

const shopifyClient = getStorefrontClient();

const blockSize = 16;

function toUrlSafe(token: string) {
return token.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}

function signData(signingKey: Buffer, data: Buffer) {
return crypto.createHmac('sha256', signingKey).update(data).digest();
}

function encryptData(encryptionKey: Buffer, data: string) {
const iv = crypto.randomBytes(blockSize);
const cipher = crypto.createCipheriv('aes-128-cbc', encryptionKey, iv);
return Buffer.concat([iv, cipher.update(data, 'utf8'), cipher.final()]);
}

export type MultipassCustomerData = {
email: string;
} & Record<string, unknown>;

export function createMultipassToken(customerData: MultipassCustomerData) {
const keyMaterial = crypto.createHash('sha256').update(shopifyMultipassSecret).digest();
const encryptionKey = keyMaterial.slice(0, blockSize);
const signingKey = keyMaterial.slice(blockSize, 32);

customerData.created_at = new Date().toISOString();

const encrypted = encryptData(encryptionKey, JSON.stringify(customerData));
const token = Buffer.concat([encrypted, signData(signingKey, encrypted)]).toString('base64');

return toUrlSafe(token);
}

export async function getMultipassCustomerAccessToken({
account,
profile,
Expand Down Expand Up @@ -53,10 +88,10 @@ export async function getMultipassCustomerAccessToken({
email,
errors: customerUserErrors
},
'Multipass signin failure'
'Signin multipass failure'
);

throw new Error(error?.code ?? 'UNKNOWN');
throw new MultipassError(error?.code ?? 'UNKNOWN');
}

return customerAccessToken.accessToken;
Expand Down
35 changes: 0 additions & 35 deletions src/lib/multipass.ts

This file was deleted.

0 comments on commit 40c367f

Please sign in to comment.