├── .stylelintignore ├── .npmrc ├── .eslintignore ├── .shopify-cli.yml ├── .travis.yml ├── .stylelintrc.js ├── .prettierignore ├── theme-app-extension ├── blocks │ ├── abi.js │ └── crypto-wallet.liquid ├── .shopify-cli.yml ├── assets │ └── style.css └── crypto-wallet.js ├── server ├── index.js └── server.js ├── .env.example ├── webpack.config.js ├── .dependabot └── config.yml ├── .gitignore ├── .eslintrc.js ├── .editorconfig ├── next.config.js ├── pages ├── index.js └── _app.js ├── LICENSE.md ├── package.json ├── README.md └── SECURITY.md /.stylelintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | @shopify:registry=https://registry.yarnpkg.com 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | coverage 4 | 5 | 6 | -------------------------------------------------------------------------------- /.shopify-cli.yml: -------------------------------------------------------------------------------- 1 | --- 2 | project_type: node 3 | organization_id: 2234859 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | script: 3 | - npm run build 4 | node_js: 5 | - '10' 6 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['stylelint-config-shopify/prettier'], 3 | }; 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | node_modules 3 | build 4 | coverage 5 | .sewing-kit 6 | *.svg 7 | app/types/graphql.ts 8 | -------------------------------------------------------------------------------- /theme-app-extension/blocks/abi.js: -------------------------------------------------------------------------------- 1 | export const ABI = [ 2 | "function balanceOf(address owner) view returns (uint256)", 3 | ] 4 | -------------------------------------------------------------------------------- /theme-app-extension/.shopify-cli.yml: -------------------------------------------------------------------------------- 1 | --- 2 | project_type: :extension 3 | organization_id: 0 4 | EXTENSION_TYPE: THEME_APP_EXTENSION 5 | -------------------------------------------------------------------------------- /theme-app-extension/assets/style.css: -------------------------------------------------------------------------------- 1 | #crypto-wallet-button { 2 | cursor: pointer; 3 | border: none; 4 | border-radius: 10px; 5 | padding: 10px; 6 | } 7 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | require('@babel/register')({ 2 | presets: ['@babel/preset-env'], 3 | ignore: ['node_modules'] 4 | }); 5 | 6 | // Import the rest of our application. 7 | module.exports = require('./server.js'); 8 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SHOPIFY_API_KEY="YOUR_SHOPIFY_API_KEY" 2 | SHOPIFY_API_SECRET="YOUR_SHOPIFY_SECRET" 3 | HOST="YOUR_NGROK_TUNNEL_URL" 4 | SHOP="my-shop-name.myshopify.com" 5 | SCOPES=write_products,write_customers,write_draft_orders 6 | PORT=3000 7 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./theme-app-extension/crypto-wallet.js", 5 | output: { 6 | path: path.resolve(__dirname, "./theme-app-extension/assets"), 7 | filename: "crypto-wallet.min.js", 8 | // publicPath, 9 | }, 10 | plugins: [], 11 | mode: "production", 12 | module: {}, 13 | }; 14 | -------------------------------------------------------------------------------- /.dependabot/config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | update_configs: 3 | # Keep package.json up to date as soon as 4 | # new versions are published to the npm registry 5 | - package_manager: "javascript" 6 | directory: "/" 7 | update_schedule: "weekly" 8 | default_reviewers: 9 | - "Shopify/platform-dev-tools-education" 10 | version_requirement_updates: "auto" 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Environment Configuration 2 | .env 3 | 4 | # Dependency directory 5 | node_modules 6 | 7 | # Test coverage directory 8 | coverage 9 | 10 | # Ignore Apple macOS Desktop Services Store 11 | .DS_Store 12 | 13 | # Logs 14 | logs 15 | *.log 16 | 17 | # ngrok tunnel file 18 | config/tunnel.pid 19 | 20 | # next build output 21 | .next/ 22 | 23 | 24 | crypto-wallet.min.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'plugin:shopify/react', 4 | 'plugin:shopify/polaris', 5 | 'plugin:shopify/jest', 6 | 'plugin:shopify/webpack', 7 | ], 8 | rules: { 9 | 'import/no-unresolved': 'off', 10 | }, 11 | overrides: [ 12 | { 13 | files: ['*.test.*'], 14 | rules: { 15 | 'shopify/jsx-no-hardcoded-content': 'off', 16 | }, 17 | }, 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | # Markdown syntax specifies that trailing whitespaces can be meaningful, 12 | # so let’s not trim those. e.g. 2 trailing spaces = linebreak (
) 13 | # See https://daringfireball.net/projects/markdown/syntax#p 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const { parsed: localEnv } = require("dotenv").config(); 2 | 3 | const webpack = require("webpack"); 4 | const apiKey = JSON.stringify(process.env.SHOPIFY_API_KEY); 5 | 6 | module.exports = { 7 | webpack: (config) => { 8 | const env = { API_KEY: apiKey }; 9 | config.plugins.push(new webpack.DefinePlugin(env)); 10 | 11 | // Add ESM support for .mjs files in webpack 4 12 | config.module.rules.push({ 13 | test: /\.mjs$/, 14 | include: /node_modules/, 15 | type: "javascript/auto", 16 | }); 17 | 18 | return config; 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { Heading, Page, TextStyle, Layout, EmptyState } from "@shopify/polaris"; 2 | const img = "https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg"; 3 | const Index = () => ( 4 | 5 | 6 | alert("TODO: Select products"), 11 | }} 12 | image={img} 13 | > 14 |

