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

Commit 4db4974

Browse files
committed
Allow offline sessions in loadCurrentSession
1 parent 89ea780 commit 4db4974

File tree

7 files changed

+107
-16
lines changed

7 files changed

+107
-16
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
88
## Unreleased
99
- Minor text/doc changes
1010
- Added `2021-01` API version to enum. [#117](https://github.com/shopify/shopify-node-api/pull/117)
11+
- Allow retrieving offline sessions using `loadCurrentSession`. [#119](https://github.com/shopify/shopify-node-api/pull/119)
1112

1213
## [1.0.0]
1314

docs/usage/oauth.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -112,16 +112,16 @@ You can use the `Shopify.Utils.loadCurrentSession()` method to load an online se
112112

113113
As mentioned in the previous sections, you can use the OAuth methods to create both offline and online sessions. Once the process is completed, the session will be stored as per your `Context.SESSION_STORAGE` strategy, and can be retrieved with the below utitilies.
114114

115-
- To load an online session:
115+
- To load a session, you can use the following method. You can load both online and offline sessions from the current request / response objects.
116116
```ts
117-
await Shopify.Utils.loadCurrentSession(request, response)
117+
await Shopify.Utils.loadCurrentSession(request, response, isOnline);
118118
```
119-
- To load an offline session:
119+
- If you need to load a session for a background job, you can get offline sessions directly from the shop.
120120
```ts
121-
await Shopify.Utils.loadOfflineSession(shop)
121+
await Shopify.Utils.loadOfflineSession(shop);
122122
```
123123

124-
The library supports creating both offline and online sessions for the same shop, so it is up to the app to call the appropriate loading method depending on its needs.
124+
**Note**: the `loadOfflineSession` method does not perform any validations on the `shop` parameter. You should avoid calling it from user inputs or URLs.
125125

126126
## Detecting scope changes
127127

src/auth/oauth/oauth.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,15 @@ const ShopifyOAuth = {
224224
/**
225225
* Extracts the current session id from the request / response pair.
226226
*
227-
* @param request HTTP request object
227+
* @param request HTTP request object
228228
* @param response HTTP response object
229+
* @param isOnline Whether to load online (default) or offline sessions (optional)
229230
*/
230-
getCurrentSessionId(request: http.IncomingMessage, response: http.ServerResponse): string | undefined {
231+
getCurrentSessionId(
232+
request: http.IncomingMessage,
233+
response: http.ServerResponse,
234+
isOnline = true,
235+
): string | undefined {
231236
let currentSessionId: string | undefined;
232237

233238
if (Context.IS_EMBEDDED_APP) {
@@ -239,13 +244,20 @@ const ShopifyOAuth = {
239244
}
240245

241246
const jwtPayload = decodeSessionToken(matches[1]);
242-
currentSessionId = this.getJwtSessionId(jwtPayload.dest.replace(/^https:\/\//, ''), jwtPayload.sub);
247+
const shop = jwtPayload.dest.replace(/^https:\/\//, '');
248+
if (isOnline) {
249+
currentSessionId = this.getJwtSessionId(shop, jwtPayload.sub);
250+
} else {
251+
currentSessionId = this.getOfflineSessionId(shop);
252+
}
243253
}
244254
}
245255

246-
// We fall back to the cookie session to allow apps to load their skeleton page after OAuth, so they can set up App
247-
// Bridge and get a new JWT.
256+
// Non-embedded apps will always load sessions using cookies. However, we fall back to the cookie session for
257+
// embedded apps to allow apps to load their skeleton page after OAuth, so they can set up App Bridge and get a new
258+
// JWT.
248259
if (!currentSessionId) {
260+
// We still want to get the offline session id from the cookie to make sure it's validated
249261
currentSessionId = this.getCookieSessionId(request, response);
250262
}
251263

src/utils/delete-current-session.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@ import * as ShopifyErrors from '../error';
77
/**
88
* Finds and deletes the current user's session, based on the given request and response
99
*
10-
* @param req Current HTTP request
11-
* @param res Current HTTP response
10+
* @param request Current HTTP request
11+
* @param response Current HTTP response
12+
* @param isOnline Whether to load online (default) or offline sessions (optional)
1213
*/
1314
export default async function deleteCurrentSession(
1415
request: http.IncomingMessage,
1516
response: http.ServerResponse,
17+
isOnline = true,
1618
): Promise<boolean | never> {
1719
Context.throwIfUninitialized();
1820

19-
const sessionId = ShopifyOAuth.getCurrentSessionId(request, response);
21+
const sessionId = ShopifyOAuth.getCurrentSessionId(request, response, isOnline);
2022
if (!sessionId) {
2123
throw new ShopifyErrors.SessionNotFound('No active session found.');
2224
}

src/utils/load-current-session.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@ import {Session} from '../auth/session';
77
/**
88
* Loads the current user's session, based on the given request and response.
99
*
10-
* @param req Current HTTP request
11-
* @param res Current HTTP response
10+
* @param request Current HTTP request
11+
* @param response Current HTTP response
12+
* @param isOnline Whether to load online (default) or offline sessions (optional)
1213
*/
1314
export default async function loadCurrentSession(
1415
request: http.IncomingMessage,
1516
response: http.ServerResponse,
17+
isOnline = true,
1618
): Promise<Session | undefined> {
1719
Context.throwIfUninitialized();
1820

19-
const sessionId = ShopifyOAuth.getCurrentSessionId(request, response);
21+
const sessionId = ShopifyOAuth.getCurrentSessionId(request, response, isOnline);
2022
if (!sessionId) {
2123
return Promise.resolve(undefined);
2224
}

src/utils/test/delete-current-session.test.ts

+38
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {Session} from '../../auth/session';
1111
import {JwtPayload} from '../decode-session-token';
1212
import deleteCurrentSession from '../delete-current-session';
1313
import loadCurrentSession from '../load-current-session';
14+
import {ShopifyOAuth} from '../../auth/oauth/oauth';
1415

1516
jest.mock('cookies');
1617

@@ -67,6 +68,43 @@ describe('deleteCurrenSession', () => {
6768
await expect(loadCurrentSession(req, res)).resolves.toBe(undefined);
6869
});
6970

71+
it('finds and deletes the current offline session when using cookies', async () => {
72+
Context.IS_EMBEDDED_APP = false;
73+
Context.initialize(Context);
74+
75+
const req = {} as http.IncomingMessage;
76+
const res = {} as http.ServerResponse;
77+
78+
const cookieId = ShopifyOAuth.getOfflineSessionId('test-shop.myshopify.io');
79+
80+
const session = new Session(cookieId);
81+
await expect(Context.SESSION_STORAGE.storeSession(session)).resolves.toEqual(true);
82+
83+
Cookies.prototype.get.mockImplementation(() => cookieId);
84+
85+
await expect(deleteCurrentSession(req, res, false)).resolves.toBe(true);
86+
await expect(loadCurrentSession(req, res, false)).resolves.toBe(undefined);
87+
});
88+
89+
it('finds and deletes the current offline session when using JWT', async () => {
90+
Context.IS_EMBEDDED_APP = true;
91+
Context.initialize(Context);
92+
93+
const token = jwt.sign(jwtPayload, Context.API_SECRET_KEY, {algorithm: 'HS256'});
94+
const req = {
95+
headers: {
96+
authorization: `Bearer ${token}`,
97+
},
98+
} as http.IncomingMessage;
99+
const res = {} as http.ServerResponse;
100+
101+
const session = new Session(ShopifyOAuth.getOfflineSessionId('test-shop.myshopify.io'));
102+
await expect(Context.SESSION_STORAGE.storeSession(session)).resolves.toEqual(true);
103+
104+
await expect(deleteCurrentSession(req, res, false)).resolves.toBe(true);
105+
await expect(loadCurrentSession(req, res, false)).resolves.toBe(undefined);
106+
});
107+
70108
it('throws an error when no cookie is found', async () => {
71109
Context.IS_EMBEDDED_APP = false;
72110
Context.initialize(Context);

src/utils/test/load-current-session.test.ts

+36
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as ShopifyErrors from '../../error';
1010
import {Session} from '../../auth/session';
1111
import {JwtPayload} from '../decode-session-token';
1212
import loadCurrentSession from '../load-current-session';
13+
import {ShopifyOAuth} from '../../auth/oauth/oauth';
1314

1415
jest.mock('cookies');
1516

@@ -136,4 +137,39 @@ describe('loadCurrentSession', () => {
136137

137138
await expect(loadCurrentSession(req, res)).resolves.toEqual(session);
138139
});
140+
141+
it('loads offline sessions from cookies', async () => {
142+
Context.IS_EMBEDDED_APP = false;
143+
Context.initialize(Context);
144+
145+
const req = {} as http.IncomingMessage;
146+
const res = {} as http.ServerResponse;
147+
148+
const cookieId = ShopifyOAuth.getOfflineSessionId('test-shop.myshopify.io');
149+
150+
const session = new Session(cookieId);
151+
await expect(Context.SESSION_STORAGE.storeSession(session)).resolves.toEqual(true);
152+
153+
Cookies.prototype.get.mockImplementation(() => cookieId);
154+
155+
await expect(loadCurrentSession(req, res, false)).resolves.toEqual(session);
156+
});
157+
158+
it('loads offline sessions from JWT token', async () => {
159+
Context.IS_EMBEDDED_APP = true;
160+
Context.initialize(Context);
161+
162+
const token = jwt.sign(jwtPayload, Context.API_SECRET_KEY, {algorithm: 'HS256'});
163+
const req = {
164+
headers: {
165+
authorization: `Bearer ${token}`,
166+
},
167+
} as http.IncomingMessage;
168+
const res = {} as http.ServerResponse;
169+
170+
const session = new Session(ShopifyOAuth.getOfflineSessionId('test-shop.myshopify.io'));
171+
await expect(Context.SESSION_STORAGE.storeSession(session)).resolves.toEqual(true);
172+
173+
await expect(loadCurrentSession(req, res, false)).resolves.toEqual(session);
174+
});
139175
});

0 commit comments

Comments
 (0)