Skip to content
This repository has been archived by the owner on Jan 30, 2023. It is now read-only.

Commit

Permalink
Add option to return redirect URL as a header in verifyRequest
Browse files Browse the repository at this point in the history
  • Loading branch information
paulomarg committed Mar 22, 2021
1 parent d50cdc1 commit 2bf49ac
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 4 deletions.
56 changes: 56 additions & 0 deletions src/verify-request/tests/verify-request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import jwt from 'jsonwebtoken';
import verifyRequest from '../verify-request';
import {clearSession} from '../utilities';
import {TEST_COOKIE_NAME, TOP_LEVEL_OAUTH_COOKIE_NAME} from '../../index';
import {REAUTH_HEADER, REAUTH_URL_HEADER} from '../verify-token';
import { clear } from 'console';

const TEST_SHOP = 'testshop.myshopify.io';
Expand Down Expand Up @@ -168,6 +169,29 @@ describe('verifyRequest', () => {
`${authRoute}?shop=${TEST_SHOP}`,
);
});

it('returns a header if setting is active', async () => {
// Session exists but has already expired
const session = await Shopify.Context.SESSION_STORAGE.loadSession(jwtSessionId);
session.expires = new Date(Date.now() - 10);
await Shopify.Utils.storeSession(session);

const verifyRequestMiddleware = verifyRequest({returnHeader: true});
const ctx = createMockContext({
redirect: jest.fn(),
headers: { authorization: `Bearer ${jwtToken}` }
});
const next = jest.fn();

await verifyRequestMiddleware(ctx, next);

expect(ctx.redirect).not.toHaveBeenCalled();
expect(ctx.response.status).toBe(403);
expect(ctx.response.headers).toEqual(expect.objectContaining({
[REAUTH_HEADER.toLowerCase()]: '1',
[REAUTH_URL_HEADER.toLowerCase()]: `/auth?shop=${TEST_SHOP}`,
}));
});
});

describe('when there is no session', () => {
Expand Down Expand Up @@ -262,6 +286,38 @@ describe('verifyRequest', () => {

expect(await clearSession(ctx)).toBeUndefined();
});

it('returns a header if setting is active', async () => {
const jwtPayload = {
iss: `https://${TEST_SHOP}/admin`,
dest: `https://${TEST_SHOP}`,
aud: Shopify.Context.API_KEY,
sub: TEST_USER,
exp: (Date.now() + 3600000) / 1000,
nbf: 1234,
iat: 1234,
jti: '4321',
sid: 'abc123',
};

const jwtToken = jwt.sign(jwtPayload, Shopify.Context.API_SECRET_KEY, { algorithm: 'HS256' });

const verifyRequestMiddleware = verifyRequest({returnHeader: true});
const ctx = createMockContext({
redirect: jest.fn(),
headers: { authorization: `Bearer ${jwtToken}` }
});
const next = jest.fn();

await verifyRequestMiddleware(ctx, next);

expect(ctx.redirect).not.toHaveBeenCalled();
expect(ctx.response.status).toBe(403);
expect(ctx.response.headers).toEqual(expect.objectContaining({
[REAUTH_HEADER.toLowerCase()]: '1',
[REAUTH_URL_HEADER.toLowerCase()]: `/auth?shop=${TEST_SHOP}`,
}));
});
});
});

Expand Down
1 change: 1 addition & 0 deletions src/verify-request/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface Routes {

type VerifyRequestOptions = {
accessMode: AccessMode;
returnHeader: boolean;
}

export type Options = Partial<VerifyRequestOptions> & Partial<Routes>;
5 changes: 3 additions & 2 deletions src/verify-request/verify-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import {Options, Routes} from './types';
import {DEFAULT_ACCESS_MODE} from '../auth';

export default function verifyRequest(givenOptions: Options = {}) {
const {accessMode} = {
const {accessMode, returnHeader} = {
accessMode: DEFAULT_ACCESS_MODE,
returnHeader: false,
...givenOptions
};
const routes: Routes = {
Expand All @@ -18,6 +19,6 @@ export default function verifyRequest(givenOptions: Options = {}) {

return compose([
loginAgainIfDifferentShop(routes, accessMode),
verifyToken(routes, accessMode)
verifyToken(routes, accessMode, returnHeader)
]);
}
29 changes: 27 additions & 2 deletions src/verify-request/verify-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import {Routes} from './types';
import {redirectToAuth} from './utilities';
import {DEFAULT_ACCESS_MODE} from '../auth';

export function verifyToken(routes: Routes, accessMode: AccessMode = DEFAULT_ACCESS_MODE) {
export const REAUTH_HEADER = 'X-Shopify-API-Request-Failure-Reauthorize';
export const REAUTH_URL_HEADER = 'X-Shopify-API-Request-Failure-Reauthorize-Url';

export function verifyToken(routes: Routes, accessMode: AccessMode = DEFAULT_ACCESS_MODE, returnHeader = false) {
return async function verifyTokenMiddleware(
ctx: Context,
next: NextFunction,
Expand All @@ -30,6 +33,28 @@ export function verifyToken(routes: Routes, accessMode: AccessMode = DEFAULT_ACC

ctx.cookies.set(TEST_COOKIE_NAME, '1');

redirectToAuth(routes, ctx);
if (returnHeader) {
ctx.response.status = 403;
ctx.response.set(REAUTH_HEADER, '1');

let shop: string;
if (session) {
shop = session.shop;
} else if (Shopify.Context.IS_EMBEDDED_APP) {
const authHeader: string = ctx.req.headers.authorization;
const matches = authHeader?.match(/Bearer (.*)/);
if (matches) {
const payload = Shopify.Utils.decodeSessionToken(matches[1]);
shop = payload.dest.replace('https://', '');
}
}

if (shop) {
ctx.response.set(REAUTH_URL_HEADER, `${routes.authRoute}?shop=${shop}`);
}
return;
} else {
redirectToAuth(routes, ctx);
}
};
}

0 comments on commit 2bf49ac

Please sign in to comment.