Repository: gridaco/cors.sh Branch: main Commit: 78f4896c751a Files: 295 Total size: 447.1 KB Directory structure: gitextract_ynffi0i6/ ├── .archives/ │ ├── console/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── babel.config.js │ │ ├── logo/ │ │ │ └── index.tsx │ │ ├── next-env.d.ts │ │ ├── next.config.js │ │ ├── package.json │ │ ├── pages/ │ │ │ ├── [id].tsx │ │ │ ├── _app.tsx │ │ │ ├── index.tsx │ │ │ └── new.tsx │ │ ├── styles/ │ │ │ ├── Home.module.css │ │ │ └── globals.css │ │ └── tsconfig.json │ └── homepage/ │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── components/ │ │ ├── appbar/ │ │ │ ├── appbar-parent-site.tsx │ │ │ ├── appbar.tsx │ │ │ └── index.ts │ │ ├── button/ │ │ │ └── index.tsx │ │ ├── chatwood/ │ │ │ └── index.jsx │ │ ├── collabsible-info-card/ │ │ │ └── index.tsx │ │ ├── cta-footer/ │ │ │ └── index.tsx │ │ ├── cta-onboarding/ │ │ │ └── index.tsx │ │ ├── cta-test-it/ │ │ │ └── index.tsx │ │ ├── demo-terminal/ │ │ │ └── index.tsx │ │ ├── icons/ │ │ │ └── check-filled.tsx │ │ ├── index.ts │ │ ├── logo/ │ │ │ └── index.tsx │ │ └── pricing/ │ │ ├── card.tsx │ │ ├── free-for-opensource.tsx │ │ └── index.ts │ ├── grida/ │ │ ├── .gitkeep │ │ ├── AppbarGroup.tsx │ │ ├── SectionCtaLast.tsx │ │ ├── SectionDisclaimer.tsx │ │ ├── SectionHero.tsx │ │ ├── SectionPricing.tsx │ │ └── SectionUsage.tsx │ ├── grida.config.js │ ├── k/ │ │ ├── examples.ts │ │ ├── external-links.ts │ │ ├── host.ts │ │ ├── index.ts │ │ └── price.ts │ ├── layouts/ │ │ ├── payment-required-page.tsx │ │ ├── pricing-card-list.tsx │ │ └── step-layout.tsx │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages/ │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ ├── disclaimer.tsx │ │ ├── get-started/ │ │ │ └── index.tsx │ │ ├── index.tsx │ │ ├── oauth/ │ │ │ └── callback.tsx │ │ ├── onboarding/ │ │ │ ├── [id].tsx │ │ │ ├── complete.tsx │ │ │ ├── index.tsx │ │ │ ├── payment-success-with-issue.tsx │ │ │ └── payment-success.tsx │ │ └── too-many-requests.tsx │ ├── public/ │ │ └── robots.txt │ ├── styles/ │ │ └── globals.css │ └── tsconfig.json ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── apply-for-oss-program.yml │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .gitmodules ├── .nvmrc ├── .vscode/ │ └── settings.json ├── LICENSE ├── README.md ├── branding/ │ └── readme.md ├── cli/ │ ├── bin.ts │ ├── index.ts │ ├── jes.config.js │ ├── package.json │ ├── readme.md │ ├── tsconfig.json │ ├── update/ │ │ └── index.ts │ └── version/ │ └── index.ts ├── design/ │ └── readme.md ├── docs/ │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── blog/ │ │ ├── 2019-05-28-first-blog-post.md │ │ ├── 2019-05-29-long-blog-post.md │ │ ├── 2021-08-01-mdx-blog-post.mdx │ │ ├── 2021-08-26-welcome/ │ │ │ └── index.md │ │ └── authors.yml │ ├── docs/ │ │ ├── guides/ │ │ │ ├── _category_.json │ │ │ ├── api-instagram/ │ │ │ │ └── index.md │ │ │ ├── api-tiktok/ │ │ │ │ └── index.md │ │ │ ├── figma-plugin/ │ │ │ │ └── index.md │ │ │ └── shopify-app/ │ │ │ └── index.md │ │ ├── intro.md │ │ ├── migrate-from-cors.bridged.cc.md │ │ ├── tutorial-basics/ │ │ │ ├── _category_.json │ │ │ ├── congratulations.md │ │ │ ├── create-a-blog-post.md │ │ │ ├── create-a-document.md │ │ │ ├── create-a-page.md │ │ │ ├── deploy-your-site.md │ │ │ └── markdown-features.mdx │ │ ├── tutorial-extras/ │ │ │ ├── _category_.json │ │ │ ├── manage-docs-versions.md │ │ │ └── translate-your-site.md │ │ └── what-is-cors.md │ ├── docusaurus.config.js │ ├── package.json │ ├── sidebars.js │ ├── src/ │ │ └── css/ │ │ └── custom.css │ └── static/ │ ├── .nojekyll │ └── index.html ├── examples/ │ ├── a-simple-demo/ │ │ ├── index.html │ │ └── index.js │ └── readme.md ├── package.json ├── packages/ │ ├── api/ │ │ ├── index.ts │ │ └── package.json │ └── app-ui/ │ ├── components/ │ │ ├── api-key-reveal.tsx │ │ ├── index.ts │ │ └── underline-button.tsx │ ├── layouts/ │ │ ├── form-page-latout.ts │ │ ├── index.ts │ │ └── page-close-button.tsx │ ├── package.json │ ├── tsconfig.json │ └── utils/ │ └── index.ts ├── playground/ │ └── readme.md ├── services/ │ ├── auth.proxy.cors.sh/ │ │ ├── .gitignore │ │ ├── deploy/ │ │ │ ├── prod.sh │ │ │ └── staging.sh │ │ ├── handler.ts │ │ ├── package.json │ │ ├── readme.md │ │ └── serverless.yml │ ├── lagacy/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── lib/ │ │ │ ├── cors.ts │ │ │ └── utils.ts │ │ ├── package.json │ │ ├── serverless.yml │ │ ├── src/ │ │ │ ├── _util/ │ │ │ │ └── size.ts │ │ │ ├── app.ts │ │ │ ├── auth/ │ │ │ │ ├── _tmp_static_api_keys/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── README.md │ │ │ │ │ └── index.js │ │ │ │ ├── index.ts │ │ │ │ └── static-account-api-key-auth.ts │ │ │ ├── index.ts │ │ │ ├── limit/ │ │ │ │ ├── README.md │ │ │ │ ├── blacklist-origin.ts │ │ │ │ ├── index.ts │ │ │ │ ├── payload-limit.ts │ │ │ │ └── unknown-host-limit.ts │ │ │ └── usage/ │ │ │ ├── README.md │ │ │ ├── index.ts │ │ │ └── model.ts │ │ └── tsconfig.json │ ├── mail.cors.sh/ │ │ ├── onboarding.mjml │ │ ├── onboarding_with_payment_success.mjml │ │ └── render/ │ │ ├── onboarding.subject │ │ ├── onboarding.template.html │ │ ├── onboarding_with_payment_success.subject │ │ └── onboarding_with_payment_success.template.html │ ├── proxy.cors.sh/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── contributing.md │ │ ├── deploy/ │ │ │ ├── prod.sh │ │ │ └── staging.sh │ │ ├── docs/ │ │ │ └── README.md │ │ ├── lib/ │ │ │ ├── cors.ts │ │ │ └── utils.ts │ │ ├── package.json │ │ ├── serverless.yml │ │ ├── src/ │ │ │ ├── _util/ │ │ │ │ ├── size.ts │ │ │ │ └── x-header.ts │ │ │ ├── app.ts │ │ │ ├── auth/ │ │ │ │ ├── _tmp_static_api_keys/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── README.md │ │ │ │ │ └── index.js │ │ │ │ ├── index.ts │ │ │ │ ├── keys-synced.ts │ │ │ │ ├── keys-temp.ts │ │ │ │ ├── keys.ts │ │ │ │ └── legacy/ │ │ │ │ ├── index.ts │ │ │ │ └── static-account-api-key-auth.ts │ │ │ ├── index.ts │ │ │ ├── k/ │ │ │ │ └── index.ts │ │ │ ├── limit/ │ │ │ │ ├── README.md │ │ │ │ ├── blacklist-origin.ts │ │ │ │ ├── index.ts │ │ │ │ ├── payload-limit.ts │ │ │ │ └── rate-limit.ts │ │ │ └── usage/ │ │ │ ├── README.md │ │ │ ├── dynamo/ │ │ │ │ ├── log.ts │ │ │ │ ├── model.ts │ │ │ │ ├── readme.md │ │ │ │ └── table.yml │ │ │ ├── index.ts │ │ │ └── type.ts │ │ └── tsconfig.json │ ├── readme.md │ └── services.cors.sh/ │ ├── .gitignore │ ├── app.ts │ ├── auth/ │ │ ├── index.ts │ │ ├── jwt.ts │ │ ├── key.ts │ │ ├── middleware.ts │ │ └── readme.md │ ├── clients/ │ │ ├── index.ts │ │ ├── prisma.ts │ │ ├── ses.ts │ │ ├── slack.ts │ │ └── stripe.ts │ ├── controllers/ │ │ ├── _telemetry.ts │ │ └── applications.ts │ ├── deploy/ │ │ ├── dev.sh │ │ ├── prod.sh │ │ └── staging.sh │ ├── index.ts │ ├── jest.config.js │ ├── keygen/ │ │ └── index.ts │ ├── package.json │ ├── prisma/ │ │ └── schema.prisma │ ├── routes/ │ │ ├── applications/ │ │ │ └── index.ts │ │ ├── auth/ │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── onboarding/ │ │ │ └── index.ts │ │ ├── payments/ │ │ │ ├── index.ts │ │ │ └── readme.md │ │ └── webhooks-stripe/ │ │ └── index.ts │ ├── scripts/ │ │ └── aes-256-cbc-creds.js │ ├── serverless.yml │ ├── ses-email-templates/ │ │ └── index.js │ ├── sync/ │ │ ├── index.ts │ │ └── type.ts │ ├── test/ │ │ ├── config.env.test │ │ └── setup-tests.ts │ ├── tsconfig.json │ └── webpack.config.js └── web/ ├── .eslintrc.json ├── .gitignore ├── README.md ├── app/ │ ├── (console)/ │ │ ├── console/ │ │ │ ├── [id]/ │ │ │ │ └── page.tsx │ │ │ ├── new/ │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── (home)/ │ │ ├── contact/ │ │ │ └── page.tsx │ │ ├── get-started/ │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── onboarding/ │ │ │ ├── [id]/ │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── page.tsx │ │ ├── pricing/ │ │ │ └── page.tsx │ │ └── too-many-requests/ │ │ └── page.tsx │ └── globals.css ├── components/ │ ├── chatwoot/ │ │ └── index.jsx │ ├── collapsible-info-card.tsx │ ├── console/ │ │ └── application-list.tsx │ ├── faq/ │ │ └── index.tsx │ ├── free-for-opensource/ │ │ └── index.tsx │ ├── ga.tsx │ ├── header.tsx │ ├── home-background.tsx │ ├── home-hover-card.tsx │ ├── landing/ │ │ └── send-me-an-api-key-form.tsx │ ├── logo/ │ │ └── index.tsx │ └── pricing/ │ ├── index.tsx │ └── mini.tsx ├── k/ │ ├── examples.ts │ ├── external-links.ts │ ├── faq.json │ ├── host.ts │ ├── index.ts │ ├── plans.json │ ├── plans.test.json │ └── plans.ts ├── layouts/ │ └── payment-required-page.tsx ├── motions/ │ └── electron/ │ └── index.tsx ├── next.config.js ├── package.json ├── pages/ │ └── onboarding/ │ ├── complete.tsx │ ├── payment-success-with-issue.tsx │ └── payment-success.tsx ├── postcss.config.js ├── public/ │ └── robots.txt ├── tailwind.config.ts ├── tsconfig.json └── utils/ └── email-validation.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .archives/console/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env.local .env.development.local .env.test.local .env.production.local # vercel .vercel ================================================ FILE: .archives/console/README.md ================================================ # [cors.sh/console](https://cors.sh/console) (`8824`) ## Getting Started First, run the development server: ```bash npm run dev # or yarn dev ``` Open [http://localhost:8824](http://localhost:8824) with your browser to see the result. ## Scheduled Migration The cors.sh/console is scheduled to be migrated to grida console (console.grida.co). ================================================ FILE: .archives/console/babel.config.js ================================================ module.exports = { presets: ["next/babel", "linaria/babel"], plugins: [], }; ================================================ FILE: .archives/console/logo/index.tsx ================================================ import React from "react"; export function Logo({ color = "black", width = 104, }: { width?: React.CSSProperties["width"]; color?: React.CSSProperties["color"]; }) { return ( ); } ================================================ FILE: .archives/console/next-env.d.ts ================================================ /// /// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. ================================================ FILE: .archives/console/next.config.js ================================================ const withLinaria = require("next-linaria"); const withTM = require("next-transpile-modules")([ "@app/ui", "@cors.sh/service-api", "@editor-ui/console", ]); /** * @type {import('next').NextConfig} */ const nextconfig = { basePath: "/console", }; module.exports = withTM(withLinaria(nextconfig)); ================================================ FILE: .archives/console/package.json ================================================ { "name": "console.cors.sh", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev -p 8824", "build": "next build", "start": "next start -p 8824" }, "dependencies": { "@cors.sh/service-api": "0.0.0", "@editor-ui/console": "0.0.0", "@radix-ui/react-icons": "^1.1.1", "copy-to-clipboard": "^3.3.3", "framer-motion": "^8.1.3", "linaria": "^4.1.2", "next": "12.2.5", "next-linaria": "^0.11.0", "react": "18.2.0", "react-dom": "18.2.0", "react-hot-toast": "^2.4.0" }, "devDependencies": { "@types/node": "^18.7.15", "@types/react": "18.0.18", "next-transpile-modules": "^9.0.0", "typescript": "4.8.2" } } ================================================ FILE: .archives/console/pages/[id].tsx ================================================ import React, { useEffect } from "react"; import styled from "@emotion/styled"; import { Pencil1Icon } from "@radix-ui/react-icons"; import client, { ApplicationWithApiKey } from "@cors.sh/service-api"; import { FormPageLayout, PageCloseButton } from "@app/ui/layouts"; import { Button, TextFormField } from "@editor-ui/console"; import { Logo } from "logo"; import { UnderlineButton } from "@app/ui/components"; import { ApiKeyReveal } from "@app/ui/components"; export default function ApplicationDetailPage({ application, }: { application: ApplicationWithApiKey; }) { return (
Archive application
); } function EditableTitle({ initialValue = "" }: { initialValue?: string }) { const [editing, setEditing] = React.useState(false); const [text, setText] = React.useState(initialValue); const ref = React.useRef(null); useEffect(() => { if (editing) { // select all ref.current?.focus(); ref.current?.setSelectionRange(0, text.length); } }, [editing]); return ( setEditing(true)} onBlur={() => setEditing(false)} ref={ref} readOnly={!editing} contentEditable onChange={(e) => { setText(e.target.value); }} onKeyDown={(e) => { if (e.key === "Enter") { setEditing(false); } }} value={text} /> ); } const TitleInputWrapper = styled.div` position: relative; display: flex; align-items: center; justify-content: space-between; gap: 8px; input { width: 100%; box-sizing: border-box; font-size: 24px; font-weight: bold; border: none; text-align: center; outline: none; } .edit-button { position: absolute; right: 0; top: 0; bottom: 0; margin: auto; opacity: 0; cursor: pointer; border: none; background: none; outline: none; transition: opacity 0.2s ease; } &:hover { .edit-button { opacity: 1; } } `; export async function getServerSideProps(context: any) { const { id } = context.query; // const application = await client.getApplication(id); // return { // props: { // application, // }, // }; return { props: { application: { id, name: "my-portfolio-website", apikey_live: "prod_1223-xasx-xxe2", apikey_test: "test_xxasdj-xxd9-x2hx", }, }, }; } ================================================ FILE: .archives/console/pages/_app.tsx ================================================ import React from "react"; import { Toaster } from "react-hot-toast"; import "../styles/globals.css"; import type { AppProps } from "next/app"; function App({ Component, pageProps }: AppProps) { return ( <> ); } export default App; ================================================ FILE: .archives/console/pages/index.tsx ================================================ import React from "react"; import styled from "@emotion/styled"; import Link from "next/link"; import { Logo } from "logo"; import { FormPageLayout } from "@app/ui/layouts"; import Head from "next/head"; import { ArrowRightIcon } from "@radix-ui/react-icons"; import { UnderlineButton } from "@app/ui/components"; export default function ConsoleIndex({ applications, }: { applications: any[]; }) { return ( <> CORS.SH - Console
{applications.map((application) => ( ))}
Create new application Manage subscription
); } const ApplicationList = styled.div` display: flex; flex-direction: column; align-items: stretch; width: 100%; gap: 8px; `; function ApplicationItem({ id, name, allowedOrigins, }: { id: string; name: string; allowedOrigins: string[]; }) { return ( {name} ({id}) ); } const ItemWrap = styled.div` flex: 1; display: flex; cursor: pointer; justify-content: space-between; flex-direction: row; align-items: center; flex: none; border-radius: 4px; border: solid 1px rgba(0, 0, 0, 0.1); box-sizing: border-box; padding: 21px; &:hover { border: solid 1px rgba(0, 0, 0, 0.1); background-color: rgba(0, 0, 0, 0.02); box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); } transition: all 0.2s ease-in-out; `; export async function getServerSideProps(context: any) { // fetch all my applications return { props: { applications: [ { id: "1", name: "My app", }, { id: "2", name: "My app 2", }, ], }, }; } ================================================ FILE: .archives/console/pages/new.tsx ================================================ import React, { useEffect } from "react"; import { Button, TextFormField } from "@editor-ui/console"; import client from "@cors.sh/service-api"; import { useRouter } from "next/router"; import { FormPageLayout } from "@app/ui/layouts"; export default function NewApplicationPage() { const router = useRouter(); const [name, setName] = React.useState(""); const [allowedOrigins, setAllowedOrigins] = React.useState(""); const [isBusy, setIsBusy] = React.useState(false); const [isValid, setIsValid] = React.useState(false); const validateUrls = (urls: string) => { const lines = urls.split(",").map((line) => line.trim()); for (const line of lines) { try { new URL(line); } catch (e) { return false; } } return true; }; const onCreateNewClick = () => { setIsBusy(true); client .createApplication({ name: name, allowedOrigins: allowedOrigins .split(",") .map((origin) => origin.trim()), }) .then((r) => { router.push({ pathname: "[id]", query: { id: r.id }, }); }) .finally(() => { setIsBusy(false); }); }; useEffect(() => { setIsValid(name.length > 0 && validateUrls(allowedOrigins)); }, [name, allowedOrigins]); return (

Create new application

); } ================================================ FILE: .archives/console/styles/Home.module.css ================================================ .container { min-height: 100vh; padding: 0 0.5rem; display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh; } .main { padding: 5rem 0; flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; } .footer { width: 100%; height: 100px; border-top: 1px solid #eaeaea; display: flex; justify-content: center; align-items: center; } .footer a { display: flex; justify-content: center; align-items: center; flex-grow: 1; } .title a { color: #0070f3; text-decoration: none; } .title a:hover, .title a:focus, .title a:active { text-decoration: underline; } .title { margin: 0; line-height: 1.15; font-size: 4rem; } .title, .description { text-align: center; } .description { line-height: 1.5; font-size: 1.5rem; } .code { background: #fafafa; border-radius: 5px; padding: 0.75rem; font-size: 1.1rem; font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace; } .grid { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; max-width: 800px; margin-top: 3rem; } .card { margin: 1rem; padding: 1.5rem; text-align: left; color: inherit; text-decoration: none; border: 1px solid #eaeaea; border-radius: 10px; transition: color 0.15s ease, border-color 0.15s ease; width: 45%; } .card:hover, .card:focus, .card:active { color: #0070f3; border-color: #0070f3; } .card h2 { margin: 0 0 1rem 0; font-size: 1.5rem; } .card p { margin: 0; font-size: 1.25rem; line-height: 1.5; } .logo { height: 1em; margin-left: 0.5rem; } @media (max-width: 600px) { .grid { width: 100%; flex-direction: column; } } ================================================ FILE: .archives/console/styles/globals.css ================================================ html, body { padding: 0; margin: 0; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; } a { color: inherit; text-decoration: none; } * { box-sizing: border-box; } ================================================ FILE: .archives/console/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": false, "noImplicitAny": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "baseUrl": "." }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] } ================================================ FILE: .archives/homepage/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env.local .env.development.local .env.test.local .env.production.local # vercel .vercel # grida .env .grida ================================================ FILE: .archives/homepage/README.md ================================================ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). ## Getting Started First, run the development server: ```bash npm run dev # or yarn dev ``` Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. ## Learn More To learn more about Next.js, take a look at the following resources: - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! ## Deploy on Vercel The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. ================================================ FILE: .archives/homepage/babel.config.js ================================================ module.exports = { presets: ["next/babel", "linaria/babel"], plugins: [], }; ================================================ FILE: .archives/homepage/components/appbar/appbar-parent-site.tsx ================================================ import React from "react"; import styled from "@emotion/styled"; import Image from "next/image"; /** * `` ('appbar-parent-site') * - [Open in Figma](https://figma.com/file/aPfdtNb1aGFIN9p05cmmVY?node-id=19:1937) * - [Open in Grida](https://code.grida.co/files/aPfdtNb1aGFIN9p05cmmVY?node=19:1937) * * * --- * @example * ```tsx * import React from "react"; * * export default function () { * return ( * <> * 👇 instanciate widget like below. 👇 * * * ) * } * ``` * --- * @params {any} props - this widget does not requires props. you can pass custom dynamic props to the widget as you want (on typescript, it will raise type check issues). * --- * @preview * ![](https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/1f5abd8c-95d5-4bd8-a931-e935de073cf7) * --- * @remarks * @see {@link https://grida.co/docs} for more information. * --- * Code generated by grida.co | engine 0.0.1 (Apache-2.0) | Generated code under CC0 (public domain) *This code is free to use, modify, and redistribute. (aknowledgment is not required)* * * * ![Made with Grida](https://bridged-service-static.s3.us-west-1.amazonaws.com/branding/logo/32.png) * * */ export function AppbarParentSite() { return ( Grida Logo White Grida ); } const RootWrapperAppbarParentSite = styled.a` cursor: pointer; background-color: black; position: relative; height: 50px; `; const Home = styled.div` display: flex; justify-content: flex-start; flex-direction: row; align-items: center; gap: 8px; box-sizing: border-box; position: absolute; left: calc((calc((50% + -484px)) - 37px)); top: 12px; width: 73px; height: 26px; `; const Logo64 = styled.div` width: 25px; height: 26px; overflow: hidden; position: relative; `; const LogoShapeOnly = styled.div` width: 25px; height: 25px; position: absolute; left: 0px; top: 0px; `; const Union = styled.img` width: 25px; height: 25px; object-fit: cover; position: absolute; left: 0px; top: 0px; `; const Grida = styled.span` color: white; text-overflow: ellipsis; font-size: 16px; font-family: Helvetica, sans-serif; font-weight: 700; letter-spacing: -1px; text-align: left; `; ================================================ FILE: .archives/homepage/components/appbar/appbar.tsx ================================================ import React from "react"; import styled from "@emotion/styled"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; /** * `` ('appbar') * - [Open in Figma](https://figma.com/file/aPfdtNb1aGFIN9p05cmmVY?node-id=18:1889) * - [Open in Grida](https://code.grida.co/files/aPfdtNb1aGFIN9p05cmmVY?node=18:1889) * * * --- * @example * ```tsx * import React from "react"; * * export default function () { * return ( * <> * 👇 instanciate widget like below. 👇 * * * ) * } * ``` * --- * @params {any} props - this widget does not requires props. you can pass custom dynamic props to the widget as you want (on typescript, it will raise type check issues). * --- * @preview * ![](https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/d8000780-f44d-4ccf-b5da-f132856fc5aa) * --- * @remarks * @see {@link https://grida.co/docs} for more information. * --- * Code generated by grida.co | engine 0.0.1 (Apache-2.0) | Generated code under CC0 (public domain) *This code is free to use, modify, and redistribute. (aknowledgment is not required)* * * * ![Made with Grida](https://bridged-service-static.s3.us-west-1.amazonaws.com/branding/logo/32.png) * * */ export function Appbar() { const router = useRouter(); const onGetStartedClick = () => { router.push("/get-started"); }; return ( CORS.SH Logo (svg) Get started ); } const MenuItem = ({ label, href }: { label: string; href: string }) => { return ( {/* */} {label} {/* */} ); }; const RootWrapperAppbar = styled.header` background-color: white; position: relative; z-index: 9; height: 80px; `; const ContentFrame = styled.div` width: 1040px; overflow: hidden; position: absolute; left: calc((calc((50% + 0px)) - 520px)); top: 0px; bottom: 0px; `; const Leading = styled.div` display: flex; justify-content: flex-start; flex-direction: row; align-items: center; gap: 120px; box-sizing: border-box; position: absolute; left: 0px; top: calc((calc((50% + 0px)) - 30px)); width: 732px; height: 60px; `; const Home = styled.div` display: flex; justify-content: flex-start; flex-direction: row; align-items: center; flex: none; gap: 8px; box-sizing: border-box; `; const Menus = styled.nav` display: flex; justify-content: flex-start; flex-direction: row; align-items: center; flex: none; gap: 36px; height: 60px; box-sizing: border-box; `; const MenuContainer = styled(Link)` display: flex; justify-content: flex-start; flex-direction: row; align-items: flex-start; flex: none; box-sizing: border-box; `; const BaseHeaderPrimaryMenu = styled.div` display: flex; justify-content: flex-start; flex-direction: column; align-items: flex-start; flex: none; gap: 16px; box-sizing: border-box; `; const MenuMargin = styled.div` height: 2px; background-color: white; align-self: stretch; flex-shrink: 0; opacity: 0; `; const MenuTextContainer = styled.div` display: flex; justify-content: center; flex-direction: row; align-items: center; flex: none; box-sizing: border-box; cursor: pointer; `; const MenuText = styled.span` color: rgb(139, 139, 139); text-overflow: ellipsis; font-size: 16px; font-family: "Helvetica Neue", sans-serif; font-weight: 500; text-align: left; `; const HeaderCtaSignupPlain = styled.div` display: flex; justify-content: flex-start; flex-direction: row; align-items: center; position: absolute; top: calc((calc((50% + 0px)) - 16px)); right: 0px; min-width: 80px; height: 32px; `; const HeaderCtaSignupPlain_0001 = styled.div` display: flex; justify-content: center; flex-direction: row; align-items: center; gap: 10px; border-radius: 35px; box-sizing: border-box; `; const GetStarted = styled.span` cursor: pointer; color: black; text-overflow: ellipsis; font-size: 16px; font-family: Roboto, sans-serif; font-weight: 500; text-align: right; `; ================================================ FILE: .archives/homepage/components/appbar/index.ts ================================================ export * from "./appbar"; export * from "./appbar-parent-site"; ================================================ FILE: .archives/homepage/components/button/index.tsx ================================================ import React, { forwardRef } from "react"; import styled from "@emotion/styled"; type Props = React.PropsWithRef< { onClick?: () => void; } & React.PropsWithChildren >; function _Button({ children, // @ts-ignore ref, onClick, }: Props) { return ( {children} ); } export const Button = forwardRef(_Button); const ButtonBase = styled.button` background-color: black; border-radius: 2px; padding: 10px; color: white; font-size: 14px; font-family: Inter, sans-serif; font-weight: 400; border: none; outline: none; cursor: pointer; :hover { opacity: 0.8; } :disabled { opacity: 0.5; } :active { opacity: 1; } :focus { } `; ================================================ FILE: .archives/homepage/components/chatwood/index.jsx ================================================ import React from "react"; class ChatwootWidget extends React.Component { componentDidMount() { // Add Chatwoot Settings window.chatwootSettings = { hideMessageBubble: false, position: "right", // This can be left or right locale: "en", // Language to be set type: "standard", // [standard, expanded_bubble] }; // Paste the script from inbox settings except the ); } function SeoMeta() { return ( <> ); } ================================================ FILE: .archives/homepage/pages/_document.tsx ================================================ import { Head, Html, Main, NextScript } from "next/document"; export default function _Document() { return ( {/* roboto mono */} {/* inter */} {/* Nanum Pen Script */}
); } ================================================ FILE: .archives/homepage/pages/disclaimer.tsx ================================================ import React from "react"; export default function DisclaimerPage() { return <>Disclaimer; } ================================================ FILE: .archives/homepage/pages/get-started/index.tsx ================================================ import React, { useCallback, useRef, useEffect } from "react"; import { useRouter } from "next/router"; import { FormPageLayout } from "@app/ui/layouts"; import { validateUrls } from "@app/ui/utils"; import { Button, FormFieldBase, FormFieldLabel, TextFormField, } from "@editor-ui/console"; import Select from "react-select"; import client from "@cors.sh/service-api"; import * as k from "../../k"; import { toast } from "react-hot-toast"; export default function GetstartedPage({ price: _price }: { price: string }) { const [name, setName] = React.useState(""); const [price, setPrice] = React.useState(_price); const [allowedOrigins, setAllowedOrigins] = React.useState(""); const [valid, setValid] = React.useState(false); const [isBusy, setIsBusy] = React.useState(false); const [isValid, setIsValid] = React.useState(false); const stateRef = useRef(_price); stateRef.current = price; const router = useRouter(); const pricing_options = Object.values(pricing); const onPriceChange = (price: string) => { setPrice(price); }; const onTypeKey = (key: string) => { // todo: validate key via api setValid(key.length > 10); }; const onEnter = () => { // this is required because onEnter can also be invoked from input's callback if (valid) { onNextClick(); } }; useEffect(() => { setIsValid(name.length > 0 && validateUrls(allowedOrigins)); }, [name, allowedOrigins]); const onNextClick = useCallback(async () => { setIsBusy(true); try { // log begin_checkout event const pricedata = pricing[price]; // @ts-ignore window.gtag("event", "begin_checkout", { value: pricedata.value, currency: pricedata.currency, items: [ { item_id: pricedata.id, item_name: pricedata.label, }, ], }); } catch (e) {} try { // create onboarding application const form = { name: name ? name : undefined, allowedOrigins: allowedOrigins .split(",") .map((x) => x.trim()) .filter(Boolean), priceId: price, }; const application = await client.onboardingWithForm(form); const onboarding_id = application.id; // redirect user to payment page let redirect; switch (stateRef.current) { case k.PRICE_PAY_AS_YOU_GO: { // TODO: add stripe integration. redirect = "https://forms.gle/GXDGPAoM9fhZrQh77"; break; } case k.PRICE_FREE_MONTHLY: { // the free plan does not require payments, so we can skip to create new project right away. redirect = window.location.protocol + window.location.host + "/console/new"; break; } default: { let params = new URLSearchParams(); params.append("onboarding_id", onboarding_id); // TODO: multiple search params not supported by accounts.grida.co?redirect_uri=x redirect = k.SERVER_URL + "/payments/checkout/new" + "?" + params; break; } } router.replace(redirect); } catch (e) { toast.error("Oops. something went wrong. please try again."); setIsBusy(false); } }, [name, allowedOrigins, price]); return (

Get started

Ready to use cors.sh? select your plan and let’s create your first project.

you can update the fields later

{/* pricing plan select */} Plan
{ if (e.key === "Enter") { onEnter(); } }} options={[ { value: "pro-monthly", label: "Pro - $4/Mo" }, { value: "pro-yearly", label: "Pro - $3/Mo (Pay annualy, save $12)", }, ]} defaultValue={{ value: "pro", label: "Pro - $4/Mo" }} />
); } ================================================ FILE: .archives/homepage/pages/onboarding/payment-success-with-issue.tsx ================================================ import React from "react"; import client from "@cors.sh/service-api"; import { Button } from "@editor-ui/console"; import Head from "next/head"; export default function PaymentSuccessButThereWasAProblem({ error, message, session, application, }: { error: "identity_conflict" | string; message: string; session: string; application: { id: string; name: string; }; }) { return ( <> CORS.SH - Problem with your subscription

There was a problem with your subscription - "{error}"

{message}

Your Information

Copy the data below when you contact customer support

session: {session}
            application: {application.id} ({application.name})
          
); } export async function getServerSideProps(context: any) { const { error, message, session_id, application_id, onboarding_id } = context.query; if (!session_id || !application_id || !onboarding_id) { // invalid entry return { redirect: { destination: "/", permanent: false, }, }; } try { const application = await client.getOnboardingApplication(onboarding_id); return { props: { error, message, session: session_id || null, application, }, }; } catch (e) { // 404 return { notFound: true, }; } } ================================================ FILE: .archives/homepage/pages/onboarding/payment-success.tsx ================================================ import React, { useEffect } from "react"; import client from "@cors.sh/service-api"; import { useRouter } from "next/router"; import Head from "next/head"; import { Button, TextFormField } from "@editor-ui/console"; import { FormPageLayout, PageCloseButton } from "@app/ui/layouts"; import { toast } from "react-hot-toast"; // page redirected from stripe once the payment is successful export default function PaymentSuccessPage({ application, session, isOnboarding, }: { session: string; isOnboarding: boolean; application: { id: string; name: string; allowedOrigins: string[]; }; }) { const [isBusy, setBusy] = React.useState(false); const router = useRouter(); useEffect(() => { // GA4 conversion - Purchase // @ts-ignore window.gtag("event", "purchase", { transaction_id: session, value: 4, currency: "USD", // }); }, []); const onNext = () => { setBusy(true); // convert to application. client .convertApplication(application.id, session) .then((d) => { // move to complete router.push({ pathname: "/onboarding/complete", query: { app: d.id, checkout_session_id: session, }, }); }) .catch((e) => { toast.error("Something went wrong. Please try again later."); }) .finally(() => { setBusy(false); }); }; return ( <> CORS.SH - Complete <>

Thank you for your subscription

You can now create as many project you want without unlimited hourly rate :)

Let’s finish up your first project.

); } export async function getServerSideProps(context: any) { const { session_id, application_id, customer_id, onboarding_id } = context.query; if (!session_id) { // invalid entry return { redirect: { destination: "/", permanent: false, }, }; } if (!onboarding_id) { return { redirect: { destination: "/console", permanent: false, }, }; } try { const application = await client.getOnboardingApplication(onboarding_id); return { props: { session: session_id || null, application, customer_id, }, }; } catch (e) { // 404 return { notFound: true, }; } } ================================================ FILE: .archives/homepage/pages/too-many-requests.tsx ================================================ import React from "react"; import { PricingCardsList } from "../layouts/pricing-card-list"; import { StepLayout } from "../layouts/step-layout"; import * as k from "../k"; export default function TooMayRequestsPage() { return (
{}} nextPromptLabel="Sign in with Grida and continue" > {}} />
); } ================================================ FILE: .archives/homepage/public/robots.txt ================================================ # Block all crawlers for /console User-agent: * Disallow: /console # Allow all crawlers User-agent: * Allow: / ================================================ FILE: .archives/homepage/styles/globals.css ================================================ html, body { padding: 0; margin: 0; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; } h1, h2, h3, h4, h5, h6, p { margin: 0; } a { color: inherit; text-decoration: none; } * { box-sizing: border-box; } ================================================ FILE: .archives/homepage/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "baseUrl": "." }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] } ================================================ FILE: .github/ISSUE_TEMPLATE/apply-for-oss-program.yml ================================================ name: Apply for OSS Program description: Apply your OSS Project for free pro-plan of cors.sh title: "[Application] for my OSS Project" labels: [application] assignees: [softmarshmallow] body: - type: checkboxes attributes: label: You are applying for validated OSS program description: Please ensure your project is open to public. options: - label: My project is OSS licensed and open to public. (Must have LICENSE file) required: true - label: My project has more than **10 ⭐️stars** on Github, which is the required for this program. required: true - type: textarea attributes: label: Repository URL to your project description: Copy & paste url to your project repo validations: required: true - type: textarea attributes: label: URL to your website description: Please let us know the url to your website if you have one. validations: required: false - type: textarea attributes: label: Expected Quota of usage description: How much API calls do you expect to occur via your site? validations: required: true - type: textarea attributes: label: Details description: | examples: - **Category**: Blockchain - **License**: MIT - **Date of release**: 12/25/2023 value: | - Category: - License: - Date of release: render: markdown validations: required: false - type: textarea attributes: label: Anything else? description: | Links? References? Anything that will give us more context about the project! Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. validations: required: false - type: markdown attributes: value: | Before you continue.. - I made my repo open to public - My repo clearly states the license - I will **star** cors.sh (this repo) - I will follow Grida on Github ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* package-lock.json # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test # parcel-bundler cache (https://parceljs.org/) .cache # Next.js build output .next # Nuxt.js build / generate output .nuxt dist # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and *not* Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port # .DS_Store .DS_Store ================================================ FILE: .gitmodules ================================================ [submodule "packages/editor-ui"] path = packages/editor-ui url = https://github.com/reflect-ui/reflect-editor-ui ================================================ FILE: .nvmrc ================================================ v20.9.0 ================================================ FILE: .vscode/settings.json ================================================ { "files.exclude": { "**/.git": true, "**/.svn": true, "**/.hg": true, "**/CVS": true, "**/.DS_Store": true, "**/Thumbs.db": true, "**/node_modules": true }, "search.exclude": { "**/node_modules": true, "**/.build": true, "**/.lock": true, "**/*.code-search": true } } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2022 Grida Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # cors.sh ![cors.sh](./branding/artwork_cors.sh.jpg) The only cors proxy service all you'll ever need. | [Website](https://cors.sh) | [Proxy (proxy.cors.sh)](https://proxy.cors.sh) | [Playground](https://cors.sh/playground) | [Docs](https://cors.sh/docs) | ## Usage **Quick testing** Use [cors.sh/playground](https://cors.sh/playground) for testing out your cors blocked request. **JS** Add `proxy.cors.sh` to your request. For example, ```js fetch("https://proxy.cors.sh/https://example.com/"); ``` ## [`cors.sh/playground`](https://cors.sh/playground), The testing environment (forked from hoppscotch) - [cors.sh/playground](https://cors.sh/playground) - [gridaco/playground.cors.sh](https://github.com/gridaco/playground.cors.sh) - [hoppscotch/hoppscotch](https://github.com/hoppscotch/hoppscotch) ## Projects using CORS.SH - https://github.com/IMGROOT2/algo - https://github.com/alejandroch1202/dollar-monitor - https://github.com/Iconem/search-satellite-imagery/ - https://github.com/PavelLaptev/JSON-to-Figma - https://github.com/TomRadford/shootdrop ## Contributing ```bash # clone initially git clone --recurse-submodules https://github.com/gridaco/cors.sh # updating submodules (once required) git submodule update --init --recursive ``` ## Disclaimer 1. This project's intend is to serve developers a reliable cors proxy service with fast response for their development. Using a cors proxy service to connect to your own server is not a best practice. We'll consistently optimize our service infra to keep the paid version affordable as possible. 2. The original code behind cors proxy is by Rob wu's cors-anywhere and the playground is forked from hoppscotch. both licensed under MIT, and our project cors.sh is also licensed under MIT License. ## TODOs - Cost optimization - make it more cheap & provide free version to all. - Management console - Enable usesrs to create projects as much as they want. - OSS Application pipeline - Make OSS developers to get their api key right-on and get verified later. ================================================ FILE: branding/readme.md ================================================ # branding resources accross the site - favicon.ico - for `/`, `/docs`, `/playground` ================================================ FILE: cli/bin.ts ================================================ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import { checkForUpdate } from "./update"; export default async function cli() { await checkForUpdate(); yargs(hideBin(process.argv)) .option("cwd", { type: "string", default: process.cwd(), requiresArg: false, }) .option("dry-run", { type: "boolean", default: false, requiresArg: false, }) .global(["cwd", "dry-run"]) .command( "init", "init grida project", () => {}, ({ cwd }) => { // init(cwd); } ) .demandCommand(1) .parse(); } ================================================ FILE: cli/index.ts ================================================ #!/usr/bin/env node import cli from "./bin"; process.on("SIGINT", () => { process.exit(0); // now the "exit" event will fire }); // if main if (require.main === module) { cli(); } ================================================ FILE: cli/jes.config.js ================================================ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ module.exports = { preset: "ts-jest", testEnvironment: "node", }; ================================================ FILE: cli/package.json ================================================ { "name": "@cors.sh/cli", "version": "0.0.1", "private": false, "license": "Apache-2.0", "description": "cors.sh cli", "homepage": "https://cors.sh", "repository": "https://github.com/gridaco/cors.sh", "dependencies": { "@base-sdk-fp/auth": "^0.1.4", "boxen": "^7.0.0", "dotenv": "^16.0.1", "enquirer": "^2.3.6", "keytar": "^7.9.0", "node-fetch": "^3.2.10", "node-machine-id": "^1.1.12", "open": "^8.4.0", "ora": "^5.4.0", "yargs": "^17.2.1" }, "scripts": { "clean": "rimraf dist", "dev": "ts-node index.ts", "dev:watch": "ts-node-dev index.ts --watch", "test": "jest", "build": "ncc build index.ts -o dist -e keytar -e glob -e dotenv", "prepack": "yarn test && yarn clean && yarn build" }, "devDependencies": { "@types/glob": "^7.2.0", "@types/node": "^18.6.1", "@types/semver": "^7.3.10", "@types/which": "^2.0.1", "@types/yargs": "^17.0.3", "@vercel/ncc": "^0.34.0", "jest": "^28.1.3", "ts-jest": "^28.0.7", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", "typescript": "^4.7.4" }, "bin": { "cors": "./dist/index.js" }, "files": [ "dist", "README.md", "LICENSE" ], "publishConfig": { "access": "public" } } ================================================ FILE: cli/readme.md ================================================ # cors.sh management cli - cors list project - cors new project - cors edit project `` ================================================ FILE: cli/tsconfig.json ================================================ { "compilerOptions": { "target": "ES2015", "outDir": "dist", "module": "CommonJS", "strict": false, "resolveJsonModule": true, "esModuleInterop": true, "moduleResolution": "node" }, "exclude": ["dist", "node_modules", "**/*.spec.ts", "__test__"] } ================================================ FILE: cli/update/index.ts ================================================ import { version, name } from "../version"; import boxen from "boxen"; import semver from "semver"; import fetch from "node-fetch"; import chalk from "chalk"; /** * */ export async function checkForUpdate() { // fetch latest version from npm const { version: latestVersion } = (await ( await fetch(`https://registry.npmjs.org/${name}/latest`) ).json()) as { version: string }; // compare versions if (semver.gt(latestVersion, version)) { // show update message console.log(makeUpdateMessage({ latest: latestVersion })); } } function makeUpdateMessage({ latest }: { latest: string }): string { return boxen( `${name} ${chalk.green(`v${latest}`)} is now available! Run \`${chalk.green(`npm i -g ${name}`)}\` to update.`, { padding: 1, margin: 1, borderStyle: "classic", textAlignment: "center", } ); } ================================================ FILE: cli/version/index.ts ================================================ import { version, name } from "../package.json"; export { version, name }; ================================================ FILE: design/readme.md ================================================ # cors.sh design ## Figma file - https://www.figma.com/file/aPfdtNb1aGFIN9p05cmmVY/cors.sh ================================================ FILE: docs/.gitignore ================================================ # Dependencies /node_modules # Production /build # Generated files .docusaurus .cache-loader # Misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: docs/README.md ================================================ # Website This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. ### Installation ``` $ yarn ``` ### Local Development ``` $ yarn start ``` This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. ### Build ``` $ yarn build ``` This command generates static content into the `build` directory and can be served using any static contents hosting service. ### Deployment Using SSH: ``` $ USE_SSH=true yarn deploy ``` Not using SSH: ``` $ GIT_USER= yarn deploy ``` If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. ================================================ FILE: docs/babel.config.js ================================================ module.exports = { presets: [require.resolve('@docusaurus/core/lib/babel/preset')], }; ================================================ FILE: docs/blog/2019-05-28-first-blog-post.md ================================================ --- slug: first-blog-post title: First Blog Post authors: name: Gao Wei title: Docusaurus Core Team url: https://github.com/wgao19 image_url: https://github.com/wgao19.png tags: [hola, docusaurus] --- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet ================================================ FILE: docs/blog/2019-05-29-long-blog-post.md ================================================ --- slug: long-blog-post title: Long Blog Post authors: endi tags: [hello, docusaurus] --- This is the summary of a very long blog post, Use a `` comment to limit blog post size in the list view. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet ================================================ FILE: docs/blog/2021-08-01-mdx-blog-post.mdx ================================================ --- slug: mdx-blog-post title: MDX Blog Post authors: [slorber] tags: [docusaurus] --- Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). :::tip Use the power of React to create interactive blog posts. ```js ``` ::: ================================================ FILE: docs/blog/2021-08-26-welcome/index.md ================================================ --- slug: welcome title: Welcome authors: [slorber, yangshun] tags: [facebook, hello, docusaurus] --- [Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog). Simply add Markdown files (or folders) to the `blog` directory. Regular blog authors can be added to `authors.yml`. The blog post date can be extracted from filenames, such as: - `2019-05-30-welcome.md` - `2019-05-30-welcome/index.md` A blog post folder can be convenient to co-locate blog post images: ![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg) The blog supports tags as well! **And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config. ================================================ FILE: docs/blog/authors.yml ================================================ endi: name: Endilie Yacop Sucipto title: Maintainer of Docusaurus url: https://github.com/endiliey image_url: https://github.com/endiliey.png yangshun: name: Yangshun Tay title: Front End Engineer @ Facebook url: https://github.com/yangshun image_url: https://github.com/yangshun.png slorber: name: Sébastien Lorber title: Docusaurus maintainer url: https://sebastienlorber.com image_url: https://github.com/slorber.png ================================================ FILE: docs/docs/guides/_category_.json ================================================ { "label": "Guides", "position": 4, "link": { "type": "generated-index", "description": "Popular use cases of cors proxy" } } ================================================ FILE: docs/docs/guides/api-instagram/index.md ================================================ --- sidebar_position: 9 --- # For instagram api ================================================ FILE: docs/docs/guides/api-tiktok/index.md ================================================ --- sidebar_position: 9 --- # For tiktok api ================================================ FILE: docs/docs/guides/figma-plugin/index.md ================================================ --- title: For Figma plugin sidebar_position: 1 --- # CORS Proxy for developing your figma plugin ## Examples Figma Assistant by Grida We've initially made cors.sh for use of our own. The assistant uses cors.sh for requesting to our server. ================================================ FILE: docs/docs/guides/shopify-app/index.md ================================================ --- title: For Shopify app sidebar_position: 2 --- # CORS Proxy for developing your shopify app Shopify app ================================================ FILE: docs/docs/intro.md ================================================ --- sidebar_position: 1 --- # Tutorial Intro Let's discover **Docusaurus in less than 5 minutes**. ## Getting Started Get started by **creating a new site**. Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**. ### What you'll need - [Node.js](https://nodejs.org/en/download/) version 16.14 or above: - When installing Node.js, you are recommended to check all checkboxes related to dependencies. ## Generate a new site Generate a new Docusaurus site using the **classic template**. The classic template will automatically be added to your project after you run the command: ```bash npm init docusaurus@latest my-website classic ``` You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor. The command also installs all necessary dependencies you need to run Docusaurus. ## Start your site Run the development server: ```bash cd my-website npm run start ``` The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there. The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/. Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes. ================================================ FILE: docs/docs/migrate-from-cors.bridged.cc.md ================================================ # Migrate from cors.bridged.cc If you are a `cors.bridged.cc` user, you will have api key in shape like this `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` You can use the same api key for `cors.sh`, but this will no longer work from October 30, 2022. To migrate, create new app from cors.sh console and replace the api key from your web app project. **ACTION REQUIRED:** - register new app on cors.sh - replace the existing api key in your web project issued from cors.sh on previous step - replace `cors.bridged.cc` to `api.cors.sh` For example with cURL, Previous ```curl GET https://cors.bridged.cc/https://google.com -H 'cors-api-key: xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' ``` To.. ```curl GET https://api.cors.sh/https://google.com -H 'cors-api-key: live_xxxx-xxxx-xxxx-xxxx' ``` ================================================ FILE: docs/docs/tutorial-basics/_category_.json ================================================ { "label": "Tutorial - Basics", "position": 2, "link": { "type": "generated-index", "description": "5 minutes to learn the most important Docusaurus concepts." } } ================================================ FILE: docs/docs/tutorial-basics/congratulations.md ================================================ --- sidebar_position: 6 --- # Congratulations! You have just learned the **basics of Docusaurus** and made some changes to the **initial template**. Docusaurus has **much more to offer**! Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**. Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610) ## What's next? - Read the [official documentation](https://docusaurus.io/). - Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout) - Add a [search bar](https://docusaurus.io/docs/search) - Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase) - Get involved in the [Docusaurus Community](https://docusaurus.io/community/support) ================================================ FILE: docs/docs/tutorial-basics/create-a-blog-post.md ================================================ --- sidebar_position: 3 --- # Create a Blog Post Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed... ## Create your first Post Create a file at `blog/2021-02-28-greetings.md`: ```md title="blog/2021-02-28-greetings.md" --- slug: greetings title: Greetings! authors: - name: Joel Marcey title: Co-creator of Docusaurus 1 url: https://github.com/JoelMarcey image_url: https://github.com/JoelMarcey.png - name: Sébastien Lorber title: Docusaurus maintainer url: https://sebastienlorber.com image_url: https://github.com/slorber.png tags: [greetings] --- Congratulations, you have made your first post! Feel free to play around and edit this post as much you like. ``` A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings). ================================================ FILE: docs/docs/tutorial-basics/create-a-document.md ================================================ --- sidebar_position: 2 --- # Create a Document Documents are **groups of pages** connected through: - a **sidebar** - **previous/next navigation** - **versioning** ## Create your first Doc Create a Markdown file at `docs/hello.md`: ```md title="docs/hello.md" # Hello This is my **first Docusaurus document**! ``` A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello). ## Configure the Sidebar Docusaurus automatically **creates a sidebar** from the `docs` folder. Add metadata to customize the sidebar label and position: ```md title="docs/hello.md" {1-4} --- sidebar_label: 'Hi!' sidebar_position: 3 --- # Hello This is my **first Docusaurus document**! ``` It is also possible to create your sidebar explicitly in `sidebars.js`: ```js title="sidebars.js" module.exports = { tutorialSidebar: [ { type: 'category', label: 'Tutorial', // highlight-next-line items: ['hello'], }, ], }; ``` ================================================ FILE: docs/docs/tutorial-basics/create-a-page.md ================================================ --- sidebar_position: 1 --- # Create a Page Add **Markdown or React** files to `src/pages` to create a **standalone page**: - `src/pages/index.js` → `localhost:3000/` - `src/pages/foo.md` → `localhost:3000/foo` - `src/pages/foo/bar.js` → `localhost:3000/foo/bar` ## Create your first React Page Create a file at `src/pages/my-react-page.js`: ```jsx title="src/pages/my-react-page.js" import React from 'react'; import Layout from '@theme/Layout'; export default function MyReactPage() { return (

