Skip to content
This repository has been archived by the owner on Apr 11, 2024. It is now read-only.

Commit

Permalink
Extend cookie OAuth session to allow initial app loads
Browse files Browse the repository at this point in the history
  • Loading branch information
paulomarg committed Jan 13, 2021
1 parent d033347 commit 0e75466
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 12 deletions.
10 changes: 6 additions & 4 deletions src/auth/oauth/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,18 @@ const ShopifyOAuth = {
secure: true,
});

// If this is an online session for an embedded app, we assume it will be loaded from a JWT from here on out
// If this is an online session for an embedded app, we assume it will be loaded from a JWT from here on out. The
// cookie session is preserved for 30s so that the app skeleton page can still be loaded using it
if (Context.IS_EMBEDDED_APP && currentSession.isOnline) {
const onlineInfo = currentSession.onlineAccesInfo as OnlineAccessInfo;
const jwtSessionId = this.getJwtSessionId(currentSession.shop, '' + onlineInfo.associated_user.id);
const jwtSession = Session.cloneSession(currentSession, jwtSessionId);
await Context.deleteSession(currentSession.id);
await Context.storeSession(jwtSession);
} else {
await Context.storeSession(currentSession);

currentSession.expires = new Date(Date.now() + 30000);
}

await Context.storeSession(currentSession);
},

/**
Expand Down
12 changes: 10 additions & 2 deletions src/auth/oauth/test/oauth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ describe('validateAuthCallback', () => {
}
});

test('converts an OAuth session into a JWT one if is online', async () => {
test('converts an OAuth session into a JWT one if it is online', async () => {
Context.IS_EMBEDDED_APP = true;
Context.initialize(Context);

Expand Down Expand Up @@ -284,7 +284,15 @@ describe('validateAuthCallback', () => {
fetchMock.mockResponse(JSON.stringify(successResponse));
await ShopifyOAuth.validateAuthCallback(req, res, testCallbackQuery);

await expect(Context.loadSession(cookies.id)).resolves.toBeUndefined();
let expectedCookieExpiration = Date.now() / 1000;
expectedCookieExpiration += 30;
const cookieSession = await Context.loadSession(cookies.id);
expect(cookieSession).not.toBeUndefined();

if (cookieSession?.expires) {
const actualCookieExpiration: number = cookieSession.expires.getTime() / 1000;
expect(Math.abs(expectedCookieExpiration - actualCookieExpiration)).toBeLessThan(1); // 1-second grace period
}

const jwtPayload: JwtPayload = {
iss: `https://${shop}/admin`,
Expand Down
8 changes: 5 additions & 3 deletions src/utils/load-current-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ export default async function loadCurrentSession(
const jwtPayload = decodeSessionToken(matches[1]);
const jwtSessionId = ShopifyOAuth.getJwtSessionId(jwtPayload.dest.replace(/^https:\/\//, ''), jwtPayload.sub);
session = await Context.loadSession(jwtSessionId);
} else {
throw new ShopifyErrors.MissingJwtTokenError('Missing authorization header');
}
} else {
}

// We fall back to the cookie session to allow apps to load their skeleton page after OAuth, so they can set up App
// Bridge and get a new JWT.
if (!session) {
const sessionCookie = ShopifyOAuth.getCookieSessionId(request, response);
if (sessionCookie) {
session = await Context.loadSession(sessionCookie);
Expand Down
25 changes: 23 additions & 2 deletions src/utils/test/load-current-session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ describe('loadCurrentSession', () => {
await expect(loadCurrentSession(req, res)).resolves.toEqual(session);
});

it('throws error if no authorization header is present', async () => {
it('loads nothing if no authorization header is present', async () => {
Context.IS_EMBEDDED_APP = true;
Context.initialize(Context);

const req = { headers: {} } as http.IncomingMessage;
const res = {} as http.ServerResponse;

await expect(() => loadCurrentSession(req, res)).rejects.toBeInstanceOf(ShopifyErrors.MissingJwtTokenError);
await expect(loadCurrentSession(req, res)).resolves.toBeUndefined();
});

it('loads nothing if there is no session for embedded apps', async () => {
Expand Down Expand Up @@ -114,4 +114,25 @@ describe('loadCurrentSession', () => {

await expect(() => loadCurrentSession(req, res)).rejects.toBeInstanceOf(ShopifyErrors.MissingJwtTokenError);
});

it('falls back to the cookie session for embedded apps', async () => {
Context.IS_EMBEDDED_APP = true;
Context.initialize(Context);

const req = {
headers: {
authorization: '',
},
} as http.IncomingMessage;
const res = {} as http.ServerResponse;

const cookieId = '1234-this-is-a-cookie-session-id';

const session = new Session(cookieId);
await expect(Context.storeSession(session)).resolves.toEqual(true);

Cookies.prototype.get.mockImplementation(() => cookieId);

await expect(loadCurrentSession(req, res)).resolves.toEqual(session);
});
});
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.10.1.tgz#cc323bad8e8a533d4822f45ce4e5326f36e42177"
integrity sha512-aYNbO+FZ/3KGeQCEkNhHFRIzBOUgc7QvcVNKXbfnhDkSfwUv91JsQQa10rDgKSTSLkXZ1UIyPe4FJJNVgw1xWQ==

"@types/node@^14.14.1":
"@types/[email protected]":
version "14.14.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.1.tgz#b8d6e8a84b119ae51fd0593c71eb3a9dd31fea4e"
integrity sha512-D2/Xwp9c49JhIWq7SIrdvoYyGdq6yXkr5FTldN4rus9XljYFBul6P2epAID8xepOpL4ffcx09C05FZGK/1AIkw==
Expand Down

0 comments on commit 0e75466

Please sign in to comment.