├── .env.example ├── .eslintrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── stale.yml ├── .gitignore ├── .gitpod.yml ├── .prettierignore ├── .prettierrc.json ├── .vercelignore ├── LICENSE.md ├── README.md ├── components ├── FeatureCard.tsx ├── Footer.tsx ├── FrameworkCard.tsx ├── Navbar.tsx ├── PwaImages.tsx ├── Shield.tsx ├── Spinner.tsx ├── dashboard │ ├── Navbar.tsx │ └── SiteCard.tsx └── site │ ├── AdvancedSettingsTab.tsx │ ├── CodeSnippet.tsx │ ├── GeneralSettingsTab.tsx │ └── SiteHead.tsx ├── docs ├── _sidebar.txt ├── framework-agnostic.md ├── how-it-works.md ├── index.md ├── security.md ├── self-hosting.md ├── with-angular.md ├── with-html.md ├── with-nextjs-11.md ├── with-nextjs.md ├── with-nuxtjs.md ├── with-svelte-kit.md ├── with-svelte.md └── with-vue.md ├── lib ├── baseUrl.ts ├── blockLogins.ts ├── compareHashedPasswords.ts ├── deleteSite.ts ├── features.tsx ├── fetcher.ts ├── frameworks.ts ├── getLastLogin.ts ├── getSuccessfulLogins.ts ├── getUnsuccessfulLogins.ts ├── handleNewSite.ts ├── hashPassword.ts ├── rate-limit.ts ├── sortSiteCardsByUpdatedDate.ts ├── useSites.ts ├── validateAndUpdateSiteData.ts └── validatePassword.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── packages └── script.js ├── pages ├── _app.tsx ├── _document.tsx ├── api │ ├── add-new-site.ts │ ├── auth │ │ └── [...auth0].ts │ ├── fetch-sites.ts │ ├── get-site-from-site-id.ts │ ├── login-to-site.ts │ ├── site │ │ ├── block-logins.ts │ │ ├── delete-site.ts │ │ ├── update-caption.ts │ │ ├── update-logo-url.ts │ │ ├── update-max-login-duration.ts │ │ ├── update-name.ts │ │ ├── update-site-desc.ts │ │ ├── update-site-password.ts │ │ └── update-title.ts │ └── verify-token.ts ├── dashboard │ └── index.tsx ├── errors │ ├── logins-blocked.tsx │ ├── noscript.tsx │ └── request-blocked.tsx ├── index.tsx ├── new.tsx ├── p │ └── [siteId].tsx ├── site │ └── [siteId].tsx ├── support.tsx └── with.tsx ├── postcss.config.js ├── public ├── empty.png ├── favicon.ico ├── frameworks.png ├── frameworks │ ├── angular.png │ ├── frameworks.gif │ ├── gatsby.png │ ├── html.png │ ├── nextjs.png │ ├── nuxtjs.png │ ├── preact.png │ ├── react.png │ ├── svelte.png │ └── vue.png ├── icons │ ├── apple-icon-180.png │ ├── apple-splash-dark-1125-2436.jpg │ ├── apple-splash-dark-1136-640.jpg │ ├── apple-splash-dark-1170-2532.jpg │ ├── apple-splash-dark-1242-2208.jpg │ ├── apple-splash-dark-1242-2688.jpg │ ├── apple-splash-dark-1284-2778.jpg │ ├── apple-splash-dark-1334-750.jpg │ ├── apple-splash-dark-1536-2048.jpg │ ├── apple-splash-dark-1620-2160.jpg │ ├── apple-splash-dark-1668-2224.jpg │ ├── apple-splash-dark-1668-2388.jpg │ ├── apple-splash-dark-1792-828.jpg │ ├── apple-splash-dark-2048-1536.jpg │ ├── apple-splash-dark-2048-2732.jpg │ ├── apple-splash-dark-2160-1620.jpg │ ├── apple-splash-dark-2208-1242.jpg │ ├── apple-splash-dark-2224-1668.jpg │ ├── apple-splash-dark-2388-1668.jpg │ ├── apple-splash-dark-2436-1125.jpg │ ├── apple-splash-dark-2532-1170.jpg │ ├── apple-splash-dark-2688-1242.jpg │ ├── apple-splash-dark-2732-2048.jpg │ ├── apple-splash-dark-2778-1284.jpg │ ├── apple-splash-dark-640-1136.jpg │ ├── apple-splash-dark-750-1334.jpg │ ├── apple-splash-dark-828-1792.jpg │ ├── favicon-196.png │ ├── manifest-icon-192.png │ └── manifest-icon-512.png ├── logo.svg ├── manifest.json ├── noscript-error-banner.png ├── ogimage.png ├── staticshield-100x100.png ├── staticshield-rounded.png ├── staticshield-with-bg.png ├── staticshield.png ├── thumbnail.png └── vercel.svg ├── scripts └── prepare-db.js ├── styles ├── Home.module.css ├── globals.css └── theme.ts ├── tailwind.config.js ├── tsconfig.json ├── types └── interfaces.ts ├── utils ├── addHarperDbRecord.ts ├── fetchSites.ts ├── getHashedPasswordFromSiteId.ts ├── getSiteFromSiteId.ts ├── update │ ├── blockLogins.ts │ ├── deleteSite.ts │ ├── updateCaption.ts │ ├── updateLogoUrl.ts │ ├── updateMaxLoginDuration.ts │ ├── updateSiteDesc.ts │ ├── updateSiteName.ts │ ├── updateSitePassword.ts │ └── updateTitle.ts ├── updateLoginCount.ts └── updateUnsuccessfulLoginsCount.ts └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | AUTH0_SECRET= 2 | AUTH0_BASE_URL= 3 | AUTH0_ISSUER_BASE_URL= 4 | AUTH0_CLIENT_ID= 5 | AUTH0_CLIENT_SECRET= 6 | HARPERDB_URL= 7 | HARPERDB_KEY= 8 | HASH_SECRET= 9 | JWT_TOKEN= 10 | TOKEN_SECRET= 11 | NEXT_PUBLIC_BASE_URL= -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next", 3 | "rules": { 4 | "no-unused-vars": ["warn", { "varsIgnorePattern": "^_" }] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.buymeacoffee.com/lalitcodes'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: '/' 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 0 8 | assignees: 9 | - 'Lalit2005' 10 | reviewers: 11 | - 'Lalit2005' 12 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Comment to post when marking an issue as stale. Set to `false` to disable 10 | markComment: > 11 | This issue has been automatically marked as stale because it has not had 12 | recent activity. It will be closed if no further activity occurs. Thank you 13 | for your contributions. 14 | # Comment to post when closing a stale issue. Set to `false` to disable 15 | closeComment: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # script 4 | public/script.js 5 | public/script.js.map 6 | 7 | # dependencies 8 | /node_modules 9 | /.pnp 10 | .pnp.js 11 | 12 | # testing 13 | /coverage 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | 19 | # production 20 | /build 21 | 22 | # misc 23 | .parcel-cache 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # local env files 33 | .env.local 34 | .env.development.local 35 | .env.test.local 36 | .env.production.local 37 | 38 | # vercel 39 | .vercel 40 | 41 | # VSCode extensions 42 | .VSCodeCounter 43 | public/workbox-*.js.map 44 | public/workbox-*.js 45 | public/sw.js.map 46 | public/sw.js 47 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | tasks: 6 | - init: yarn install 7 | command: yarn dev 8 | 9 | 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | .vercel -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "jsxBracketSameLine": true, 4 | "jsxSingleQuote": true, 5 | "singleQuote": true, 6 | "bracketSpacing": true, 7 | "semi": true, 8 | "useTabs": false, 9 | "tabWidth": 2 10 | } 11 | -------------------------------------------------------------------------------- /.vercelignore: -------------------------------------------------------------------------------- 1 | docs/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lalit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://user-images.githubusercontent.com/69138026/123915493-30b28c80-d99e-11eb-8a64-2d07977661d0.png) 2 | 3 | ![image](https://user-images.githubusercontent.com/69138026/123914848-7b7fd480-d99d-11eb-9bca-463f4d7e68e6.png) 4 | 5 | # [How I built it](https://lalit2005.hashnode.dev/staticshield) 6 | 7 | ## Demo 8 | 9 | Password protect a website in less than 2 mins!! 10 | 11 | https://user-images.githubusercontent.com/69138026/123917671-ab7ca700-d9a0-11eb-8531-4297d2b12ced.mp4 12 | 13 | 14 | ## Some useful links that you might require 15 | 16 | 1. StaticShield website - https://staticshield.vercel.app 17 | 2. StaticShield Docs - https://staticshield.vercel.app/docs/ 18 | 3. GitHub repo - https://github.com/Lalit2005/staticshield 19 | 4. GitHub repo (docs) - https://github.com/Lalit2005/staticshield-docs 20 | 5. GitHub repo (StaticShield examples) - https://github.com/Lalit2005/staticshield-examples 21 | 22 | ## Tech stack 📚 23 | 24 | 1. Next.js - The most amazing React framework on the planet 25 | 2. TailwindCSS - Styling 26 | 3. Geist UI - React component library 27 | 4. HarperDB - Database 28 | 5. Nextra - Documentation 29 | 6. Auth0 - Authentication 30 | 7. Axios - API requests 31 | 8. React Hook Form - Form validation 32 | 9. Zod - Validation 33 | 10. Web3forms - Form submissions 34 | 11. SWR - Remote data fetching 35 | 12. Typescript - Type checking 36 | 13. Uglify-js - Minifying script 37 | 14. Vercel - Hosting 38 | -------------------------------------------------------------------------------- /components/FeatureCard.tsx: -------------------------------------------------------------------------------- 1 | interface NavBarProps { 2 | feature: string; 3 | children: JSX.Element; 4 | } 5 | 6 | export default function FeatureCard(props: NavBarProps) { 7 | const { feature, children } = props; 8 | return ( 9 |
10 |
11 |
12 | {children} 13 |
14 |