My React page

This is a React page

); } ``` A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page). ## Create your first Markdown Page Create a file at `src/pages/my-markdown-page.md`: ```mdx title="src/pages/my-markdown-page.md" # My Markdown page This is a Markdown page ``` A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page). ================================================ FILE: docs/docs/tutorial-basics/deploy-your-site.md ================================================ --- sidebar_position: 5 --- # Deploy your site Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**). It builds your site as simple **static HTML, JavaScript and CSS files**. ## Build your site Build your site **for production**: ```bash npm run build ``` The static files are generated in the `build` folder. ## Deploy your site Test your production build locally: ```bash npm run serve ``` The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/). You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**). ================================================ FILE: docs/docs/tutorial-basics/markdown-features.mdx ================================================ --- sidebar_position: 4 --- # Markdown Features Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**. ## Front Matter Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/): ```text title="my-doc.md" // highlight-start --- id: my-doc-id title: My document title description: My document description slug: /my-custom-url --- // highlight-end ## Markdown heading Markdown text with [links](./hello.md) ``` ## Links Regular Markdown links are supported, using url paths or relative file paths. ```md Let's see how to [Create a page](/create-a-page). ``` ```md Let's see how to [Create a page](./create-a-page.md). ``` **Result:** Let's see how to [Create a page](./create-a-page.md). ## Images Regular Markdown images are supported. You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`): ```md ![Docusaurus logo](/img/docusaurus.png) ``` ![Docusaurus logo](/img/docusaurus.png) You can reference images relative to the current file as well, as shown in [the extra guides](../tutorial-extras/manage-docs-versions.md). ## Code Blocks Markdown code blocks are supported with Syntax highlighting. ```jsx title="src/components/HelloDocusaurus.js" function HelloDocusaurus() { return (

