/
verify-request.ts
117 lines (103 loc) · 4.24 KB
/
verify-request.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import Shopify from "@shopify/shopify-api";
import { HttpResponseError } from "@shopify/shopify-api/dist/error";
import { Context, Next } from "koa";
import { setTopLevelOAuthCookieValue } from "./top-level-oauth-redirect";
type VerifyRequestOptions = {
accessMode?: "online" | "offline";
returnHeader?: boolean;
authRoute?: string;
};
const defaultOptions: VerifyRequestOptions = {
accessMode: "online",
authRoute: "/auth",
returnHeader: false,
};
const REAUTH_HEADER = "X-Shopify-API-Request-Failure-Reauthorize";
const REAUTH_URL_HEADER = "X-Shopify-API-Request-Failure-Reauthorize-Url";
function getAuthUrl(authRoute: string, shop: string): string {
return `${authRoute}?shop=${shop}`;
}
export default function verifyRequest(options?: VerifyRequestOptions) {
const { accessMode, returnHeader, authRoute } = {
...defaultOptions,
...options,
};
return async function verifyTokenMiddleware(ctx: Context, next: Next) {
const session = await Shopify.Utils.loadCurrentSession(
ctx.req,
ctx.res,
accessMode === "online"
);
const { query } = ctx;
const shop = query.shop ? query.shop.toString() : "";
// Login again if the shops don't match
if (session && shop && session.shop !== shop) {
await clearSession(ctx, accessMode);
const redirectUrl = getAuthUrl(authRoute, shop);
ctx.redirect(redirectUrl);
return;
}
if (session) {
// Verify session is valid
if (session.isActive()) {
try {
// I think we need to verify on Shopify's side that the access token is valid, because otherwise anyone could just make their own 'valid' token and use it.
// Of course if we're making requests to Shopify's API afterwords it will fail with an error, but we still need this check since we aren't always making a Shopify request when verifying this token (it might be an API request for our server, and we need to be sure it's the correct user).
// Make a request to make sure the token is valid on Shopify's end. If not, we'll get a 401 and have to re-authorize.
const client = new Shopify.Clients.Rest(session.shop, session.accessToken);
await client.get({ path: "shop" }); // Fetch /shop route on Shopify to verify the token is valid
setTopLevelOAuthCookieValue(ctx, null); // Clear the cookie
await next();
return;
} catch (err) {
if (err instanceof HttpResponseError && err.code == 401) {
// Session not valid, we will re-authorize
} else {
throw err;
}
}
}
}
// If we get here, either the session is invalid or we need to re-authorize
// We need to re-authenticate
if (returnHeader) {
// Return a header to the client so they can re-authorize
ctx.response.status = 401;
ctx.response.set(REAUTH_HEADER, "1"); // Tell the client to re-authorize by setting the reauth header
// Get the shop from the session, or the auth header (we can't get it from the query if we're making a post request)
let shop: string;
if (session) {
shop = session.shop; // Get shop from the session token
} else if (Shopify.Context.IS_EMBEDDED_APP) {
shop = getShopFromAuthHeader(ctx); // Get shop from auth header
}
const reauthUrl = getAuthUrl(authRoute, shop);
ctx.response.set(REAUTH_URL_HEADER, reauthUrl); // Set the reauth url header
} else {
// Otherwise redirect to the auth page
const redirectUrl = getAuthUrl(authRoute, shop);
ctx.redirect(redirectUrl);
}
};
}
async function clearSession(ctx: Context, accessMode = defaultOptions.accessMode) {
try {
await Shopify.Utils.deleteCurrentSession(ctx.req, ctx.res, accessMode === "online");
} catch (error) {
if (error instanceof Shopify.Errors.SessionNotFound) {
// We can just move on if no sessions were cleared
} else {
throw error;
}
}
}
function getShopFromAuthHeader(ctx: Context) {
const authHeader: string = ctx.req.headers.authorization;
const matches = authHeader?.match(/Bearer (.*)/);
if (matches) {
const payload = Shopify.Utils.decodeSessionToken(matches[1]);
const shop = payload.dest.replace("https://", "");
return shop;
}
return null;
}