{feature}

15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Text } from '@geist-ui/react'; 2 | import NextLink from 'next/link'; 3 | 4 | const Footer: React.FC<{ alignRight?: boolean }> = ({ alignRight = true }) => { 5 | return ( 6 |
7 |
9 |
10 | 11 | Frameworks 12 | 13 | 14 | Nextjs 11 15 | 16 | 17 | Nextjs 18 | 19 | 20 | Svelte 21 | 22 | 23 | Svelte Kit 24 | 25 | 26 | Vuejs 27 | 28 | 29 | Nuxtjs 30 | 31 | 32 | React 33 | 34 | 35 | Gatsby 36 | 37 | 38 | HTML 39 | 40 |
41 |
42 | 43 | Useful Links 44 | 45 | 46 | 47 | Guides 48 | 49 | 50 | 51 | 52 | Docs 53 | 54 | 55 | 56 | 57 | Support 58 | 59 | 60 | 61 | Made by Lalit 62 | 63 |
64 |
65 |
66 | ); 67 | }; 68 | 69 | export default Footer; 70 | -------------------------------------------------------------------------------- /components/FrameworkCard.tsx: -------------------------------------------------------------------------------- 1 | import { FrameworkCardProps } from 'types/interfaces'; 2 | import Image from 'next/image'; 3 | import NextLink from 'next/link'; 4 | 5 | const FrameworkCard: React.FC = ({ name, link, img }) => { 6 | return ( 7 | 8 |
9 |
10 | 11 |
12 |

{name}

13 |
14 |
15 | ); 16 | }; 17 | 18 | export default FrameworkCard; 19 | -------------------------------------------------------------------------------- /components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Row, Text, useClickAway } from '@geist-ui/react'; 2 | import NextLink from 'next/link'; 3 | import Image from 'next/image'; 4 | import { useState, useRef } from 'react'; 5 | import { Popover } from '@geist-ui/react'; 6 | import Logo from '../public/staticshield.png'; 7 | import HamburgerMenu from 'react-hamburger-menu'; 8 | 9 | export default function Navbar() { 10 | const [isVisible, setIsVisible] = useState(false); 11 | const popoverRef = useRef(); 12 | useClickAway(popoverRef, () => setIsVisible(false)); 13 | const menuContent = () => { 14 | return ( 15 |
16 | 17 |

StaticShield

18 |
19 |
20 | 21 | 22 |

Sign In

23 |
24 | 25 |
26 | 27 |
28 | 29 | 30 |

Sign Up

31 |
32 | 33 |
34 | 35 |
36 | 37 | 38 | 39 |

Guides

40 |
41 | 42 |
43 |
44 | 45 |
46 | 47 | 48 | 49 |

Docs

50 |
51 | 52 |
53 |
54 |
55 | ); 56 | }; 57 | 58 | return ( 59 | 60 |
61 | 62 |
63 | StaticShield 64 | 65 | Static 66 | 67 | Shield 68 | 69 | 70 | BETA 71 |
72 |
73 |
74 |
75 | 76 | Docs 77 | 78 |
79 |
80 | 81 | 82 | Guides 83 | 84 | 85 |
86 |
87 | 90 | Examples 91 | 92 |
93 |
94 | 95 | 96 | Support 97 | 98 | 99 |
100 |
101 |
102 |
103 | 104 | 107 | Sign Up 108 | 109 | 110 |
111 |
112 | 113 | 116 | Sign In 117 | 118 | 119 |
120 |
121 |
122 | 123 | { 128 | { 129 | setIsVisible(!isVisible); 130 | } 131 | }} 132 | isOpen={isVisible} 133 | /> 134 | 135 |
136 |
137 |
138 | ); 139 | } 140 | -------------------------------------------------------------------------------- /components/PwaImages.tsx: -------------------------------------------------------------------------------- 1 | const PwaImages = () => { 2 | return ( 3 | <> 4 | 10 | 11 | 12 | 17 | 22 | 27 | 32 | 37 | 42 | 47 | 52 | 57 | 62 | 67 | 72 | 77 | 82 | 87 | 92 | 97 | 102 | 107 | 112 | 117 | 122 | 127 | 132 | 137 | 142 | 143 | ); 144 | }; 145 | 146 | export default PwaImages; 147 | -------------------------------------------------------------------------------- /components/Shield.tsx: -------------------------------------------------------------------------------- 1 | export default function Shield() { 2 | return ( 3 | 9 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /components/Spinner.tsx: -------------------------------------------------------------------------------- 1 | export default function Spinner(props) { 2 | return ( 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 169 |
170 | ); 171 | } 172 | -------------------------------------------------------------------------------- /components/dashboard/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Avatar, 3 | Badge, 4 | Button, 5 | Link, 6 | Popover, 7 | Spinner, 8 | Text, 9 | Textarea, 10 | Tooltip, 11 | useToasts, 12 | } from '@geist-ui/react'; 13 | import { DashboardNavbarProps } from 'types/interfaces'; 14 | import NextLink from 'next/link'; 15 | import Image from 'next/image'; 16 | import Logo from '../../public/staticshield.png'; 17 | import { 18 | Book, 19 | LogOut, 20 | PlusSquare, 21 | Send, 22 | Smile, 23 | User, 24 | } from '@geist-ui/react-icons'; 25 | import { useState } from 'react'; 26 | import useWeb3forms from 'use-web3forms'; 27 | import { useForm } from 'react-hook-form'; 28 | 29 | interface FormValues { 30 | feedback: string; 31 | email: string; 32 | name: string; 33 | } 34 | 35 | export default function DashboardNavbar(props: DashboardNavbarProps) { 36 | const [loading, setLoading] = useState(false); 37 | const [_, setToast] = useToasts(); 38 | const { user, isNewSiteButtonVisible } = props; 39 | 40 | const { register, handleSubmit, reset } = useForm({ 41 | defaultValues: { 42 | email: user?.email, 43 | name: user?.name, 44 | }, 45 | }); 46 | 47 | const { submit } = useWeb3forms({ 48 | apikey: process.env.NEXT_PUBLIC_WEB3FORMS_KEY, 49 | onSuccess: () => { 50 | setLoading(false); 51 | setToast({ 52 | text: 'Your feedback was successfully delivered', 53 | type: 'success', 54 | }); 55 | reset(); 56 | }, 57 | onError: () => { 58 | setLoading(false); 59 | setToast({ 60 | text: 'Your feedback was successfully delivered', 61 | type: 'success', 62 | }); 63 | }, 64 | }); 65 | 66 | const avatarPopoverContent = () => { 67 | return ( 68 |
69 | 70 |

71 | 72 | {user?.name} 73 |

74 |
75 | 76 | 77 | 78 |

79 | 80 | Add new site 81 |

82 | 83 |
84 | 85 | 86 | 87 |

88 | 89 | Logout 90 |

91 | 92 |
93 | 94 | 95 | 96 |

97 | 98 | Support 99 |

100 | 101 |
102 | 103 | 104 | 105 | 106 |

107 | 108 | Docs 109 |

110 | 111 |
112 |
113 | ); 114 | }; 115 | 116 | const feedbackPopover = () => ( 117 | <> 118 |
{ 120 | setLoading(true); 121 | handleSubmit(submit)(); 122 | }}> 123 | Thanks for giving feedback 😀 124 | 125 |