Hello, Docusaurus!

) } ``` ```jsx title="src/components/HelloDocusaurus.js" function HelloDocusaurus() { return

Hello, Docusaurus!

; } ``` ## Admonitions Docusaurus has a special syntax to create admonitions and callouts: :::tip My tip Use this awesome feature option ::: :::danger Take care This action is dangerous ::: :::tip My tip Use this awesome feature option ::: :::danger Take care This action is dangerous ::: ## MDX and React Components [MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**: ```jsx export const Highlight = ({children, color}) => ( { alert(`You clicked the color ${color} with label ${children}`) }}> {children} ); This is Docusaurus green ! This is Facebook blue ! ``` export const Highlight = ({children, color}) => ( { alert(`You clicked the color ${color} with label ${children}`); }}> {children} ); This is Docusaurus green ! This is Facebook blue ! ================================================ FILE: docs/docs/tutorial-extras/_category_.json ================================================ { "label": "Tutorial - Extras", "position": 3, "link": { "type": "generated-index" } } ================================================ FILE: docs/docs/tutorial-extras/manage-docs-versions.md ================================================ --- sidebar_position: 1 --- # Manage Docs Versions Docusaurus can manage multiple versions of your docs. ## Create a docs version Release a version 1.0 of your project: ```bash npm run docusaurus docs:version 1.0 ``` The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created. Your docs now have 2 versions: - `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs - `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs** ## Add a Version Dropdown To navigate seamlessly across versions, add a version dropdown. Modify the `docusaurus.config.js` file: ```js title="docusaurus.config.js" module.exports = { themeConfig: { navbar: { items: [ // highlight-start { type: 'docsVersionDropdown', }, // highlight-end ], }, }, }; ``` The docs version dropdown appears in your navbar: ![Docs Version Dropdown](./img/docsVersionDropdown.png) ## Update an existing version It is possible to edit versioned docs in their respective folder: - `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello` - `docs/hello.md` updates `http://localhost:3000/docs/next/hello` ================================================ FILE: docs/docs/tutorial-extras/translate-your-site.md ================================================ --- sidebar_position: 2 --- # Translate your site Let's translate `docs/intro.md` to French. ## Configure i18n Modify `docusaurus.config.js` to add support for the `fr` locale: ```js title="docusaurus.config.js" module.exports = { i18n: { defaultLocale: 'en', locales: ['en', 'fr'], }, }; ``` ## Translate a doc Copy the `docs/intro.md` file to the `i18n/fr` folder: ```bash mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/ cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md ``` Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French. ## Start your localized site Start your site on the French locale: ```bash npm run start -- --locale fr ``` Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated. :::caution In development, you can only use one locale at a same time. ::: ## Add a Locale Dropdown To navigate seamlessly across languages, add a locale dropdown. Modify the `docusaurus.config.js` file: ```js title="docusaurus.config.js" module.exports = { themeConfig: { navbar: { items: [ // highlight-start { type: 'localeDropdown', }, // highlight-end ], }, }, }; ``` The locale dropdown now appears in your navbar: ![Locale Dropdown](./img/localeDropdown.png) ## Build your localized site Build your site for a specific locale: ```bash npm run build -- --locale fr ``` Or build your site to include all the locales at once: ```bash npm run build ``` ================================================ FILE: docs/docs/what-is-cors.md ================================================ --- title: What is CORS? --- # What is `CORS` ? `CORS` Cross-Origin Resource Sharing ## Do I need a cors proxy? In short, - **NO:** For your own backend - **YES:** If.. - Your front end is a plugin that runs on top of other sites. (e.g. shopify, figma plugin) - You are accessign third party api (e.g. instagram, twitter) and the api server responds with limited CORS headers. ## Okay, it seems I don't need cors proxy service, What can I do? If you're application is a general web app and you have control over the backend, here are some quick guides for each backend frameworks. - Express - Koa - Hapi - Fastify - NestJS - AWS Lambda ================================================ FILE: docs/docusaurus.config.js ================================================ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion const lightCodeTheme = require("prism-react-renderer/themes/github"); const darkCodeTheme = require("prism-react-renderer/themes/dracula"); /** @type {import('@docusaurus/types').Config} */ const config = { title: "cors.sh", tagline: "Get started with CORS.SH", url: "https://docs.cors.sh", // @ts-ignore baseUrl: process.env.BASE_URL, onBrokenLinks: "throw", onBrokenMarkdownLinks: "warn", favicon: "img/favicon.ico", // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. organizationName: "gridaco", // Usually your GitHub org/user name. projectName: "cors.sh", // Usually your repo name. // Even if you don't use internalization, you can use this field to set useful // metadata like html lang. For example, if your site is Chinese, you may want // to replace "en" with "zh-Hans". i18n: { defaultLocale: "en", locales: ["en"], }, presets: [ [ "classic", /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { breadcrumbs: false, routeBasePath: "/", path: "docs", sidebarPath: require.resolve("./sidebars.js"), // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: "https://github.com/gridaco/cors.sh/tree/main/docs/", }, blog: { showReadingTime: true, // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: "https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/", }, // tracking googleAnalytics: { trackingID: "G-XG051N1VS3", }, theme: { customCss: require.resolve("./src/css/custom.css"), }, }), ], ], themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ navbar: { logo: { alt: "CORS.SH By Grida", src: "img/logo.svg", width: 100, href: "https://cors.sh/", }, items: [ { type: "doc", docId: "intro", position: "left", label: "Tutorial", }, { to: "/blog", label: "Blog", position: "left" }, { href: "https://github.com/facebook/docusaurus", label: "GitHub", position: "right", }, ], }, footer: { style: "light", links: [ { title: "Docs", items: [ { label: "Tutorial", to: "/docs/intro", }, ], }, { title: "Community", items: [ { label: "Slack Channel", href: "https://grida.co/join-slack", }, { label: "Twitter", href: "https://twitter.com/grida_co", }, ], }, { title: "More", items: [ { label: "Blog", to: "/blog", }, { label: "GitHub", href: "https://github.com/gridaco/cors.sh", }, ], }, ], copyright: `Copyright © ${new Date().getFullYear()} Grida, Inc. Built with Docusaurus.`, }, prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, }, }), }; module.exports = config; ================================================ FILE: docs/package.json ================================================ { "name": "docs", "version": "0.0.0", "private": true, "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { "@docusaurus/core": "2.0.1", "@docusaurus/preset-classic": "2.0.1", "@mdx-js/react": "^1.6.22", "clsx": "^1.2.1", "prism-react-renderer": "^1.3.5", "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { "@docusaurus/module-type-aliases": "2.0.1" }, "browserslist": { "production": [ ">0.5%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "engines": { "node": ">=16.14" } } ================================================ FILE: docs/sidebars.js ================================================ /** * Creating a sidebar enables you to: - create an ordered group of docs - render a sidebar for each doc of that group - provide next/previous navigation The sidebars can be generated from the filesystem, or explicitly defined here. Create as many sidebars as you want. */ // @ts-check /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { // By default, Docusaurus generates a sidebar from the docs folder structure tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], // But you can create a sidebar manually /* tutorialSidebar: [ { type: 'category', label: 'Tutorial', items: ['hello'], }, ], */ }; module.exports = sidebars; ================================================ FILE: docs/src/css/custom.css ================================================ /** * Any CSS included here will be global. The classic template * bundles Infima by default. Infima is a CSS framework designed to * work well for content-centric websites. */ /* You can override the default Infima variables here. */ :root { --ifm-color-primary: #2e8555; --ifm-color-primary-dark: #29784c; --ifm-color-primary-darker: #277148; --ifm-color-primary-darkest: #205d3b; --ifm-color-primary-light: #33925d; --ifm-color-primary-lighter: #359962; --ifm-color-primary-lightest: #3cad6e; --ifm-code-font-size: 95%; --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); } /* For readability concerns, you should choose a lighter palette in dark mode. */ [data-theme='dark'] { --ifm-color-primary: #25c2a0; --ifm-color-primary-dark: #21af90; --ifm-color-primary-darker: #1fa588; --ifm-color-primary-darkest: #1a8870; --ifm-color-primary-light: #29d5b0; --ifm-color-primary-lighter: #32d8b4; --ifm-color-primary-lightest: #4fddbf; --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); } ================================================ FILE: docs/static/.nojekyll ================================================ ================================================ FILE: docs/static/index.html ================================================ Your Site Title Here If you are not redirected automatically, follow this link. ================================================ FILE: examples/a-simple-demo/index.html ================================================ ================================================ FILE: examples/a-simple-demo/index.js ================================================ ================================================ FILE: examples/readme.md ================================================ # Examples directory ================================================ FILE: package.json ================================================ { "name": "cors.sh", "version": "0.0.0", "description": "The only cors proxy service all you'll ever need.", "repository": "https://github.com/gridaco/cors.sh.git", "authors": [ "Rob-W", "hoppscotch", "softmarshmallow" ], "license": "MIT", "private": true, "workspaces": [ "web", "homepage", "console", "packages/*", "packages/editor-ui/packages/*" ], "scripts": { "homepage": "yarn workspace homepage run dev" }, "resolutions": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0" } } ================================================ FILE: packages/api/index.ts ================================================ import Axios, { AxiosInstance, AxiosError } from "axios"; const HOST = process.env.NODE_ENV === "production" ? "https://services.cors.sh" : "http://localhost:4021"; export class Client { private _client: AxiosInstance; constructor(credentials: { "x-cors-service-checkout-session-id"?: string }) { this._client = Axios.create({ baseURL: HOST, headers: { ...credentials, }, }); } async onboardingWithEmail({ email }: { email: string }) { try { return ( await this._client.post( "/onboarding/with-email", { email } ) ).data; } catch (e) { if (e instanceof AxiosError) { switch (e.response?.status) { case 409: throw new AlreadySignedUp(); case 429: throw new OnboardingApiKeyAlreadyRequested(); } } } } async onboardingWithForm({ name, allowedOrigins = [], priceId, }: { name?: string; allowedOrigins?: string[]; priceId?: string; }) { return ( await this._client.post("/onboarding/with-form", { name: name, allowedOrigins: allowedOrigins, priceId: priceId, }) ).data; } async getOnboardingApplication(id: string) { return (await this._client.get(`/onboarding/${id}`)) .data; } async convertApplication(onboarding_id: string, checkout_session_id: string) { return ( await this._client.post( `/onboarding/${onboarding_id}/convert`, { checkout_session_id, } ) ).data; } async createApplication( payload: CreateApplicationRequest ): Promise { const { data } = await this._client.post( "/applications", payload ); return data; } async getApplication(id: string): Promise { return (await this._client.get(`/applications/${id}`)) .data; } async listApplications(): Promise { return (await this._client.get("/applications")).data; } async deleteApplication(id: string) { await this._client.delete(`/applications/${id}`); } async updateApplication(id: string, payload: UpdateApplicationRequest) { await this._client.put(`/applications/${id}`, payload); } } /** * client that is used for signing in the user. */ const _auth_client = Axios.create({ baseURL: HOST, withCredentials: false, }); /** * sign-in to cors.sh service. the authentication flow is.. * * 1. user sign-in to grida.co (with first pary proxy authentication) * 2. user calles this function with the token. * 3. services.cors.sh will set the secure http only cookie. * 4. use the _signed_client to make future requests. * @returns */ export async function signin({ grida_token }: { grida_token: string }) { try { return await _auth_client.post("/auth/signin", { headers: { "Proxy-Authorization": `Bearer ${grida_token}`, }, }); } catch (e) { return false; } } export class AlreadySignedUp extends Error { constructor() { super("already signed up"); } } export class OnboardingApiKeyAlreadyRequested extends Error { constructor() { super("onboarding api key already requested"); } } export interface CreateApplicationRequest { name: string; allowedOrigins: string[]; } export interface Application { name: string; id: string; allowedOrigins: string[]; } export interface OnboardingApplication { id: string; email: string; name?: string; allowedOrigins: string[]; priceId?: string; } export type ApplicationWithApiKey = Application & { apikey_test: string; apikey_live: string; }; export type ApplicationDetail = ApplicationWithApiKey; export type CreateAplicationResponse = ApplicationWithApiKey; export type UpdateApplicationRequest = Application; export default new Client({}); ================================================ FILE: packages/api/package.json ================================================ { "name": "@cors.sh/service-api", "version": "0.0.0", "dependencies": { "axios": "^0.27.2" } } ================================================ FILE: packages/app-ui/components/api-key-reveal.tsx ================================================ import React from "react"; import copy from "copy-to-clipboard"; import toast from "react-hot-toast"; import styled from "@emotion/styled"; import { EyeOpenIcon } from "@radix-ui/react-icons"; export function ApiKeyReveal({ keys, }: { keys: { test: string; prod: string }; }) { const [masked, setMasked] = React.useState(true); const onCopy = (text: string) => { copy(text); toast.success("Copied to clipboard"); }; const keydisplay = (key: string, masked: boolean) => { if (masked) { // leave the first 5 characters as is // replace all characters except "-" that with x const first5 = key.slice(0, 5); const target = key.slice(5); // replace all characters except "-" with x const masked = target.replace(/[^-]/g, "x"); return first5 + masked.slice(5); } else { return key; } }; const Item = ({ sign: key }: { sign: string }) => { return ( onCopy(key)}> {keydisplay(key, masked)}
); }; return (
{ setMasked(false); }} style={{ visibility: masked ? "visible" : "hidden", }} >
        API Keys
        

# for testing


# for production
); } const CodeBlock = styled.code` position: relative; display: block; width: 100%; padding: 21px; background: black; color: white; border-radius: 4px; font-size: 12px; font-family: monospace; font-weight: 400; overflow-x: scroll; .reveal { position: absolute; cursor: pointer; top: 12px; right: 12px; opacity: 0; transition: opacity 0.2s ease-in-out; } .key { cursor: pointer; } pre { margin: 0; } &:hover { .reveal { opacity: 1; } } `; ================================================ FILE: packages/app-ui/components/index.ts ================================================ export * from "./underline-button"; export * from "./api-key-reveal"; ================================================ FILE: packages/app-ui/components/underline-button.tsx ================================================ import React from "react"; import styled from "@emotion/styled"; export const UnderlineButton = React.forwardRef(function UnderlineButton({ children }: React.PropsWithChildren<{}>, forwaredRef: React.Ref) { return <_Button ref={forwaredRef}>{children}; }) const _Button = styled.button` background: none; border: none; padding: 0; font: inherit; cursor: pointer; outline: inherit; text-decoration: underline; color: rgba(0, 0, 0, 0.5); `; ================================================ FILE: packages/app-ui/layouts/form-page-latout.ts ================================================ "use client"; import styled from "@emotion/styled"; export const FormPageLayout = styled.div` font-family: sans-serif; display: flex; flex-direction: column; margin: auto; align-items: center; justify-content: center; max-width: 320px; min-height: 100vh; h1, p { text-align: center; } .description { margin-top: 16px; } .body { display: flex; flex-direction: column; gap: 21px; width: 100%; } .form { margin-top: 60px; display: flex; flex-direction: column; gap: 21px; width: 100%; } .close { position: absolute; top: 16px; right: 16px; margin: 0 !important; padding: 4px !important; background: none; border: none; cursor: pointer; color: #ccc; &:hover { color: black; } } `; ================================================ FILE: packages/app-ui/layouts/index.ts ================================================ export * from "./form-page-latout"; export * from "./page-close-button"; ================================================ FILE: packages/app-ui/layouts/page-close-button.tsx ================================================ import React from "react"; // import { useRouter } from "next/router"; import { Cross2Icon } from "@radix-ui/react-icons"; export function PageCloseButton() { // const router = useRouter(); return ( ); } ================================================ FILE: packages/app-ui/package.json ================================================ { "name": "@app/ui", "version": "0.0.0" } ================================================ FILE: packages/app-ui/tsconfig.json ================================================ { "compilerOptions": { "jsx": "react", "esModuleInterop": true } } ================================================ FILE: packages/app-ui/utils/index.ts ================================================ export const validateUrls = (urls: string) => { const lines = urls.split(",").map((line) => line.trim()); for (const line of lines) { try { new URL(line); } catch (e) { return false; } } return true; }; ================================================ FILE: playground/readme.md ================================================ # cors.sh/playground Visit [cors.sh/playground](https://cors.sh/playground) to try out the playground. Source code available at [gridaco/playground.cors.sh](https://github.com/gridaco/playground.cors.sh). ================================================ FILE: services/auth.proxy.cors.sh/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: services/auth.proxy.cors.sh/deploy/prod.sh ================================================ sls deploy --stage production \ --param="domain=true" ================================================ FILE: services/auth.proxy.cors.sh/deploy/staging.sh ================================================ sls deploy --stage production \ --param="domain=true" ================================================ FILE: services/auth.proxy.cors.sh/handler.ts ================================================ // this uses dynamodb to read api key signatures // signature types are.. // tmp - this is not stored in db. // live / test - this is stored in db. const CORS_API_KEY_HEADER = "x-cors-api-key"; // API Gateway authorizer for serverless functions module.exports.authorize = async (event) => { // get the api key from the header const key = event.headers[CORS_API_KEY_HEADER]; // const { mode, signature } = keyinfo(key); // switch (mode) { // case "temp": { // break; // } // case "live": { // break; // } // case "test": { // break; // } // case "v2022": { // // // } // } // Authorized return { principalId: "user", policyDocument: { Version: "2012-10-17", Statement: [ { Action: "execute-api:Invoke", Effect: "Allow", Resource: event.methodArn, }, ], }, // Optional context context: { key, }, // Optional output with custom properties of the String, Number or Boolean type. usageIdentifierKey: "123456789", }; }; ================================================ FILE: services/auth.proxy.cors.sh/package.json ================================================ { "name": "auth.proxy.cors.sh", "version": "0.0.0", "dependencies": { "aws-sdk": "^2.1290.0" } } ================================================ FILE: services/auth.proxy.cors.sh/readme.md ================================================ # API Authorizer for proxy.cors.sh ## Architecture This layer will use the synced key signatures direcly from the table, then proxy.cors.sh will use this as an authorizer. > The table is managed by the service layer, this layer only performs GetItem. Access points are secured by domain, this service's source is safe to be public on github. ================================================ FILE: services/auth.proxy.cors.sh/serverless.yml ================================================ # Welcome to serverless. Read the docs # https://serverless.com/framework/docs/ service: cors-proxy-authorizer useDotenv: true custom: customDomain: domainName: auth.proxy.cors.sh basePath: "" stage: ${opt:stage, self:provider.stage} createRoute53Record: false # enabled only for production - configured by package.json script with --domain flag. enabled: ${param:domain, false} serverless-offline: httpPort: 4022 noPrependStageInUrl: true provider: name: aws memorySize: 128 runtime: nodejs14.x apiGateway: shouldStartNameWithService: true binaryMediaTypes: - "*/*" endpointType: regional region: us-west-1 environment: # managed by service.cors.sh DYNAMODB_TABLE_SERVICE_KEYS: "$cors-proxy-service-keys-${opt:stage, self:provider.stage}" iamRoleStatements: - Effect: Allow Action: # this service only needs to read the service keys - dynamodb:GetItem Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE_SERVICE_KEYS}" # The `functions` block defines what code to deploy functions: api: handler: handler.authorize maximumEventAge: 60 maximumRetryAttempts: 0 timeout: 12 events: - http: path: /{proxy+} method: any - http: path: / method: get plugins: - serverless-plugin-typescript - serverless-plugin-optimize - serverless-offline - serverless-domain-manager ================================================ FILE: services/lagacy/.gitignore ================================================ # compiled output /dist /node_modules .build # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json ================================================ FILE: services/lagacy/README.md ================================================ # Legacy cors.bridged.cc All requests to cors.bridged.cc will be redirected to cors.sh This is done by api-gateway in AWS console. Keep the code as original as the last state of active service ================================================ FILE: services/lagacy/lib/cors.ts ================================================ import * as httpProxy from "http-proxy"; import * as https from "https"; import * as http from "http"; import * as url from "url"; import { getProxyForUrl } from "proxy-from-env"; import { withCORS, isValidHostName, parseURL } from "./utils"; interface OptionParams { httpProxyOptions: httpProxy.ServerOptions; httpsOptions?: https.ServerOptions; [x: string]: any; } function proxyRequest(req, res, proxy) { var location = req.corsAnywhereRequestState.location; req.url = location.path; var proxyOptions: any = { changeOrigin: false, prependPath: false, target: location, headers: { host: location.host, }, buffer: { pipe: function(proxyReq) { var proxyReqOn = proxyReq.on; proxyReq.on = function(eventName, listener) { if (eventName !== "response") { return proxyReqOn.call(this, eventName, listener); } return proxyReqOn.call(this, "response", function(proxyRes) { if (onProxyResponse(proxy, proxyReq, proxyRes, req, res)) { try { listener(proxyRes); } catch (err) { proxyReq.emit("error", err); } } }); }; return req.pipe(proxyReq); }, }, }; var proxyThroughUrl = req.corsAnywhereRequestState.getProxyForUrl( location.href ); if (proxyThroughUrl) { proxyOptions.target = proxyThroughUrl; proxyOptions.toProxy = true; req.url = location.href; } try { proxy.web(req, res, proxyOptions); } catch (err) { proxy.emit("error", err, req, res); } } function onProxyResponse(proxy, proxyReq, proxyRes, req, res) { var requestState = req.corsAnywhereRequestState; var statusCode = proxyRes.statusCode; if (!requestState.redirectCount_) { res.setHeader("x-request-url", requestState.location.href); } if ( statusCode === 301 || statusCode === 302 || statusCode === 303 || statusCode === 307 || statusCode === 308 ) { var locationHeader = proxyRes.headers.location; var parsedLocation; if (locationHeader) { locationHeader = url.resolve(requestState.location.href, locationHeader); parsedLocation = parseURL(locationHeader); } if (parsedLocation) { if (statusCode === 301 || statusCode === 302 || statusCode === 303) { requestState.redirectCount_ = requestState.redirectCount_ + 1 || 1; if (requestState.redirectCount_ <= requestState.maxRedirects) { res.setHeader( "X-CORS-Redirect-" + requestState.redirectCount_, statusCode + " " + locationHeader ); req.method = "GET"; req.headers["content-length"] = "0"; delete req.headers["content-type"]; requestState.location = parsedLocation; req.removeAllListeners(); proxyReq.removeAllListeners("error"); proxyReq.once("error", function catchAndIgnoreError() {}); proxyReq.abort(); proxyRequest(req, res, proxy); return false; } } proxyRes.headers.location = requestState.proxyBaseUrl + "/" + locationHeader; } } delete proxyRes.headers["set-cookie"]; delete proxyRes.headers["set-cookie2"]; proxyRes.headers["x-final-url"] = requestState.location.href; withCORS(proxyRes.headers, req); return true; } function getHandler(options, proxy) { var corsAnywhere: any = { getProxyForUrl: getProxyForUrl, // Function that specifies the proxy to use maxRedirects: 5, // Maximum number of redirects to be followed. originBlacklist: [], // Requests from these origins will be blocked. originWhitelist: [], // If non-empty, requests not from an origin in this list will be blocked. checkRateLimit: null, // Function that may enforce a rate-limit by returning a non-empty string. redirectSameOrigin: false, // Redirect the client to the requested URL for same-origin requests. requireHeader: null, // Require a header to be set? removeHeaders: [], // Strip these request headers. setHeaders: {}, // Set these request headers. corsMaxAge: 0, // If set, an Access-Control-Max-Age header with this value (in seconds) will be added. helpFile: __dirname + "/help.txt", }; Object.keys(corsAnywhere).forEach(function(option) { if (Object.prototype.hasOwnProperty.call(options, option)) { corsAnywhere[option] = options[option]; } }); if (corsAnywhere.requireHeader) { if (typeof corsAnywhere.requireHeader === "string") { corsAnywhere.requireHeader = [corsAnywhere.requireHeader.toLowerCase()]; } else if ( !Array.isArray(corsAnywhere.requireHeader) || corsAnywhere.requireHeader.length === 0 ) { corsAnywhere.requireHeader = null; } else { corsAnywhere.requireHeader = corsAnywhere.requireHeader.map(function( headerName ) { return headerName.toLowerCase(); }); } } var hasRequiredHeaders = function(headers) { return ( !corsAnywhere.requireHeader || corsAnywhere.requireHeader.some(function(headerName) { return Object.hasOwnProperty.call(headers, headerName); }) ); }; return function(req, res) { req.corsAnywhereRequestState = { getProxyForUrl: corsAnywhere.getProxyForUrl, maxRedirects: corsAnywhere.maxRedirects, corsMaxAge: corsAnywhere.corsMaxAge, }; var cors_headers = withCORS({}, req); if (req.method === "OPTIONS") { res.writeHead(200, cors_headers); res.end(); return; } var location: any = parseURL(req.url.slice(1)); if (location.host === "iscorsneeded") { res.writeHead(200, { "Content-Type": "text/plain" }); res.end("no"); return; } if (location.port > 65535) { res.writeHead(400, "Invalid port", cors_headers); res.end("Port number too large: " + location.port); return; } if (!/^\/https?:/.test(req.url) && !isValidHostName(location.hostname)) { res.writeHead(404, "Invalid host", cors_headers); res.end("Invalid host: " + location.hostname); return; } if (!hasRequiredHeaders(req.headers)) { res.writeHead(400, "Header required", cors_headers); res.end( "Missing required request header. Must specify one of: " + corsAnywhere.requireHeader ); return; } var origin = req.headers.origin || ""; if (corsAnywhere.originBlacklist.indexOf(origin) >= 0) { res.writeHead(403, "Forbidden", cors_headers); res.end( 'The origin "' + origin + '" was blacklisted by the operator of this proxy.' ); return; } if ( corsAnywhere.originWhitelist.length && corsAnywhere.originWhitelist.indexOf(origin) === -1 ) { res.writeHead(403, "Forbidden", cors_headers); res.end( 'The origin "' + origin + '" was not whitelisted by the operator of this proxy.' ); return; } var rateLimitMessage = corsAnywhere.checkRateLimit && corsAnywhere.checkRateLimit(origin); if (rateLimitMessage) { res.writeHead(429, "Too Many Requests", cors_headers); res.end( 'The origin "' + origin + '" has sent too many requests.\n' + rateLimitMessage ); return; } if ( corsAnywhere.redirectSameOrigin && origin && location.href[origin.length] === "/" && location.href.lastIndexOf(origin, 0) === 0 ) { cors_headers.vary = "origin"; cors_headers["cache-control"] = "private"; cors_headers.location = location.href; res.writeHead(301, "Please use a direct request", cors_headers); res.end(); return; } var isRequestedOverHttps = req.connection.encrypted || /^\s*https/.test(req.headers["x-forwarded-proto"]); var proxyBaseUrl = (isRequestedOverHttps ? "https://" : "http://") + req.headers.host; corsAnywhere.removeHeaders.forEach(function(header) { delete req.headers[header]; }); Object.keys(corsAnywhere.setHeaders).forEach(function(header) { req.headers[header] = corsAnywhere.setHeaders[header]; }); req.corsAnywhereRequestState.location = location; req.corsAnywhereRequestState.proxyBaseUrl = proxyBaseUrl; proxyRequest(req, res, proxy); }; } export const createServer = (options: OptionParams) => { var server: https.Server | http.Server; var httpProxyOptions: httpProxy.ServerOptions = { xfwd: true, // Append X-Forwarded-* headers }; options = options || {}; if (options.httpProxyOptions) { Object.keys(options.httpProxyOptions).forEach(function(option) { httpProxyOptions[option] = options.httpProxyOptions[option]; }); } var proxy: httpProxy = httpProxy.createServer(httpProxyOptions); var requestHandler = getHandler(options, proxy); if (options.httpsOptions) { server = https.createServer(options.httpsOptions, requestHandler); } else { server = http.createServer(requestHandler); } proxy.on("error", function( err: Error, _: http.IncomingMessage, res: http.ServerResponse ) { if (res.headersSent) { if (res.writableEnded === false) { res.end(); } return; } var headerNames = res.getHeaderNames ? res.getHeaderNames() : //@ts-ignore Object.keys(res._headers || {}); headerNames.forEach(function(name) { res.removeHeader(name); }); res.writeHead(404, { "Access-Control-Allow-Origin": "*" }); res.end("Not found because of proxy error: " + err); }); return server; }; ================================================ FILE: services/lagacy/lib/utils.ts ================================================ import * as net from "net"; import * as url from "url"; export const regexp = /\.(?:AAA|AARP|ABARTH|ABB|ABBOTT|ABBVIE|ABC|ABLE|ABOGADO|ABUDHABI|AC|ACADEMY|ACCENTURE|ACCOUNTANT|ACCOUNTANTS|ACO|ACTIVE|ACTOR|AD|ADAC|ADS|ADULT|AE|AEG|AERO|AETNA|AF|AFAMILYCOMPANY|AFL|AFRICA|AG|AGAKHAN|AGENCY|AI|AIG|AIGO|AIRBUS|AIRFORCE|AIRTEL|AKDN|AL|ALFAROMEO|ALIBABA|ALIPAY|ALLFINANZ|ALLSTATE|ALLY|ALSACE|ALSTOM|AM|AMERICANEXPRESS|AMERICANFAMILY|AMEX|AMFAM|AMICA|AMSTERDAM|ANALYTICS|ANDROID|ANQUAN|ANZ|AO|AOL|APARTMENTS|APP|APPLE|AQ|AQUARELLE|AR|ARAB|ARAMCO|ARCHI|ARMY|ARPA|ART|ARTE|AS|ASDA|ASIA|ASSOCIATES|AT|ATHLETA|ATTORNEY|AU|AUCTION|AUDI|AUDIBLE|AUDIO|AUSPOST|AUTHOR|AUTO|AUTOS|AVIANCA|AW|AWS|AX|AXA|AZ|AZURE|BA|BABY|BAIDU|BANAMEX|BANANAREPUBLIC|BAND|BANK|BAR|BARCELONA|BARCLAYCARD|BARCLAYS|BAREFOOT|BARGAINS|BASEBALL|BASKETBALL|BAUHAUS|BAYERN|BB|BBC|BBT|BBVA|BCG|BCN|BD|BE|BEATS|BEAUTY|BEER|BENTLEY|BERLIN|BEST|BESTBUY|BET|BF|BG|BH|BHARTI|BI|BIBLE|BID|BIKE|BING|BINGO|BIO|BIZ|BJ|BLACK|BLACKFRIDAY|BLANCO|BLOCKBUSTER|BLOG|BLOOMBERG|BLUE|BM|BMS|BMW|BN|BNL|BNPPARIBAS|BO|BOATS|BOEHRINGER|BOFA|BOM|BOND|BOO|BOOK|BOOKING|BOOTS|BOSCH|BOSTIK|BOSTON|BOT|BOUTIQUE|BOX|BR|BRADESCO|BRIDGESTONE|BROADWAY|BROKER|BROTHER|BRUSSELS|BS|BT|BUDAPEST|BUGATTI|BUILD|BUILDERS|BUSINESS|BUY|BUZZ|BV|BW|BY|BZ|BZH|CA|CAB|CAFE|CAL|CALL|CALVINKLEIN|CAM|CAMERA|CAMP|CANCERRESEARCH|CANON|CAPETOWN|CAPITAL|CAPITALONE|CAR|CARAVAN|CARDS|CARE|CAREER|CAREERS|CARS|CARTIER|CASA|CASE|CASEIH|CASH|CASINO|CAT|CATERING|CATHOLIC|CBA|CBN|CBRE|CBS|CC|CD|CEB|CENTER|CEO|CERN|CF|CFA|CFD|CG|CH|CHANEL|CHANNEL|CHASE|CHAT|CHEAP|CHINTAI|CHRISTMAS|CHROME|CHRYSLER|CHURCH|CI|CIPRIANI|CIRCLE|CISCO|CITADEL|CITI|CITIC|CITY|CITYEATS|CK|CL|CLAIMS|CLEANING|CLICK|CLINIC|CLINIQUE|CLOTHING|CLOUD|CLUB|CLUBMED|CM|CN|CO|COACH|CODES|COFFEE|COLLEGE|COLOGNE|COM|COMCAST|COMMBANK|COMMUNITY|COMPANY|COMPARE|COMPUTER|COMSEC|CONDOS|CONSTRUCTION|CONSULTING|CONTACT|CONTRACTORS|COOKING|COOKINGCHANNEL|COOL|COOP|CORSICA|COUNTRY|COUPON|COUPONS|COURSES|CR|CREDIT|CREDITCARD|CREDITUNION|CRICKET|CROWN|CRS|CRUISE|CRUISES|CSC|CU|CUISINELLA|CV|CW|CX|CY|CYMRU|CYOU|CZ|DABUR|DAD|DANCE|DATA|DATE|DATING|DATSUN|DAY|DCLK|DDS|DE|DEAL|DEALER|DEALS|DEGREE|DELIVERY|DELL|DELOITTE|DELTA|DEMOCRAT|DENTAL|DENTIST|DESI|DESIGN|DEV|DHL|DIAMONDS|DIET|DIGITAL|DIRECT|DIRECTORY|DISCOUNT|DISCOVER|DISH|DIY|DJ|DK|DM|DNP|DO|DOCS|DOCTOR|DODGE|DOG|DOHA|DOMAINS|DOT|DOWNLOAD|DRIVE|DTV|DUBAI|DUCK|DUNLOP|DUNS|DUPONT|DURBAN|DVAG|DVR|DZ|EARTH|EAT|EC|ECO|EDEKA|EDU|EDUCATION|EE|EG|EMAIL|EMERCK|ENERGY|ENGINEER|ENGINEERING|ENTERPRISES|EPOST|EPSON|EQUIPMENT|ER|ERICSSON|ERNI|ES|ESQ|ESTATE|ESURANCE|ET|ETISALAT|EU|EUROVISION|EUS|EVENTS|EVERBANK|EXCHANGE|EXPERT|EXPOSED|EXPRESS|EXTRASPACE|FAGE|FAIL|FAIRWINDS|FAITH|FAMILY|FAN|FANS|FARM|FARMERS|FASHION|FAST|FEDEX|FEEDBACK|FERRARI|FERRERO|FI|FIAT|FIDELITY|FIDO|FILM|FINAL|FINANCE|FINANCIAL|FIRE|FIRESTONE|FIRMDALE|FISH|FISHING|FIT|FITNESS|FJ|FK|FLICKR|FLIGHTS|FLIR|FLORIST|FLOWERS|FLY|FM|FO|FOO|FOOD|FOODNETWORK|FOOTBALL|FORD|FOREX|FORSALE|FORUM|FOUNDATION|FOX|FR|FREE|FRESENIUS|FRL|FROGANS|FRONTDOOR|FRONTIER|FTR|FUJITSU|FUJIXEROX|FUN|FUND|FURNITURE|FUTBOL|FYI|GA|GAL|GALLERY|GALLO|GALLUP|GAME|GAMES|GAP|GARDEN|GB|GBIZ|GD|GDN|GE|GEA|GENT|GENTING|GEORGE|GF|GG|GGEE|GH|GI|GIFT|GIFTS|GIVES|GIVING|GL|GLADE|GLASS|GLE|GLOBAL|GLOBO|GM|GMAIL|GMBH|GMO|GMX|GN|GODADDY|GOLD|GOLDPOINT|GOLF|GOO|GOODHANDS|GOODYEAR|GOOG|GOOGLE|GOP|GOT|GOV|GP|GQ|GR|GRAINGER|GRAPHICS|GRATIS|GREEN|GRIPE|GROCERY|GROUP|GS|GT|GU|GUARDIAN|GUCCI|GUGE|GUIDE|GUITARS|GURU|GW|GY|HAIR|HAMBURG|HANGOUT|HAUS|HBO|HDFC|HDFCBANK|HEALTH|HEALTHCARE|HELP|HELSINKI|HERE|HERMES|HGTV|HIPHOP|HISAMITSU|HITACHI|HIV|HK|HKT|HM|HN|HOCKEY|HOLDINGS|HOLIDAY|HOMEDEPOT|HOMEGOODS|HOMES|HOMESENSE|HONDA|HONEYWELL|HORSE|HOSPITAL|HOST|HOSTING|HOT|HOTELES|HOTELS|HOTMAIL|HOUSE|HOW|HR|HSBC|HT|HU|HUGHES|HYATT|HYUNDAI|IBM|ICBC|ICE|ICU|ID|IE|IEEE|IFM|IKANO|IL|IM|IMAMAT|IMDB|IMMO|IMMOBILIEN|IN|INDUSTRIES|INFINITI|INFO|ING|INK|INSTITUTE|INSURANCE|INSURE|INT|INTEL|INTERNATIONAL|INTUIT|INVESTMENTS|IO|IPIRANGA|IQ|IR|IRISH|IS|ISELECT|ISMAILI|IST|ISTANBUL|IT|ITAU|ITV|IVECO|IWC|JAGUAR|JAVA|JCB|JCP|JE|JEEP|JETZT|JEWELRY|JIO|JLC|JLL|JM|JMP|JNJ|JO|JOBS|JOBURG|JOT|JOY|JP|JPMORGAN|JPRS|JUEGOS|JUNIPER|KAUFEN|KDDI|KE|KERRYHOTELS|KERRYLOGISTICS|KERRYPROPERTIES|KFH|KG|KH|KI|KIA|KIM|KINDER|KINDLE|KITCHEN|KIWI|KM|KN|KOELN|KOMATSU|KOSHER|KP|KPMG|KPN|KR|KRD|KRED|KUOKGROUP|KW|KY|KYOTO|KZ|LA|LACAIXA|LADBROKES|LAMBORGHINI|LAMER|LANCASTER|LANCIA|LANCOME|LAND|LANDROVER|LANXESS|LASALLE|LAT|LATINO|LATROBE|LAW|LAWYER|LB|LC|LDS|LEASE|LECLERC|LEFRAK|LEGAL|LEGO|LEXUS|LGBT|LI|LIAISON|LIDL|LIFE|LIFEINSURANCE|LIFESTYLE|LIGHTING|LIKE|LILLY|LIMITED|LIMO|LINCOLN|LINDE|LINK|LIPSY|LIVE|LIVING|LIXIL|LK|LOAN|LOANS|LOCKER|LOCUS|LOFT|LOL|LONDON|LOTTE|LOTTO|LOVE|LPL|LPLFINANCIAL|LR|LS|LT|LTD|LTDA|LU|LUNDBECK|LUPIN|LUXE|LUXURY|LV|LY|MA|MACYS|MADRID|MAIF|MAISON|MAKEUP|MAN|MANAGEMENT|MANGO|MAP|MARKET|MARKETING|MARKETS|MARRIOTT|MARSHALLS|MASERATI|MATTEL|MBA|MC|MCKINSEY|MD|ME|MED|MEDIA|MEET|MELBOURNE|MEME|MEMORIAL|MEN|MENU|MEO|MERCKMSD|METLIFE|MG|MH|MIAMI|MICROSOFT|MIL|MINI|MINT|MIT|MITSUBISHI|MK|ML|MLB|MLS|MM|MMA|MN|MO|MOBI|MOBILE|MOBILY|MODA|MOE|MOI|MOM|MONASH|MONEY|MONSTER|MOPAR|MORMON|MORTGAGE|MOSCOW|MOTO|MOTORCYCLES|MOV|MOVIE|MOVISTAR|MP|MQ|MR|MS|MSD|MT|MTN|MTR|MU|MUSEUM|MUTUAL|MV|MW|MX|MY|MZ|NA|NAB|NADEX|NAGOYA|NAME|NATIONWIDE|NATURA|NAVY|NBA|NC|NE|NEC|NET|NETBANK|NETFLIX|NETWORK|NEUSTAR|NEW|NEWHOLLAND|NEWS|NEXT|NEXTDIRECT|NEXUS|NF|NFL|NG|NGO|NHK|NI|NICO|NIKE|NIKON|NINJA|NISSAN|NISSAY|NL|NO|NOKIA|NORTHWESTERNMUTUAL|NORTON|NOW|NOWRUZ|NOWTV|NP|NR|NRA|NRW|NTT|NU|NYC|NZ|OBI|OBSERVER|OFF|OFFICE|OKINAWA|OLAYAN|OLAYANGROUP|OLDNAVY|OLLO|OM|OMEGA|ONE|ONG|ONL|ONLINE|ONYOURSIDE|OOO|OPEN|ORACLE|ORANGE|ORG|ORGANIC|ORIGINS|OSAKA|OTSUKA|OTT|OVH|PA|PAGE|PANASONIC|PANERAI|PARIS|PARS|PARTNERS|PARTS|PARTY|PASSAGENS|PAY|PCCW|PE|PET|PF|PFIZER|PG|PH|PHARMACY|PHD|PHILIPS|PHONE|PHOTO|PHOTOGRAPHY|PHOTOS|PHYSIO|PIAGET|PICS|PICTET|PICTURES|PID|PIN|PING|PINK|PIONEER|PIZZA|PK|PL|PLACE|PLAY|PLAYSTATION|PLUMBING|PLUS|PM|PN|PNC|POHL|POKER|POLITIE|PORN|POST|PR|PRAMERICA|PRAXI|PRESS|PRIME|PRO|PROD|PRODUCTIONS|PROF|PROGRESSIVE|PROMO|PROPERTIES|PROPERTY|PROTECTION|PRU|PRUDENTIAL|PS|PT|PUB|PW|PWC|PY|QA|QPON|QUEBEC|QUEST|QVC|RACING|RADIO|RAID|RE|READ|REALESTATE|REALTOR|REALTY|RECIPES|RED|REDSTONE|REDUMBRELLA|REHAB|REISE|REISEN|REIT|RELIANCE|REN|RENT|RENTALS|REPAIR|REPORT|REPUBLICAN|REST|RESTAURANT|REVIEW|REVIEWS|REXROTH|RICH|RICHARDLI|RICOH|RIGHTATHOME|RIL|RIO|RIP|RMIT|RO|ROCHER|ROCKS|RODEO|ROGERS|ROOM|RS|RSVP|RU|RUGBY|RUHR|RUN|RW|RWE|RYUKYU|SA|SAARLAND|SAFE|SAFETY|SAKURA|SALE|SALON|SAMSCLUB|SAMSUNG|SANDVIK|SANDVIKCOROMANT|SANOFI|SAP|SAPO|SARL|SAS|SAVE|SAXO|SB|SBI|SBS|SC|SCA|SCB|SCHAEFFLER|SCHMIDT|SCHOLARSHIPS|SCHOOL|SCHULE|SCHWARZ|SCIENCE|SCJOHNSON|SCOR|SCOT|SD|SE|SEARCH|SEAT|SECURE|SECURITY|SEEK|SELECT|SENER|SERVICES|SES|SEVEN|SEW|SEX|SEXY|SFR|SG|SH|SHANGRILA|SHARP|SHAW|SHELL|SHIA|SHIKSHA|SHOES|SHOP|SHOPPING|SHOUJI|SHOW|SHOWTIME|SHRIRAM|SI|SILK|SINA|SINGLES|SITE|SJ|SK|SKI|SKIN|SKY|SKYPE|SL|SLING|SM|SMART|SMILE|SN|SNCF|SO|SOCCER|SOCIAL|SOFTBANK|SOFTWARE|SOHU|SOLAR|SOLUTIONS|SONG|SONY|SOY|SPACE|SPIEGEL|SPOT|SPREADBETTING|SR|SRL|SRT|ST|STADA|STAPLES|STAR|STARHUB|STATEBANK|STATEFARM|STATOIL|STC|STCGROUP|STOCKHOLM|STORAGE|STORE|STREAM|STUDIO|STUDY|STYLE|SU|SUCKS|SUPPLIES|SUPPLY|SUPPORT|SURF|SURGERY|SUZUKI|SV|SWATCH|SWIFTCOVER|SWISS|SX|SY|SYDNEY|SYMANTEC|SYSTEMS|SZ|TAB|TAIPEI|TALK|TAOBAO|TARGET|TATAMOTORS|TATAR|TATTOO|TAX|TAXI|TC|TCI|TD|TDK|TEAM|TECH|TECHNOLOGY|TEL|TELECITY|TELEFONICA|TEMASEK|TENNIS|TEVA|TF|TG|TH|THD|THEATER|THEATRE|TIAA|TICKETS|TIENDA|TIFFANY|TIPS|TIRES|TIROL|TJ|TJMAXX|TJX|TK|TKMAXX|TL|TM|TMALL|TN|TO|TODAY|TOKYO|TOOLS|TOP|TORAY|TOSHIBA|TOTAL|TOURS|TOWN|TOYOTA|TOYS|TR|TRADE|TRADING|TRAINING|TRAVEL|TRAVELCHANNEL|TRAVELERS|TRAVELERSINSURANCE|TRUST|TRV|TT|TUBE|TUI|TUNES|TUSHU|TV|TVS|TW|TZ|UA|UBANK|UBS|UCONNECT|UG|UK|UNICOM|UNIVERSITY|UNO|UOL|UPS|US|UY|UZ|VA|VACATIONS|VANA|VANGUARD|VC|VE|VEGAS|VENTURES|VERISIGN|VERSICHERUNG|VET|VG|VI|VIAJES|VIDEO|VIG|VIKING|VILLAS|VIN|VIP|VIRGIN|VISA|VISION|VISTA|VISTAPRINT|VIVA|VIVO|VLAANDEREN|VN|VODKA|VOLKSWAGEN|VOLVO|VOTE|VOTING|VOTO|VOYAGE|VU|VUELOS|WALES|WALMART|WALTER|WANG|WANGGOU|WARMAN|WATCH|WATCHES|WEATHER|WEATHERCHANNEL|WEBCAM|WEBER|WEBSITE|WED|WEDDING|WEIBO|WEIR|WF|WHOSWHO|WIEN|WIKI|WILLIAMHILL|WIN|WINDOWS|WINE|WINNERS|WME|WOLTERSKLUWER|WOODSIDE|WORK|WORKS|WORLD|WOW|WS|WTC|WTF|XBOX|XEROX|XFINITY|XIHUAN|XIN|XN--11B4C3D|XN--1CK2E1B|XN--1QQW23A|XN--2SCRJ9C|XN--30RR7Y|XN--3BST00M|XN--3DS443G|XN--3E0B707E|XN--3HCRJ9C|XN--3OQ18VL8PN36A|XN--3PXU8K|XN--42C2D9A|XN--45BR5CYL|XN--45BRJ9C|XN--45Q11C|XN--4GBRIM|XN--54B7FTA0CC|XN--55QW42G|XN--55QX5D|XN--5SU34J936BGSG|XN--5TZM5G|XN--6FRZ82G|XN--6QQ986B3XL|XN--80ADXHKS|XN--80AO21A|XN--80AQECDR1A|XN--80ASEHDB|XN--80ASWG|XN--8Y0A063A|XN--90A3AC|XN--90AE|XN--90AIS|XN--9DBQ2A|XN--9ET52U|XN--9KRT00A|XN--B4W605FERD|XN--BCK1B9A5DRE4C|XN--C1AVG|XN--C2BR7G|XN--CCK2B3B|XN--CG4BKI|XN--CLCHC0EA0B2G2A9GCD|XN--CZR694B|XN--CZRS0T|XN--CZRU2D|XN--D1ACJ3B|XN--D1ALF|XN--E1A4C|XN--ECKVDTC9D|XN--EFVY88H|XN--ESTV75G|XN--FCT429K|XN--FHBEI|XN--FIQ228C5HS|XN--FIQ64B|XN--FIQS8S|XN--FIQZ9S|XN--FJQ720A|XN--FLW351E|XN--FPCRJ9C3D|XN--FZC2C9E2C|XN--FZYS8D69UVGM|XN--G2XX48C|XN--GCKR3F0F|XN--GECRJ9C|XN--GK3AT1E|XN--H2BREG3EVE|XN--H2BRJ9C|XN--H2BRJ9C8C|XN--HXT814E|XN--I1B6B1A6A2E|XN--IMR513N|XN--IO0A7I|XN--J1AEF|XN--J1AMH|XN--J6W193G|XN--JLQ61U9W7B|XN--JVR189M|XN--KCRX77D1X4A|XN--KPRW13D|XN--KPRY57D|XN--KPU716F|XN--KPUT3I|XN--L1ACC|XN--LGBBAT1AD8J|XN--MGB9AWBF|XN--MGBA3A3EJT|XN--MGBA3A4F16A|XN--MGBA7C0BBN0A|XN--MGBAAKC7DVF|XN--MGBAAM7A8H|XN--MGBAB2BD|XN--MGBAI9AZGQP6J|XN--MGBAYH7GPA|XN--MGBB9FBPOB|XN--MGBBH1A|XN--MGBBH1A71E|XN--MGBC0A9AZCG|XN--MGBCA7DZDO|XN--MGBERP4A5D4AR|XN--MGBGU82A|XN--MGBI4ECEXP|XN--MGBPL2FH|XN--MGBT3DHD|XN--MGBTX2B|XN--MGBX4CD0AB|XN--MIX891F|XN--MK1BU44C|XN--MXTQ1M|XN--NGBC5AZD|XN--NGBE9E0A|XN--NGBRX|XN--NODE|XN--NQV7F|XN--NQV7FS00EMA|XN--NYQY26A|XN--O3CW4H|XN--OGBPF8FL|XN--P1ACF|XN--P1AI|XN--PBT977C|XN--PGBS0DH|XN--PSSY2U|XN--Q9JYB4C|XN--QCKA1PMC|XN--QXAM|XN--RHQV96G|XN--ROVU88B|XN--RVC1E0AM3E|XN--S9BRJ9C|XN--SES554G|XN--T60B56A|XN--TCKWE|XN--TIQ49XQYJ|XN--UNUP4Y|XN--VERMGENSBERATER-CTB|XN--VERMGENSBERATUNG-PWB|XN--VHQUV|XN--VUQ861B|XN--W4R85EL8FHU5DNRA|XN--W4RS40L|XN--WGBH1C|XN--WGBL6A|XN--XHQ521B|XN--XKC2AL3HYE2A|XN--XKC2DL3A5EE0H|XN--Y9A3AQ|XN--YFRO4I67O|XN--YGBI2AMMX|XN--ZFR164B|XPERIA|XXX|XYZ|YACHTS|YAHOO|YAMAXUN|YANDEX|YE|YODOBASHI|YOGA|YOKOHAMA|YOU|YOUTUBE|YT|YUN|ZA|ZAPPOS|ZARA|ZERO|ZIP|ZIPPO|ZM|ZONE|ZUERICH|ZW)$/i; export const withCORS = (headers, request) => { headers["access-control-allow-origin"] = "*"; var corsMaxAge = request.corsAnywhereRequestState.corsMaxAge; if (request.method === "OPTIONS" && corsMaxAge) { headers["access-control-max-age"] = corsMaxAge; } if (request.headers["access-control-request-method"]) { headers["access-control-allow-methods"] = request.headers["access-control-request-method"]; delete request.headers["access-control-request-method"]; } if (request.headers["access-control-request-headers"]) { headers["access-control-allow-headers"] = request.headers["access-control-request-headers"]; delete request.headers["access-control-request-headers"]; } headers["access-control-expose-headers"] = Object.keys(headers).join(","); return headers; }; export const isValidHostName = hostname => { return !!( regexp.test(hostname) || net.isIPv4(hostname) || net.isIPv6(hostname) ); }; export const parseURL = req_url => { var match = req_url.match( /^(?:(https?:)?\/\/)?(([^\/?]+?)(?::(\d{0,5})(?=[\/?]|$))?)([\/?][\S\s]*|$)/i ); if (!match) { return null; } if (!match[1]) { if (/^https?:/i.test(req_url)) { return null; } if (req_url.lastIndexOf("//", 0) === -1) { req_url = "//" + req_url; } req_url = (match[4] === "443" ? "https:" : "http:") + req_url; } var parsed = url.parse(req_url); if (!parsed.hostname) { return null; } return parsed; }; ================================================ FILE: services/lagacy/package.json ================================================ { "name": "cors.bridged.cc", "description": "free cors service for everyone", "version": "0.1.0", "scripts": { "deploy:prod": "sls deploy", "dev": "sls offline start" }, "dependencies": { "aws-serverless-express": "^3.4.0", "dynamoose": "^2.7.3", "express": "^4.17.1", "express-useragent": "^1.0.15", "http-proxy": "^1.18.1", "moment": "^2.29.1", "nanoid": "^3.1.23", "proxy-from-env": "^1.1.0", "response-time": "^2.3.2", "serverless-http": "^2.7.0" }, "devDependencies": { "@hewmen/serverless-plugin-typescript": "^1.1.17", "@types/aws-lambda": "^8.10.71", "@types/express": "^4.17.11", "@types/http-proxy": "^1.17.5", "@types/node": "^14.14.25", "@types/proxy-from-env": "^1.0.1", "@types/response-time": "^2.3.4", "serverless": "^2.22.0", "serverless-domain-manager": "^5.1.0", "serverless-dynamodb-local": "^0.2.39", "serverless-offline": "^6.8.0", "serverless-plugin-dynamo-autoscaling": "^1.0.1", "serverless-plugin-optimize": "^4.1.4-rc.1", "typescript": "^4.1.3" } } ================================================ FILE: services/lagacy/serverless.yml ================================================ # Welcome to serverless. Read the docs # https://serverless.com/framework/docs/ frameworkVersion: "2" service: cors useDotenv: true custom: customDomain: # legacy. won't support in the future domainName: cors.bridged.cc basePath: "" stage: ${self:provider.stage} createRoute53Record: true # - http: # domainName: cors.grida.cc # basePath: "" # stage: "production" # createRoute53Record: true serverless-offline: httpPort: 4012 provider: name: aws memorySize: 128 runtime: nodejs12.x apiGateway: shouldStartNameWithService: true binaryMediaTypes: - "*/*" endpointType: regional region: us-west-1 environment: AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1" DYNAMODB_TABLE_USAGE_LOG: "${self:service}-usage-log-${opt:stage, self:provider.stage}" iamRoleStatements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem - dynamodb:DescribeTable Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE_USAGE_LOG}" resources: Resources: usageLogTable: Type: AWS::DynamoDB::Table Properties: TableName: "${self:provider.environment.DYNAMODB_TABLE_USAGE_LOG}" KeySchema: - AttributeName: id KeyType: HASH AttributeDefinitions: - AttributeName: id AttributeType: S - AttributeName: app AttributeType: S GlobalSecondaryIndexes: - IndexName: appIndex KeySchema: - AttributeName: app KeyType: HASH Projection: ProjectionType: "ALL" ProvisionedThroughput: ReadCapacityUnits: 10 WriteCapacityUnits: 10 # no write is required ProvisionedThroughput: ReadCapacityUnits: 10 # no read is required WriteCapacityUnits: 10 # The `functions` block defines what code to deploy functions: api: handler: src/index.handler maximumEventAge: 60 maximumRetryAttempts: 0 timeout: 12 events: - http: path: /{proxy+} method: any - http: path: / method: get plugins: - "@hewmen/serverless-plugin-typescript" - serverless-plugin-optimize - serverless-dynamodb-local - serverless-offline - serverless-domain-manager ================================================ FILE: services/lagacy/src/_util/size.ts ================================================ export const MB = 1048576; /** * 1 seconds in ms */ export const SEC = 1000; ================================================ FILE: services/lagacy/src/app.ts ================================================ import * as express from "express"; import * as useragent from "express-useragent"; import * as corsProxy from "../lib/cors"; import * as responsetime from "response-time"; import { logRequest } from "./usage"; import { blaklistoriginlimit, payloadlimit } from "./limit"; // import { unauthorizedAppBlocking } from "./auth"; const app = express(); const cors_proxy = corsProxy.createServer({ // https://github.com/Rob--W/cors-anywhere/issues/39 requireHeader: ["origin", "x-requested-with"], // requireHeader: [], removeHeaders: [ "cookie", "cookie2", "x-request-start", "x-request-id", "via", "connect-time", "total-route-time", ], redirectSameOrigin: true, httpProxyOptions: { xfwd: false, }, }); app.get("/", function (req, res) { res.redirect("https://app.cors.bridged.cc/"); }); app.use(blaklistoriginlimit); // 1 app.use(payloadlimit); // 2 // disabling due to cors.sh migration // app.use(unauthorizedAppBlocking); // 3 app.use( responsetime({ suffix: false, }) ); app.use(useragent.express()); // -- execution order matters -- // (1) app.use((req, res, next) => { if (res.headersSent) { return; } try { cors_proxy.emit("request", req, res); next(); } catch (_) {} }); // (2) /** * after response middleware */ app.use((req, res) => { res.on("finish", () => { logRequest(req, res); }); }); // -- execution order matters -- /** * global error handler */ app.use(((err, req, res, next) => { if (res.headersSent) { return; } try { return res.status(500).json({ message: "Internal Server Error", error: err, issue: "https://github.com/bridgedxyz/base/issues", }); } catch (_) {} }) as express.ErrorRequestHandler); export { app }; ================================================ FILE: services/lagacy/src/auth/_tmp_static_api_keys/.gitignore ================================================ # manuall managed credentials (temporal) keys.js ================================================ FILE: services/lagacy/src/auth/_tmp_static_api_keys/README.md ================================================ # Temporal manually managed api key store ================================================ FILE: services/lagacy/src/auth/_tmp_static_api_keys/index.js ================================================ export { default as keys } from "./keys"; ================================================ FILE: services/lagacy/src/auth/index.ts ================================================ export { unauthorizedAppBlocking } from "./static-account-api-key-auth"; ================================================ FILE: services/lagacy/src/auth/static-account-api-key-auth.ts ================================================ import * as express from "express"; import { keys } from "./_tmp_static_api_keys"; /** * cors.grida.cc static api key header */ const STATIC_CORS_ACCOUNT_API_KEY_HEADER = "x-cors-grida-api-key"; const nokey401UnAuthorized = () => { return "https://bit.ly/2UnZSA8"; // return { // message: `CORS Proxy request from origin "${origin}" is not allowed. Request is unauthorized`, // issue: "https://github.com/bridgedxyz/base/issues/23", // }; }; export const unauthorizedAppBlocking = ( req: express.Request, res: express.Response, next ) => { // skip api key check for preflight requests if (req.method == "OPTIONS" || req.method == "HEAD") { next(); } const apikey: string = req.headers[ STATIC_CORS_ACCOUNT_API_KEY_HEADER ] as string; if (apikey && validate_api_key(apikey)) { next(); } else { res.status(401).send(nokey401UnAuthorized()); return; } }; function validate_api_key(apikey: string) { if (!apikey || apikey == "") { return false; } const found = (keys as string[]).find(s => s === apikey); if (found) { return true; } return false; } ================================================ FILE: services/lagacy/src/index.ts ================================================ import * as serverlessExpress from "aws-serverless-express"; import { APIGatewayProxyHandler } from "aws-lambda"; import { app } from "./app"; const binaryMimeTypes = [ '*/*' // https://github.com/vendia/serverless-express/blob/master/examples/basic-starter/lambda.js // 'application/javascript', // 'application/json', // 'application/octet-stream', // 'application/xml', // 'font/eot', // 'font/opentype', // 'font/otf', // 'image/jpeg', // 'image/png', // 'image/svg+xml', // 'text/comma-separated-values', // 'text/css', // 'text/html', // 'text/javascript', // 'text/plain', // 'text/text', // 'text/xml' ] const server = serverlessExpress.createServer(app, null, binaryMimeTypes) export const handler: APIGatewayProxyHandler = (event, context) => { serverlessExpress.proxy(server, event, context); }; ================================================ FILE: services/lagacy/src/limit/README.md ================================================ # Limitation handler ================================================ FILE: services/lagacy/src/limit/blacklist-origin.ts ================================================ import * as express from "express"; /** * explicitly black listed origins. these are not registered to use base. */ const blacklisted_origin: string[] = [ "REJECTME", // WAITING FOR SERVICE PROVIDER'S ACTION "titronline.org", "titr.online", "showsport.xyz", "cdn14.esp-cdn.xyz", "digi-hdsport.com", "siunus.github.io", // ILLEGAL OR AUDULT WEBSITE (PERMINANTLY BLOCKED) "twerktvnaija.com", ]; const blacked401UnAuthorized = (origin: string) => { return "https://bit.ly/2UnZSA8"; // return { // message: `CORS Proxy request from origin "${origin}" is not allowed. Request is unauthorized`, // issue: "https://github.com/bridgedxyz/base/issues/23", // }; }; export const blaklistoriginlimit = ( req: express.Request, res: express.Response, next ) => { const origin = req.headers["origin"]; if (origin) { if (blacked(origin)) { res.status(401).send(blacked401UnAuthorized(origin)); return; } } next(); }; function blacked(origin: string): boolean { // patterns // 1. www. // 2. http// // 3. https// // 4. http:// // 5. .... try { const u = new URL(origin); return blacklisted_origin.some(o => { return u.hostname == o || u.hostname == "www." + o; }); } catch (_) { // we cannot handle url that is invalid (need better logic for this) return false; } } ================================================ FILE: services/lagacy/src/limit/index.ts ================================================ export * from "./payload-limit"; export * from "./blacklist-origin"; ================================================ FILE: services/lagacy/src/limit/payload-limit.ts ================================================ import * as https from "https"; import * as http from "http"; import { MB } from "../_util/size"; /** * this is to save data transfer cost. And basically we should not use CORS Proxy to load large files. * * https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html */ const MAX_TARGET_RESOURCE_MB = 2; // 6mb is max supported by api gateway. (supports up to 2mb on proxy) export const payloadlimit = (req, res, next) => { const requrl = req.originalUrl.substring(1); const agent = requrl.startsWith("https:") ? https : http; agent .request(requrl, { method: "HEAD" }, _resp => { const len = Number(_resp.headers["content-length"]); if (len && len > MB * MAX_TARGET_RESOURCE_MB) { // reject if data larger than 30mb. res.status(413).send({ message: `Requested resource exceeds ${MAX_TARGET_RESOURCE_MB}mb`, issue: "https://github.com/bridgedxyz/base/issues/23", }); } else { // if content-length is undefined or less than 30mb, procceed. next(); } }) .on("error", err => { // ignore error for this // target, which is anonymous, might not support HEAD request. next(); }) .end(); }; ================================================ FILE: services/lagacy/src/limit/unknown-host-limit.ts ================================================ import { MB, SEC } from "../_util/size"; export const unknownhostlimit = (req, res, next) => { const limit = { timeout: 6 * SEC, size: 0.1 * MB, }; }; function known(url: string): boolean { try { const u = new URL(url); if (isIpAddress(u.hostname)) { return false; } return true; } catch (_) { return false; } } /** * pure host name e.g. 1.1.1.1 or www.grida.co * @param hostname * @returns */ function isIpAddress(hostname: string): boolean { return hostname.match(/[a-z]/i) ? false : true; } interface SingleProxyRequestLimitPolicy { timeout: number; // max time in ms size: number; // max size in bytes } ================================================ FILE: services/lagacy/src/usage/README.md ================================================ # Usage Logging ## Data we collect - client ip (for managing x-rate-limit) - client ua (for malicious usage preventation) - request url information (only host) (for understanding api usage) - we do not collect request query - we do not collect request body - we do not collect response body - response result (for collecting success rate) (collection as http status code) - duration (for tracking x-rate-limit for execution time) - payload (for tracking x-rate-limit for data transfer) - app (for identifying the request) (this is determined by api key you are using. pretty obvious) ================================================ FILE: services/lagacy/src/usage/index.ts ================================================ import * as express from "express"; import * as dynamoose from "dynamoose"; import * as https from "https"; const agent = new https.Agent({ keepAlive: true, rejectUnauthorized: true, maxSockets: 1000, }); dynamoose.aws.sdk.config.update({ httpOptions: { agent: agent, }, }); import { CorsProxyApiRequest, CorsProxyApiRequestLog, CorsRequestLogModel, } from "./model"; import { nanoid } from "nanoid"; /** * * @param req * @param res */ export async function logRequest(req: express.Request, res: express.Response) { // do not log the request if client error. (413 or 401 or 400) if (500 > res.statusCode && res.statusCode >= 400) { return; } const ip = (req.headers["x-forwarded-for"] || req.socket.remoteAddress) as string; const timestamp = new Date(); const origin = req.headers["origin"] || undefined; //@ts-ignore (useragent is provided by above useragent.express()) const _uaobj = req.useragent; const ua = _uaobj.source; // gives the raw ua string const url = res.get("x-request-url"); const payload = Number( (() => { const _cl = res.get("content-length"); return _cl ? _cl : 0; })() ); const duration = Number( (() => { const _rt = res.get("x-response-time"); return _rt ? _rt : 0; })() ); await log({ ip: ip, origin: origin, ua: ua, duration: duration, size: payload, at: timestamp, target: url, app: "anonymous", }); } async function log(request: CorsProxyApiRequest) { // console.log("request", request); const id = nanoid(); const billedduration = Math.ceil(request.duration / 100) * 100; // billed duration is stepped by 100ms try { const payload = new CorsRequestLogModel({ id: id, billed_duration: billedduration, ...request, }); await payload.save(); } catch (_) { // do nothing } } ================================================ FILE: services/lagacy/src/usage/model.ts ================================================ import * as dynamoose from "dynamoose"; import { nanoid } from "nanoid"; export type AppId = string | "anonymous" | "official-demo"; export interface CorsProxyApiRequest { /** * ip address of request client (could be server / app / web) */ ip?: string; /** * compressed / raw user agent data of the request */ ua?: string; /** * request origin from request headers */ origin?: string; /** * target resource url */ target: string; /** * duration in ms */ duration: number; /** * data payload */ size: number; /** * request timestamp */ at: Date; /** * the user/requester app */ app: AppId; } export interface CorsProxyApiRequestLog extends CorsProxyApiRequest { /** * unique id of the request */ id: string; /** * billing duration in ms - ceils with 100ms */ billed_duration: number; } export const CorsRequestLogSchema = new dynamoose.Schema( { id: { type: String, required: true, default: () => nanoid(), }, app: { type: String, required: true, index: { name: "appIndex", }, }, ua: { type: String, required: false, }, at: { type: Date, required: true, }, size: { type: Number, required: true, }, ip: { type: String, required: false, }, target: { type: String, required: true, }, duration: { type: Number, required: true, }, billed_duration: { type: Number, required: true, }, }, { saveUnknown: true, } ); const CORS_REQUEST_LOG_TABLE_NAME = process.env .DYNAMODB_TABLE_USAGE_LOG as string; export const CorsRequestLogModel = dynamoose.model( CORS_REQUEST_LOG_TABLE_NAME, CorsRequestLogSchema, { create: true, } ); ================================================ FILE: services/lagacy/tsconfig.json ================================================ { "compilerOptions": { "preserveConstEnums": true, "strictNullChecks": true, "sourceMap": true, "allowJs": true, "target": "es5", "outDir": ".build", "moduleResolution": "node", "lib": ["es2015"], "rootDir": "./" } } ================================================ FILE: services/mail.cors.sh/onboarding.mjml ================================================ .break { word-break: break-all !important; }

