Commit 79fe63ad authored by Ates Goral's avatar Ates Goral
Browse files

Copy koa-shopify-auth from quilt

parents
node_modules
.sewing-kit
.DS_STORE
build
dist
*.mjs
*.esnext
*.js
*.d.ts
*.log
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
<!-- ## [Unreleased] -->
## [3.1.68] - 2020-08-26
- Wrap `encodeURI` values in double quotes [1613](https://github.com/Shopify/quilt/pull/1613)
## [3.1.67] - 2020-08-26
- URI encode `config` on redirection page [1612](https://github.com/Shopify/quilt/pull/1612)
## [3.1.65] - 2020-07-06
- Include `prefix` when redirect to the root endpoint [1498](https://github.com/Shopify/quilt/pull/1498)
## [3.1.63] - 2020-05-25
### Fixed
- Protect against reflected XSS vulnerability [1455](https://github.com/Shopify/quilt/pull/1455)
## [3.1.62] - 2015-05-20
### Fixed
- Include `prefix` when we redirect to the `/auth` path.
## [3.1.61] - 2020-05-01
- Fixes ITP 2.3 and Safari 13.1 enable cookies loop [1413](https://github.com/Shopify/quilt/pull/1413)
## [3.1.56] - 2020-02-03
- Package now forces cookies.secure to be true [1255](https://github.com/Shopify/quilt/pull/1255)
- Package sets cookies to samesite:none and secure [1251](https://github.com/Shopify/quilt/pull/1251)
## [3.1.54] - 2020-01-24
- Updated redirect script to use App Bridge [1242](https://github.com/Shopify/quilt/pull/1242)
## [3.1.37] - 2019-09-23
### Fixed
- No longer errors out on fresh installs with no session [1022](https://github.com/Shopify/quilt/pull/1022)
## [3.1.36] - 2019-08-30
### Fixed
- Package no longer allows sessions from one shop to bleed over into another [940](https://github.com/Shopify/quilt/pull/940)
## [3.1.32] - 2019-08-15
### Fixed
- Package now lists missing '@shopify/network' dependency [862](https://github.com/Shopify/quilt/pull/862)
## [3.1.31] - 2019-08-13
### Fixed
- Installation no longer fails if accessToken is invalid [#844](https://github.com/Shopify/quilt/pull/844)
## [3.1.14] - 2019-02-05
### Fixed
- OAuth route no longer rejects uppercase shop domains [#493](https://github.com/Shopify/quilt/pull/493)
## [3.1.11] - 2019-01-10
### Fixed
- HMAC validation no longer breaks when params are unsorted [#451](https://github.com/Shopify/quilt/pull/451)
## [3.1.10] - 2019-01-09
- Start of Changelog
# `@shopify/koa-shopify-auth`
[![Build Status](https://travis-ci.org/Shopify/quilt.svg?branch=master)](https://travis-ci.org/Shopify/quilt)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE.md) [![npm version](https://badge.fury.io/js/%40shopify%2Fkoa-shopify-auth.svg)](https://badge.fury.io/js/%40shopify%2Fkoa-shopify-auth)
Middleware to authenticate a [Koa](http://koajs.com/) application with [Shopify](https://www.shopify.ca/).
Sister module to [`@shopify/shopify-express`](https://www.npmjs.com/package/@shopify/shopify-express), but simplified.
Features you might know from the express module like the webhook middleware and proxy will be presented as their [own packages instead](https://github.com/Shopify/quilt/blob/master/packages/koa-shopify-graphql-proxy/README.md).
## Warning: 3.1.61-3.1.62 vulnerable to reflected XSS
Versions 3.1.61 and 3.1.62 are vulnerable to a reflected XSS attack. Please update to the latest version to protect your app.
## Installation
```bash
$ yarn add @shopify/koa-shopify-auth
```
## Usage
This package exposes `shopifyAuth` by default, and `verifyRequest` as a named export.
```js
import shopifyAuth, {verifyRequest} from '@shopify/koa-shopify-auth';
```
### shopifyAuth
Returns an authentication middleware taking up (by default) the routes `/auth` and `/auth/callback`.
```js
app.use(
shopifyAuth({
// if specified, mounts the routes off of the given path
// eg. /shopify/auth, /shopify/auth/callback
// defaults to ''
prefix: '/shopify',
// your shopify app api key
apiKey: SHOPIFY_API_KEY,
// your shopify app secret
secret: SHOPIFY_SECRET,
// scopes to request on the merchants store
scopes: ['write_orders, write_products'],
// set access mode, default is 'online'
accessMode: 'offline',
// callback for when auth is completed
afterAuth(ctx) {
const {shop, accessToken} = ctx.session;
console.log('We did it!', accessToken);
ctx.redirect('/');
},
}),
);
```
#### `/auth`
This route starts the oauth process. It expects a `?shop` parameter and will error out if one is not present. To install it in a store just go to `/auth?shop=myStoreSubdomain`.
### `/auth/callback`
You should never have to manually go here. This route is purely for shopify to send data back during the oauth process.
### verifyRequest
Returns a middleware to verify requests before letting them further in the chain.
```javascript
app.use(
verifyRequest({
// path to redirect to if verification fails
// defaults to '/auth'
authRoute: '/foo/auth',
// path to redirect to if verification fails and there is no shop on the query
// defaults to '/auth'
fallbackRoute: '/install',
}),
);
```
### Example app
```javascript
import 'isomorphic-fetch';
import Koa from 'koa';
import session from 'koa-session';
import shopifyAuth, {verifyRequest} from '@shopify/koa-shopify-auth';
const {SHOPIFY_API_KEY, SHOPIFY_SECRET} = process.env;
const app = new Koa();
app.keys = [SHOPIFY_SECRET];
app
// sets up secure session data on each request
.use(session({secure: true, sameSite: 'none'}, app))
// sets up shopify auth
.use(
shopifyAuth({
apiKey: SHOPIFY_API_KEY,
secret: SHOPIFY_SECRET,
scopes: ['write_orders, write_products'],
afterAuth(ctx) {
const {shop, accessToken} = ctx.session;
console.log('We did it!', accessToken);
ctx.redirect('/');
},
}),
)
// everything after this point will require authentication
.use(verifyRequest())
// application code
.use(ctx => {
ctx.body = '🎉';
});
```
## Gotchas
### Fetch
This app uses `fetch` to make requests against shopify, and expects you to have it polyfilled. The example app code above includes a call to import it.
### Session
Though you can use `shopifyAuth` without a session middleware configured, `verifyRequest` expects you to have one. If you don't want to use one and have some other solution to persist your credentials, you'll need to build your own verifiction function.
### Testing locally
By default this app requires that you use a `myshopify.com` host in the `shop` parameter. You can modify this to test against a local/staging environment via the `myShopifyDomain` option to `shopifyAuth` (e.g. `myshopify.io`).
{
"name": "@shopify/koa-shopify-auth",
"version": "3.1.68",
"license": "MIT",
"description": "Middleware to authenticate a Koa application with Shopify",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"scripts": {
"build": "tsc --p tsconfig.json"
},
"publishConfig": {
"access": "public",
"@shopify:registry": "https://registry.npmjs.org"
},
"author": "Shopify Inc.",
"repository": {
"type": "git",
"url": "git+https://github.com/Shopify/quilt.git",
"directory": "packages/koa-shopify-auth"
},
"bugs": {
"url": "https://github.com/Shopify/quilt/issues"
},
"homepage": "https://github.com/Shopify/quilt/blob/master/packages/koa-shopify-auth/README.md",
"dependencies": {
"@shopify/network": "^1.5.0",
"koa-compose": ">=3.0.0 <4.0.0",
"nonce": "^1.0.4",
"safe-compare": "^1.1.2",
"tslib": "^1.9.3"
},
"devDependencies": {
"@sewing-kit/cli": "^0.1.6",
"@sewing-kit/config": "^0.1.5",
"@sewing-kit/plugin-eslint": "^0.1.4",
"@sewing-kit/plugin-javascript": "^0.1.16",
"@sewing-kit/plugin-jest": "^0.1.19",
"@sewing-kit/plugin-package-flexible-outputs": "^0.1.19",
"@sewing-kit/plugin-typescript": "^0.1.19",
"@shopify/jest-dom-mocks": "^2.9.1",
"@shopify/jest-koa-mocks": "^2.2.3",
"@types/koa": "^2.0.0",
"@types/koa-compose": "*",
"@types/safe-compare": "^1.1.0",
"koa": "^2.5.0"
},
"sideEffects": false,
"files": [
"dist/*",
"!tsconfig.tsbuildinfo"
]
}
import {createPackage, Runtime} from '@sewing-kit/config';
import {javascript} from '@sewing-kit/plugin-javascript';
import {typescript, workspaceTypeScript} from '@sewing-kit/plugin-typescript';
import {eslint} from '@sewing-kit/plugin-eslint';
import {jest} from '@sewing-kit/plugin-jest';
import {buildFlexibleOutputs} from '@sewing-kit/plugin-package-flexible-outputs';
export default createPackage((pkg) => {
pkg.runtime(Runtime.Node);
pkg.entry({root: './src/index'});
// pkg.entry({
// name: 'matchers',
// root: './matchers.entry',
// });
// pkg.entry({
// name: 'testing',
// root: './testing.entry',
// });
pkg.use(
buildFlexibleOutputs(),
jest(),
eslint(),
javascript(),
typescript(),
workspaceTypeScript(),
);
});
// Copied from https://github.com/Shopify/shopify_app
const itpHelper = `(function() {
function ITPHelper(opts) {
this.itpContent = document.getElementById('TopLevelInteractionContent');
this.itpAction = document.getElementById('TopLevelInteractionButton');
this.redirectUrl = opts.redirectUrl;
}
ITPHelper.prototype.redirect = function() {
sessionStorage.setItem('shopify.top_level_interaction', true);
window.location.href = this.redirectUrl;
}
ITPHelper.prototype.userAgentIsAffected = function() {
return Boolean(document.hasStorageAccess);
}
ITPHelper.prototype.canPartitionCookies = function() {
var versionRegEx = /Version\\/12\\.0\\.?\\d? Safari/;
return versionRegEx.test(navigator.userAgent);
}
ITPHelper.prototype.setUpContent = function(onClick) {
this.itpContent.style.display = 'block';
this.itpAction.addEventListener('click', this.redirect.bind(this));
}
ITPHelper.prototype.execute = function() {
if (!this.itpContent) {
return;
}
if (this.userAgentIsAffected()) {
this.setUpContent();
} else {
this.redirect();
}
}
this.ITPHelper = ITPHelper;
})(window);`;
export default itpHelper;
const polarisCss = `html,
body {
min-height: 100%;
height: 100%;
font-size: 1.5rem;
font-weight: 400;
line-height: 2rem;
text-transform: initial;
letter-spacing: initial;
font-weight: 400;
color: #212b36;
font-family: -apple-system, BlinkMacSystemFont, San Francisco, Roboto,
Segoe UI, Helvetica Neue, sans-serif;
}
@media (min-width: 40em) {
html,
body {
font-size: 1.4rem;
}
}
html {
position: relative;
font-size: 62.5%;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
text-size-adjust: 100%;
text-rendering: optimizeLegibility;
}
body {
min-height: 100%;
margin: 0;
padding: 0;
background-color: #f4f6f8;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
margin: 0;
font-size: 1em;
font-weight: 400;
}
#CookiePartitionPrompt, #RequestStorageAccess {
display: none;
}
.Polaris-Page {
margin: 0 auto;
padding: 0;
max-width: 99.8rem;
}
@media (min-width: 30.625em) {
.Polaris-Page {
padding: 0 2rem;
}
}
@media (min-width: 46.5em) {
.Polaris-Page {
padding: 0 3.2rem;
}
}
.Polaris-Page__Content {
margin: 2rem 0;
}
@media (min-width: 46.5em) {
.Polaris-Page__Content {
margin-top: 2rem;
}
}
@media (min-width: 46.5em) {
.Polaris-Page {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
}
.Polaris-Layout {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
margin-top: -2rem;
margin-left: -2rem;
}
.Polaris-Layout__Section {
-webkit-box-flex: 2;
-ms-flex: 2 2 48rem;
flex: 2 2 48rem;
min-width: 51%;
}
.Polaris-Layout__Section--fullWidth {
-webkit-box-flex: 1;
-ms-flex: 1 1 100%;
flex: 1 1 100%;
}
.Polaris-Layout__Section {
max-width: calc(100% - 2rem);
margin-top: 2rem;
margin-left: 2rem;
}
.Polaris-Stack {
margin-top: -1.6rem;
margin-left: -1.6rem;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-box-align: stretch;
-ms-flex-align: stretch;
align-items: stretch;
}
.Polaris-Stack > .Polaris-Stack__Item {
margin-top: 1.6rem;
margin-left: 1.6rem;
max-width: calc(100% - 1.6rem);
}
.Polaris-Stack__Item {
-webkit-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
min-width: 0;
}
.Polaris-Heading {
font-size: 1.7rem;
font-weight: 600;
line-height: 2.4rem;
margin: 0;
}
@media (min-width: 40em) {
.Polaris-Heading {
font-size: 1.6rem;
}
}
.Polaris-Card {
overflow: hidden;
background-color: white;
box-shadow: 0 0 0 1px rgba(63, 63, 68, 0.05),
0 1px 3px 0 rgba(63, 63, 68, 0.15);
}
.Polaris-Card + .Polaris-Card {
margin-top: 2rem;
}
@media (min-width: 30.625em) {
.Polaris-Card {
border-radius: 3px;
}
}
.Polaris-Card__Header {
padding: 2rem 2rem 0;
}
.Polaris-Card__Section {
padding: 2rem;
}
.Polaris-Card__Section + .Polaris-Card__Section {
border-top: 1px solid #dfe3e8;
}
.Polaris-Card__Section--subdued {
background-color: #f9fafb;
}
.Polaris-Stack--distributionTrailing {
-webkit-box-pack: end;
-ms-flex-pack: end;
justify-content: flex-end;
}
.Polaris-Stack--vertical {
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
.Polaris-Button {
fill: #637381;
position: relative;
display: -webkit-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
min-height: 3.6rem;
min-width: 3.6rem;
margin: 0;
padding: 0.7rem 1.6rem;
background: linear-gradient(to bottom, white, #f9fafb);
border: 1px solid #c4cdd5;
box-shadow: 0 1px 0 0 rgba(22, 29, 37, 0.05);
border-radius: 3px;
line-height: 1;
color: #212b36;
text-align: center;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
text-decoration: none;
transition-property: background, border, box-shadow;
transition-duration: 200ms;
transition-timing-function: cubic-bezier(0.64, 0, 0.35, 1);
}
.Polaris-Button:hover {
background: linear-gradient(to bottom, #f9fafb, #f4f6f8);
border-color: #c4cdd5;
}
.Polaris-Button:focus {
border-color: #5c6ac4;
outline: 0;
box-shadow: 0 0 0 1px #5c6ac4;
}
.Polaris-Button:active {
background: linear-gradient(to bottom, #f4f6f8, #f4f6f8);
border-color: #c4cdd5;
box-shadow: 0 0 0 0 transparent, inset 0 1px 1px 0 rgba(99, 115, 129, 0.1),
inset 0 1px 4px 0 rgba(99, 115, 129, 0.2);
}
.Polaris-Button.Polaris-Button--disabled {
fill: #919eab;