Select products to change their price temporarily.

15 |
16 |
17 |
18 | ); 19 | export default Index; 20 | -------------------------------------------------------------------------------- /theme-app-extension/blocks/crypto-wallet.liquid: -------------------------------------------------------------------------------- 1 | 10 | 11 | {% schema %} 12 | { 13 | "name": "Connect Wallet", 14 | "target": "section", 15 | "javascript": "crypto-wallet.min.js", 16 | "stylesheet": "style.css", 17 | "settings":[ 18 | { 19 | "type": "text", 20 | "id": "button_label", 21 | "label": "Button Label", 22 | "default": "Connect with a Wallet" 23 | }, 24 | { 25 | "type": "color", 26 | "id": "button_color", 27 | "label": "Button Color", 28 | "default": "#000000" 29 | }, 30 | { 31 | "type": "color", 32 | "id": "button_text_color", 33 | "label": "Button Text Color", 34 | "default": "#FFFFFF" 35 | } 36 | 37 | 38 | ] 39 | } 40 | {% endschema %} -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shopify 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /theme-app-extension/crypto-wallet.js: -------------------------------------------------------------------------------- 1 | import { Web3Provider } from "@ethersproject/providers"; 2 | import Web3Modal from "web3modal"; 3 | import WalletConnectProvider from "@walletconnect/web3-provider"; 4 | import ABI from './blocks/abi'; 5 | 6 | async function getWeb3Modal() { 7 | const web3Modal = new Web3Modal({ 8 | network: "mainnet", 9 | cacheProvider: false, 10 | providerOptions: { 11 | walletconnect: { 12 | package: WalletConnectProvider, 13 | options: { 14 | infuraId: "995b087f3910499a829e5e460ca55e5c", 15 | }, 16 | }, 17 | }, 18 | }); 19 | return web3Modal; 20 | } 21 | 22 | /* invoke this function to prompt user and connect wallet */ 23 | async function connectWallet() { 24 | const web3Modal = await getWeb3Modal(); 25 | const connection = await web3Modal.connect(); 26 | const provider = new Web3Provider(connection); 27 | const accounts = await provider.listAccounts(); 28 | console.log({ accounts }); 29 | if (accounts.length === 0) return; 30 | 31 | const discountForAccount = await getDiscountCodeForAccount(accounts); 32 | if (!discountForAccount) return; 33 | alert("Discount has been applied!"); 34 | window.location.href = `/discount/${discountForAccount}`; // this will apply the discount 35 | } 36 | 37 | async function getDiscountCodeForAccount(accounts) { 38 | // TODO: check if account is eligible for discount on the backend 39 | // Generate one time unique discount code using https://shopify.dev/api/admin-rest/2021-07/resources/discountcode#[post]/admin/api/2021-07/price_rules/%7Bprice_rule_id%7D/discount_codes.json 40 | 41 | // const contractAddress = 'GET FROM BACKEND'; 42 | // const erc20 = new ethers.Contract(contractAddress, ABI, provider); 43 | // const balance = await erc20.balanceOf(accounts) 44 | return "pepe"; 45 | } 46 | 47 | const button = document.querySelector("#crypto-wallet-button"); 48 | button.addEventListener("click", connectWallet); 49 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import ApolloClient from "apollo-boost"; 2 | import { ApolloProvider } from "react-apollo"; 3 | import App from "next/app"; 4 | import { AppProvider } from "@shopify/polaris"; 5 | import { Provider, useAppBridge } from "@shopify/app-bridge-react"; 6 | import { authenticatedFetch } from "@shopify/app-bridge-utils"; 7 | import { Redirect } from "@shopify/app-bridge/actions"; 8 | import "@shopify/polaris/dist/styles.css"; 9 | import translations from "@shopify/polaris/locales/en.json"; 10 | 11 | function userLoggedInFetch(app) { 12 | const fetchFunction = authenticatedFetch(app); 13 | 14 | return async (uri, options) => { 15 | const response = await fetchFunction(uri, options); 16 | 17 | if ( 18 | response.headers.get("X-Shopify-API-Request-Failure-Reauthorize") === "1" 19 | ) { 20 | const authUrlHeader = response.headers.get( 21 | "X-Shopify-API-Request-Failure-Reauthorize-Url" 22 | ); 23 | 24 | const redirect = Redirect.create(app); 25 | redirect.dispatch(Redirect.Action.APP, authUrlHeader || `/auth`); 26 | return null; 27 | } 28 | 29 | return response; 30 | }; 31 | } 32 | 33 | function MyProvider(props) { 34 | const app = useAppBridge(); 35 | 36 | const client = new ApolloClient({ 37 | fetch: userLoggedInFetch(app), 38 | fetchOptions: { 39 | credentials: "include", 40 | }, 41 | }); 42 | 43 | const Component = props.Component; 44 | 45 | return ( 46 | 47 | 48 | 49 | ); 50 | } 51 | 52 | class MyApp extends App { 53 | render() { 54 | const { Component, pageProps, host } = this.props; 55 | return ( 56 | 57 | 64 | 65 | 66 | 67 | ); 68 | } 69 | } 70 | 71 | MyApp.getInitialProps = async ({ ctx }) => { 72 | return { 73 | host: ctx.query.host, 74 | }; 75 | }; 76 | 77 | export default MyApp; 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shopify-app-node", 3 | "version": "1.0.0", 4 | "description": "Shopify's node app for CLI tool", 5 | "scripts": { 6 | "test": "jest", 7 | "dev": "cross-env NODE_ENV=development nodemon ./server/index.js --watch ./server/index.js", 8 | "build": "NEXT_TELEMETRY_DISABLED=1 next build", 9 | "start": "cross-env NODE_ENV=production node ./server/index.js", 10 | "create-extension-js-bundle": "npx webpack --config webpack.config.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/Shopify/shopify-app-node.git" 15 | }, 16 | "author": "Shopify Inc.", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/shopify/shopify-app-node/issues" 20 | }, 21 | "dependencies": { 22 | "@babel/core": "7.12.10", 23 | "@babel/polyfill": "^7.6.0", 24 | "@babel/preset-env": "^7.12.11", 25 | "@babel/register": "^7.12.10", 26 | "@ethersproject/providers": "^5.5.1", 27 | "@shopify/app-bridge-react": "^2.0.2", 28 | "@shopify/app-bridge-utils": "^2.0.2", 29 | "@shopify/koa-shopify-auth": "^4.1.2", 30 | "@shopify/polaris": "^6.2.0", 31 | "@walletconnect/web3-provider": "^1.6.6", 32 | "apollo-boost": "^0.4.9", 33 | "cross-env": "^7.0.3", 34 | "dotenv": "^8.2.0", 35 | "graphql": "^14.5.8", 36 | "isomorphic-fetch": "^3.0.0", 37 | "koa": "^2.13.1", 38 | "koa-router": "^10.0.0", 39 | "koa-session": "^6.1.0", 40 | "next": "^10.0.4", 41 | "next-env": "^1.1.0", 42 | "node-fetch": "^2.6.1", 43 | "react": "^16.10.1", 44 | "react-apollo": "^3.1.3", 45 | "react-dom": "^16.10.1", 46 | "web3modal": "^1.9.4", 47 | "webpack": "^4.44.1" 48 | }, 49 | "devDependencies": { 50 | "@babel/plugin-transform-runtime": "^7.12.10", 51 | "@babel/preset-stage-3": "^7.0.0", 52 | "babel-jest": "26.6.3", 53 | "babel-register": "^6.26.0", 54 | "enzyme": "3.11.0", 55 | "enzyme-adapter-react-16": "1.15.5", 56 | "husky": "^4.3.6", 57 | "jest": "26.6.3", 58 | "lint-staged": "^10.5.3", 59 | "nodemon": "^2.0.0", 60 | "prettier": "2.2.1", 61 | "react-addons-test-utils": "15.6.2", 62 | "react-test-renderer": "16.14.0", 63 | "webpack-cli": "^4.9.1" 64 | }, 65 | "husky": { 66 | "hooks": { 67 | "pre-commit": "lint-staged" 68 | } 69 | }, 70 | "lint-staged": { 71 | "*.{js,css,json,md}": [ 72 | "prettier --write" 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shopify MetaMask (or any crypto wallet?) Integration 2 | 3 | [More details here](https://forum.developerdao.com/t/shopify-metamask-app-idea/636) 4 | 5 | ## Project will consist of 3 parts: 6 | 7 | - NextJS frontend: 8 | - An admin dashboard where shop owner configures which contract holder is eligible for discounts/custom merch 9 | - NextJS backend: 10 | - Server that stores the data 11 | - Dynamically create promo codes for discounts using Shopify API (?) - need to do some research on that 12 | - [A widget](https://shopify.dev/apps/online-store/theme-app-extensions/extensions-framework#app-embed-blocks) that 13 | - connects with [crypto wallet](https://twitter.com/developer_dao/status/1466080091327369225) 14 | - talks to server to verify if user is eligible for discounts/custom merch 15 | - fetches promo code from server and applies it to the user's cart 16 | 17 | ## Requirements 18 | 19 | - If you don’t have one, [create a Shopify partner account](https://partners.shopify.com/signup). 20 | - If you don’t have one, [create a Development store](https://help.shopify.com/en/partners/dashboard/development-stores#create-a-development-store) where you can install and test your app. 21 | - In the Partner dashboard, [create a new app](https://help.shopify.com/en/api/tools/partner-dashboard/your-apps#create-a-new-app). You’ll need this app’s API credentials during the setup process. 22 | - Download `NGROK` - is a free service that makes it easy to host your app in development. [Sign up for NGROK](https://ngrok.com/signup). 23 | 24 | ## Development 25 | 26 | - Clone the project and run `npm install` 27 | - Create `.env` from `.env.example` 28 | - Add `SHOPIFY_API_KEY=` from your Shopify App settings 29 | - Add `SHOPIFY_API_SECRET=` 30 | - Add `SHOP=.myshopify.com` 31 | - `SCOPES` will be updated, for now just leave them as in the example 32 | - Start NGROK and replace `HOST` with your `NGROK URL` 33 | 34 | In your Shopify App settings: 35 | 36 | - Set `App URL` to `NGROK URL` 37 | - Set `Allowed redirection URL(s)` to `/auth/callback`, for example: https://1231231.ngrok.io/auth/callback* 38 | 39 | > _You will need to update it every time you change `NGROK URL`_ 40 | 41 | - `npm run dev` 42 | - Click `Select store` under `Test your app` in Shopify App settings 43 | - This should prompt you to install the app in your development store and open it in an admin dashboard. Common error is `The redirect URI is not whitelisted`, in that case you need to update `NGROK URL` in `Allowed redirection URL(s)` 44 | 45 | ## Theme App Extension 46 | 47 | Example of a theme app extension: https://github.com/Shopify/theme-extension-getting-started 48 | 49 | - `cd theme-app-extension` and `shopify extension register`, choose `THEME_APP_EXTENSION` type 50 | - `npm run create-extension-js-bundle` to bundle the `crypto-wallet.js` file and put it into `theme-app-extension/assets` folder 51 | - `shopify extension push` and follow the instructions to install extension on the shopify theme or look at the Demo: https://www.loom.com/share/9c21c12fc567417e9f3e6e910b65f874 52 | - My demo shopify page: https://sergey-metamask-test.myshopify.com, store password: `rubado` 53 | 54 | WIP: 55 | https://www.loom.com/share/9cb9caccd1494387937ae0ce6b614d66 56 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | import "@babel/polyfill"; 2 | import dotenv from "dotenv"; 3 | import "isomorphic-fetch"; 4 | import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth"; 5 | import Shopify, { ApiVersion } from "@shopify/shopify-api"; 6 | import Koa from "koa"; 7 | import next from "next"; 8 | import Router from "koa-router"; 9 | 10 | dotenv.config(); 11 | const port = parseInt(process.env.PORT, 10) || 8081; 12 | const dev = process.env.NODE_ENV !== "production"; 13 | const app = next({ 14 | dev, 15 | }); 16 | const handle = app.getRequestHandler(); 17 | 18 | Shopify.Context.initialize({ 19 | API_KEY: process.env.SHOPIFY_API_KEY, 20 | API_SECRET_KEY: process.env.SHOPIFY_API_SECRET, 21 | SCOPES: process.env.SCOPES.split(","), 22 | HOST_NAME: process.env.HOST.replace(/https:\/\/|\/$/g, ""), 23 | API_VERSION: ApiVersion.October20, 24 | IS_EMBEDDED_APP: true, 25 | // This should be replaced with your preferred storage strategy 26 | SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(), 27 | }); 28 | 29 | // Storing the currently active shops in memory will force them to re-login when your server restarts. You should 30 | // persist this object in your app. 31 | const ACTIVE_SHOPIFY_SHOPS = {}; 32 | 33 | app.prepare().then(async () => { 34 | const server = new Koa(); 35 | const router = new Router(); 36 | server.keys = [Shopify.Context.API_SECRET_KEY]; 37 | server.use( 38 | createShopifyAuth({ 39 | async afterAuth(ctx) { 40 | // Access token and shop available in ctx.state.shopify 41 | const { shop, accessToken, scope } = ctx.state.shopify; 42 | const host = ctx.query.host; 43 | ACTIVE_SHOPIFY_SHOPS[shop] = scope; 44 | 45 | const response = await Shopify.Webhooks.Registry.register({ 46 | shop, 47 | accessToken, 48 | path: "/webhooks", 49 | topic: "APP_UNINSTALLED", 50 | webhookHandler: async (topic, shop, body) => 51 | delete ACTIVE_SHOPIFY_SHOPS[shop], 52 | }); 53 | 54 | if (!response.success) { 55 | console.log( 56 | `Failed to register APP_UNINSTALLED webhook: ${response.result}` 57 | ); 58 | } 59 | 60 | // Redirect to app with shop parameter upon auth 61 | ctx.redirect(`/?shop=${shop}&host=${host}`); 62 | }, 63 | }) 64 | ); 65 | 66 | const handleRequest = async (ctx) => { 67 | await handle(ctx.req, ctx.res); 68 | ctx.respond = false; 69 | ctx.res.statusCode = 200; 70 | }; 71 | 72 | router.post("/webhooks", async (ctx) => { 73 | try { 74 | await Shopify.Webhooks.Registry.process(ctx.req, ctx.res); 75 | console.log(`Webhook processed, returned status code 200`); 76 | } catch (error) { 77 | console.log(`Failed to process webhook: ${error}`); 78 | } 79 | }); 80 | 81 | router.post( 82 | "/graphql", 83 | verifyRequest({ returnHeader: true }), 84 | async (ctx, next) => { 85 | await Shopify.Utils.graphqlProxy(ctx.req, ctx.res); 86 | } 87 | ); 88 | 89 | router.get("(/_next/static/.*)", handleRequest); // Static content is clear 90 | router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear 91 | router.get("(.*)", async (ctx) => { 92 | const shop = ctx.query.shop; 93 | 94 | // This shop hasn't been seen yet, go through OAuth to create a session 95 | if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) { 96 | ctx.redirect(`/auth?shop=${shop}`); 97 | } else { 98 | await handleRequest(ctx); 99 | } 100 | }); 101 | 102 | server.use(router.allowedMethods()); 103 | server.use(router.routes()); 104 | server.listen(port, () => { 105 | console.log(`> Ready on http://localhost:${port}`); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported versions 4 | 5 | ### New features 6 | 7 | New features will only be added to the master branch and will not be made available in point releases. 8 | 9 | ### Bug fixes 10 | 11 | Only the latest release series will receive bug fixes. When enough bugs are fixed and its deemed worthy to release a new gem, this is the branch it happens from. 12 | 13 | ### Security issues 14 | 15 | Only the latest release series will receive patches and new versions in case of a security issue. 16 | 17 | ### Severe security issues 18 | 19 | For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team. 20 | 21 | ### Unsupported Release Series 22 | 23 | When a release series is no longer supported, it's your own responsibility to deal with bugs and security issues. If you are not comfortable maintaining your own versions, you should upgrade to a supported version. 24 | 25 | ## Reporting a bug 26 | 27 | All security bugs in shopify repositories should be reported to [our hackerone program](https://hackerone.com/shopify) 28 | Shopify's whitehat program is our way to reward security researchers for finding serious security vulnerabilities in the In Scope properties listed at the bottom of this page, including our core application (all functionality associated with a Shopify store, particularly your-store.myshopify.com/admin) and certain ancillary applications. 29 | 30 | ## Disclosure Policy 31 | 32 | We look forward to working with all security researchers and strive to be respectful, always assume the best and treat others as peers. We expect the same in return from all participants. To achieve this, our team strives to: 33 | 34 | - Reply to all reports within one business day and triage within two business days (if applicable) 35 | - Be as transparent as possible, answering all inquires about our report decisions and adding hackers to duplicate HackerOne reports 36 | - Award bounties within a week of resolution (excluding extenuating circumstances) 37 | - Only close reports as N/A when the issue reported is included in Known Issues, Ineligible Vulnerabilities Types or lacks evidence of a vulnerability 38 | 39 | **The following rules must be followed in order for any rewards to be paid:** 40 | 41 | - You may only test against shops you have created which include your HackerOne YOURHANDLE @ wearehackerone.com registered email address. 42 | - You must not attempt to gain access to, or interact with, any shops other than those created by you. 43 | - The use of commercial scanners is prohibited (e.g., Nessus). 44 | - Rules for reporting must be followed. 45 | - Do not disclose any issues publicly before they have been resolved. 46 | - Shopify reserves the right to modify the rules for this program or deem any submissions invalid at any time. Shopify may cancel the whitehat program without notice at any time. 47 | - Contacting Shopify Support over chat, email or phone about your HackerOne report is not allowed. We may disqualify you from receiving a reward, or from participating in the program altogether. 48 | - You are not an employee of Shopify; employees should report bugs to the internal bug bounty program. 49 | - You hereby represent, warrant and covenant that any content you submit to Shopify is an original work of authorship and that you are legally entitled to grant the rights and privileges conveyed by these terms. You further represent, warrant and covenant that the consent of no other person or entity is or will be necessary for Shopify to use the submitted content. 50 | - By submitting content to Shopify, you irrevocably waive all moral rights which you may have in the content. 51 | - All content submitted by you to Shopify under this program is licensed under the MIT License. 52 | - You must report any discovered vulnerability to Shopify as soon as you have validated the vulnerability. 53 | - Failure to follow any of the foregoing rules will disqualify you from participating in this program. 54 | 55 | ** Please see our [Hackerone Profile](https://hackerone.com/shopify) for full details 56 | 57 | ## Receiving Security Updates 58 | 59 | To recieve all general updates to vulnerabilities, please subscribe to our hackerone [Hacktivity](https://hackerone.com/shopify/hacktivity) 60 | --------------------------------------------------------------------------------