Your API key for
proxy.cors.sh

Your temporary API key is..

{{CODE}}

Copy & Paste this api key to your api call (XHR) header.

Example usage

You can use the api key like below. For more example, visit https://cors.sh/#usage
fetch('https://proxy.cors.sh/https://acme.com', {
  headers: {
  'x-cors-api-key': '{{CODE}}'
  }
})

Next (Action required)

Since this key is a temporary key to get you started, you have to sign-in & claim your account to get a permanent one. This key will only be valid for 3 days. 👉 Get the permanent key button not working? copy & paste this link to your browser {{ONBOARDINGLINK}}
A Grida Product | Contact
================================================ FILE: services/mail.cors.sh/onboarding_with_payment_success.mjml ================================================ .break { word-break: break-all !important; }

Thank you for
your subscription

We’re all set. Let’s get rid of the cors errors by extending your api call with proxy.cors.sh like below.

Your API key for your first application “{{APPLICATIONNAME}}” is..

# for testing
{{CODE_TEST}}

# for production
{{CODE_LIVE}}

Copy & Paste this api key to your api call (XHR) header.

Example usage

You can use the api key like below. For more example, visit https://cors.sh/#usage
fetch('https://proxy.cors.sh/https://acme.com', {
  headers: {
  'x-cors-api-key': '{{CODE_TEST}}'
  }
})
Read the docs
A Grida Product | Contact
================================================ FILE: services/mail.cors.sh/render/onboarding.subject ================================================ CORS.SH | your API Key for cors.proxy.sh ================================================ FILE: services/mail.cors.sh/render/onboarding.template.html ================================================

Your API key for
proxy.cors.sh

Your temporary API key is..

{{CODE}}

Copy & Paste this api key to your api call (XHR) header.

Example usage

You can use the api key like below. For more example, visit https://cors.sh/#usage
fetch('https://proxy.cors.sh/https://acme.com', {
  headers: {
  'x-cors-api-key': '{{CODE}}'
  }
})

Next (Action required)

Since this key is a temporary key to get you started, you have to sign-in & claim your account to get a permanent one. This key will only be valid for 3 days.
👉 Get the permanent key
button not working? copy & paste this link to your browser {{ONBOARDINGLINK}}
================================================ FILE: services/mail.cors.sh/render/onboarding_with_payment_success.subject ================================================ CORS.SH | Your first project ================================================ FILE: services/mail.cors.sh/render/onboarding_with_payment_success.template.html ================================================

Thank you for
your subscription

We’re all set. Let’s get rid of the cors errors by extending your api call with proxy.cors.sh like below.

Your API key for your first application “{{APPLICATIONNAME}}” is..

# for testing
{{CODE_TEST}}

# for production
{{CODE_LIVE}}

Copy & Paste this api key to your api call (XHR) header.

Example usage

