security: restrict installs to ALLOWED_SHOP and remove generic landing form
This commit is contained in:
@@ -6,13 +6,46 @@ import { Form, useActionData, useLoaderData } from "react-router";
|
||||
import { login } from "../../shopify.server";
|
||||
import { loginErrorMessage } from "./error.server";
|
||||
|
||||
function enforceAllowedShop(request: Request) {
|
||||
const allowedShop = process.env.ALLOWED_SHOP?.trim();
|
||||
if (!allowedShop) return;
|
||||
const url = new URL(request.url);
|
||||
const fromQuery = url.searchParams.get("shop");
|
||||
let fromBody: string | null = null;
|
||||
// Action requests submit the shop in the form body; we re-read it here.
|
||||
// (request.formData() can only be consumed once, so we clone.)
|
||||
if (request.method === "POST") {
|
||||
// We can't await the clone here without making this async; instead the
|
||||
// caller awaits the actual action and we re-validate the redirect target
|
||||
// via the query string check above. The action wrapper below also runs
|
||||
// a body check before delegating to `login()`.
|
||||
}
|
||||
if (fromQuery && fromQuery.toLowerCase() !== allowedShop.toLowerCase() && !fromBody) {
|
||||
throw new Response("This app is private.", { status: 403 });
|
||||
}
|
||||
}
|
||||
|
||||
async function enforceAllowedShopFromBody(request: Request) {
|
||||
const allowedShop = process.env.ALLOWED_SHOP?.trim();
|
||||
if (!allowedShop) return request;
|
||||
const cloned = request.clone();
|
||||
const form = await cloned.formData();
|
||||
const shop = (form.get("shop") ?? "").toString().trim().toLowerCase();
|
||||
if (shop && shop !== allowedShop.toLowerCase()) {
|
||||
throw new Response("This app is private.", { status: 403 });
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
enforceAllowedShop(request);
|
||||
const errors = loginErrorMessage(await login(request));
|
||||
|
||||
return { errors };
|
||||
return { errors, allowedShop: process.env.ALLOWED_SHOP ?? null };
|
||||
};
|
||||
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
await enforceAllowedShopFromBody(request);
|
||||
const errors = loginErrorMessage(await login(request));
|
||||
|
||||
return {
|
||||
@@ -23,7 +56,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
export default function Auth() {
|
||||
const loaderData = useLoaderData<typeof loader>();
|
||||
const actionData = useActionData<typeof action>();
|
||||
const [shop, setShop] = useState("");
|
||||
const [shop, setShop] = useState(loaderData.allowedShop ?? "");
|
||||
const { errors } = actionData || loaderData;
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user