You can use the api key like below. For more example, visit https://cors.sh/#usage
fetch('https://proxy.cors.sh/https://acme.com', {
  headers: {
  'x-cors-api-key': '{{CODE_TEST}}'
  }
})
================================================ FILE: services/proxy.cors.sh/.gitignore ================================================ # compiled output /dist /node_modules .build # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json # env .env .env.staging .env.production ================================================ FILE: services/proxy.cors.sh/README.md ================================================ # CORS.BRIDGED.CC ![cors.sh cover artwork](./docs/artwork_cors.sh.jpg) ## The Web deployment app.cors.bridged.cc is linked to repositoy https://github.com/bridgedxyz/proxy-client, deployed via aws amplify on `proxy-client`'s `app.cors.bridged.xyz` branch **other cd options** using serverless-finch (dropped & not used) > this is an obsolete approach of deploying web to s3, which we are no longer using in serverless.yml ``` custom: scripts: hooks: 'before:package:createDeploymentArtifacts': yarn --cwd web build && sls client deploy client: bucketName: app.cors.bridged.cc distributionFolder: web/build indexDocument: index.html errorDocument: index.html plugins: - serverless-finch ``` ``` yarn add --dev serverless-plugin-scripts serverless-finch ``` ================================================ FILE: services/proxy.cors.sh/contributing.md ================================================ ## running redis server locally ``` docker run -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server:latest ``` ================================================ FILE: services/proxy.cors.sh/deploy/prod.sh ================================================ sls deploy --stage production \ --param="domain=true" ================================================ FILE: services/proxy.cors.sh/deploy/staging.sh ================================================ sls deploy --stage staging \ --param="domain=false" ================================================ FILE: services/proxy.cors.sh/docs/README.md ================================================ # Docs & Image resources directory ================================================ FILE: services/proxy.cors.sh/lib/cors.ts ================================================ import * as httpProxy from "http-proxy"; import * as https from "https"; import * as http from "http"; import * as url from "url"; import { getProxyForUrl } from "proxy-from-env"; import { withCORS, isValidHostName, parseURL } from "./utils"; interface OptionParams { httpProxyOptions: httpProxy.ServerOptions; httpsOptions?: https.ServerOptions; [x: string]: any; } function proxyRequest(req, res, proxy) { var location = req.corsAnywhereRequestState.location; req.url = location.path; var proxyOptions: any = { changeOrigin: false, prependPath: false, target: location, headers: { host: location.host, }, buffer: { pipe: function (proxyReq) { var proxyReqOn = proxyReq.on; proxyReq.on = function (eventName, listener) { if (eventName !== "response") { return proxyReqOn.call(this, eventName, listener); } return proxyReqOn.call(this, "response", function (proxyRes) { if (onProxyResponse(proxy, proxyReq, proxyRes, req, res)) { try { listener(proxyRes); } catch (err) { proxyReq.emit("error", err); } } }); }; return req.pipe(proxyReq); }, }, }; var proxyThroughUrl = req.corsAnywhereRequestState.getProxyForUrl( location.href ); if (proxyThroughUrl) { proxyOptions.target = proxyThroughUrl; proxyOptions.toProxy = true; req.url = location.href; } try { proxy.web(req, res, proxyOptions); } catch (err) { proxy.emit("error", err, req, res); } } function onProxyResponse(proxy, proxyReq, proxyRes, req, res) { var requestState = req.corsAnywhereRequestState; var statusCode = proxyRes.statusCode; if (!requestState.redirectCount_) { res.setHeader("x-request-url", requestState.location.href); } if ( statusCode === 301 || statusCode === 302 || statusCode === 303 || statusCode === 307 || statusCode === 308 ) { var locationHeader = proxyRes.headers.location; var parsedLocation; if (locationHeader) { locationHeader = url.resolve(requestState.location.href, locationHeader); parsedLocation = parseURL(locationHeader); } if (parsedLocation) { if (statusCode === 301 || statusCode === 302 || statusCode === 303) { requestState.redirectCount_ = requestState.redirectCount_ + 1 || 1; if (requestState.redirectCount_ <= requestState.maxRedirects) { res.setHeader( "X-CORS-Redirect-" + requestState.redirectCount_, statusCode + " " + locationHeader ); req.method = "GET"; req.headers["content-length"] = "0"; delete req.headers["content-type"]; requestState.location = parsedLocation; req.removeAllListeners(); proxyReq.removeAllListeners("error"); proxyReq.once("error", function catchAndIgnoreError() {}); proxyReq.abort(); proxyRequest(req, res, proxy); return false; } } proxyRes.headers.location = requestState.proxyBaseUrl + "/" + locationHeader; } } delete proxyRes.headers["set-cookie"]; delete proxyRes.headers["set-cookie2"]; proxyRes.headers["x-final-url"] = requestState.location.href; withCORS(proxyRes.headers, req); return true; } function getHandler(options, proxy) { var corsAnywhere: any = { getProxyForUrl: getProxyForUrl, // Function that specifies the proxy to use maxRedirects: 5, // Maximum number of redirects to be followed. originBlacklist: [], // Requests from these origins will be blocked. originWhitelist: [], // If non-empty, requests not from an origin in this list will be blocked. checkRateLimit: null, // Function that may enforce a rate-limit by returning a non-empty string. redirectSameOrigin: false, // Redirect the client to the requested URL for same-origin requests. requireHeader: null, // Require a header to be set? removeHeaders: [], // Strip these request headers. setHeaders: {}, // Set these request headers. corsMaxAge: 0, // If set, an Access-Control-Max-Age header with this value (in seconds) will be added. helpFile: __dirname + "/help.txt", }; Object.keys(corsAnywhere).forEach(function (option) { if (Object.prototype.hasOwnProperty.call(options, option)) { corsAnywhere[option] = options[option]; } }); if (corsAnywhere.requireHeader) { if (typeof corsAnywhere.requireHeader === "string") { corsAnywhere.requireHeader = [corsAnywhere.requireHeader.toLowerCase()]; } else if ( !Array.isArray(corsAnywhere.requireHeader) || corsAnywhere.requireHeader.length === 0 ) { corsAnywhere.requireHeader = null; } else { corsAnywhere.requireHeader = corsAnywhere.requireHeader.map(function ( headerName ) { return headerName.toLowerCase(); }); } } var hasRequiredHeaders = function (headers) { return ( !corsAnywhere.requireHeader || corsAnywhere.requireHeader.some(function (headerName) { return Object.hasOwnProperty.call(headers, headerName); }) ); }; return function (req, res) { req.corsAnywhereRequestState = { getProxyForUrl: corsAnywhere.getProxyForUrl, maxRedirects: corsAnywhere.maxRedirects, corsMaxAge: corsAnywhere.corsMaxAge, }; var cors_headers = withCORS({}, req); if (req.method === "OPTIONS") { res.writeHead(200, cors_headers); res.end(); return; } var location: any = parseURL(req.url.slice(1)); if (location.host === "iscorsneeded") { res.writeHead(200, { "Content-Type": "text/plain" }); res.end("no"); return; } if (location.port > 65535) { res.writeHead(400, "Invalid port", cors_headers); res.end("Port number too large: " + location.port); return; } if (!/^\/https?:/.test(req.url) && !isValidHostName(location.hostname)) { res.writeHead(404, "Invalid host", cors_headers); res.end("Invalid host: " + location.hostname); return; } if (!hasRequiredHeaders(req.headers)) { res.writeHead(400, "Header required", cors_headers); res.end( "Missing required request header. Must specify one of: " + corsAnywhere.requireHeader + "\n" + "If you are testing out, you can try at https://cors.sh/playground" ); return; } var origin = req.headers.origin || ""; if (corsAnywhere.originBlacklist.indexOf(origin) >= 0) { res.writeHead(403, "Forbidden", cors_headers); res.end( 'The origin "' + origin + '" was blacklisted by the operator of this proxy.' ); return; } if ( corsAnywhere.originWhitelist.length && corsAnywhere.originWhitelist.indexOf(origin) === -1 ) { res.writeHead(403, "Forbidden", cors_headers); res.end( 'The origin "' + origin + '" was not whitelisted by the operator of this proxy.' ); return; } var rateLimitMessage = corsAnywhere.checkRateLimit && corsAnywhere.checkRateLimit(origin); if (rateLimitMessage) { res.writeHead(429, "Too Many Requests", cors_headers); res.end( 'The origin "' + origin + '" has sent too many requests.\n' + rateLimitMessage ); return; } if ( corsAnywhere.redirectSameOrigin && origin && location.href[origin.length] === "/" && location.href.lastIndexOf(origin, 0) === 0 ) { cors_headers.vary = "origin"; cors_headers["cache-control"] = "private"; cors_headers.location = location.href; res.writeHead(301, "Please use a direct request", cors_headers); res.end(); return; } var isRequestedOverHttps = req.connection.encrypted || /^\s*https/.test(req.headers["x-forwarded-proto"]); var proxyBaseUrl = (isRequestedOverHttps ? "https://" : "http://") + req.headers.host; corsAnywhere.removeHeaders.forEach(function (header) { delete req.headers[header]; }); Object.keys(corsAnywhere.setHeaders).forEach(function (header) { req.headers[header] = corsAnywhere.setHeaders[header]; }); req.corsAnywhereRequestState.location = location; req.corsAnywhereRequestState.proxyBaseUrl = proxyBaseUrl; proxyRequest(req, res, proxy); }; } export const createServer = (options: OptionParams) => { var server: https.Server | http.Server; var httpProxyOptions: httpProxy.ServerOptions = { xfwd: true, // Append X-Forwarded-* headers }; options = options || {}; if (options.httpProxyOptions) { Object.keys(options.httpProxyOptions).forEach(function (option) { httpProxyOptions[option] = options.httpProxyOptions[option]; }); } var proxy: httpProxy = httpProxy.createServer(httpProxyOptions); var requestHandler = getHandler(options, proxy); if (options.httpsOptions) { server = https.createServer(options.httpsOptions, requestHandler); } else { server = http.createServer(requestHandler); } proxy.on( "error", function (err: Error, _: http.IncomingMessage, res: http.ServerResponse) { if (process.env.NODE_ENV !== "production") { console.error("Proxy error: ", err, res); } if (res.headersSent) { if (res.writableEnded === false) { res.end(); } return; } var headerNames = res.getHeaderNames ? res.getHeaderNames() : //@ts-ignore Object.keys(res._headers || {}); headerNames.forEach(function (name) { res.removeHeader(name); }); res.writeHead(404, { "Access-Control-Allow-Origin": "*" }); res.end("Not found because of proxy error: " + err); } ); return server; }; ================================================ FILE: services/proxy.cors.sh/lib/utils.ts ================================================ import * as net from "net"; import * as url from "url"; export const regexp = /\.(?:AAA|AARP|ABARTH|ABB|ABBOTT|ABBVIE|ABC|ABLE|ABOGADO|ABUDHABI|AC|ACADEMY|ACCENTURE|ACCOUNTANT|ACCOUNTANTS|ACO|ACTIVE|ACTOR|AD|ADAC|ADS|ADULT|AE|AEG|AERO|AETNA|AF|AFAMILYCOMPANY|AFL|AFRICA|AG|AGAKHAN|AGENCY|AI|AIG|AIGO|AIRBUS|AIRFORCE|AIRTEL|AKDN|AL|ALFAROMEO|ALIBABA|ALIPAY|ALLFINANZ|ALLSTATE|ALLY|ALSACE|ALSTOM|AM|AMERICANEXPRESS|AMERICANFAMILY|AMEX|AMFAM|AMICA|AMSTERDAM|ANALYTICS|ANDROID|ANQUAN|ANZ|AO|AOL|APARTMENTS|APP|APPLE|AQ|AQUARELLE|AR|ARAB|ARAMCO|ARCHI|ARMY|ARPA|ART|ARTE|AS|ASDA|ASIA|ASSOCIATES|AT|ATHLETA|ATTORNEY|AU|AUCTION|AUDI|AUDIBLE|AUDIO|AUSPOST|AUTHOR|AUTO|AUTOS|AVIANCA|AW|AWS|AX|AXA|AZ|AZURE|BA|BABY|BAIDU|BANAMEX|BANANAREPUBLIC|BAND|BANK|BAR|BARCELONA|BARCLAYCARD|BARCLAYS|BAREFOOT|BARGAINS|BASEBALL|BASKETBALL|BAUHAUS|BAYERN|BB|BBC|BBT|BBVA|BCG|BCN|BD|BE|BEATS|BEAUTY|BEER|BENTLEY|BERLIN|BEST|BESTBUY|BET|BF|BG|BH|BHARTI|BI|BIBLE|BID|BIKE|BING|BINGO|BIO|BIZ|BJ|BLACK|BLACKFRIDAY|BLANCO|BLOCKBUSTER|BLOG|BLOOMBERG|BLUE|BM|BMS|BMW|BN|BNL|BNPPARIBAS|BO|BOATS|BOEHRINGER|BOFA|BOM|BOND|BOO|BOOK|BOOKING|BOOTS|BOSCH|BOSTIK|BOSTON|BOT|BOUTIQUE|BOX|BR|BRADESCO|BRIDGESTONE|BROADWAY|BROKER|BROTHER|BRUSSELS|BS|BT|BUDAPEST|BUGATTI|BUILD|BUILDERS|BUSINESS|BUY|BUZZ|BV|BW|BY|BZ|BZH|CA|CAB|CAFE|CAL|CALL|CALVINKLEIN|CAM|CAMERA|CAMP|CANCERRESEARCH|CANON|CAPETOWN|CAPITAL|CAPITALONE|CAR|CARAVAN|CARDS|CARE|CAREER|CAREERS|CARS|CARTIER|CASA|CASE|CASEIH|CASH|CASINO|CAT|CATERING|CATHOLIC|CBA|CBN|CBRE|CBS|CC|CD|CEB|CENTER|CEO|CERN|CF|CFA|CFD|CG|CH|CHANEL|CHANNEL|CHASE|CHAT|CHEAP|CHINTAI|CHRISTMAS|CHROME|CHRYSLER|CHURCH|CI|CIPRIANI|CIRCLE|CISCO|CITADEL|CITI|CITIC|CITY|CITYEATS|CK|CL|CLAIMS|CLEANING|CLICK|CLINIC|CLINIQUE|CLOTHING|CLOUD|CLUB|CLUBMED|CM|CN|CO|COACH|CODES|COFFEE|COLLEGE|COLOGNE|COM|COMCAST|COMMBANK|COMMUNITY|COMPANY|COMPARE|COMPUTER|COMSEC|CONDOS|CONSTRUCTION|CONSULTING|CONTACT|CONTRACTORS|COOKING|COOKINGCHANNEL|COOL|COOP|CORSICA|COUNTRY|COUPON|COUPONS|COURSES|CR|CREDIT|CREDITCARD|CREDITUNION|CRICKET|CROWN|CRS|CRUISE|CRUISES|CSC|CU|CUISINELLA|CV|CW|CX|CY|CYMRU|CYOU|CZ|DABUR|DAD|DANCE|DATA|DATE|DATING|DATSUN|DAY|DCLK|DDS|DE|DEAL|DEALER|DEALS|DEGREE|DELIVERY|DELL|DELOITTE|DELTA|DEMOCRAT|DENTAL|DENTIST|DESI|DESIGN|DEV|DHL|DIAMONDS|DIET|DIGITAL|DIRECT|DIRECTORY|DISCOUNT|DISCOVER|DISH|DIY|DJ|DK|DM|DNP|DO|DOCS|DOCTOR|DODGE|DOG|DOHA|DOMAINS|DOT|DOWNLOAD|DRIVE|DTV|DUBAI|DUCK|DUNLOP|DUNS|DUPONT|DURBAN|DVAG|DVR|DZ|EARTH|EAT|EC|ECO|EDEKA|EDU|EDUCATION|EE|EG|EMAIL|EMERCK|ENERGY|ENGINEER|ENGINEERING|ENTERPRISES|EPOST|EPSON|EQUIPMENT|ER|ERICSSON|ERNI|ES|ESQ|ESTATE|ESURANCE|ET|ETISALAT|EU|EUROVISION|EUS|EVENTS|EVERBANK|EXCHANGE|EXPERT|EXPOSED|EXPRESS|EXTRASPACE|FAGE|FAIL|FAIRWINDS|FAITH|FAMILY|FAN|FANS|FARM|FARMERS|FASHION|FAST|FEDEX|FEEDBACK|FERRARI|FERRERO|FI|FIAT|FIDELITY|FIDO|FILM|FINAL|FINANCE|FINANCIAL|FIRE|FIRESTONE|FIRMDALE|FISH|FISHING|FIT|FITNESS|FJ|FK|FLICKR|FLIGHTS|FLIR|FLORIST|FLOWERS|FLY|FM|FO|FOO|FOOD|FOODNETWORK|FOOTBALL|FORD|FOREX|FORSALE|FORUM|FOUNDATION|FOX|FR|FREE|FRESENIUS|FRL|FROGANS|FRONTDOOR|FRONTIER|FTR|FUJITSU|FUJIXEROX|FUN|FUND|FURNITURE|FUTBOL|FYI|GA|GAL|GALLERY|GALLO|GALLUP|GAME|GAMES|GAP|GARDEN|GB|GBIZ|GD|GDN|GE|GEA|GENT|GENTING|GEORGE|GF|GG|GGEE|GH|GI|GIFT|GIFTS|GIVES|GIVING|GL|GLADE|GLASS|GLE|GLOBAL|GLOBO|GM|GMAIL|GMBH|GMO|GMX|GN|GODADDY|GOLD|GOLDPOINT|GOLF|GOO|GOODHANDS|GOODYEAR|GOOG|GOOGLE|GOP|GOT|GOV|GP|GQ|GR|GRAINGER|GRAPHICS|GRATIS|GREEN|GRIPE|GROCERY|GROUP|GS|GT|GU|GUARDIAN|GUCCI|GUGE|GUIDE|GUITARS|GURU|GW|GY|HAIR|HAMBURG|HANGOUT|HAUS|HBO|HDFC|HDFCBANK|HEALTH|HEALTHCARE|HELP|HELSINKI|HERE|HERMES|HGTV|HIPHOP|HISAMITSU|HITACHI|HIV|HK|HKT|HM|HN|HOCKEY|HOLDINGS|HOLIDAY|HOMEDEPOT|HOMEGOODS|HOMES|HOMESENSE|HONDA|HONEYWELL|HORSE|HOSPITAL|HOST|HOSTING|HOT|HOTELES|HOTELS|HOTMAIL|HOUSE|HOW|HR|HSBC|HT|HU|HUGHES|HYATT|HYUNDAI|IBM|ICBC|ICE|ICU|ID|IE|IEEE|IFM|IKANO|IL|IM|IMAMAT|IMDB|IMMO|IMMOBILIEN|IN|INDUSTRIES|INFINITI|INFO|ING|INK|INSTITUTE|INSURANCE|INSURE|INT|INTEL|INTERNATIONAL|INTUIT|INVESTMENTS|IO|IPIRANGA|IQ|IR|IRISH|IS|ISELECT|ISMAILI|IST|ISTANBUL|IT|ITAU|ITV|IVECO|IWC|JAGUAR|JAVA|JCB|JCP|JE|JEEP|JETZT|JEWELRY|JIO|JLC|JLL|JM|JMP|JNJ|JO|JOBS|JOBURG|JOT|JOY|JP|JPMORGAN|JPRS|JUEGOS|JUNIPER|KAUFEN|KDDI|KE|KERRYHOTELS|KERRYLOGISTICS|KERRYPROPERTIES|KFH|KG|KH|KI|KIA|KIM|KINDER|KINDLE|KITCHEN|KIWI|KM|KN|KOELN|KOMATSU|KOSHER|KP|KPMG|KPN|KR|KRD|KRED|KUOKGROUP|KW|KY|KYOTO|KZ|LA|LACAIXA|LADBROKES|LAMBORGHINI|LAMER|LANCASTER|LANCIA|LANCOME|LAND|LANDROVER|LANXESS|LASALLE|LAT|LATINO|LATROBE|LAW|LAWYER|LB|LC|LDS|LEASE|LECLERC|LEFRAK|LEGAL|LEGO|LEXUS|LGBT|LI|LIAISON|LIDL|LIFE|LIFEINSURANCE|LIFESTYLE|LIGHTING|LIKE|LILLY|LIMITED|LIMO|LINCOLN|LINDE|LINK|LIPSY|LIVE|LIVING|LIXIL|LK|LOAN|LOANS|LOCKER|LOCUS|LOFT|LOL|LONDON|LOTTE|LOTTO|LOVE|LPL|LPLFINANCIAL|LR|LS|LT|LTD|LTDA|LU|LUNDBECK|LUPIN|LUXE|LUXURY|LV|LY|MA|MACYS|MADRID|MAIF|MAISON|MAKEUP|MAN|MANAGEMENT|MANGO|MAP|MARKET|MARKETING|MARKETS|MARRIOTT|MARSHALLS|MASERATI|MATTEL|MBA|MC|MCKINSEY|MD|ME|MED|MEDIA|MEET|MELBOURNE|MEME|MEMORIAL|MEN|MENU|MEO|MERCKMSD|METLIFE|MG|MH|MIAMI|MICROSOFT|MIL|MINI|MINT|MIT|MITSUBISHI|MK|ML|MLB|MLS|MM|MMA|MN|MO|MOBI|MOBILE|MOBILY|MODA|MOE|MOI|MOM|MONASH|MONEY|MONSTER|MOPAR|MORMON|MORTGAGE|MOSCOW|MOTO|MOTORCYCLES|MOV|MOVIE|MOVISTAR|MP|MQ|MR|MS|MSD|MT|MTN|MTR|MU|MUSEUM|MUTUAL|MV|MW|MX|MY|MZ|NA|NAB|NADEX|NAGOYA|NAME|NATIONWIDE|NATURA|NAVY|NBA|NC|NE|NEC|NET|NETBANK|NETFLIX|NETWORK|NEUSTAR|NEW|NEWHOLLAND|NEWS|NEXT|NEXTDIRECT|NEXUS|NF|NFL|NG|NGO|NHK|NI|NICO|NIKE|NIKON|NINJA|NISSAN|NISSAY|NL|NO|NOKIA|NORTHWESTERNMUTUAL|NORTON|NOW|NOWRUZ|NOWTV|NP|NR|NRA|NRW|NTT|NU|NYC|NZ|OBI|OBSERVER|OFF|OFFICE|OKINAWA|OLAYAN|OLAYANGROUP|OLDNAVY|OLLO|OM|OMEGA|ONE|ONG|ONL|ONLINE|ONYOURSIDE|OOO|OPEN|ORACLE|ORANGE|ORG|ORGANIC|ORIGINS|OSAKA|OTSUKA|OTT|OVH|PA|PAGE|PANASONIC|PANERAI|PARIS|PARS|PARTNERS|PARTS|PARTY|PASSAGENS|PAY|PCCW|PE|PET|PF|PFIZER|PG|PH|PHARMACY|PHD|PHILIPS|PHONE|PHOTO|PHOTOGRAPHY|PHOTOS|PHYSIO|PIAGET|PICS|PICTET|PICTURES|PID|PIN|PING|PINK|PIONEER|PIZZA|PK|PL|PLACE|PLAY|PLAYSTATION|PLUMBING|PLUS|PM|PN|PNC|POHL|POKER|POLITIE|PORN|POST|PR|PRAMERICA|PRAXI|PRESS|PRIME|PRO|PROD|PRODUCTIONS|PROF|PROGRESSIVE|PROMO|PROPERTIES|PROPERTY|PROTECTION|PRU|PRUDENTIAL|PS|PT|PUB|PW|PWC|PY|QA|QPON|QUEBEC|QUEST|QVC|RACING|RADIO|RAID|RE|READ|REALESTATE|REALTOR|REALTY|RECIPES|RED|REDSTONE|REDUMBRELLA|REHAB|REISE|REISEN|REIT|RELIANCE|REN|RENT|RENTALS|REPAIR|REPORT|REPUBLICAN|REST|RESTAURANT|REVIEW|REVIEWS|REXROTH|RICH|RICHARDLI|RICOH|RIGHTATHOME|RIL|RIO|RIP|RMIT|RO|ROCHER|ROCKS|RODEO|ROGERS|ROOM|RS|RSVP|RU|RUGBY|RUHR|RUN|RW|RWE|RYUKYU|SA|SAARLAND|SAFE|SAFETY|SAKURA|SALE|SALON|SAMSCLUB|SAMSUNG|SANDVIK|SANDVIKCOROMANT|SANOFI|SAP|SAPO|SARL|SAS|SAVE|SAXO|SB|SBI|SBS|SC|SCA|SCB|SCHAEFFLER|SCHMIDT|SCHOLARSHIPS|SCHOOL|SCHULE|SCHWARZ|SCIENCE|SCJOHNSON|SCOR|SCOT|SD|SE|SEARCH|SEAT|SECURE|SECURITY|SEEK|SELECT|SENER|SERVICES|SES|SEVEN|SEW|SEX|SEXY|SFR|SG|SH|SHANGRILA|SHARP|SHAW|SHELL|SHIA|SHIKSHA|SHOES|SHOP|SHOPPING|SHOUJI|SHOW|SHOWTIME|SHRIRAM|SI|SILK|SINA|SINGLES|SITE|SJ|SK|SKI|SKIN|SKY|SKYPE|SL|SLING|SM|SMART|SMILE|SN|SNCF|SO|SOCCER|SOCIAL|SOFTBANK|SOFTWARE|SOHU|SOLAR|SOLUTIONS|SONG|SONY|SOY|SPACE|SPIEGEL|SPOT|SPREADBETTING|SR|SRL|SRT|ST|STADA|STAPLES|STAR|STARHUB|STATEBANK|STATEFARM|STATOIL|STC|STCGROUP|STOCKHOLM|STORAGE|STORE|STREAM|STUDIO|STUDY|STYLE|SU|SUCKS|SUPPLIES|SUPPLY|SUPPORT|SURF|SURGERY|SUZUKI|SV|SWATCH|SWIFTCOVER|SWISS|SX|SY|SYDNEY|SYMANTEC|SYSTEMS|SZ|TAB|TAIPEI|TALK|TAOBAO|TARGET|TATAMOTORS|TATAR|TATTOO|TAX|TAXI|TC|TCI|TD|TDK|TEAM|TECH|TECHNOLOGY|TEL|TELECITY|TELEFONICA|TEMASEK|TENNIS|TEVA|TF|TG|TH|THD|THEATER|THEATRE|TIAA|TICKETS|TIENDA|TIFFANY|TIPS|TIRES|TIROL|TJ|TJMAXX|TJX|TK|TKMAXX|TL|TM|TMALL|TN|TO|TODAY|TOKYO|TOOLS|TOP|TORAY|TOSHIBA|TOTAL|TOURS|TOWN|TOYOTA|TOYS|TR|TRADE|TRADING|TRAINING|TRAVEL|TRAVELCHANNEL|TRAVELERS|TRAVELERSINSURANCE|TRUST|TRV|TT|TUBE|TUI|TUNES|TUSHU|TV|TVS|TW|TZ|UA|UBANK|UBS|UCONNECT|UG|UK|UNICOM|UNIVERSITY|UNO|UOL|UPS|US|UY|UZ|VA|VACATIONS|VANA|VANGUARD|VC|VE|VEGAS|VENTURES|VERISIGN|VERSICHERUNG|VET|VG|VI|VIAJES|VIDEO|VIG|VIKING|VILLAS|VIN|VIP|VIRGIN|VISA|VISION|VISTA|VISTAPRINT|VIVA|VIVO|VLAANDEREN|VN|VODKA|VOLKSWAGEN|VOLVO|VOTE|VOTING|VOTO|VOYAGE|VU|VUELOS|WALES|WALMART|WALTER|WANG|WANGGOU|WARMAN|WATCH|WATCHES|WEATHER|WEATHERCHANNEL|WEBCAM|WEBER|WEBSITE|WED|WEDDING|WEIBO|WEIR|WF|WHOSWHO|WIEN|WIKI|WILLIAMHILL|WIN|WINDOWS|WINE|WINNERS|WME|WOLTERSKLUWER|WOODSIDE|WORK|WORKS|WORLD|WOW|WS|WTC|WTF|XBOX|XEROX|XFINITY|XIHUAN|XIN|XN--11B4C3D|XN--1CK2E1B|XN--1QQW23A|XN--2SCRJ9C|XN--30RR7Y|XN--3BST00M|XN--3DS443G|XN--3E0B707E|XN--3HCRJ9C|XN--3OQ18VL8PN36A|XN--3PXU8K|XN--42C2D9A|XN--45BR5CYL|XN--45BRJ9C|XN--45Q11C|XN--4GBRIM|XN--54B7FTA0CC|XN--55QW42G|XN--55QX5D|XN--5SU34J936BGSG|XN--5TZM5G|XN--6FRZ82G|XN--6QQ986B3XL|XN--80ADXHKS|XN--80AO21A|XN--80AQECDR1A|XN--80ASEHDB|XN--80ASWG|XN--8Y0A063A|XN--90A3AC|XN--90AE|XN--90AIS|XN--9DBQ2A|XN--9ET52U|XN--9KRT00A|XN--B4W605FERD|XN--BCK1B9A5DRE4C|XN--C1AVG|XN--C2BR7G|XN--CCK2B3B|XN--CG4BKI|XN--CLCHC0EA0B2G2A9GCD|XN--CZR694B|XN--CZRS0T|XN--CZRU2D|XN--D1ACJ3B|XN--D1ALF|XN--E1A4C|XN--ECKVDTC9D|XN--EFVY88H|XN--ESTV75G|XN--FCT429K|XN--FHBEI|XN--FIQ228C5HS|XN--FIQ64B|XN--FIQS8S|XN--FIQZ9S|XN--FJQ720A|XN--FLW351E|XN--FPCRJ9C3D|XN--FZC2C9E2C|XN--FZYS8D69UVGM|XN--G2XX48C|XN--GCKR3F0F|XN--GECRJ9C|XN--GK3AT1E|XN--H2BREG3EVE|XN--H2BRJ9C|XN--H2BRJ9C8C|XN--HXT814E|XN--I1B6B1A6A2E|XN--IMR513N|XN--IO0A7I|XN--J1AEF|XN--J1AMH|XN--J6W193G|XN--JLQ61U9W7B|XN--JVR189M|XN--KCRX77D1X4A|XN--KPRW13D|XN--KPRY57D|XN--KPU716F|XN--KPUT3I|XN--L1ACC|XN--LGBBAT1AD8J|XN--MGB9AWBF|XN--MGBA3A3EJT|XN--MGBA3A4F16A|XN--MGBA7C0BBN0A|XN--MGBAAKC7DVF|XN--MGBAAM7A8H|XN--MGBAB2BD|XN--MGBAI9AZGQP6J|XN--MGBAYH7GPA|XN--MGBB9FBPOB|XN--MGBBH1A|XN--MGBBH1A71E|XN--MGBC0A9AZCG|XN--MGBCA7DZDO|XN--MGBERP4A5D4AR|XN--MGBGU82A|XN--MGBI4ECEXP|XN--MGBPL2FH|XN--MGBT3DHD|XN--MGBTX2B|XN--MGBX4CD0AB|XN--MIX891F|XN--MK1BU44C|XN--MXTQ1M|XN--NGBC5AZD|XN--NGBE9E0A|XN--NGBRX|XN--NODE|XN--NQV7F|XN--NQV7FS00EMA|XN--NYQY26A|XN--O3CW4H|XN--OGBPF8FL|XN--P1ACF|XN--P1AI|XN--PBT977C|XN--PGBS0DH|XN--PSSY2U|XN--Q9JYB4C|XN--QCKA1PMC|XN--QXAM|XN--RHQV96G|XN--ROVU88B|XN--RVC1E0AM3E|XN--S9BRJ9C|XN--SES554G|XN--T60B56A|XN--TCKWE|XN--TIQ49XQYJ|XN--UNUP4Y|XN--VERMGENSBERATER-CTB|XN--VERMGENSBERATUNG-PWB|XN--VHQUV|XN--VUQ861B|XN--W4R85EL8FHU5DNRA|XN--W4RS40L|XN--WGBH1C|XN--WGBL6A|XN--XHQ521B|XN--XKC2AL3HYE2A|XN--XKC2DL3A5EE0H|XN--Y9A3AQ|XN--YFRO4I67O|XN--YGBI2AMMX|XN--ZFR164B|XPERIA|XXX|XYZ|YACHTS|YAHOO|YAMAXUN|YANDEX|YE|YODOBASHI|YOGA|YOKOHAMA|YOU|YOUTUBE|YT|YUN|ZA|ZAPPOS|ZARA|ZERO|ZIP|ZIPPO|ZM|ZONE|ZUERICH|ZW)$/i; export const withCORS = (headers, request) => { headers["access-control-allow-origin"] = "*"; var corsMaxAge = request.corsAnywhereRequestState.corsMaxAge; if (request.method === "OPTIONS" && corsMaxAge) { headers["access-control-max-age"] = corsMaxAge; } if (request.headers["access-control-request-method"]) { headers["access-control-allow-methods"] = request.headers["access-control-request-method"]; delete request.headers["access-control-request-method"]; } if (request.headers["access-control-request-headers"]) { headers["access-control-allow-headers"] = request.headers["access-control-request-headers"]; delete request.headers["access-control-request-headers"]; } headers["access-control-expose-headers"] = Object.keys(headers).join(","); return headers; }; export const isValidHostName = hostname => { return !!( regexp.test(hostname) || net.isIPv4(hostname) || net.isIPv6(hostname) ); }; export const parseURL = req_url => { var match = req_url.match( /^(?:(https?:)?\/\/)?(([^\/?]+?)(?::(\d{0,5})(?=[\/?]|$))?)([\/?][\S\s]*|$)/i ); if (!match) { return null; } if (!match[1]) { if (/^https?:/i.test(req_url)) { return null; } if (req_url.lastIndexOf("//", 0) === -1) { req_url = "//" + req_url; } req_url = (match[4] === "443" ? "https:" : "http:") + req_url; } var parsed = url.parse(req_url); if (!parsed.hostname) { return null; } return parsed; }; ================================================ FILE: services/proxy.cors.sh/package.json ================================================ { "name": "proxy.cors.sh", "description": "free cors service for everyone", "version": "0.1.0", "scripts": { "clean": "rimraf .build .serverless", "deploy:prod": "yarn clean && source deploy/prod.sh", "deploy:staging": "yarn clean && source deploy/staging.sh", "dev": "sls offline start --stage development", "build": "yarn clean && sls package --stage development" }, "dependencies": { "aws-serverless-express": "^3.4.0", "dayjs": "^1.11.7", "express": "^4.17.1", "express-rate-limit": "^6.5.2", "express-useragent": "^1.0.15", "http-proxy": "^1.18.1", "nanoid": "^3.1.23", "proxy-from-env": "^1.1.0", "rate-limit-redis": "^3.0.1", "redis": "^4.3.0", "response-time": "^2.3.2", "serverless-http": "^2.7.0" }, "devDependencies": { "@types/aws-lambda": "^8.10.71", "@types/express": "^4.17.11", "@types/http-proxy": "^1.17.5", "@types/node": "^18.7.14", "@types/proxy-from-env": "^1.0.1", "@types/response-time": "^2.3.4", "rimraf": "^3.0.2", "serverless": "^2.22.0", "serverless-domain-manager": "^5.1.0", "serverless-offline": "^6.8.0", "serverless-plugin-dynamo-autoscaling": "^1.0.1", "serverless-plugin-optimize": "^4.1.4-rc.1", "serverless-plugin-typescript": "^2.1.2", "typescript": "^4.1.3" } } ================================================ FILE: services/proxy.cors.sh/serverless.yml ================================================ # Welcome to serverless. Read the docs # https://serverless.com/framework/docs/ service: cors-proxy useDotenv: true custom: customDomain: domainName: proxy.cors.sh basePath: "" stage: ${opt:stage, self:provider.stage} createRoute53Record: false # enabled only for production - configured by package.json script with --domain flag. enabled: ${param:domain, false} serverless-offline: httpPort: 4020 lambdaPort: 3020 noPrependStageInUrl: true provider: name: aws memorySize: 128 runtime: nodejs14.x apiGateway: shouldStartNameWithService: true binaryMediaTypes: - "*/*" endpointType: regional region: us-west-1 environment: STAGE: ${opt:stage, self:provider.stage} RATE_LIMIT_REDIS_URL: ${env:RATE_LIMIT_REDIS_URL} RATE_LIMIT_REDIS_USERNAME: ${env:RATE_LIMIT_REDIS_USERNAME} RATE_LIMIT_REDIS_PASSWORD: ${env:RATE_LIMIT_REDIS_PASSWORD} API_KEY_TEMP_AES_KEY: ${env:API_KEY_TEMP_AES_KEY} API_KEY_TEMP_AES_IV: ${env:API_KEY_TEMP_AES_IV} AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1" # for auth, managed by service.cors.sh DYNAMODB_TABLE_SERVICE_KEYS: "cors-proxy-service-keys-${opt:stage, self:provider.stage}" iamRoleStatements: - Effect: Allow Action: # this service only needs to read the service keys - dynamodb:GetItem Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE_SERVICE_KEYS}" # The `functions` block defines what code to deploy functions: api: handler: src/index.handler maximumEventAge: 60 maximumRetryAttempts: 0 timeout: 12 events: - http: path: /{proxy+} method: any # authorizer: # name: cors-proxy-authorizer # identitySource: "method.request.header.x-cors-api-key" # type: token # resultTtlInSeconds: 3600 # 1 hour # arn: ${env:CORS_PROXY_AUTHORIZER_ARN} - http: path: / method: get plugins: - serverless-plugin-typescript - serverless-plugin-optimize - serverless-offline - serverless-domain-manager ================================================ FILE: services/proxy.cors.sh/src/_util/size.ts ================================================ export const MB = 1048576; /** * 1 seconds in ms */ export const SEC = 1000; ================================================ FILE: services/proxy.cors.sh/src/_util/x-header.ts ================================================ import { Request } from "express"; export function headerfrom( headers: Request["headers"], key: string | string[] ): string | null { const keys = Array.isArray(key) ? key : [key]; for (const k of keys) { const v = headers[k]; if (v) { return v as string; } } return null; } ================================================ FILE: services/proxy.cors.sh/src/app.ts ================================================ import * as express from "express"; import * as useragent from "express-useragent"; import * as corsProxy from "../lib/cors"; import * as responsetime from "response-time"; import { logRequest } from "./usage"; import { blaklistoriginlimit, payloadlimit } from "./limit"; import limiter from "./limit/rate-limit"; import { authorization } from "./auth"; const app = express(); const cors_proxy = corsProxy.createServer({ // https://github.com/Rob--W/cors-anywhere/issues/39 requireHeader: ["origin", "x-requested-with"], // requireHeader: [], removeHeaders: [ "cookie", "cookie2", "x-request-start", "x-request-id", "via", "connect-time", "total-route-time", ], redirectSameOrigin: true, httpProxyOptions: { xfwd: false, }, }); app.get("/", function (req, res) { res.redirect("https://cors.sh/"); }); app.use(blaklistoriginlimit); // 1 app.use(payloadlimit); // 2 app.use(authorization); // 3 // response time middleware app.use( responsetime({ suffix: false, }) as any ); // user agent middleware app.use(useragent.express()); // hourly rate limiter app.use(limiter.hourly); // monthly (28-day) rate limiter app.use(limiter.monthly); // -- execution order matters -- // (0) app.use((req, res, next) => { // support 'x-strict-request-url' // refer issue: https://github.com/gridaco/cors.sh/issues/38 const x_strict_request_url = req.headers["x-strict-request-url"]; if (x_strict_request_url && typeof x_strict_request_url === "string") { // need prefix with '/' (required by cors_proxy to parse) req.url = "/" + x_strict_request_url; next(); return; } next(); }); // (1) app.use((req, res, next) => { if (res.headersSent) { return; } try { cors_proxy.emit("request", req, res); next(); } catch (_) {} }); // (2) /** * after response middleware */ app.use((req, res) => { res.on("finish", () => { logRequest(req, res); }); }); // -- execution order matters -- /** * global error handler */ app.use(((err, req, res, next) => { if (res.headersSent) { return next(err); } try { console.error(err); res.status(500).json({ message: "Internal Server Error", error: err, issue: "https://github.com/gridaco/cors.sh/issues", }); } catch (_) {} }) as express.ErrorRequestHandler); export { app }; ================================================ FILE: services/proxy.cors.sh/src/auth/_tmp_static_api_keys/.gitignore ================================================ # manuall managed credentials (temporal) keys.js ================================================ FILE: services/proxy.cors.sh/src/auth/_tmp_static_api_keys/README.md ================================================ # Temporal manually managed api key store ================================================ FILE: services/proxy.cors.sh/src/auth/_tmp_static_api_keys/index.js ================================================ export { default as keys } from "./keys"; ================================================ FILE: services/proxy.cors.sh/src/auth/index.ts ================================================ import * as express from "express"; import { validate_tmp_key } from "./keys-temp"; import { verify_synced_key } from "./keys-synced"; import { keyinfo } from "./keys"; import { headerfrom } from "../_util/x-header"; import { STATIC_CORS_ACCOUNT_API_KEY_HEADERS } from "../k"; import * as legacy from "./legacy"; function parseApiKey(req: express.Request): string | undefined | null { const apikey_from_header = headerfrom( req.headers, STATIC_CORS_ACCOUNT_API_KEY_HEADERS ); const api_key_q = "cors_api_key"; // parsing from query requires parsing the url then, parsing the query, since the api call can contain the original url with their query. let apikey_from_query; try { // since url starts with "/" we need to remove the first character, then decode the url const url = decodeURI(req.url.slice(1)); const params = new URL(url).searchParams; apikey_from_query = params.get(api_key_q); } catch (e) { apikey_from_query = req.query[api_key_q] as string; } return apikey_from_header || apikey_from_query; } export async function authorization( req: express.Request, res: express.Response, next ) { // 0. demo - no auth, allow all requests from https://cors.sh/playground (https://playground.cors.sh), rate limited by fixed rate // - uses ip as id // 1. legacy - static api key, rate limited by fixed rate // - uses api key as id // 2. anonymous localhost - only allow requests from localhost, rate limited by fixed rate // - uses ip as id // 3. temporary key - allow requests from anywhere (unless expired), rate limited by fixed rate // - uses api key // 4. signed key - allow requests from anywhere (unless configured), rate limited by purchased plan // // TODO: // - uses api key as subscription id // - live key - allow requests from anywhere (unless configured), rate limited by purchased plan // - test key - allow requests from anywhere (unless configured), rate limited by fixed rate // skip api key check for preflight requests if (req.method == "OPTIONS" || req.method == "HEAD") { next(); } const apikey = parseApiKey(req); if (!apikey) { // no key, anonymous const id = anynymous_request_identity(req); const authorization: AuthorizationInfo = { authorized: false, id, tier: "anonymous", }; res.locals.authorization = authorization; next(); return; } const { signature, mode } = keyinfo(apikey!); const origin = req.headers.origin; const { ip, hostname } = req; if ( origin === "https://cors.sh" || origin === "https://playground.cors.sh" || origin === "https://cors.sh/playground" ) { const authorization: AuthorizationInfo = { authorized: true, id: ip, tier: "unlimited", skip_rate_limit: true, }; res.locals.authorization = authorization; next(); return; } switch (mode) { case "live": case "test": { if (process.env.STAGE !== "production") { // bypass auth check in dev const authorization: AuthorizationInfo = { authorized: true, id: "dev", tier: "unlimited", }; res.locals.authorization = authorization; next(); return; // } // TODO: explicit handling for test / live key const verified = await verify_synced_key(signature); if (verified) { const { plan, billing_group } = verified; const authorization: AuthorizationInfo = { authorized: true, id: billing_group, tier: plan, }; res.locals.authorization = authorization; next(); return; } else { res .status(402) .send( "Your account is suspended. Please make a payment at https://cors.sh" ); return; } break; } case "temp": { const verified = validate_tmp_key(signature); if (verified) { const authorization: AuthorizationInfo = { authorized: true, id: signature, tier: "free", }; res.locals.authorization = authorization; next(); return; } else { res .status(401) .send("Temporay key expired Get a new one from https://cors.sh"); return; } break; } case "v2022": { const verified = legacy.validate_api_key(apikey!); if (verified) { const authorization: AuthorizationInfo = { authorized: true, id: signature, tier: "2022.t1", }; res.locals.authorization = authorization; next(); return; } else { res .status(401) .send("Unauthorized. Get a api key from https://cors.sh"); return; } break; } } } export interface AuthorizationInfo { authorized: boolean; id: string | "demo"; tier: "anonymous" | "free" | "unlimited" | "2022.t1" | "2023.t1" | "2023.t2"; skip_rate_limit?: boolean; } function anynymous_request_identity(req: express.Request) { if (req.hostname == "localhost") { return req.ip; } else { return req.hostname; } } ================================================ FILE: services/proxy.cors.sh/src/auth/keys-synced.ts ================================================ import { DynamoDB } from "aws-sdk"; import * as day from "dayjs"; const db = new DynamoDB.DocumentClient(); const TABLE = process.env.DYNAMODB_TABLE_SERVICE_KEYS!; export async function verify_synced_key(key: string): Promise< | { plan: "2023.t1" | "2023.t2"; billing_group: string; } | false > { const record = await find(key); if (!record) { return false; } const { active, expires_at, billing_group, plan } = record; if (active && expires_at > day().unix()) { return { plan: plan as any, billing_group, }; } return false; } async function find(key: string): Promise { const { Item } = await db .get({ TableName: TABLE, Key: { key: key, }, }) .promise(); if (!Item) { return null; } return Item as ServiceKeyInfo; } interface ServiceKeyInfo { key: string; plan: string; config: { allowed_origins: string[]; allowed_targets: string[]; }; active: boolean; billing_group: string; expires_at: number; synced_at: number; } ================================================ FILE: services/proxy.cors.sh/src/auth/keys-temp.ts ================================================ import * as crypto from "crypto"; import * as day from "dayjs"; const API_KEY_TEMP_AES_KEY: string = process.env.API_KEY_TEMP_AES_KEY!; const API_KEY_TEMP_AES_IV: string = process.env.API_KEY_TEMP_AES_IV!; const _aes_key = Buffer.from(API_KEY_TEMP_AES_KEY, "hex"); const _aes_iv = Buffer.from(API_KEY_TEMP_AES_IV, "hex"); const TMP_KEY_EXP_IN_DAYS = 3; export function validate_tmp_key(signature: string) { // decode signature that uses aes-256-cbc, then validate with totp. let decipher = crypto.createDecipheriv("aes-256-cbc", _aes_key, _aes_iv); try { let token = decipher.update(signature, "hex", "utf8") + decipher.final("utf8"); // token is a unix timestamp // the token should match (now ~ now + TMP_KEY_EXP_IN_DAYS) const now = day(); const token_day = day.unix(Number(token)); const diff = token_day.diff(now, "day"); if (diff > TMP_KEY_EXP_IN_DAYS) { return false; } return true; } catch (e) { return false; } } ================================================ FILE: services/proxy.cors.sh/src/auth/keys.ts ================================================ export function keyinfo(key: string): { signature: string; mode: "temp" | "live" | "test" | "v2022"; } { const mode = key.split("_")[0]; const signature = key.split("_")[1]; switch (mode) { case "temp": case "live": case "test": { return { mode, signature, }; } default: { // legacy key return { mode: "v2022", signature: key, }; } } } ================================================ FILE: services/proxy.cors.sh/src/auth/legacy/index.ts ================================================ export * from "./static-account-api-key-auth"; ================================================ FILE: services/proxy.cors.sh/src/auth/legacy/static-account-api-key-auth.ts ================================================ import { keys } from "../_tmp_static_api_keys"; // const nokey401UnAuthorized = () => { // return "https://bit.ly/2UnZSA8"; // // return { // // message: `CORS Proxy request from origin "${origin}" is not allowed. Request is unauthorized`, // // issue: "https://github.com/bridgedxyz/base/issues/23", // // }; // }; // export const unauthorizedAppBlocking = ( // req: express.Request, // res: express.Response, // next // ) => { // // skip api key check for preflight requests // if (req.method == "OPTIONS" || req.method == "HEAD") { // next(); // } // const apikey = headerfrom(req.headers, STATIC_CORS_ACCOUNT_API_KEY_HEADERS); // if (apikey && validate_api_key(apikey)) { // next(); // } else { // res.status(401).send(nokey401UnAuthorized()); // return; // } // }; export function validate_api_key(apikey: string) { if (!apikey || apikey == "") { return false; } const found = (keys as string[]).find((s) => s === apikey); if (found) { return true; } return false; } ================================================ FILE: services/proxy.cors.sh/src/index.ts ================================================ import * as serverlessExpress from "aws-serverless-express"; import { APIGatewayProxyHandler } from "aws-lambda"; import { app } from "./app"; const binaryMimeTypes = [ '*/*' // https://github.com/vendia/serverless-express/blob/master/examples/basic-starter/lambda.js // 'application/javascript', // 'application/json', // 'application/octet-stream', // 'application/xml', // 'font/eot', // 'font/opentype', // 'font/otf', // 'image/jpeg', // 'image/png', // 'image/svg+xml', // 'text/comma-separated-values', // 'text/css', // 'text/html', // 'text/javascript', // 'text/plain', // 'text/text', // 'text/xml' ] const server = serverlessExpress.createServer(app, null, binaryMimeTypes) export const handler: APIGatewayProxyHandler = (event, context) => { serverlessExpress.proxy(server, event, context); }; ================================================ FILE: services/proxy.cors.sh/src/k/index.ts ================================================ /** * proxy.cors.sh static api key header */ export const STATIC_CORS_ACCOUNT_API_KEY_HEADERS = [ "x-cors-grida-api-key", // legacy header for cors.bridged.cc "x-cors-api-key", // header for cors.sh ]; export const RATE_LIMIT_FREE_AUTHORIZED_PER_HOUR = 500; // 500 export const RATE_LIMIT_FREE_ANONYMOUS_PER_HOUR = 100; // 100 export const RATE_LIMIT_PAID_PER_MONTH_DEFAULT = 500000; export const RATE_LIMIT_PAID_PER_MONTH_T1 = 500000; // $4 / Mo export const RATE_LIMIT_PAID_PER_MONTH_T2 = 2500000; // $20 / Mo ================================================ FILE: services/proxy.cors.sh/src/limit/README.md ================================================ # Limitation handler ================================================ FILE: services/proxy.cors.sh/src/limit/blacklist-origin.ts ================================================ import * as express from "express"; /** * explicitly black listed origins. these are not registered to use base. */ const blacklisted_origin: string[] = [ "REJECTME", // WAITING FOR SERVICE PROVIDER'S ACTION "titronline.org", "titr.online", "showsport.xyz", "cdn14.esp-cdn.xyz", "digi-hdsport.com", "siunus.github.io", // ILLEGAL OR AUDULT WEBSITE (PERMINANTLY BLOCKED) "twerktvnaija.com", ]; const blacked401UnAuthorized = (origin: string) => { return "https://bit.ly/2UnZSA8"; // return { // message: `CORS Proxy request from origin "${origin}" is not allowed. Request is unauthorized`, // issue: "https://github.com/bridgedxyz/base/issues/23", // }; }; export const blaklistoriginlimit = ( req: express.Request, res: express.Response, next ) => { const origin = req.headers["origin"]; if (origin) { if (blacked(origin)) { res.status(401).send(blacked401UnAuthorized(origin)); return; } } next(); }; function blacked(origin: string): boolean { // patterns // 1. www. // 2. http// // 3. https// // 4. http:// // 5. .... try { const u = new URL(origin); return blacklisted_origin.some((o) => { return u.hostname == o || u.hostname == "www." + o; }); } catch (_) { // we cannot handle url that is invalid (need better logic for this) return false; } } ================================================ FILE: services/proxy.cors.sh/src/limit/index.ts ================================================ export * from "./payload-limit"; export * from "./blacklist-origin"; ================================================ FILE: services/proxy.cors.sh/src/limit/payload-limit.ts ================================================ import * as https from "https"; import * as http from "http"; import { MB } from "../_util/size"; /** * this is to save data transfer cost. And basically we should not use CORS Proxy to load large files. * * https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html */ const MAX_TARGET_RESOURCE_MB = 2; // 6mb is max supported by api gateway. (supports up to 2mb on proxy) export const payloadlimit = (req, res, next) => { const requrl = req.originalUrl.substring(1); const agent = requrl.startsWith("https:") ? https : http; agent .request(requrl, { method: "HEAD" }, (_resp) => { const len = Number(_resp.headers["content-length"]); if (len && len > MB * MAX_TARGET_RESOURCE_MB) { // reject if data larger than 30mb. res.status(413).send({ message: `Requested resource exceeds ${MAX_TARGET_RESOURCE_MB}mb`, issue: "https://github.com/bridgedxyz/base/issues/23", }); } else { // if content-length is undefined or less than 30mb, procceed. next(); } }) .on("error", (err) => { // ignore error for this // target, which is anonymous, might not support HEAD request. next(); }) .end(); }; ================================================ FILE: services/proxy.cors.sh/src/limit/rate-limit.ts ================================================ import { Request, Response } from "express"; import rateLimit from "express-rate-limit"; import RedisStore from "rate-limit-redis"; import { createClient } from "redis"; import { RATE_LIMIT_FREE_ANONYMOUS_PER_HOUR, RATE_LIMIT_FREE_AUTHORIZED_PER_HOUR, RATE_LIMIT_PAID_PER_MONTH_DEFAULT, RATE_LIMIT_PAID_PER_MONTH_T1, RATE_LIMIT_PAID_PER_MONTH_T2, } from "../k"; import type { AuthorizationInfo } from "../auth"; // rather if locally ran by `sls offline` const IS_OFFLINE = process.env.IS_OFFLINE; const client = createClient({ url: process.env.RATE_LIMIT_REDIS_URL, username: process.env.RATE_LIMIT_REDIS_USERNAME, password: process.env.RATE_LIMIT_REDIS_PASSWORD, socket: IS_OFFLINE ? { // 10 minutes connectTimeout: 10 * 60 * 1000, keepAlive: 10 * 60 * 1000, } : undefined, // ... (see https://github.com/redis/node-redis/blob/master/docs/client-configuration.md) }); // TODO: make sure connected client.connect(); /** * shared for all rate limiters * @returns */ const keygenerator = (req: Request, res: Response) => { const { id } = (res.locals.authorization as AuthorizationInfo) ?? {}; // return ip by default return id || req.ip; }; /** * 429 handler * Used by all rate limiters */ const excess_handler = (req: Request, res: Response, next) => { const { authorized } = (res.locals.authorization as AuthorizationInfo) ?? {}; // 429 handler if (authorized) { res .status(429) .send( "Too Many Requests.\nYou have reached the maximum number of requests per hour for Free tier. Please upgrade your plan to remove this limit." ); } else { res .status(429) .send( "Too Many Requests.\nThis request is anonymous and rate limited. To upgrade, please add an api key at https://cors.sh" ); } }; const limiter_free_per_hour = rateLimit({ // Rate limiter configuration windowMs: 60 * 1000 * 60, // 1 hour max: (req: Request, res: Response) => { const { tier } = (res.locals.authorization as AuthorizationInfo) ?? {}; switch (tier) { case "unlimited": return Infinity; case "free": return RATE_LIMIT_FREE_AUTHORIZED_PER_HOUR; // per hour case "anonymous": return RATE_LIMIT_FREE_ANONYMOUS_PER_HOUR; // per hour case "2022.t1": // legacy, all free version return RATE_LIMIT_FREE_AUTHORIZED_PER_HOUR; // per hour case "2023.t1": case "2023.t2": { // paid version // no limit, passed by skip() return Infinity; } default: // anonymous (free) return RATE_LIMIT_FREE_ANONYMOUS_PER_HOUR; // per hour } }, standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers skip: (req: Request, res: Response) => { if (req.method == "OPTIONS" || req.method == "HEAD") { return true; } let force_skip = false; const { skip_rate_limit, tier } = (res.locals.authorization as AuthorizationInfo) ?? {}; if (tier === "2023.t1" || tier === "2023.t2") { // since no monthly limit, skip the hourly limit for paid version force_skip = true; } if (force_skip || skip_rate_limit) { return true; } res.locals.rate_limit_handled = true; return false; }, keyGenerator: keygenerator, handler: excess_handler, // Redis store configuration store: new RedisStore({ sendCommand: (...args: string[]) => client.sendCommand(args), }), }); const MONTH28MS = 60 * 1000 * 60 * 24 * 28; // 28 days in ms // Consider: monthly rate limit might require bigger redis instance const limiter_paid_per_month = rateLimit({ // 28-day month windowMs: MONTH28MS, // max: RATE_LIMIT_PAID_PER_MONTH_T1, max: (req: Request, res: Response) => { const { tier } = (res.locals.authorization as AuthorizationInfo) ?? {}; switch (tier) { case "2023.t1": { // paid version tier 1 ($4 / Mo) return RATE_LIMIT_PAID_PER_MONTH_T1; } case "2023.t2": { // paid version tier 2 ($20 / Mo) return RATE_LIMIT_PAID_PER_MONTH_T2; } case "unlimited": return Infinity; case "anonymous": // anon, blocked by hourly limiter anyway case "free": // free, blocked by hourly limiter anyway case "2022.t1": // legacy, all free version default: // anonymous (free) return RATE_LIMIT_PAID_PER_MONTH_DEFAULT; // per month } }, skip: (req: Request, res: Response) => { if (req.method == "OPTIONS" || req.method == "HEAD") { return true; } // if rate limit handled by hourly limiter, skip this one return res.locals.rate_limit_handled; }, keyGenerator: keygenerator, handler: excess_handler, store: new RedisStore({ sendCommand: (...args: string[]) => client.sendCommand(args), }), }); // FIXME: - We have to merge the two limiters into one - seems the skip does not do what we thought it does (It always uses the hourly limiter. even for paid plan, causing it to be infinite) export default { hourly: limiter_free_per_hour, monthly: limiter_paid_per_month, }; ================================================ FILE: services/proxy.cors.sh/src/usage/README.md ================================================ # Usage Logging ## Data we collect - client ip (for managing x-rate-limit) - client ua (for malicious usage preventation) - request url information (only host) (for understanding api usage) - we do not collect request query - we do not collect request body - we do not collect response body - response result (for collecting success rate) (collection as http status code) - duration (for tracking x-rate-limit for execution time) - payload (for tracking x-rate-limit for data transfer) - app (for identifying the request) (this is determined by api key you are using. pretty obvious) ================================================ FILE: services/proxy.cors.sh/src/usage/dynamo/log.ts ================================================ import type { CorsProxyApiRequest, CorsProxyApiRequestLog } from "../type"; import { CorsRequestLogModel } from "./model"; import { nanoid } from "nanoid"; import * as dynamoose from "dynamoose"; import * as https from "https"; const agent = new https.Agent({ keepAlive: true, rejectUnauthorized: true, maxSockets: 1000, }); dynamoose.aws.sdk.config.update({ httpOptions: { agent: agent, }, }); async function log( request: CorsProxyApiRequest & { billed_duration: number; } ) { // console.log("request", request); const id = nanoid(); try { const payload = new CorsRequestLogModel({ id: id, ...request, }); await payload.save(); } catch (_) { // do nothing } } ================================================ FILE: services/proxy.cors.sh/src/usage/dynamo/model.ts ================================================ import * as dynamoose from "dynamoose"; import { nanoid } from "nanoid"; export const CorsRequestLogSchema = new dynamoose.Schema( { id: { type: String, required: true, default: () => nanoid(), }, app: { type: String, required: true, index: { name: "appIndex", }, }, ua: { type: String, required: false, }, at: { type: Date, required: true, }, size: { type: Number, required: true, }, ip: { type: String, required: false, }, target: { type: String, required: true, }, duration: { type: Number, required: true, }, billed_duration: { type: Number, required: true, }, }, { saveUnknown: true, } ); const CORS_REQUEST_LOG_TABLE_NAME = process.env .DYNAMODB_TABLE_USAGE_LOG as string; export const CorsRequestLogModel = dynamoose.model( CORS_REQUEST_LOG_TABLE_NAME, CorsRequestLogSchema, { create: true, } ); ================================================ FILE: services/proxy.cors.sh/src/usage/dynamo/readme.md ================================================ # dynamo logger (disabled) to enable, - update serverless.yml using table.yml - install dynamoose ================================================ FILE: services/proxy.cors.sh/src/usage/dynamo/table.yml ================================================ # use this configuration on serverless.yml to enable logging. provider: name: aws environment: DYNAMODB_TABLE_USAGE_LOG: "${self:service}-usage-log-${opt:stage, self:provider.stage}" iamRoleStatements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem - dynamodb:DescribeTable Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE_USAGE_LOG}" resources: Resources: usageLogTable: Type: AWS::DynamoDB::Table Properties: TableName: "${self:provider.environment.DYNAMODB_TABLE_USAGE_LOG}" KeySchema: - AttributeName: id KeyType: HASH AttributeDefinitions: - AttributeName: id AttributeType: S - AttributeName: app AttributeType: S GlobalSecondaryIndexes: - IndexName: appIndex KeySchema: - AttributeName: app KeyType: HASH Projection: ProjectionType: "ALL" ProvisionedThroughput: ReadCapacityUnits: 10 WriteCapacityUnits: 10 # no write is required ProvisionedThroughput: ReadCapacityUnits: 10 # no read is required WriteCapacityUnits: 10 ================================================ FILE: services/proxy.cors.sh/src/usage/index.ts ================================================ import * as express from "express"; import type { CorsProxyApiRequestLog } from "./type"; type Log = Omit; /** * * @param req * @param res */ export async function logRequest(req: express.Request, res: express.Response) { // do not log the request if client error. (413 or 401 or 400) if (500 > res.statusCode && res.statusCode >= 400) { return; } const ip = (req.headers["x-forwarded-for"] || req.socket.remoteAddress) as string; const timestamp = new Date(); const origin = req.headers["origin"] || undefined; //@ts-ignore (useragent is provided by above useragent.express()) const _uaobj = req.useragent; const ua = _uaobj.source; // gives the raw ua string const url = res.get("x-request-url"); const payload = Number( (() => { const _cl = res.get("content-length"); return _cl ? _cl : 0; })() ); const duration = Number( (() => { const _rt = res.get("x-response-time"); return _rt ? _rt : 0; })() ); const billed_duration = Math.ceil(duration / 100) * 100; // billed duration is stepped by 100ms // request id from api gateway // const request_id = req.headers["x-request-id"] as string; const metric: Log = { ip: ip, origin: origin, ua: ua, duration: duration, size: payload, at: timestamp, target: url, app: "anonymous", billed_duration, }; systemlog(metric); // await log(metric); } function systemlog(request: Log) { console.log(request); } ================================================ FILE: services/proxy.cors.sh/src/usage/type.ts ================================================ export type AppId = string | "anonymous" | "official-demo"; export interface CorsProxyApiRequest { /** * ip address of request client (could be server / app / web) */ ip?: string; /** * compressed / raw user agent data of the request */ ua?: string; /** * request origin from request headers */ origin?: string; /** * target resource url */ target?: string; /** * duration in ms */ duration: number; /** * data payload */ size: number; /** * request timestamp */ at: Date; /** * the user/requester app */ app: AppId; } export interface CorsProxyApiRequestLog extends CorsProxyApiRequest { /** * unique id of the request */ id: string; /** * billing duration in ms - ceils with 100ms */ billed_duration: number; } ================================================ FILE: services/proxy.cors.sh/tsconfig.json ================================================ { "compilerOptions": { "preserveConstEnums": true, "strictNullChecks": true, "sourceMap": false, "allowJs": true, "strict": false, "target": "es5", "outDir": ".build", "moduleResolution": "node", "lib": ["es2015"], "rootDir": "./" } } ================================================ FILE: services/readme.md ================================================ # service layers - proxy layer - contributes only to proxying the request. - shards part of the service layer db once a day. - uses dynamo db & takes full serverless approach. - handles millions of requests every hour. - service layer - contributes to authorization and subscriptions management. - uses mongodb - uess prisma - can be alted to ec2 instalce service. - handles few thousand requests every hour. ## Shared keys - `JWT_SECRET` the jwt secret is shared accross services, the token issueing can be only done by the service layer, and the proxy layer can only verify the token. ================================================ FILE: services/services.cors.sh/.gitignore ================================================ .build .webpack # we use yarn. package-lock.json # env .env .env.staging .env.production .env.local .env.*.local ================================================ FILE: services/services.cors.sh/app.ts ================================================ import express from "express"; import useragent from "express-useragent"; import cors from "cors"; import bodyParser from "body-parser"; import router from "./routes"; function logErrors(err, req, res, next) { console.error(err.stack); next(err); } const app = express(); app.get("/", (req, res) => { res.redirect("https://cors.sh"); }); app.use(cors()); app.use( bodyParser.urlencoded({ extended: true, }) ); app.use(useragent.express()); app.use(bodyParser.json()); app.use(logErrors); app.use(router); export { app }; ================================================ FILE: services/services.cors.sh/auth/index.ts ================================================ export * from "./key"; export * from "./jwt"; export * from "./middleware"; ================================================ FILE: services/services.cors.sh/auth/jwt.ts ================================================ import jwt from "jsonwebtoken"; const _SECRET = process.env.SERVICE_JWT_SECRET as string; export interface ServiceUserSignature { type: "customer"; service: "cors.sh"; id: string; } export function decode_jwt(token: string): ServiceUserSignature { return jwt.verify(token, _SECRET) as ServiceUserSignature; } export function encode_jwt(service_user_id: string): string { const payload: ServiceUserSignature = { type: "customer", service: "cors.sh", id: service_user_id, }; return jwt.sign(payload, _SECRET); } export function verify(token: string) { const pl = jwt.verify(token, _SECRET) as ServiceUserSignature; if (pl.service == "cors.sh" && pl.type == "customer") { return pl.id; } else { throw new Error("invalid token"); } } ================================================ FILE: services/services.cors.sh/auth/key.ts ================================================ export const SECURE_BROWSER_COOKIE_AUTH_KEY = ".cors.sh/auth-token"; ================================================ FILE: services/services.cors.sh/auth/middleware.ts ================================================ import { verify } from "./jwt"; import { Request, Response } from "express"; import { prisma, stripe } from "../clients"; import { SECURE_BROWSER_COOKIE_AUTH_KEY } from "./key"; // since the secure cookie cannot be forged, we have no additional validation on the jwt from client's secure http only cookie. // the possible hacking scenario is.. // 1. the hacker steals the jwt secret (impossible, but somehow.) // 2. the hacker finds out the customer id of services.cors.sh // 3. the hacker sets the cookie with the jwt token, and the customer id. // - seems secure enough for now. export async function authMiddleware(req: Request, res: Response, next) { const _1 = await standardAuthorizer(req); if (_1) { const { customer } = _1; res.locals.customer = customer; next(); return; } const _2 = await checoutSessionAuthorizer(req); if (_2) { const { customer } = _2; res.locals.customer = customer; next(); return; } return res.status(401).json({ error: "Unauthorized" }); } async function checoutSessionAuthorizer(req: Request) { const checkout_session_id = req.headers["x-cors-service-checkout-session-id"]; if (!checkout_session_id) { return false; } const checkout_session = await stripe.checkout.sessions.retrieve( checkout_session_id as string ); const { customer: stripe_customer_id } = checkout_session; const customer = await prisma.customer.findUnique({ where: { stripeId: stripe_customer_id as string, }, }); if (!customer) { return false; } return { customer, }; } async function standardAuthorizer(req: Request): Promise { const authorization = req.signedCookies?.[SECURE_BROWSER_COOKIE_AUTH_KEY]; if (!authorization) { return false; } try { const customerid = verify(authorization); const customer = await prisma.customer.findUnique({ where: { id: customerid, }, }); return { customer, }; } catch (e) { return false; } } ================================================ FILE: services/services.cors.sh/auth/readme.md ================================================ # service auth (not proxy.cors.sh api key auth) ## .env ``` ... SERVICE_JWT_SECRET=... ... ``` ================================================ FILE: services/services.cors.sh/clients/index.ts ================================================ export * from "./prisma"; export * from "./stripe"; export * from "./ses"; ================================================ FILE: services/services.cors.sh/clients/prisma.ts ================================================ import { PrismaClient } from "@prisma/client"; export const prisma = new PrismaClient(); ================================================ FILE: services/services.cors.sh/clients/ses.ts ================================================ import * as AWS from "@aws-sdk/client-ses"; const SENDER_EMAIL = "no-reply@cors.sh"; const ses = new AWS.SES({}); interface EmailBody_Raw { subject: string; text: string; } interface EmailBody_Html { subject: string; html: string; } type EmailContent = EmailBody_Raw | EmailBody_Html; export function emailWithTemplate(to: string, template: string, data: object) { return ses.sendTemplatedEmail({ Destination: { ToAddresses: [to], }, Source: SENDER_EMAIL, Template: template, TemplateData: JSON.stringify(data), }); } export async function email( to: string, content: EmailContent ): Promise { try { const sendResp = await ses.sendEmail({ Destination: { ToAddresses: [to], }, Source: SENDER_EMAIL, Message: { Subject: { Data: content.subject, }, Body: "text" in content ? { Text: { Data: content.text, }, } : { Html: { Data: content.html, }, }, }, }); } catch (error) { console.error(error); throw new Error("failed while sending email"); } return true; } ================================================ FILE: services/services.cors.sh/clients/slack.ts ================================================ // send message to slack for telementry import axios from "axios"; const token = process.env.SLACK_TOKEN; const channel = process.env.SLACK_CHANNEL; export const blocks = ({ title, data, }: { title: string; data: { [key: string]: any }; }) => { return [ { type: "section", text: { type: "mrkdwn", text: `*${title}*`, }, }, ...Object.keys(data).reduce((acc, key) => { const value = data[key]; if (value === undefined) { return acc; } const valuetext = pretty_value(value); const keytext = pretty_key(key); return [ ...acc, { type: "section", text: { type: "mrkdwn", text: `${keytext}: ${valuetext}`, }, }, ]; }, []), ]; }; const pretty_value = (value: any) => { switch (typeof value) { case "string": return value; case "number": return value.toString(); case "object": default: return JSON.stringify(value); } }; /** * pretty print the key string * * - replace `_` and `-` with ` ` * - capitalize the first letter * * e.g. * - total_users_so_far -> Total users so far */ function pretty_key(key: string) { return key .replace(/_/g, " ") .replace(/-/g, " ") .replace(/^\w/, (c) => c.toUpperCase()); } export function slack({ blocks }: { blocks: Array }) { return axios.post( "https://slack.com/api/chat.postMessage", { blocks: blocks, // env Channel Id channel: channel, }, { headers: { // env TOKEN Authorization: `Bearer ${token}`, }, } ); } ================================================ FILE: services/services.cors.sh/clients/stripe.ts ================================================ import { Stripe } from "stripe"; export const stripe = new Stripe(process.env.STRIPE_API_KEY, { apiVersion: "2022-08-01", }); ================================================ FILE: services/services.cors.sh/controllers/_telemetry.ts ================================================ import type { OnboardingApplications, Application } from "@prisma/client"; import { prisma } from "../clients"; import { slack, blocks } from "../clients/slack"; export async function logNewOnboardingProc(data: OnboardingApplications) { const total = await prisma.onboardingApplications.count(); const { id, name, email } = data; const title = `New Onboarding Application`; const dataToLog = { name, email, total, }; const message = blocks({ title, data: dataToLog }); await slack({ blocks: message }); } export async function logNewApplication(data: Application) { const total = await prisma.application.count(); const { name } = data; const title = `New Application`; const dataToLog = { name, total, }; const message = blocks({ title, data: dataToLog }); await slack({ blocks: message }); } ================================================ FILE: services/services.cors.sh/controllers/applications.ts ================================================ import { prisma, stripe, emailWithTemplate } from "../clients"; import { sign_live_key, sign_temporary_key, sign_test_key } from "../keygen"; import { nanoid } from "nanoid"; import type { Application, OnboardingApplications } from "@prisma/client"; import sync from "../sync"; import { logNewOnboardingProc, logNewApplication } from "./_telemetry"; type CreateOnboardingApplicationBody = | { type: "with-form"; name: string; allowedOrigins: string[]; priceId: string; } | { type: "with-email"; email: string; }; export async function createOnboardingApplication( body: CreateOnboardingApplicationBody, send_onboarding_email_if_possible: boolean = true ) { let email_available = false; let email: string; let name: string; let allowedOrigins: string[]; let priceId: string; switch (body.type) { case "with-email": { email = body.email; name = "My first cors.sh app"; email_available = true; break; } case "with-form": { email = `${nanoid(10)}@unknown-users.cors.sh`; name = body.name; allowedOrigins = body.allowedOrigins; priceId = body.priceId; break; } } const { key: tmpkey, expires_at } = sign_temporary_key(); let data: OnboardingApplications; // check if email exists let duplicated: OnboardingApplications | null = null; if (email_available) { duplicated = await prisma.onboardingApplications.findUnique({ where: { email: email }, }); } // if duplicated, update the existing one with the new key. if (duplicated) { // if the last interaction is within 5 minutes, ignore the request if (duplicated.updatedAt) { } data = await prisma.onboardingApplications.update({ where: { id: duplicated.id }, data: { // this will be renewed since the signature contains the expiration time key: tmpkey, }, }); } else { data = await prisma.onboardingApplications.create({ data: { key: tmpkey, email: email, name: name, allowedOrigins: allowedOrigins ?? [], expiresAt: expires_at.toDate(), priceId, }, }); } if (email_available && send_onboarding_email_if_possible) { // send an email to the user const res = await sendOnboardingEmail(email, data); console.log("email sent", res); } // log event to slack try { !duplicated && (await logNewOnboardingProc(data)); } catch (e) { console.error("failed to log new onboarding application", e); // not critical } return { id: data.id, // omit the key since it also works as a verification code for faster signup // key: "omitted" email: data.email, name: data.name, allowedOrigins: data.allowedOrigins, priceId: data.priceId, }; } export async function getOnboardingApplication(id: string) { return await prisma.onboardingApplications.findUnique({ where: { id: id }, select: { // omit private data id: true, name: true, email: true, allowedOrigins: true, priceId: true, createdAt: true, updatedAt: true, }, }); } async function sendOnboardingEmail( email: string, application?: OnboardingApplications ) { if (!application) { application = await prisma.onboardingApplications.findUnique({ where: { email: email }, }); } if (!application) { return false; } const { key, emailSentAt } = application; // if the last interaction is within 1 minutes, ignore the request if (emailSentAt && emailSentAt.length > 0) { const lastSentAt = emailSentAt[emailSentAt.length - 1]; if (lastSentAt) { const diff = new Date().getTime() - lastSentAt.getTime(); if (diff < 60000) { return false; } } } // sls stage await emailWithTemplate( email, `mail_cors_sh_onboarding_${process.env.STAGE}`, { CODE: key, ONBOARDINGLINK: `https://cors.sh/onboarding/${application.id}`, } ); // update the tmp app await prisma.onboardingApplications.update({ where: { id: application.id }, data: { emailSentAt: { push: new Date(), }, }, }); return true; } export async function signApplication(application: Application) { // const { key: apikey_test } = sign_test_key(application.signature_test); const { key: apikey_live } = sign_live_key(application.signature_live); const payload = { ...application, apikey_test, apikey_live, }; return payload; } export async function createApplication({ id, name, allowedOrigins, owner, }: { id?: string; name: string; allowedOrigins?: string[]; owner: { id: string }; }) { const placeholder_test = nanoid(10); const placeholder_live = nanoid(10); const application = await prisma.application.create({ data: { id: id, name: name, signature_test: placeholder_test, signature_live: placeholder_live, allowedOrigins: allowedOrigins ?? [], owner: { connect: { id: owner.id, }, }, }, }); const salt_test = nanoid(10); const salt_live = nanoid(10); const signature_test = application.id + salt_test; const signature_live = application.id + salt_live; const updated = await prisma.application.update({ where: { id: application.id }, data: { signature_test, signature_live, }, }); // once application is created and initially signed, sync to the keys table await sync(updated); return updated; } export async function convertApplication({ onboarding_id, checkout_session_id, }: { onboarding_id: string; checkout_session_id: string; }) { const checkout_session = await stripe.checkout.sessions.retrieve( checkout_session_id as string ); const { customer: stripe_customer } = checkout_session; const stripe_customer_id = typeof stripe_customer === "string" ? stripe_customer : stripe_customer.id; const customer = await prisma.customer.findUnique({ where: { stripeId: stripe_customer_id }, }); // place this right before application create since its a delete and cannot be undone. (reduce chance of error when it fails) const tmp = await prisma.onboardingApplications.delete({ where: { id: onboarding_id as string }, }); const application = await createApplication({ id: tmp.id, name: tmp.name || "Untitled", allowedOrigins: tmp.allowedOrigins, owner: { id: customer.id }, }); const signed = await signApplication(application); // once application is created and initially signed, sync to the keys table await sync(application); // send email to the user try { await emailWithTemplate( customer.email, `mail_cors_sh_onboarding_with_payment_success_${process.env.STAGE}`, { APPLICATIONNAME: tmp.name, CODE_LIVE: signed.apikey_live, CODE_TEST: signed.apikey_test, } ); console.log("email sent", true); // // set email verified to true? } catch (e) { console.error("email failed", e); } // log event to slack try { await logNewApplication(signed); } catch (e) { console.error("failed to log new application", e); // not critical } return application; } ================================================ FILE: services/services.cors.sh/deploy/dev.sh ================================================ sls deploy --stage development \ --param="domain=false" \ --param="service-keys-rcu=1" \ --param="service-keys-wcu=1" ================================================ FILE: services/services.cors.sh/deploy/prod.sh ================================================ sls deploy --stage production \ --param="domain=true" \ --param="service-keys-rcu=256" \ --param="service-keys-wcu=4" ================================================ FILE: services/services.cors.sh/deploy/staging.sh ================================================ sls deploy --stage staging \ --param="domain=false" \ --param="service-keys-rcu=1" \ --param="service-keys-wcu=1" ================================================ FILE: services/services.cors.sh/index.ts ================================================ import { configure as serverlessExpress } from "@codegenie/serverless-express"; import { APIGatewayProxyHandler } from "aws-lambda"; import { app } from "./app"; export const handler: APIGatewayProxyHandler = serverlessExpress({ app }); ================================================ FILE: services/services.cors.sh/jest.config.js ================================================ // jest config - ts module.exports = { preset: "ts-jest", setupFiles: ["/test/setup-tests.ts"], }; ================================================ FILE: services/services.cors.sh/keygen/index.ts ================================================ import crypto from "crypto"; import day from "dayjs"; const API_KEY_TEMP_AES_KEY = process.env.API_KEY_TEMP_AES_KEY; const _aes_key = Buffer.from(API_KEY_TEMP_AES_KEY, "hex"); const API_KEY_TEMP_AES_IV: string = process.env.API_KEY_TEMP_AES_IV!; const _aes_iv = Buffer.from(API_KEY_TEMP_AES_IV, "hex"); const API_KEY_TEST_HASH_SECRET = process.env.API_KEY_TEST_HASH_SECRET; const API_KEY_LIVE_HASH_SECRET = process.env.API_KEY_LIVE_HASH_SECRET; const API_KEY_HASH_SECRET_BY_TYPE = { test: API_KEY_TEST_HASH_SECRET, live: API_KEY_LIVE_HASH_SECRET, } as const; interface PermanentKey { signature: string; type: "test" | "live"; } const TMP_KEY_PREFIX = "temp"; const TEST_KEY_PREFIX = "test"; const LIVE_KEY_PREFIX = "live"; const TMP_KEY_EXP_IN_DAYS = 3; function test_key(signature: string): PermanentKey { return { signature, type: "test", }; } function live_key(signature: string): PermanentKey { return { signature, type: "live", }; } export function sign_temporary_key() { const expires_at = day().add(TMP_KEY_EXP_IN_DAYS, "day"); const token = expires_at.unix().toString(); // convert to complex string with aes let cipher = crypto.createCipheriv("aes-256-cbc", _aes_key, _aes_iv); // this may be considered as insecure. yes. it may be vulnerable to ciphertext attack, // even though it is fine because we have a rate-limit for temporary api keys and it does not have a clear usecase. for hackers to take benefits from this. let encrypted = cipher.update(token, "utf8", "hex") + cipher.final("hex"); return { key: prefix("temp") + "_" + encrypted, expires_at, }; } export function sign(signature: string, type: "test" | "live") { switch (type) { case "test": return sign_test_key(signature); case "live": return sign_live_key(signature); } } export function sign_test_key(signature: string) { const token = encrypt(test_key(signature), "test"); return { key: prefix("test") + "_" + token, token, }; } export function sign_live_key(signature: string) { const token = encrypt(live_key(signature), "live"); return { key: prefix("live") + "_" + token, token, }; } // // migrate this to cors.sh export function validate_jwt(token: string) { if (token.startsWith(TMP_KEY_PREFIX)) { // } else if (token.startsWith(TEST_KEY_PREFIX)) { // } else if (token.startsWith(LIVE_KEY_PREFIX)) { // } else { throw "Invalid token format"; } } function prefix(type: "test" | "live" | "temp") { switch (type) { case "test": return TEST_KEY_PREFIX; case "live": return LIVE_KEY_PREFIX; case "temp": return TMP_KEY_PREFIX; } } function encrypt(data: string | object, type: "test" | "live") { const key = API_KEY_HASH_SECRET_BY_TYPE[type]; const hmac = crypto.createHmac("sha256", key); hmac.update(JSON.stringify(data)); return hmac.digest("hex"); } ================================================ FILE: services/services.cors.sh/package.json ================================================ { "name": "@cors.sh/service-layer", "version": "0.0.0", "description": "CORS.SH's service layer for managing api keys & subscription", "homepage": "https://cors.sh", "scripts": { "prisma:format": "npx prisma format", "prisma:studio": "npx prisma studio", "prisma:generate": "npx prisma generate", "prisma:generate:watch": "npx prisma generate --watch", "postinstall": "npm run prisma:generate && npm run prisma:format", "db:push": "npx prisma db push", "clean": "rimraf .build", "dev": "sls offline start --stage development", "deploy:staging": "yarn clean && source ./deploy/staging.sh", "deploy:prod": "yarn clean && yarn db:push && source ./deploy/prod.sh", "deploy:dev": "yarn clean && yarn db:push && source ./deploy/dev.sh", "package": "sls package --stage development", "sls:ses-template:deply": "sls ses-template deploy --stage development", "test": "jest" }, "dependencies": { "@aws-sdk/client-ses": "^3.163.0", "@codegenie/serverless-express": "^4.16.0", "@prisma/client": "^4.8.0", "axios": "^1.2.2", "body-parser": "^1.20.0", "cors": "^2.8.5", "dayjs": "^1.11.5", "express": "^4.18.1", "express-useragent": "^1.0.15", "jsonwebtoken": "^8.5.1", "multer": "^1.4.4", "nanoid": "^3.3.3", "reading-time": "^1.5.0", "serverless-http": "^3.0.1", "stripe": "^10.7.0" }, "devDependencies": { "@haftahave/serverless-ses-template": "^4.0.5", "@types/aws-lambda": "^8.10.95", "@types/body-parser": "^1.19.2", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", "@types/jest": "^29.2.5", "@types/jsonwebtoken": "^8.5.9", "@types/multer": "^1.4.7", "@types/node": "^17.0.29", "jest": "^29.3.1", "rimraf": "^3.0.2", "serverless": "^3.17.0", "serverless-domain-manager": "^6.0.3", "serverless-offline": "^8.7.0", "serverless-webpack": "^5.15.1", "serverless-webpack-prisma": "^1.1.0", "ts-jest": "^29.0.3", "ts-loader": "^9.3.0", "typescript": "^5.8.2", "webpack": "^5.72.1", "webpack-node-externals": "^3.0.0" } } ================================================ FILE: services/services.cors.sh/prisma/schema.prisma ================================================ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema datasource db { provider = "mongodb" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" binaryTargets = ["native", "rhel-openssl-1.0.x"] } model Application { id String @id @default(auto()) @map("_id") @db.ObjectId name String @default("Untitled") allowedOrigins String[] allowedTargets String[] owner Customer @relation(fields: [ownerId], references: [id]) ownerId String @db.ObjectId archivedAt DateTime? createdAt DateTime @default(now()) // follows the user's plan + 1Mo // if user is using a monthly plan, it will be now + 2Mo (initially) // everytime the subscription is renewed, it will be end date + 1Mo expiresAt DateTime? // signature for live api key signature_live String @unique // signature for test api key signature_test String @unique @@map("applications") } model OnboardingApplications { id String @id @default(auto()) @map("_id") @db.ObjectId email String @unique name String? allowedOrigins String[] @default([]) priceId String? key String @unique emailSentAt DateTime[] @default([]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // now + n hours expiresAt DateTime @@map("applications_onboarding") } // grida workspace <> stripe customer model Customer { id String @id @default(auto()) @map("_id") @db.ObjectId email String @unique emailVerified Boolean @default(false) stripeId String @unique // grida workspace (user) id // workspaceId String @unique // subscriptions Subscription[] applications Application[] createdAt DateTime @default(now()) @@map("customers") } // model Subscription { // @@map("subscriptions") // id String @id @default(auto()) @map("_id") @db.ObjectId // customer Customer @relation(fields: [customerId], references: [id]) // customerId String @db.ObjectId // // stripe subscription id // subscriptionId String @unique // // price id from stripe (product is not saved in db.) // priceId String // createdAt DateTime @default(now()) // } ================================================ FILE: services/services.cors.sh/routes/applications/index.ts ================================================ import { Application } from "@prisma/client"; import * as express from "express"; import { prisma } from "../../clients"; import { createApplication, signApplication, } from "../../controllers/applications"; const router = express.Router(); // list all my applications router.get("/", async (req, res) => { const applications = await prisma.application.findMany({ where: { ownerId: res.locals.customer.id, }, }); res.json(applications); }); // get a single application router.get("/:id", async (req, res) => { console.info("fething application securely", { id: req.params.id, customer: res.locals.customer, }); const id = req.params.id; const application = await prisma.application.findUnique({ where: { id: id, }, }); console.info("fetched application", application); if (!application) { return res.status(404).json({ error: "application not found" }); } if (application.ownerId !== res.locals.customer.id) { return res.status(403).json({ error: "application not found" }); } const signed = await signApplication(application); res.json(msak(signed)); }); // create a new application router.post("/", async (req, res) => { // const { name } = req.body; const app = await createApplication({ name, owner: res.locals.customer, }); const signed = await signApplication(app); res.json(msak(signed)); }); router.put("/:id", async (req, res) => { const id = req.params.id; await prisma.application.update({ where: { id: id, }, data: { ...req.body, }, }); res.json({ success: true }); // or.. updated body? }); function msak( signed: Application & { apikey_test: string; apikey_live: string; } ) { return { id: signed.id, name: signed.name, allowedOrigins: signed.allowedOrigins, allowedTargets: signed.allowedTargets, // available once apikey_test: signed.apikey_test, apikey_live: signed.apikey_live, }; } export default router; ================================================ FILE: services/services.cors.sh/routes/auth/index.ts ================================================ import * as express from "express"; import Axios from "axios"; import { prisma } from "../../clients"; import { encode_jwt, SECURE_BROWSER_COOKIE_AUTH_KEY } from "../../auth"; const router = express.Router(); const authclient = Axios.create({ baseURL: "https://accounts.services.grida.co", }); // user signin with grida, retrieve oauth token from grida, sends it here, // this service will check the token, if valid, it will generate a new token for the user, // and send it back to the client with secure, http only cookie. router.post("signin", async (req, res) => { const authorization = req.headers["proxy-authorization"]; if (!authorization) { res.status(401).json({ error: "no authorization header" }); return; } // authenticate via accounts.grida.co // request auth server for authentication. const { data } = await authclient.get("/verify", { headers: { // proxy the header, in form of "Bearer " authorization, }, }); const customer = await prisma.customer.findUnique({ // @ts-ignore where: { // workspaceId: data.id, }, }); // sign customer (user) signature const jwt = encode_jwt(customer.id); // set secure cookie when authorized. res .cookie(SECURE_BROWSER_COOKIE_AUTH_KEY, jwt, { signed: true, domain: ".cors.sh", secure: true, httpOnly: true, sameSite: "strict", // no expiration. it is a secure cookie, that only has access to cors.sh. let's keep it this way for now. // expires: null }) .json({ success: true, customer: customer, }); }); export default router; ================================================ FILE: services/services.cors.sh/routes/index.ts ================================================ import * as express from "express"; import router_payments from "./payments"; import router_stripe_webhooks from "./webhooks-stripe"; import router_applications from "./applications"; import router_onboarding from "./onboarding"; import router_auth from "./auth"; import cors from "cors"; import { authMiddleware } from "../auth"; const router = express.Router(); const cors_website_only = cors({ origin: process.env.NODE_ENV === "production" ? "https://cors.sh" : "*", }); // router.use("/", router_posts); router.use("/auth", cors_website_only, router_auth); router.use("/payments", router_payments); router.use("/webhooks/stripe", router_stripe_webhooks); router.use( "/applications", cors_website_only, authMiddleware, router_applications ); router.use("/onboarding", cors_website_only, router_onboarding); export default router; ================================================ FILE: services/services.cors.sh/routes/onboarding/index.ts ================================================ import * as express from "express"; import { getOnboardingApplication, createOnboardingApplication, convertApplication, } from "../../controllers/applications"; const router = express.Router(); const blacklist = [ "nqmo.com", "qabq.com", "mailinator.com", "10minutemail.com", "guerrillamail.com", "temp-mail.org", "yopmail.com", "throwawaymail.com", "dispostable.com", "getnada.com", "maildrop.cc", "pastryofistanbul.com", "litepax.com", "raleigh-construction.com", "questtechsystems.com", "teihu.com", "oakon.com", "namestal.com" ]; // create router.post("/with-email", async (req, res) => { // create new temporary application bind to the email (form is optional) const { email } = req.body; // @requires: email if (!email) { return res.status(400).json({ error: "email is required" }); } // if (blacklist.some((d) => email.includes(d))) { return res.status(400).json({ error: "bad email - do not hack us." }); } const d = await createOnboardingApplication({ type: "with-email", email, }); res.status(201).json(d); }); router.post("/with-form", async (req, res) => { // create new temporary application bind to the form (email is optional) const { name, allowedOrigins, priceId } = req.body; const d = await createOnboardingApplication({ type: "with-form", name, allowedOrigins, priceId, }); res.status(201).json(d); }); router.get("/:id", async (req, res) => { // this route does not have a guard by design. // get onboarding application (only public data) const { id } = req.params; const d = await getOnboardingApplication(id); if (!d) { return res.status(404).json({ error: "application not found" }); } res.status(200).json(d); }); // conversion router.post("/:id/convert", async (req, res) => { const { id: onboarding_id } = req.params; const { checkout_session_id } = req.body; const application = await convertApplication({ onboarding_id, checkout_session_id, }); res.status(201).json(application); }); export default router; ================================================ FILE: services/services.cors.sh/routes/payments/index.ts ================================================ import type { Customer } from "@prisma/client"; import * as express from "express"; import { prisma, stripe } from "../../clients"; import { getOnboardingApplication } from "../../controllers/applications"; const router = express.Router(); const WEBHOST = process.env.WEBHOST; const WEBURL_CONSOLE = WEBHOST + "/console"; const PROTOCOL = process.env.NODE_ENV === "production" ? "https" : "http"; // e.g. // http://localhost:4021/payments/checkout/new?price=price_1Lda7UAvR3geCh5rVaajCSw6&onboarding_id=63b9a40c02478a88364d7202 router.get("/checkout/new", async (req, res) => { const host = req.headers.host; const { onboarding_id: _q_onboarding } = req.query; const onboarding = await getOnboardingApplication(_q_onboarding as string); if (!onboarding) { return res.status(400).json({ error: "invalid session" }); } const { priceId } = onboarding; const price = await stripe.prices.retrieve(priceId, { expand: ["product"], }); const extra_params = new URLSearchParams({ onboarding_id: _q_onboarding as string, }); const session = await stripe.checkout.sessions.create({ billing_address_collection: "auto", line_items: [ { price: price.id, // For metered billing, do not pass quantity quantity: 1, }, ], mode: "subscription", // e.g. http://localhost:8823/?success=true&session_id=cs_test_a1qQdhxwfS5kKZJ1kToxKqAr2K6yHneucfi65lIs1OPVkmoH14YNAev76S success_url: `${PROTOCOL}://${host}/payments/success?session_id={CHECKOUT_SESSION_ID}&${extra_params}`, cancel_url: `${PROTOCOL}://${host}/payments/canceled?session_id={CHECKOUT_SESSION_ID}&${extra_params}`, }); res.redirect(303, session.url); }); router.get("/success", async (req, res) => { const { session_id, onboarding_id } = req.query; const checkout_session = await stripe.checkout.sessions.retrieve( session_id as string ); const { customer: stripe_customer_id, customer_email, // can be null if created on checkout page (unless explicitly specified by our side.) customer_details, subscription, } = checkout_session; // on success, convert onboarding app to a real app (and remove the temporary app) // (if user has one.) // remove const tmp = await prisma.onboardingApplications.findUnique({ where: { id: onboarding_id as string }, }); // if onboarding's email is placeholded. get email from checkout via stripe const email = tmp.email.endsWith("@unknown-users.cors.sh") ? customer_email ?? customer_details.email : tmp.email; // create customer // TODO: what if already exists? let customer_exists_with_same_email: Customer; try { customer_exists_with_same_email = await prisma.customer.findUnique({ where: { email }, }); } catch (e) { console.error("Caught exeption while finding existing customer", e); } if (customer_exists_with_same_email) { // TODO: we'll need a better way to handle this since we need to test this with same email multiple times. // we can't tell if the purchase is for the same user or not. // yet, if the existing one is verified, we should protect it. // if not, we should give the new one a chance. const params = new URLSearchParams({ error: "identity_conflict", message: "Your payment was successful, but we detected a suspicious activity. Please contact customer support.", session_id: session_id as string, onboarding_id: tmp.id, application_id: tmp.id, }); const redirect_uri = `${WEBHOST}/onboarding/payment-success-with-issue?${params.toString()}`; res.redirect(303, redirect_uri); } else { const customer = await prisma.customer.create({ data: { stripeId: stripe_customer_id as string, email: email, emailVerified: false, }, }); const _params = { session_id: session_id as string, onboarding_id: tmp?.id, customer_id: customer.id, application_id: tmp.id, }; // prettier-ignore Object.keys(_params).forEach((key) => _params[key] === undefined ? delete _params[key] : {}); const params = new URLSearchParams(_params); const redirect_uri = `${WEBHOST}/onboarding/payment-success?${params.toString()}`; res.redirect(303, redirect_uri); } }); router.get("/canceled", async (req, res) => { // remove the temporary app const { session_id, onboarding_id } = req.query; res.redirect(303, `https://cors.sh/`); }); router.post("/portal-session", async (req, res) => { // For demonstration purposes, we're using the Checkout session to retrieve the customer ID. // Typically this is stored alongside the authenticated user in your database. const { session_id } = req.body; const checkoutSession = await stripe.checkout.sessions.retrieve(session_id); // This is the url to which the customer will be redirected when they are done // managing their billing with the portal. const returnUrl = WEBURL_CONSOLE; const portalSession = await stripe.billingPortal.sessions.create({ customer: checkoutSession.customer as string, return_url: returnUrl, }); res.redirect(303, portalSession.url); }); export default router; ================================================ FILE: services/services.cors.sh/routes/payments/readme.md ================================================ # Payments with stripe ## About This subscirption checkout follows instruction from https://stripe.com/docs/billing/quickstart which uses the checkout api, webhooks and customer portal for subscription management. Most of the service relies on stripe's built in features. ## Setting up webhook **Local testing (manual trigger)** [Install stripe cli](https://stripe.com/docs/stripe-cli) ```bash brew install stripe/stripe-cli/stripe ``` Follow the instructions at https://dashboard.stripe.com/webhooks/create?endpoint_location=local ```bash stripe login stripe listen --forward-to localhost:4242/webhook stripe trigger payment_intent.succeeded # or other event as you want ``` **Local testing (production testing)** Setup ngrok to proxy request & register webhook at https://dashboard.stripe.com/webhooks/create?endpoint_location=hosted ================================================ FILE: services/services.cors.sh/routes/webhooks-stripe/index.ts ================================================ import * as express from "express"; import { stripe } from "../../clients"; const router = express.Router(); router.post( /** * /webhooks/stripe - defined in routes/index.ts */ "/", express.raw({ type: "application/json" }), (request, response) => { let event = request.body; // Replace this endpoint secret with your endpoint's unique secret // If you are testing with the CLI, find the secret by running 'stripe listen' // If you are using an endpoint defined with the API or dashboard, look in your webhook settings // at https://dashboard.stripe.com/webhooks const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET; // Only verify the event if you have an endpoint secret defined. // Otherwise use the basic event deserialized with JSON.parse if (endpointSecret) { // Get the signature sent by Stripe const signature = request.headers["stripe-signature"]; try { event = stripe.webhooks.constructEvent( request.body, signature, endpointSecret ); } catch (err) { console.log(`⚠️ Webhook signature verification failed.`, err.message); return response.sendStatus(400); } } let subscription; let status; // Handle the event switch (event.type) { case "customer.subscription.trial_will_end": subscription = event.data.object; status = subscription.status; console.log(`Subscription status is ${status}.`); // Then define and call a method to handle the subscription trial ending. // handleSubscriptionTrialEnding(subscription); break; case "customer.subscription.deleted": subscription = event.data.object; status = subscription.status; console.log(`Subscription status is ${status}.`); // Then define and call a method to handle the subscription deleted. // handleSubscriptionDeleted(subscriptionDeleted); break; case "customer.subscription.created": subscription = event.data.object; status = subscription.status; console.log(`Subscription status is ${status}.`); // Then define and call a method to handle the subscription created. // handleSubscriptionCreated(subscription); break; case "customer.subscription.updated": subscription = event.data.object; status = subscription.status; console.log(`Subscription status is ${status}.`); // Then define and call a method to handle the subscription update. // handleSubscriptionUpdated(subscription); break; default: // Unexpected event type console.log(`Unhandled event type ${event.type}.`); } // Return a 200 response to acknowledge receipt of the event response.send(); } ); export default router; ================================================ FILE: services/services.cors.sh/scripts/aes-256-cbc-creds.js ================================================ // used for generating AES-256-CBC credentials. which is used for generating temp api codes const crypto = require("crypto"); // Generate a 256-bit (32-byte) key const key = crypto.randomBytes(32); console.log("Key:", key.toString("hex")); // Generate a 128-bit (16-byte) IV const iv = crypto.randomBytes(16); console.log("IV:", iv.toString("hex")); ================================================ FILE: services/services.cors.sh/serverless.yml ================================================ service: cors-proxy-service useDotenv: true plugins: - serverless-webpack - serverless-webpack-prisma - serverless-offline - serverless-domain-manager - "@haftahave/serverless-ses-template" custom: customDomain: domainName: services.cors.sh certificateName: "services.cors.sh" basePath: "" createRoute53Record: false stage: production # enabled only for production - configured by package.json script with --domain flag. enabled: ${param:domain, false} sesTemplates: addStage: true serverless-offline: httpPort: 4021 lambdaPort: 3021 noPrependStageInUrl: true webpack: includeModules: true provider: name: aws runtime: nodejs14.x region: us-west-1 environment: DATABASE_URL: ${env:DATABASE_URL} STRIPE_API_KEY: ${env:STRIPE_API_KEY} STRIPE_WEBHOOK_SECRET: ${env:STRIPE_WEBHOOK_SECRET} WEBHOST: ${env:WEBHOST} API_KEY_TEMP_AES_KEY: ${env:API_KEY_TEMP_AES_KEY} API_KEY_TEMP_AES_IV: ${env:API_KEY_TEMP_AES_IV} API_KEY_TEST_HASH_SECRET: ${env:API_KEY_TEST_HASH_SECRET} API_KEY_LIVE_HASH_SECRET: ${env:API_KEY_LIVE_HASH_SECRET} SERVICE_JWT_SECRET: ${env:SERVICE_JWT_SECRET} SLACK_CHANNEL: ${env:SLACK_CHANNEL} SLACK_TOKEN: ${env:SLACK_TOKEN} DYNAMODB_TABLE_SERVICE_KEYS: "${self:service}-keys-${opt:stage, self:provider.stage}" STAGE: ${opt:stage, self:provider.stage} apiGateway: # https://stackoverflow.com/questions/61003311/serverless-i-image-upload-to-s3-broken-after-deploy-local-worked-only/61003498#61003498 binaryMediaTypes: - "*/*" iamRoleStatements: # email with ses - Effect: Allow Action: - ses:SendEmail - ses:SendTemplatedEmail Resource: "*" - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem - dynamodb:DescribeTable Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE_SERVICE_KEYS}" resources: Resources: serviceKeysTable: Type: AWS::DynamoDB::Table Properties: TableName: "${self:provider.environment.DYNAMODB_TABLE_SERVICE_KEYS}" AttributeDefinitions: - AttributeName: key AttributeType: S KeySchema: - AttributeName: key KeyType: HASH ProvisionedThroughput: # $0.000145 per RCU ReadCapacityUnits: ${param:service-keys-rcu, 1} # $0.000725 per WCU WriteCapacityUnits: ${param:service-keys-wcu, 1} functions: main: handler: index.handler events: - http: method: any path: /{proxy+} cors: origin: "*" - http: method: get path: / cors: origin: "*" package: individually: true ================================================ FILE: services/services.cors.sh/ses-email-templates/index.js ================================================ const fs = require("fs"); const path = require("path"); const dir = path.join(__dirname, "../../mail.cors.sh", "render"); const templates = fs .readdirSync(dir) // list files ending with .template.html .filter((file) => file.endsWith(".template.html")); const prefix = "mail_cors_sh_"; module.exports = async (serverless, options) => templates.map((file) => { const fname = file.replace(".template.html", ""); const name = prefix + fname; const subject = fs.readFileSync(path.join(dir, `${fname}.subject`), "utf8"); const html = fs.readFileSync(path.join(dir, file), "utf8"); return { name, subject, html }; }); ================================================ FILE: services/services.cors.sh/sync/index.ts ================================================ import AWS from "aws-sdk"; import * as keygen from "../keygen"; import type { KeyInfo } from "./type"; import type { Application } from "@prisma/client"; import day from "dayjs"; const db = new AWS.DynamoDB.DocumentClient(); export default async function sync(application: Application) { const { id, signature_live, signature_test, allowedOrigins, allowedTargets } = application; // TODO: we are using application.id as the billing group, but we should // use the subscription id instead (in the future) const billing_group = id; const data = { // TODO: get plan data // for now, fixing it as "2023.t1", which is the pro plan plan: "2023.t1", allowedOrigins, allowedTargets, }; // TODO: get subscription data. to calculate expires_at // for now, we are givving all keys a 3 year expiry const expires_at = day().add(3, "year").unix(); // test sync_record(signature_test, "test", billing_group, expires_at, data); // live sync_record(signature_live, "live", billing_group, expires_at, data); } function sync_record( signature: string, type: "live" | "test", billing_group: string, expires_at: number, data: { plan: string; allowedOrigins: string[]; allowedTargets: string[]; } ) { const record: KeyInfo = { key: keygen.sign(signature, type).token, plan: data.plan, config: { allowed_origins: data.allowedOrigins, allowed_targets: data.allowedTargets, }, active: true, billing_group, expires_at, synced_at: day().unix(), }; // write to db return db .put({ TableName: process.env.DYNAMODB_TABLE_SERVICE_KEYS!, Item: record, }) .promise(); } export async function activate(id: string, active: boolean) { // update "active" field of the key // write to db return db.update({ TableName: process.env.DYNAMODB_TABLE_SERVICE_KEYS!, Key: { id, }, UpdateExpression: "set active = :active", ExpressionAttributeValues: { ":active": active, }, }); } ================================================ FILE: services/services.cors.sh/sync/type.ts ================================================ // shared db export interface KeyInfo { key: string; plan: string; config: { allowed_origins: string[]; allowed_targets: string[]; }; active: boolean; billing_group: string; expires_at: number; synced_at: number; } ================================================ FILE: services/services.cors.sh/test/config.env.test ================================================ API_KEY_TEMP_OTP_SECRET="test" ================================================ FILE: services/services.cors.sh/test/setup-tests.ts ================================================ import dotenv from "dotenv"; dotenv.config({ path: "./config.env.test" }); ================================================ FILE: services/services.cors.sh/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "noImplicitAny": false, "removeComments": true, "noLib": false, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es6", "sourceMap": true, "outDir": "./dist", "baseUrl": ".", "typeRoots": ["./node_modules/@types"] }, "exclude": ["node_modules", "dist"] } ================================================ FILE: services/services.cors.sh/webpack.config.js ================================================ /* eslint-disable @typescript-eslint/no-var-requires */ const path = require("path"); const nodeExternals = require("webpack-node-externals"); const slsw = require("serverless-webpack"); const { isLocal } = slsw.lib.webpack; module.exports = { target: "node", stats: "normal", entry: slsw.lib.entries, externals: [nodeExternals()], mode: isLocal ? "development" : "production", optimization: { concatenateModules: false }, resolve: { extensions: [".js", ".ts"] }, module: { rules: [ { test: /\.tsx?$/, loader: "ts-loader", exclude: /node_modules/, }, ], }, output: { libraryTarget: "commonjs", filename: "[name].js", path: path.resolve(__dirname, ".webpack"), }, }; ================================================ FILE: web/.eslintrc.json ================================================ { "extends": "next/core-web-vitals" } ================================================ FILE: web/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js .yarn/install-state.gz # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env*.local # vercel .vercel # typescript *.tsbuildinfo next-env.d.ts ================================================ FILE: web/README.md ================================================ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). ## Getting Started First, run the development server: ```bash npm run dev # or yarn dev # or pnpm dev # or bun dev ``` Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. ## Learn More To learn more about Next.js, take a look at the following resources: - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! ## Deploy on Vercel The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. ================================================ FILE: web/app/(console)/console/[id]/page.tsx ================================================ 'use client' import React, { useEffect } from "react"; import styled from "@emotion/styled"; import { Pencil1Icon } from "@radix-ui/react-icons"; import client, { ApplicationWithApiKey } from "@cors.sh/service-api"; import { FormPageLayout, PageCloseButton } from "@app/ui/layouts"; import { Button, TextFormField } from "@editor-ui/console"; import { Logo } from "@/components/logo"; import { UnderlineButton } from "@app/ui/components"; import { ApiKeyReveal } from "@app/ui/components"; export default function ApplicationDetailPage({ params }: { params: { id: string; } }) { const application: ApplicationWithApiKey = { id: params.id, name: "my-portfolio-website", apikey_live: "prod_1223-xasx-xxe2", apikey_test: "test_xxasdj-xxd9-x2hx", allowedOrigins: [] } return (
Archive application
); } function EditableTitle({ initialValue = "" }: { initialValue?: string }) { const [editing, setEditing] = React.useState(false); const [text, setText] = React.useState(initialValue); const ref = React.useRef(null); useEffect(() => { if (editing) { // select all ref.current?.focus(); ref.current?.setSelectionRange(0, text.length); } }, [editing]); return ( setEditing(true)} onBlur={() => setEditing(false)} ref={ref} readOnly={!editing} contentEditable onChange={(e) => { setText(e.target.value); }} onKeyDown={(e) => { if (e.key === "Enter") { setEditing(false); } }} value={text} /> ); } const TitleInputWrapper = styled.div` position: relative; display: flex; align-items: center; justify-content: space-between; gap: 8px; input { width: 100%; box-sizing: border-box; font-size: 24px; font-weight: bold; border: none; text-align: center; outline: none; } .edit-button { position: absolute; right: 0; top: 0; bottom: 0; margin: auto; opacity: 0; cursor: pointer; border: none; background: none; outline: none; transition: opacity 0.2s ease; } &:hover { .edit-button { opacity: 1; } } `; ================================================ FILE: web/app/(console)/console/new/page.tsx ================================================ 'use client' import React, { useEffect } from "react"; import { Button, TextFormField } from "@editor-ui/console"; import client from "@cors.sh/service-api"; import { useRouter } from "next/navigation"; import { FormPageLayout } from "@app/ui/layouts"; export default function NewApplicationPage() { const router = useRouter(); const [name, setName] = React.useState(""); const [allowedOrigins, setAllowedOrigins] = React.useState(""); const [isBusy, setIsBusy] = React.useState(false); const [isValid, setIsValid] = React.useState(false); const validateUrls = (urls: string) => { const lines = urls.split(",").map((line) => line.trim()); for (const line of lines) { try { new URL(line); } catch (e) { return false; } } return true; }; const onCreateNewClick = () => { setIsBusy(true); client .createApplication({ name: name, allowedOrigins: allowedOrigins .split(",") .map((origin) => origin.trim()), }) .then((r) => { router.push(`/console/${r.id}`,); }) .finally(() => { setIsBusy(false); }); }; useEffect(() => { setIsValid(name.length > 0 && validateUrls(allowedOrigins)); }, [name, allowedOrigins]); return (

Create new application

); } ================================================ FILE: web/app/(console)/console/page.tsx ================================================ import React from "react"; import Link from "next/link"; import type { Metadata } from 'next' import { ApplicationItem, ApplicationList } from "@/components/console/application-list"; import { FormPageLayout } from "@app/ui/layouts"; import { UnderlineButton } from "@app/ui/components"; export const metadata: Metadata = { title: "Dashboard", } export default function ConsoleIndex() { const applications = [ { id: "1", name: "My app", }, { id: "2", name: "My app 2", }, ] return ( <> {/* */}
{applications.map((application) => ( ))}
{/* */} Create new application {/* */} {/* */} Manage subscription {/* */}
); } ================================================ FILE: web/app/(console)/layout.tsx ================================================ import type { Metadata } from 'next' import { Inter } from 'next/font/google' import { Toaster } from "react-hot-toast"; import '../globals.css' import GoogleAnalytics from '@/components/ga'; import ChatwootWidget from "@/components/chatwoot"; import { Heading, Link, Theme } from '@radix-ui/themes'; import { GearIcon, GitHubLogoIcon, MagnifyingGlassIcon, OpenInNewWindowIcon } from '@radix-ui/react-icons'; const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { title: { template: '%s - Console - CORS.SH', default: 'Console - CORS.SH', }, description: "One CORS Proxy you'll ever need", metadataBase: new URL('https://cors.sh'), openGraph: { images: [ "/og-image-01.jpg", ] } } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS ? ( ) : null}
{children}
) } function Sidebar() { return
CORS.SH
Dashboard Apps Events Github
} function NavItem({ children, ...props }: React.PropsWithChildren>) { return ( {children} ) } function ContentArea({ children }: React.PropsWithChildren<{}>) { return
{children}
} ================================================ FILE: web/app/(home)/contact/page.tsx ================================================ import Link from "next/link" import React from "react" export default function ContactPage() { return

Contact

} ================================================ FILE: web/app/(home)/get-started/page.tsx ================================================ 'use client' import React, { useCallback, useRef, useEffect } from "react"; import { useRouter } from "next/navigation"; import { FormPageLayout } from "@app/ui/layouts"; import { validateUrls } from "@app/ui/utils"; import { Button, FormFieldBase, FormFieldLabel, TextFormField, } from "@editor-ui/console"; import Select from "react-select"; import client from "@cors.sh/service-api"; import * as k from "@/k"; import { toast } from "react-hot-toast"; import { useSearchParams } from "next/navigation"; import { MiniPlanSelect } from "@/components/pricing/mini"; import Link from "next/link"; import { motion } from "framer-motion"; // eslint-disable-next-line @next/next/no-async-client-component export default function GetstartedPage() { const searchParams = useSearchParams() const _q_price = searchParams?.get("price") as string const validate_price_id = (price_id: string | undefined): boolean => { return price_id?.startsWith("price_") || false; }; const _price = validate_price_id(_q_price) ? _q_price : k.plans.pro.id; const [name, setName] = React.useState(""); const [price, setPrice] = React.useState(_price); const [allowedOrigins, setAllowedOrigins] = React.useState(""); const [valid, setValid] = React.useState(false); const [isBusy, setIsBusy] = React.useState(false); const [isValid, setIsValid] = React.useState(false); const stateRef = useRef(_price); stateRef.current = price; const router = useRouter(); const onPriceChange = (price: string) => { setPrice(price); }; const onEnter = () => { // this is required because onEnter can also be invoked from input's callback if (valid) { onNextClick(); } }; useEffect(() => { setIsValid(name.length > 0 && validateUrls(allowedOrigins)); }, [name, allowedOrigins]); const onNextClick = useCallback(async () => { setIsBusy(true); try { // log begin_checkout event const pricedata = (k.plans as any)[price]; // @ts-ignore window.gtag?.("event", "begin_checkout", { value: pricedata.value, currency: pricedata.currency, items: [ { item_id: pricedata.id, item_name: pricedata.label, }, ], }); } catch (e) { } try { // create onboarding application const form = { name: name ? name : undefined, allowedOrigins: allowedOrigins .split(",") .map((x) => x.trim()) .filter(Boolean), priceId: price, }; const application = await client.onboardingWithForm(form); const onboarding_id = application.id; // switch (stateRef.current) { // case k.PRICE_FREE_MONTHLY: { // // the free plan does not require payments, so we can skip to create new project right away. // redirect = // window.location.protocol + window.location.host + "/console/new"; // break; // } // } // redirect user to payment page let params = new URLSearchParams(); params.append("onboarding_id", onboarding_id); // TODO: multiple search params not supported by accounts.grida.co?redirect_uri=x const redirect = k.SERVER_URL + "/payments/checkout/new" + "?" + params; router.replace(redirect); } catch (e) { toast.error("Oops. something went wrong. please try again."); setIsBusy(false); } }, [price, name, allowedOrigins, router]); return (

Get started

Ready to use cors.sh? select your plan and let’s create your first project.

*you can update the fields later

{/* pricing plan select */}
Plan View Pricing
{ onPriceChange(id); }} value={price} options={[ { id: k.plans.pro.id, label: $3 / Mo
Annual Billing
, content: <>Save 25% with annual billing }, { id: k.plans.pro2.id, label: $4 / Mo
Monthly Billing
, content: <>- } ]} />
); } ================================================ FILE: web/app/(home)/layout.tsx ================================================ import type { Metadata } from 'next' import { Inter } from 'next/font/google' import { Header } from '@/components/header' import { Toaster } from "react-hot-toast"; import GoogleAnalytics from '@/components/ga'; import ChatwootWidget from "@/components/chatwoot"; import '../globals.css' const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { title: 'CORS.SH - A Fast & Reliable CORS Proxy for your websites', description: "One CORS Proxy you'll ever need", metadataBase: new URL('https://cors.sh'), openGraph: { images: [ "/og-image-01.jpg", ] } } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS ? ( ) : null}
{children}
) } ================================================ FILE: web/app/(home)/onboarding/[id]/page.tsx ================================================ // TODO: rename this route to /onboarding/continue?id= import Head from "next/head"; import { redirect } from "next/navigation"; import React from "react"; export default function ContinueOnboardingWithVerification() { redirect("/get-started"); return ( <> Redirecting..

Redirecting..

); } ================================================ FILE: web/app/(home)/onboarding/page.tsx ================================================ 'use client' import React, { useEffect } from "react"; import { Button, FormFieldBase, FormFieldLabel, TextFormField, } from "@editor-ui/console"; import Select from "react-select"; import client from "@cors.sh/service-api"; import { redirect, useRouter } from "next/navigation"; import { Cross2Icon } from "@radix-ui/react-icons"; import Head from "next/head"; import { FormPageLayout } from "@app/ui/layouts"; import { validateUrls } from "@app/ui/utils"; import { motion } from "framer-motion"; export default function NewApplicationPage() { redirect("/") const router = useRouter(); const [step, setStep] = React.useState<"signin" | "setup">("signin"); function Body() { switch (step) { case "signin": return ( { setStep("setup"); }} /> ); case "setup": return ; } } return ( <> CORS.SH - First App ); } function SigninForm({ onComplete }: { onComplete: () => void }) { const [tmpkey, setTmpkey] = React.useState(""); const [valid, setValid] = React.useState(false); const onTypeKey = (key: string) => { // todo: validate key via api setValid(key.length > 10); }; const onEnter = () => { // this is required because onEnter can also be invoked from input's callback if (valid) { onComplete(); } }; return ( <>

Enter your API key from your inbox to get started

); } function SetupForm() { const router = useRouter(); const [name, setName] = React.useState(""); const [allowedOrigins, setAllowedOrigins] = React.useState(""); const [isBusy, setIsBusy] = React.useState(false); const [isValid, setIsValid] = React.useState(false); const [isPricingVisible, setIsPricingVisible] = React.useState(false); const onEnter = () => { if (!isValid) { return; } setIsBusy(true); client .createApplication({ name: name, allowedOrigins: allowedOrigins .split(",") .map((origin) => origin.trim()), }) .then((r) => { router.push(`/console/${r.id}`,); }) .finally(() => { setIsBusy(false); }); }; useEffect(() => { setIsValid(name.length > 0 && validateUrls(allowedOrigins)); }, [name, allowedOrigins]); useEffect(() => { if (isValid) { setTimeout(() => { setIsPricingVisible(true); }, 1800); } }, [isValid]); return ( <>

Now, Let's configure your first application

{/* pricing plan select */} Plan
{ setEmail(e.target.value); }} /> ) } ================================================ FILE: web/components/logo/index.tsx ================================================ import React from "react"; export function Logo({ color = "black", width = 104, }: { width?: React.CSSProperties["width"]; color?: React.CSSProperties["color"]; }) { return ( ); } ================================================ FILE: web/components/pricing/index.tsx ================================================ import React from "react"; import styled from "@emotion/styled"; import { motion } from "framer-motion"; export function PricingCard({ plan, price, desc, features, action, style = {}, }: { plan: string; price: { value: number | string; currency?: string; unit?: string; }; desc?: string; features: React.ReactNode; action?: React.ReactNode; style?: React.CSSProperties; }) { return ( {plan}
{typeof price.value === "string" ? price.value : `${price.currency}${price.value}`} {price.unit} {desc && ( {desc} )}
{features}
{action}
); } const PricingCardWrapper = styled(motion.div)` --card-border-rgb: 200, 200, 200; display: flex; flex-direction: column; padding: 20px; border: solid 2px rgba(var(--card-border-rgb), 0.1); border-radius: 8px; position: relative; font-family: Inter, sans-serif; text-align: left; &:hover { border: solid 2px rgba(var(--card-border-rgb), 0.2); box-shadow: 0px 0px 24px 2px rgba(var(--card-box-shadow-rgb), 0.1); } section { margin-top: 32px; } .plan { font-size: 14px; font-weight: 500; } .price { display: flex; justify-content: flex-start; flex-direction: row; align-items: center; flex: none; gap: 2px; box-sizing: border-box; .a { font-size: 32px; font-weight: 800; } .b { font-size: 13px; font-weight: 500; opacity: 0.5; } } .unit { display: flex; justify-content: flex-start; flex-direction: row; align-items: center; flex: none; gap: 4px; box-sizing: border-box; opacity: 0.8; .b { font-size: 13px; font-weight: 500; opacity: 0.5; } } .desc { font-weight: 500; opacity: 0.8; } button { box-shadow: 0px 4px 24px 2px rgba(var(--card-box-shadow-rgb), 0.2); background: white; border: solid 1px rgba(120, 120, 120, 0.8); border-radius: 4px; padding: 8px 10px; font-size: 12px; font-weight: 600; outline: none; cursor: pointer; color: black; :hover { opacity: 0.8; } :disabled { opacity: 0.5; } :active { opacity: 1; } :focus { } } .dot { width: 8px; height: 8px; background-color: rgb(var(--foreground-rgb)); border-radius: 50%; position: absolute; top: 20px; right: 20px; } `; ================================================ FILE: web/components/pricing/mini.tsx ================================================ export function MiniPlanSelect({ onChange, value, options, }: { onChange: (id: string) => void; value?: string; options: Array<{ id: string, label: React.ReactElement | string, content: React.ReactElement | string }>; }) { return
{options.map(({ id, label, content }, i) => { const selected = id === value; return ( { onChange(id); }} /> ) })}
} export function MiniPriceCard({ label, content, selected, onClick }: { label: React.ReactElement | string; content: React.ReactElement | string; selected: boolean; onClick: () => void; }) { return } ================================================ FILE: web/k/examples.ts ================================================ /** * usage code snippet */ export const examples = { simplest: ( t: string, key: string = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ) => `fetch('https://proxy.cors.sh/${t}', { headers: { 'x-cors-api-key': '${key}', } }).then(console.log);`, fetch: ( t: string, key: string = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ) => `fetch('https://proxy.cors.sh/${t}', { headers: { 'x-cors-api-key': '${key}', } }); // or... functon fetchWithProxy(url, params){ return fetch(\`https://proxy.cors.sh/\${url}\`, { ...params, headers: { ...params.headers, 'x-cors-api-key': '${key}' } }); } fetchWithProxy('${t}') `, axios: ( t: string, key: string = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ) => `import Axios from "axios"; Axios.get('https://proxy.cors.sh/${t}', { headers: { 'x-cors-api-key': '${key}', } }) // or... const client = Axios.create({ baseURL: 'https://proxy.cors.sh/' + '${t}', headers: { 'x-cors-api-key': '${key}', } }) client.get('/') `, } as const; ================================================ FILE: web/k/external-links.ts ================================================ export const LINK_APPLY_FOR_OSS_PLAN = "https://github.com/gridaco/cors.sh/issues/new?template=apply-for-oss-program.yml"; ================================================ FILE: web/k/faq.json ================================================ [ { "q": "Can I use CORS.SH Proxy for multiple websites?", "a": "Yes, with single subscription, you are able to use CORS.SH Proxy for multiple websites unlimitedly, unless it exceeds the quota." }, { "q": "How can I Apply to OSS Progam?", "a": "Any Open-source projects with 5 stars is capable to apply to OSS Program." } ] ================================================ FILE: web/k/host.ts ================================================ export const HOST = process.env.NODE_ENV === "production" ? "https://cors.sh" : "http://localhost:8823"; export const SERVER_URL = process.env.NODE_ENV === "production" ? // TODO: this url is not live. "https://services.cors.sh" : "http://localhost:4021"; ================================================ FILE: web/k/index.ts ================================================ export * from "./plans"; export * from "./external-links"; export * from "./host"; export * from "./examples"; ================================================ FILE: web/k/plans.json ================================================ { "pro": { "id": "price_1LbnTwAvR3geCh5rZm9v8CAy", "name": "Pro - Monthly", "price": { "value": 4, "currency": "usd", "symbol": "$", "interval": "month" }, "features": [ "Up to 500,000 requests per month", "500GB Bandwidth", "Unlimited Projects", "No hourly request limit", "Up to 6mb per request" ] }, "pro2": { "id": "price_1LbnV4AvR3geCh5rMEwJ5Zf1", "name": "Pro - Annually", "desc": "Annual billing only", "price": { "value": 3, "currency": "usd", "symbol": "$", "interval": "month" }, "features": [ "Up to 500,000 requests per month", "500GB Bandwidth", "Unlimited Projects", "No hourly request limit", "Up to 6mb per request" ] }, "enterprise": { "id": "price_1MNTlpAvR3geCh5r3gqzq2s7", "name": "Enterprise", "price": { "value": 499, "currency": "usd", "symbol": "$", "interval": "year" }, "features": [ "Up to 10,000,000 requests per month", "1TB Bandwidth", "Unlimited Projects", "No hourly request limit", "Max 6mb per request" ] } } ================================================ FILE: web/k/plans.test.json ================================================ { "pro": { "id": "price_1Lda7UAvR3geCh5rVaajCSw6", "name": "Pro - Monthly", "price": { "value": 4, "currency": "usd", "symbol": "$", "interval": "month" }, "features": [ "Up to 500,000 requests per month", "500GB Bandwidth", "Unlimited Projects", "No hourly request limit", "Up to 6mb per request" ] }, "pro2": { "id": "price_1MMpznAvR3geCh5ro9O4Gdlt", "desc": "Annual billing only", "name": "Pro - Annually", "price": { "value": 3, "currency": "usd", "symbol": "$", "interval": "month" }, "features": [ "Up to 500,000 requests per month", "500GB Bandwidth", "Unlimited Projects", "No hourly request limit", "Up to 6mb per request" ] }, "enterprise": { "id": "price_1MNTfvAvR3geCh5rV0KueWOk", "name": "Enterprise", "price": { "value": 499, "currency": "usd", "symbol": "$", "interval": "year" }, "features": [ "Up to 10,000,000 requests per month", "1TB Bandwidth", "Unlimited Projects", "No hourly request limit", "Max 6mb per request" ] } } ================================================ FILE: web/k/plans.ts ================================================ import plans_live from "@/k/plans.json"; import plans_test from "@/k/plans.test.json"; export const plans = process.env.NODE_ENV === "production" ? plans_live : plans_test; const price_pro_monthly = { ...plans.pro2, desc2: "Pro - Monthly", } as const; const price_pro_yearly = { ...plans.pro2, desc2: "Pro - Save 25% with Annual billing", } as const; // const enterprise_yearly: Price = { // id: plans.enterprise.id, // name: "Enterprise", // price: "$499", // unit: "Year", // features: [ // "Up to 10,000,000 requests per month", // "1TB Bandwidth", // "Unlimited Projects", // "No hourly request limit", // "Max 6mb per request", // ], // }; ================================================ FILE: web/layouts/payment-required-page.tsx ================================================ import React from "react"; export function PaymentRequiredPage() { return <>; } ================================================ FILE: web/motions/electron/index.tsx ================================================ import React from "react"; import { motion } from "framer-motion"; const LineAnimation = () => { // Define the motion variant for the moving light effect const motionVariant = { animate: { backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"], transition: { duration: 1.2, ease: "linear", repeat: Infinity, }, } }; return ( ); }; export default LineAnimation; ================================================ FILE: web/next.config.js ================================================ /** @type {import('next').NextConfig} */ const nextConfig = { transpilePackages: ["@cors.sh/service-api", "@app/ui", "@editor-ui/console"], rewrites() { return [ { source: "/playground", destination: "https://playground.cors.sh", }, { source: "/playground/:path*", destination: "https://playground.cors.sh/:path*", }, { source: "/docs", destination: "https://docs.cors.sh/intro", }, { source: "/docs/:path*", destination: "https://docs.cors.sh/:path*", }, ]; }, redirects() { return [ { source: "/http\\://:path*", destination: "https://proxy.cors.sh/http\\://:path*", basePath: false, permanent: true, }, { source: "/https\\://:path*", destination: "https://proxy.cors.sh/https\\://:path*", basePath: false, permanent: true, }, { // if pyament is canceled, go back to get started page. source: "/payments/canceled", destination: "/get-started", permanent: true, }, // { // source: "/console", // destination: "https://console.grida.co/cors-proxy", // permanent: true, // }, { source: "/docs", destination: "/docs/intro", permanent: true, }, ]; }, }; module.exports = nextConfig; ================================================ FILE: web/package.json ================================================ { "name": "homepage", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev -p 8823", "build": "next build", "start": "next start -p 8823", "lint": "next lint" }, "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/themes": "^2.0.1", "@use-gesture/react": "^10.3.0", "add": "^2.0.6", "copy-to-clipboard": "^3.3.3", "framer-motion": "^10.16.4", "next": "14.0.1", "prismjs": "^1.29.0", "react": "^18", "react-dom": "^18", "react-hot-toast": "^2.4.1", "react-select": "^5.7.7", "react-syntax-highlighter": "^15.5.0" }, "devDependencies": { "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "@types/react-syntax-highlighter": "^15.5.9", "autoprefixer": "^10.0.1", "eslint": "^8", "eslint-config-next": "14.0.1", "postcss": "^8", "tailwindcss": "^3.3.0", "typescript": "^5" } } ================================================ FILE: web/pages/onboarding/complete.tsx ================================================ import React, { useEffect } from "react"; import styled from "@emotion/styled"; import { Client, ApplicationWithApiKey } from "@cors.sh/service-api"; import Head from "next/head"; import { FormPageLayout } from "@app/ui/layouts"; import { CollapsibleInfoCard } from "@/components/collapsible-info-card"; import { UnderlineButton } from "@app/ui/components"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { examples } from "@/k"; import { ApiKeyReveal } from "@app/ui/components"; import Link from "next/link"; export default function InitialOnboardingFinalPage({ application, }: { application: ApplicationWithApiKey; }) { const demo_target_url = application.allowedOrigins[0] ?? "https://example.com"; return ( <> CORS.SH - Complete <>

Extend your api call with proxy.cors.sh

We've sent you an email with the api key.
Please check your inbox :)


Let’s get rid of the cors errors with proxy.cors.sh like below.

Thank you for using cors.sh 🙏 {/* Move to dashboard */} I need help
); } function CodeExamples({ target, apikey }: { target: string; apikey: string }) { return ( {examples.simplest(target, apikey)} ); } function MoreCodeExamples({ target, apikey, }: { target: string; apikey: string; }) { return {examples.axios(target, apikey)}; } const CodeBlock = styled(SyntaxHighlighter as any)` max-height: 240px; font-size: 12px !important; `; // //
//     GET https://proxy.corsh.sh/https://instragram.com/posts/123
//     
// -h x-cors-api-key {apikey} //
//
// const CodeBlock = styled.code` // background: black; // color: white; // border-radius: 4px; // padding: 20px; // display: block; // font-size: 12px; // line-height: 1.5; // font-family: monospace; // overflow: scroll; // pre { // margin: 0; // } // `; function VideoDemo() { return (
); } export async function getServerSideProps(context: any) { const { app, checkout_session_id } = context.query; if (!app) { return { redirect: { destination: "/", permanent: false, }, }; } try { const client = new Client({ "x-cors-service-checkout-session-id": checkout_session_id, }); const application = await client.getApplication(app); return { props: { application, }, }; } catch (e) { console.error(e); // 404 return { notFound: true, }; } } ================================================ FILE: web/pages/onboarding/payment-success-with-issue.tsx ================================================ import React from "react"; import type { Metadata } from 'next' import client from "@cors.sh/service-api"; import { Button } from "@editor-ui/console"; import Link from "next/link"; export const metadata: Metadata = { title: "CORS.SH - Problem with your subscription" } export default function PaymentSuccessButThereWasAProblem({ error, message, session, application, }: { error: "identity_conflict" | string; message: string; session: string; application: { id: string; name: string; }; }) { return (

There was a problem with your subscription - "{error}"

{message}

Your Information

Copy the data below when you contact customer support

session: {session}
            application: {application.id} ({application.name})
          
); } export async function getServerSideProps(context: any) { const { error, message, session_id, application_id, onboarding_id } = context.query; if (!session_id || !application_id || !onboarding_id) { // invalid entry return { redirect: { destination: "/", permanent: false, }, }; } try { const application = await client.getOnboardingApplication(onboarding_id); return { props: { error, message, session: session_id || null, application, }, }; } catch (e) { // 404 return { notFound: true, }; } } ================================================ FILE: web/pages/onboarding/payment-success.tsx ================================================ import React, { useEffect } from "react"; import client from "@cors.sh/service-api"; import { useRouter } from "next/router"; import { Button, TextFormField } from "@editor-ui/console"; import { FormPageLayout, PageCloseButton } from "@app/ui/layouts"; import { toast } from "react-hot-toast"; import type { Metadata } from 'next' export const metadata: Metadata = { title: "CORS.SH - Complete" } // page redirected from stripe once the payment is successful export default function PaymentSuccessPage({ application, session, isOnboarding, }: { session: string; isOnboarding: boolean; application: { id: string; name: string; allowedOrigins: string[]; }; }) { const [isBusy, setBusy] = React.useState(false); const router = useRouter(); useEffect(() => { // GA4 conversion - Purchase // @ts-ignore window.gtag?.("event", "purchase", { transaction_id: session, value: 4, currency: "USD", // }); }, []); const onNext = () => { setBusy(true); // convert to application. client .convertApplication(application.id, session) .then((d) => { // move to complete router.push({ pathname: "/onboarding/complete", query: { app: d.id, checkout_session_id: session, }, }); }) .catch((e) => { toast.error("Something went wrong. Please try again later."); }) .finally(() => { setBusy(false); }); }; return ( <> <>

Thank you for your subscription

You can now create as many project you want without unlimited hourly rate :)

Let’s finish up your first project.

); } export async function getServerSideProps(context: any) { const { session_id, application_id, customer_id, onboarding_id } = context.query; if (!session_id || !onboarding_id) { // invalid entry return { redirect: { destination: "/", permanent: false, }, }; } try { const application = await client.getOnboardingApplication(onboarding_id); return { props: { session: session_id || null, application, customer_id, }, }; } catch (e) { // 404 return { notFound: true, }; } } ================================================ FILE: web/postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } ================================================ FILE: web/public/robots.txt ================================================ # Block all crawlers for /console User-agent: * Disallow: /console # Allow all crawlers User-agent: * Allow: / ================================================ FILE: web/tailwind.config.ts ================================================ import type { Config } from 'tailwindcss' const config: Config = { content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: { backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, }, }, plugins: [], } export default config ================================================ FILE: web/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "plugins": [ { "name": "next" } ], "paths": { "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } ================================================ FILE: web/utils/email-validation.ts ================================================ const blacklist = [ "nqmo.com", "qabq.com", "mailinator.com", "10minutemail.com", "guerrillamail.com", "temp-mail.org", "yopmail.com", "throwawaymail.com", "dispostable.com", "getnada.com", "maildrop.cc", "pastryofistanbul.com", "litepax.com", "raleigh-construction.com", "questtechsystems.com", "teihu.com", "oakon.com", "namestal.com" ]; const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; export const validateEmail = (email: string) => { const emailLower = email.toLowerCase(); const domain = emailLower.split("@")[1]; if (!pattern.test(emailLower)) return false; if (blacklist.includes(domain)) return false; return true; };