├── server ├── .gitignore ├── routes │ ├── verify.js │ ├── contact.js │ ├── digest.js │ ├── image.js │ └── users.js ├── models │ ├── digest.js │ ├── contact_format.js │ ├── user_attempts.js │ └── user.js ├── util │ ├── nodemailer.js │ └── util.js ├── controllers │ ├── contact_controller.js │ ├── image_getbyuser.js │ ├── digest_controller.js │ ├── validation.js │ ├── image_search.js │ ├── verify_token.js │ ├── users_signup.js │ └── users_login.js ├── package.json ├── views │ └── unblocked.html ├── static │ └── message.js ├── README.md ├── index.js └── swagger.json ├── client ├── public │ ├── robots.txt │ ├── static │ │ └── img │ │ │ └── signup.png │ ├── manifest.json │ ├── index.html │ └── index.css ├── src │ ├── index.css │ ├── static │ │ ├── img │ │ │ └── signup.png │ │ ├── config.js │ │ └── icons_data.js │ ├── util │ │ ├── config.js │ │ ├── util.js │ │ ├── toast.js │ │ └── validation.js │ ├── components │ │ ├── Items │ │ │ ├── PasswordIcon.js │ │ │ ├── BlockedBox.js │ │ │ ├── AttackBlock.js │ │ │ └── Loader.js │ │ ├── Digest.js │ │ ├── Slider.js │ │ ├── Navbar.js │ │ ├── Footer.js │ │ ├── Contact.js │ │ ├── Landing.js │ │ ├── Login.js │ │ └── Signup.js │ ├── index.js │ └── App.js ├── tailwind.config.js ├── README.md └── package.json ├── img ├── login_1.png ├── login_2.png ├── auth_fail.png ├── auth_success.png ├── landing_page.png ├── account_blocked.png ├── registration_1.png ├── registration_2.png ├── account_unblocked.png └── notification_email.png ├── LICENSE ├── SCREENSHOTS.md └── README.md /server/.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /img/login_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prathamesh-a/graphical-password-authentication/HEAD/img/login_1.png -------------------------------------------------------------------------------- /img/login_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prathamesh-a/graphical-password-authentication/HEAD/img/login_2.png -------------------------------------------------------------------------------- /img/auth_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prathamesh-a/graphical-password-authentication/HEAD/img/auth_fail.png -------------------------------------------------------------------------------- /img/auth_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prathamesh-a/graphical-password-authentication/HEAD/img/auth_success.png -------------------------------------------------------------------------------- /img/landing_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prathamesh-a/graphical-password-authentication/HEAD/img/landing_page.png -------------------------------------------------------------------------------- /img/account_blocked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prathamesh-a/graphical-password-authentication/HEAD/img/account_blocked.png -------------------------------------------------------------------------------- /img/registration_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prathamesh-a/graphical-password-authentication/HEAD/img/registration_1.png -------------------------------------------------------------------------------- /img/registration_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prathamesh-a/graphical-password-authentication/HEAD/img/registration_2.png -------------------------------------------------------------------------------- /img/account_unblocked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prathamesh-a/graphical-password-authentication/HEAD/img/account_unblocked.png -------------------------------------------------------------------------------- /img/notification_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prathamesh-a/graphical-password-authentication/HEAD/img/notification_email.png -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | background-color: #2B2B2B; 7 | } 8 | -------------------------------------------------------------------------------- /client/src/static/img/signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prathamesh-a/graphical-password-authentication/HEAD/client/src/static/img/signup.png -------------------------------------------------------------------------------- /client/public/static/img/signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prathamesh-a/graphical-password-authentication/HEAD/client/public/static/img/signup.png -------------------------------------------------------------------------------- /client/src/static/config.js: -------------------------------------------------------------------------------- 1 | export const api = { 2 | //url: "http://localhost:5000" 3 | //url:"https://crazy-fish-tights.cyclic.app" 4 | url: "https://graphical-auth-server.onrender.com" 5 | } -------------------------------------------------------------------------------- /server/routes/verify.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { verify } from '../controllers/verify_token.js' 3 | 4 | const router = express.Router() 5 | 6 | router.get('/', verify) 7 | 8 | export { router as VerifyRoute } -------------------------------------------------------------------------------- /server/routes/contact.js: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | import { contactController } from "../controllers/contact_controller.js" 3 | 4 | const router = express.Router() 5 | 6 | router.post('/', contactController) 7 | 8 | export { router } -------------------------------------------------------------------------------- /server/routes/digest.js: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | import { digestController } from "../controllers/digest_controller.js" 3 | 4 | const router = express.Router() 5 | 6 | router.post('/', digestController) 7 | 8 | export {router as DigestRoutes} -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | ], 6 | "start_url": ".", 7 | "display": "standalone", 8 | "theme_color": "#000000", 9 | "background_color": "#ffffff" 10 | } 11 | -------------------------------------------------------------------------------- /client/src/util/config.js: -------------------------------------------------------------------------------- 1 | export const Page = { 2 | HOME_PAGE: "home", 3 | SIGNUP_PAGE: "signup", 4 | ABOUT: "about", 5 | CONTACT: "contact", 6 | LOGIN_PAGE: "loginpage" 7 | } 8 | 9 | export const header = {headers: {"Access-Control-Allow-Origin": "*"}} -------------------------------------------------------------------------------- /server/routes/image.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { getByUser } from '../controllers/image_getbyuser.js' 3 | import { search as imageSearch } from '../controllers/image_search.js' 4 | 5 | const router = express.Router() 6 | 7 | router.get('/search', imageSearch) 8 | router.get('/', getByUser) 9 | 10 | export { router } -------------------------------------------------------------------------------- /server/models/digest.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import mongooseUniqueValidator from "mongoose-unique-validator"; 3 | 4 | const Schema = mongoose.Schema 5 | 6 | const digestSchema = new Schema({ 7 | email: {type: String, required: true, unique: true} 8 | }) 9 | 10 | digestSchema.plugin(mongooseUniqueValidator) 11 | 12 | const digestModel = mongoose.model('Digest', digestSchema) 13 | 14 | export {digestModel} -------------------------------------------------------------------------------- /server/routes/users.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { loginController } from '../controllers/users_login.js' 3 | import { signupController } from '../controllers/users_signup.js' 4 | import { check } from '../controllers/validation.js' 5 | 6 | const router = express.Router() 7 | 8 | router.post('/signup', signupController) 9 | router.post('/login', loginController) 10 | 11 | router.get('/check', check) 12 | 13 | export { router } -------------------------------------------------------------------------------- /client/src/components/Items/PasswordIcon.js: -------------------------------------------------------------------------------- 1 | export default function PasswordIcon(props) { 2 | 3 | const selectedClasses = "bg-purple-500 shadow-xl" 4 | 5 | return ( 6 | props.onClick(props.id, props.iteration)} 10 | className={`w-[80%] transition duration-500 ease-in-out hover:shadow-2xl hover:scale-105 rounded-lg p-1 cursor-pointer ${props.selected ? selectedClasses : ""}`}/> 11 | ) 12 | } -------------------------------------------------------------------------------- /client/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{html,js}"], 4 | theme: { 5 | extend: {}, 6 | screens: { 7 | xs: "480px", 8 | ss: "620px", 9 | sm: "768px", 10 | md: "1024px", 11 | lg: "1200px", 12 | xl: "1700px", 13 | }, 14 | // colors: { 15 | // 'purple': '#A259FF', 16 | // 'darkgray': '#2B2B2B', 17 | // 'mdgray': '#3B3B3B', 18 | // } 19 | }, 20 | plugins: [], 21 | } 22 | -------------------------------------------------------------------------------- /server/models/contact_format.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import mongooseUniqueValidator from "mongoose-unique-validator"; 3 | 4 | const Schema = mongoose.Schema 5 | 6 | const contactFormat = new Schema({ 7 | name: {type: String, required: true}, 8 | email: {type: String, required: true}, 9 | message: {type: String, required: true} 10 | }) 11 | 12 | contactFormat.plugin(mongooseUniqueValidator) 13 | 14 | const contactFormatModel = mongoose.model('Message', contactFormat) 15 | 16 | export { contactFormatModel } -------------------------------------------------------------------------------- /server/util/nodemailer.js: -------------------------------------------------------------------------------- 1 | //import * as dotenv from 'dotenv' 2 | import { createTransport } from "nodemailer" 3 | 4 | //dotenv.config() 5 | 6 | const transporter = createTransport({ 7 | service: "gmail", 8 | auth: { 9 | user: process.env.SMPT_USER, 10 | pass: process.env.SMTP_PASSWORD 11 | } 12 | }) 13 | 14 | // const mailOptions = { 15 | // from: "graphicalpassauth@gmail.com", 16 | // to: "autipratham1671@gmail.com", 17 | // subject: "Test Email", 18 | // text: "test" 19 | // } 20 | 21 | export { transporter } -------------------------------------------------------------------------------- /client/src/util/util.js: -------------------------------------------------------------------------------- 1 | function removeElementFromArray(element, array) { 2 | const index = array.indexOf(element) 3 | if (index > -1) array.splice(index, 1) 4 | } 5 | 6 | function getNameByNumber(num) { 7 | // eslint-disable-next-line default-case 8 | switch (num) { 9 | case 1: 10 | return "First" 11 | case 2: 12 | return "Second" 13 | case 3: 14 | return "Third" 15 | case 4: 16 | return "Fourth" 17 | } 18 | } 19 | 20 | export {removeElementFromArray, getNameByNumber} -------------------------------------------------------------------------------- /server/models/user_attempts.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import mongooseUniqueValidator from "mongoose-unique-validator"; 3 | 4 | const Schema = mongoose.Schema 5 | 6 | const userAttemptsSchema = new Schema({ 7 | username: { type: String, required: true, unique: true }, 8 | email: { type: String, required: true, unique: true }, 9 | attempts: {type: Number, required: true}, 10 | token: {type: String} 11 | }) 12 | 13 | userAttemptsSchema.plugin(mongooseUniqueValidator) 14 | 15 | const userAttemptsModel = mongoose.model('UserAttempts', userAttemptsSchema) 16 | 17 | export { userAttemptsModel } -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import {ToastContainer} from "react-toastify"; 6 | import 'react-toastify/dist/ReactToastify.min.css'; 7 | import {Analytics} from "@vercel/analytics/react"; 8 | 9 | const root = ReactDOM.createRoot(document.getElementById('root')); 10 | 11 | root.render( 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import mongooseUniqueValidator from "mongoose-unique-validator"; 3 | 4 | const Schema = mongoose.Schema 5 | 6 | const userSchema = new Schema({ 7 | username: { type: String, required: true, unique: true }, 8 | email: { type: String, required: true, unique: true }, 9 | password: {type: String, required: true, minlength: 8 }, 10 | pattern: { type: [String], required: true }, 11 | sequence: { type: Boolean, required: true }, 12 | sets: {type: [[Object]], required: true} 13 | }) 14 | 15 | userSchema.plugin(mongooseUniqueValidator) 16 | 17 | const usertModel = mongoose.model('User', userSchema) 18 | 19 | export { usertModel } -------------------------------------------------------------------------------- /client/src/util/toast.js: -------------------------------------------------------------------------------- 1 | import {toast} from "react-toastify"; 2 | 3 | function Toast(message) { 4 | toast.error(message, {position: "top-center", autoClose: 3000, hideProgressBar: false, 5 | closeOnClick: true, 6 | pauseOnHover: true, 7 | draggable: true, 8 | progress: undefined, 9 | theme: "dark", 10 | }) 11 | } 12 | 13 | function successToast(message) { 14 | toast.success(message, {position: "top-center", autoClose: 3000, hideProgressBar: false, 15 | closeOnClick: true, 16 | pauseOnHover: true, 17 | draggable: true, 18 | progress: undefined, 19 | theme: "dark", 20 | }) 21 | } 22 | 23 | export {Toast, successToast} -------------------------------------------------------------------------------- /client/src/components/Items/BlockedBox.js: -------------------------------------------------------------------------------- 1 | import {faClose, faWarning} from "@fortawesome/free-solid-svg-icons"; 2 | import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; 3 | 4 | export default function BlockedBox(props) { 5 | return ( 6 |
7 |

This account has been blocked please check your email.

8 | 11 |
12 | ) 13 | } -------------------------------------------------------------------------------- /client/src/components/Items/AttackBlock.js: -------------------------------------------------------------------------------- 1 | export default function AttackBlock(props) { 2 | return ( 3 |
4 |
5 |
6 | 7 |

{props.title}


8 |
9 |
10 |

{props.desc}

11 |
12 |
13 |
14 | ) 15 | } -------------------------------------------------------------------------------- /client/src/components/Items/Loader.js: -------------------------------------------------------------------------------- 1 | import {Triangle} from "react-loader-spinner"; 2 | 3 | export default function Loader() { 4 | return ( 5 |
7 |
8 |
9 | 18 |
19 |

Loading please wait...

20 |
21 | 22 |
23 | ) 24 | } -------------------------------------------------------------------------------- /client/src/util/validation.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import {api} from "../static/config"; 3 | import {header} from "./config"; 4 | 5 | async function checkUsername(username, setLoading) { 6 | let flag 7 | setLoading(true) 8 | await axios.get(`${api.url}/api/user/check?username=${username}`, header) 9 | .then(r => { 10 | flag = r.data.exists 11 | setLoading(false) 12 | }) 13 | .catch(err => console.log(err)) 14 | setLoading(false) 15 | return flag 16 | } 17 | 18 | async function checkEmail(email, setLoading) { 19 | let flag 20 | setLoading(true) 21 | await axios.get(`${api.url}/api/user/check?email=${email}`) 22 | .then(r => { 23 | flag = r.data.exists 24 | setLoading(false) 25 | }) 26 | .catch(err => console.log(err)) 27 | setLoading(false) 28 | return flag 29 | } 30 | 31 | export { checkEmail, checkUsername } -------------------------------------------------------------------------------- /server/controllers/contact_controller.js: -------------------------------------------------------------------------------- 1 | import { contactFormatModel } from "../models/contact_format.js" 2 | import { commons } from "../static/message.js" 3 | 4 | const contact = async (req, res, next) => { 5 | 6 | const {name, email, message} = req.body 7 | 8 | if (typeof name === 'undefined' || typeof email === 'undefined' || typeof message === 'undefined') { 9 | res.status(406).json({ 10 | message: commons.invalid_params, 11 | format: "[name, email, message]" 12 | }) 13 | return next() 14 | } 15 | 16 | const contactFormat = new contactFormatModel({name, email, message}) 17 | 18 | try {contactFormat.save()} 19 | catch (err) { 20 | console.log(err) 21 | res.status(500).json({message: "Error saving into database."}) 22 | return next() 23 | } 24 | 25 | res.status(200).json({message: "Saved"}) 26 | } 27 | 28 | export {contact as contactController} -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gpa_backend", 3 | "version": "0.1.0", 4 | "description": "Backend server for the Graphical Password Authentication client.", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "start": "node index.js" 10 | }, 11 | "author": "inix", 12 | "license": "ISC", 13 | "dependencies": { 14 | "bcryptjs": "^2.4.3", 15 | "body-parser": "^1.20.1", 16 | "cors": "^2.8.5", 17 | "dotenv": "^16.0.3", 18 | "express": "^4.18.2", 19 | "jsonwebtoken": "^9.0.0", 20 | "mongodb": "^4.13.0", 21 | "mongoose": "^6.9.0", 22 | "mongoose-unique-validator": "^3.1.0", 23 | "nanoid": "^4.0.1", 24 | "node-fetch": "^3.3.0", 25 | "nodemailer": "^6.9.1", 26 | "nodemon": "^2.0.20", 27 | "swagger-jsdoc": "^6.2.8", 28 | "swagger-ui-express": "^4.6.0", 29 | "unsplash-js": "^7.0.15", 30 | "uuid": "^9.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/controllers/image_getbyuser.js: -------------------------------------------------------------------------------- 1 | import { commons, login_messages as msg } from "../static/message.js" 2 | import { usertModel as User } from "../models/user.js" 3 | 4 | const getByUser = async (req, res, next) => { 5 | 6 | var { username } = req.query 7 | username = username.toLowerCase() 8 | let existingUser 9 | 10 | if (typeof username === 'undefined') { 11 | res.status(500).json({ 12 | message: commons.invalid_params, 13 | format: "username" 14 | }) 15 | return next() 16 | } 17 | 18 | try { existingUser = await User.findOne({username: username}) } 19 | catch(err) { 20 | console.log(err) 21 | res.status(401).json({message: "Error occured while fetching from DB"}) 22 | return next() 23 | } 24 | 25 | if (!existingUser) { 26 | res.status(401).json({message: msg.user_not_exist}) 27 | return next() 28 | } 29 | 30 | res.send(existingUser.sets) 31 | } 32 | 33 | export { getByUser } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Prathamesh Auti 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 | -------------------------------------------------------------------------------- /server/views/unblocked.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Graphical Auth System 10 | 11 | 12 |
13 | 14 |

Graphical Password Auth

15 |
16 | 17 |
18 |
19 |

Your account is now unblocked!

20 |
21 |
22 | 23 |

You can close this page.

24 | 25 | -------------------------------------------------------------------------------- /server/static/message.js: -------------------------------------------------------------------------------- 1 | const commons = { 2 | invalid_params: 'Invalid request params, please check format below.', 3 | token_failed: 'Internal server error, try again later.' 4 | } 5 | 6 | const login_messages = { 7 | format: ["username", "password", "pattern" ], 8 | db_user_failed: 'Error occured while logging in, please try again later.', 9 | user_not_exist: 'User does not exists.', 10 | db_pass_failed: 'Error occured while logging in, please try again later.', 11 | invalid_credentials: 'Invalid credentials given.', 12 | success: 'Logged in successfully.' 13 | } 14 | 15 | const signup_messages = { 16 | format: ["username", "email", "password", "pattern", "sets", "sequence"], 17 | db_user_failed: 'Error occured finding user on DB, please try again later.', 18 | user_already_exist: 'User already exists.', 19 | pass_hash_err: 'Error occured while hashing passwprd, please try again later.', 20 | db_save_err: 'Error occured while saving into db, please try again later.', 21 | } 22 | 23 | const validation_messages = { 24 | search_err: 'Error occured while searching, please try again later.' 25 | } 26 | 27 | export { login_messages, signup_messages, commons, validation_messages } 28 | -------------------------------------------------------------------------------- /server/controllers/digest_controller.js: -------------------------------------------------------------------------------- 1 | import { digestModel } from "../models/digest.js" 2 | import { commons } from "../static/message.js" 3 | 4 | const digest = async (req, res, next) => { 5 | var currentEmail 6 | var newEmail 7 | const { email } = req.body 8 | 9 | if (typeof email === 'undefined') { 10 | res.status(406).json({ 11 | message: commons.invalid_params, 12 | format: "email" 13 | }) 14 | return next() 15 | } 16 | 17 | try {currentEmail = await digestModel.findOne({email: email})} 18 | catch(err) { 19 | console.log(err) 20 | res.status(500).json({message: "Error occured, try again later."}) 21 | return next() 22 | } 23 | 24 | if (currentEmail) { 25 | res.status(500).json({message: "Already subscribed."}) 26 | return next() 27 | } 28 | 29 | newEmail = new digestModel({email}) 30 | 31 | try{newEmail.save()} 32 | catch(err) { 33 | console.log(err) 34 | res.status(500).json({message: "Error occured, try again later."}) 35 | return next() 36 | } 37 | 38 | res.status(200).json({message: "Subscribed"}) 39 | 40 | } 41 | 42 | export {digest as digestController} -------------------------------------------------------------------------------- /server/controllers/validation.js: -------------------------------------------------------------------------------- 1 | import { usertModel as User } from '../models/user.js' 2 | import { commons, validation_messages as msg } from '../static/message.js'; 3 | 4 | const check = async (req, res, next) => { 5 | let user; 6 | var {username, email} = req.query 7 | 8 | if (typeof username === 'undefined' && typeof email === 'undefined') { 9 | res.status(500).json({ 10 | message: commons.invalid_params, 11 | format: "username or email" 12 | }) 13 | return next() 14 | } 15 | 16 | if (typeof email === 'undefined') { 17 | username = username.toLowerCase() 18 | try { user = await User.findOne({username: username}) } 19 | catch (err) { res.status(400).json({message: msg.search_err}) } 20 | if (user) res.status(200).json({exists: true}) 21 | else res.status(200).json({exists: false}) 22 | } 23 | else if (typeof username === 'undefined') { 24 | try { user = await User.findOne({email: email}) } 25 | catch (err) { res.status(400).json({message: msg.search_err}) } 26 | if (user) res.status(200).json({exists: true}) 27 | else res.status(200).json({exists: false}) 28 | } 29 | } 30 | 31 | export { check } -------------------------------------------------------------------------------- /server/controllers/image_search.js: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid'; 2 | import { commons } from '../static/message.js'; 3 | import { shuffleArray, unsplash } from '../util/util.js'; 4 | 5 | const search = async (req, res, next) => { 6 | 7 | const { keyword } = req.query 8 | const pages = 3 9 | const images = [] 10 | const splitArrays = [] 11 | 12 | if (typeof keyword === 'undefined') { 13 | res.status(500).json({ 14 | message: commons.invalid_params, 15 | format: "keyword" 16 | }) 17 | return next() 18 | } 19 | 20 | for(let i=0; i { 28 | images.push({ 29 | id: nanoid(), 30 | url: each.urls.small 31 | }) 32 | }) 33 | } 34 | 35 | shuffleArray(images) 36 | 37 | for(let i=0; i<64; i+=16) { 38 | splitArrays.push(images.slice(i, i+16)) 39 | } 40 | 41 | res.send(splitArrays) 42 | } 43 | 44 | export { search } -------------------------------------------------------------------------------- /server/controllers/verify_token.js: -------------------------------------------------------------------------------- 1 | import { usertModel } from '../models/user.js'; 2 | import { userAttemptsModel } from '../models/user_attempts.js'; 3 | import { commons, validation_messages as msg } from '../static/message.js'; 4 | import * as path from 'path' 5 | 6 | const verify = async (req, res, next) => { 7 | 8 | const {email, token} = req.query 9 | let user 10 | 11 | if (typeof token === 'undefined' && typeof email === 'undefined') { 12 | res.status(500).json({ 13 | message: commons.invalid_params, 14 | format: "token, email" 15 | }) 16 | return next() 17 | } 18 | 19 | try { user = await usertModel.findOne({email: email}) } 20 | catch (err) { res.status(400).json({message: msg.search_err}); return next() } 21 | if (!user) {res.status(500).json({message: "User does not exists."}); return next()} 22 | 23 | const currentUser = await userAttemptsModel.findOne({email: email}) 24 | const storedToken = currentUser.token 25 | 26 | if (storedToken === token) { 27 | await userAttemptsModel.findOneAndUpdate({email: email}, {attempts: 0, token: ""}).catch(err => console.log(err)) 28 | res.sendFile(path.resolve() + '/views/unblocked.html') 29 | } 30 | else { 31 | res.send("Account is not blocked.") 32 | } 33 | 34 | } 35 | 36 | export {verify} -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Graphical Password Authentication Client 2 | 3 | Graphical Password Authentication Client is a React-based web application that enables users to create and authenticate with image passwords in combination with alphanumeric passwords. This project is built with ReactJS and styled with Tailwind CSS. The client makes API calls to the server using Axios. 4 | 5 | ## Installation 6 | To install this project, follow these steps: 7 | 8 |

1. Clone the repository to your local machine.

9 | 10 | ```bash 11 | git clone https://github.com/prathamesh-a/graphical-password-authentication.git 12 | ``` 13 | 14 |

2. Navigate to the client folder inside the root project location.

15 | 16 | ```bash 17 | cd graphical-password-authentication/client 18 | ``` 19 | 20 |

3. Run npm install to install the necessary dependencies.

21 | 22 | ```bash 23 | npm install 24 | ``` 25 | 26 | ## Usage 27 | To use the client, follow these steps: 28 | 29 |

1. Start the client by running npm start.

30 | 31 | ```sql 32 | npm start 33 | ``` 34 |

2. Open your web browser and navigate to http://localhost:3000/ to access the client.

35 |

3. Use the client to register a new account, log in with an existing account, and create an image password by selecting a set of images from the pre-defined set.

36 | -------------------------------------------------------------------------------- /SCREENSHOTS.md: -------------------------------------------------------------------------------- 1 | # Screenshots 2 | 3 | ### Landing Page 4 | ![image](https://user-images.githubusercontent.com/89336149/225679272-eeee628c-37f7-4a24-a999-e4388d9c82a5.png) 5 | 6 | ### Registration 7 | 8 | ![image](https://user-images.githubusercontent.com/89336149/225679685-d1a48458-09f5-4c40-a710-647d2734f0ff.png) 9 | ![image](https://user-images.githubusercontent.com/89336149/225680422-bf30a489-614f-4b81-a3eb-00e192b42380.png) 10 | 11 | ### Login 12 | 13 | ![image](https://user-images.githubusercontent.com/89336149/225681087-caefcf89-b558-4815-82be-a62fd9b7afad.png) 14 | ![image](https://user-images.githubusercontent.com/89336149/225681304-c98d74b1-431e-41ed-8837-ddf715f4daf9.png) 15 | 16 | ### Authentication Success 17 | ![image](https://user-images.githubusercontent.com/89336149/225681516-5d13b7e8-332b-48bc-a9e9-9b6151f06442.png) 18 | 19 | ### Authentication Fail 20 | ![image](https://user-images.githubusercontent.com/89336149/225682076-8c227151-c50c-4b54-8e33-548ba8dad548.png) 21 | 22 | ### Account Blocked 23 | ![image](https://user-images.githubusercontent.com/89336149/225682485-3eec611f-6deb-4aea-bcc9-cb323041eed8.png) 24 | 25 | ### Notification Email 26 | ![image](https://user-images.githubusercontent.com/89336149/225683001-87a9e730-fbbc-42b1-b799-c513691f8cbd.png) 27 | 28 | ### Account Unblock 29 | ![image](https://user-images.githubusercontent.com/89336149/225683133-aa210021-56e6-4722-aba8-6abb81adcd42.png) 30 | 31 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # Graphical Password Authentication Server 2 | 3 | Graphical Password Authentication Server is a Node.js-based web application that provides API endpoints for the client to create and authenticate with image passwords in combination with alphanumeric passwords. This project is built with Express.js and uses MongoDB for database storage. 4 | 5 | ## Installation 6 | To install this project, follow these steps: 7 | 8 |

1. Clone the repository to your local machine.

9 | 10 | ```bash 11 | git clone https://github.com/prathamesh-a/graphical-password-authentication.git 12 | ``` 13 | 14 |

2. Navigate to the server folder inside the root project location.

15 | 16 | ```bash 17 | cd graphical-password-authentication/server 18 | ``` 19 | 20 |

3. Run npm install to install the necessary dependencies.

21 | 22 | ```bash 23 | npm install 24 | ``` 25 | 26 | ## Usage 27 | To use the server, follow these steps: 28 | 29 |

1. Make sure to specify the options in the .env file.

30 | 31 |

2. Start the server by running npm start.

32 | 33 | ```sql 34 | npm start 35 | ``` 36 | 37 |

3. The server will start running on port 8080.

38 |

4. Use the client to register a new account, log in with an existing account, and create an image password by selecting a set of images from the pre-defined set.

39 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphical_pass_auth", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@fortawesome/fontawesome-svg-core": "^6.2.1", 7 | "@fortawesome/free-regular-svg-icons": "^6.2.1", 8 | "@fortawesome/free-solid-svg-icons": "^6.2.1", 9 | "@fortawesome/react-fontawesome": "^0.2.0", 10 | "@testing-library/jest-dom": "^5.16.5", 11 | "@testing-library/react": "^13.4.0", 12 | "@testing-library/user-event": "^13.5.0", 13 | "@vercel/analytics": "^0.1.11", 14 | "axios": "^1.2.2", 15 | "body-scroll-lock": "^4.0.0-beta.0", 16 | "crypto-js": "^4.1.1", 17 | "framer-motion": "^10.2.1", 18 | "nanoid": "^4.0.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-loader-spinner": "^5.3.4", 22 | "react-scripts": "5.0.1", 23 | "react-switch": "^7.0.0", 24 | "react-toastify": "^9.1.1", 25 | "simple-crypto-js": "^3.0.1", 26 | "unsplash-js": "^7.0.15", 27 | "validator": "^13.7.0", 28 | "web-vitals": "^2.1.4" 29 | }, 30 | "scripts": { 31 | "start": "react-scripts start", 32 | "build": "react-scripts build", 33 | "test": "react-scripts test", 34 | "eject": "react-scripts eject" 35 | }, 36 | "eslintConfig": { 37 | "extends": [ 38 | "react-app", 39 | "react-app/jest" 40 | ] 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | }, 54 | "devDependencies": { 55 | "tailwindcss": "^3.2.4" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client/src/static/icons_data.js: -------------------------------------------------------------------------------- 1 | export var icons = 2 | [ 3 | { 4 | "id": "hSESTXkWdZ6P", 5 | "name": "goku", 6 | "url": "https://img.icons8.com/stickers/100/null/son-goku.png", 7 | "selected": false 8 | }, 9 | { 10 | "id": "aoVDoLI_jNe5", 11 | "name": "rick", 12 | "url": "https://img.icons8.com/stickers/100/null/rick-sanchez.png", 13 | "selected": false 14 | }, 15 | { 16 | "id": "wau71yfDiopJ", 17 | "name": "popeye", 18 | "url": "https://img.icons8.com/stickers/100/null/popeye.png", 19 | "selected": false 20 | }, 21 | { 22 | "id": "2Aj56b5ynwJ5", 23 | "name": "ninjaturtle", 24 | "url": "https://img.icons8.com/stickers/100/null/ninja-turtle.png", 25 | "selected": false 26 | }, 27 | { 28 | "id": "tni_yees8A-W", 29 | "name": "mario", 30 | "url": "https://img.icons8.com/stickers/100/null/super-mario.png", 31 | "selected": false 32 | }, 33 | { 34 | "id": "cECvyZW_9kSe", 35 | "name": "hulk", 36 | "url": "https://img.icons8.com/stickers/100/null/hulk.png", 37 | "selected": false 38 | }, 39 | { 40 | "id": "gKgo-53BO68B", 41 | "name": "naruto", 42 | "url": "https://img.icons8.com/stickers/100/null/naruto.png", 43 | "selected": false 44 | }, 45 | { 46 | "id": "wqYlm6RuSl3Q", 47 | "name": "vader", 48 | "url": "https://img.icons8.com/stickers/100/null/darth-vader.png", 49 | "selected": false 50 | }, 51 | { 52 | "id": "0bKJGNTVgolu", 53 | "name": "avatar", 54 | "url": "https://img.icons8.com/stickers/100/null/avatar.png", 55 | "selected": false 56 | }, 57 | ] -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import Navbar from "./components/Navbar"; 2 | import {useState} from "react"; 3 | import {Page} from "./util/config"; 4 | import Home from "./components/Landing"; 5 | import Signup from "./components/Signup"; 6 | import Footer from "./components/Footer"; 7 | import Login from "./components/Login"; 8 | import Loader from "./components/Items/Loader"; 9 | import Contact from "./components/Contact"; 10 | import Slider from "./components/Slider"; 11 | 12 | function App() { 13 | 14 | const [page, setPage] = useState("home") 15 | const [loading, setLoading] = useState(false) 16 | const [loggedIn, setLoggedIn] = useState(false) 17 | const [slider, setSlider] = useState(false) 18 | const [userInfo, setUserInfo] = useState({ 19 | username: "", 20 | email: "" 21 | }) 22 | 23 | function getCurrentPage() { 24 | switch (page) { 25 | case Page.CONTACT: 26 | return 27 | case Page.LOGIN_PAGE: 28 | return 29 | case Page.SIGNUP_PAGE: 30 | return 31 | case Page.HOME_PAGE: 32 | default: 33 | return 34 | } 35 | } 36 | 37 | return ( 38 |
39 | { loading && } 40 |
41 | {slider && } 42 | 43 | {getCurrentPage()} 44 |
45 |
46 |
47 | 48 | ); 49 | } 50 | export default App; -------------------------------------------------------------------------------- /server/util/util.js: -------------------------------------------------------------------------------- 1 | //import * as dotenv from 'dotenv' 2 | import fetch from "node-fetch"; 3 | import { createApi } from "unsplash-js"; 4 | import { userAttemptsModel } from "../models/user_attempts.js"; 5 | import { transporter } from "./nodemailer.js"; 6 | 7 | //dotenv.config() 8 | 9 | function checkArray(arr1, arr2, sequence) { 10 | if (arr1.length != arr2.length) return false; 11 | var gflag = false; 12 | if (sequence){ 13 | for(let i=0; i 0; i--) { 40 | var j = Math.floor(Math.random() * (i + 1)); 41 | var temp = array[i]; 42 | array[i] = array[j]; 43 | array[j] = temp; 44 | } 45 | } 46 | 47 | async function sendEmail(email) { 48 | const currentUser = await userAttemptsModel.findOne({email: email}) 49 | const mailOptions = { 50 | from: "graphicalpassauth@gmail.com", 51 | to: email, 52 | subject: "GPA | Account Blocked", 53 | html: `
54 |

Your account has been blocked for multiple attempts of login with invalid credentials.

55 |

Click the link below to unblock:

56 | Unblock 57 |
` 58 | } 59 | console.log("Sending email to " + email) 60 | transporter.sendMail(mailOptions, function(err, info) { 61 | if (err) console.log(err) 62 | else console.log("Email sent to " + email) 63 | }) 64 | } 65 | 66 | export {checkArray, unsplash, shuffleArray, sendEmail} -------------------------------------------------------------------------------- /client/src/components/Digest.js: -------------------------------------------------------------------------------- 1 | import {useState} from "react"; 2 | import validator from "validator/es"; 3 | import {successToast, Toast} from "../util/toast"; 4 | import axios from "axios"; 5 | import {api} from "../static/config"; 6 | 7 | export default function Digest() { 8 | 9 | const[email, setEMail] = useState("") 10 | 11 | function handleChange(event) { 12 | setEMail(event.target.value) 13 | } 14 | 15 | function handleSubmit() { 16 | if (validator.isEmail(email)) { 17 | axios.post(`${api.url}/api/digest`, {email: email}) 18 | .then(() => { 19 | successToast("Thank You For Subscribing!") 20 | clearData() 21 | }) 22 | .catch(err => { 23 | Toast(err.response.data.message) 24 | clearData() 25 | }) 26 | } 27 | else Toast("Invalid Email") 28 | 29 | } 30 | 31 | function clearData() { 32 | setEMail("") 33 | } 34 | 35 | return ( 36 |
37 |
38 | 39 |
40 | 41 |
42 |

Join Our Monthly Digest

43 |

Get Exclusive Promotions & Updates Staight To Your Box

44 |
45 | 46 | 49 |
50 |
51 |
52 | ) 53 | } -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | // import * as dotenv from 'dotenv' 2 | // dotenv.config() 3 | import bodyParser from 'body-parser' 4 | import express from 'express' 5 | import cors from 'cors' 6 | import mongoose from 'mongoose' 7 | import swaggerUi from 'swagger-ui-express' 8 | import fs from 'fs/promises' 9 | import { VerifyRoute } from './routes/verify.js' 10 | import { DigestRoutes } from './routes/digest.js' 11 | import { router as contactRoutes } from './routes/contact.js' 12 | import { router as imageRoutes } from './routes/image.js' 13 | import { router as userRoutes } from './routes/users.js' 14 | 15 | console.log(process.env) 16 | 17 | const app = express() 18 | const swaggerDocument = JSON.parse( 19 | await fs.readFile( 20 | new URL('./swagger.json', import.meta.url) 21 | ) 22 | ) 23 | 24 | app.use(cors()) 25 | app.use(bodyParser.json()) 26 | 27 | app.use('/api/verify', VerifyRoute) 28 | app.use('/api/user/', userRoutes) 29 | app.use('/api/image/', imageRoutes) 30 | app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)) 31 | app.use('/api/contact', contactRoutes) 32 | app.use('/api/digest', DigestRoutes) 33 | 34 | mongoose.set('strictQuery', true) 35 | mongoose 36 | .connect(`mongodb+srv://${process.env.DB_USERNAME}:${process.env.DB_PASSWORD}@${process.env.DB_NAME}.ajnurbv.mongodb.net/?retryWrites=true&w=majority`) 37 | .then(() => { 38 | app.listen(process.env.PORT) 39 | console.log("Server running...") 40 | }) 41 | .catch(err => console.log(err)) 42 | 43 | 44 | // const currentAttempts = await userAttemptsModel.findOne({email: "test@gmail.com"}) 45 | // userAttemptsModel.findOneAndUpdate({email: "test@gmail.com", attempts: currentAttempts.attempts+1}).then(res => console.log(res)).catch(err => console.log(err)) 46 | 47 | // await usertModel.findOne({username: "test"}) 48 | 49 | // const testAttempts = new userAttemptsModel({ 50 | // username: "test2", 51 | // email: "test2@gmail.com", 52 | // attempts: 0 53 | // }) 54 | 55 | //testAttempts.save().then(res => console.log(res)).catch(err => console.log(err)) 56 | 57 | // transporter.sendMail(mailOptions, function(err, info) { 58 | // if (err) console.log(err) 59 | // else console.log("Email Sent: " + info.response) 60 | // }) 61 | 62 | // const result = unsplash.search.getPhotos({ 63 | // query: 'cats', 64 | // perPage: 64, 65 | // orientation: 'squarish' 66 | // }).then(result => console.log(result.response.results)) -------------------------------------------------------------------------------- /server/controllers/users_signup.js: -------------------------------------------------------------------------------- 1 | //import * as dotenv from 'dotenv' 2 | import { usertModel as User } from '../models/user.js' 3 | import bcrypt from "bcryptjs" 4 | import { commons, signup_messages as msg } from '../static/message.js' 5 | import jwt from 'jsonwebtoken' 6 | import { userAttemptsModel } from '../models/user_attempts.js' 7 | 8 | //dotenv.config() 9 | 10 | const signup = async (req, res, next) => { 11 | 12 | let token 13 | let existingUser 14 | let hashedPassword 15 | var { username, email, password, pattern, sets, sequence} = req.body 16 | username = username.toLowerCase() 17 | 18 | if (typeof sets === 'undefined' || typeof username === 'undefined' || typeof email === 'undefined' || typeof password === 'undefined' || typeof pattern === 'undefined') { 19 | res.status(406).json({ 20 | message: commons.invalid_params, 21 | format: msg.format 22 | }) 23 | return 24 | } 25 | 26 | try { existingUser = await User.findOne({email: email}) } 27 | catch(err) { 28 | res.status(500).json({message: msg.db_user_failed}) 29 | return next() 30 | } 31 | 32 | if (existingUser) { 33 | res.status(500).json({message: msg.user_already_exist}) 34 | return next() 35 | } 36 | 37 | try { hashedPassword = await bcrypt.hash(password, 12) } 38 | catch(err) { 39 | res.status(500).json({message: msg.pass_hash_err}) 40 | return next() 41 | } 42 | 43 | const createdUser = new User({ 44 | username, email, password: hashedPassword, sets, pattern, sequence:false 45 | }) 46 | 47 | const attempts = new userAttemptsModel({ 48 | username, email, attempts: 0 49 | }) 50 | 51 | try { await createdUser.save() } 52 | catch (err) { 53 | console.log(err) 54 | res.status(500).json({message: msg.db_save_err}) 55 | return next() 56 | } 57 | 58 | try { await attempts.save() } 59 | catch (err) { 60 | console.log(err) 61 | res.status(500).json({message: msg.db_save_err}) 62 | return next() 63 | } 64 | 65 | try { token = jwt.sign({userId: createdUser.id, email: createdUser.email}, process.env.TOKEN_KEY) } 66 | catch (err) { 67 | res.status(500).json({message: commons.token_failed}) 68 | return next() 69 | } 70 | 71 | res.status(200).json({ username: createdUser.username, userId: createdUser.id, email: createdUser.email, token: token }) 72 | } 73 | 74 | export {signup as signupController} -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 30 | 31 | 32 | 33 | 34 | Graphical Password Auth 35 | 36 | 37 | 38 |
39 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /client/src/components/Slider.js: -------------------------------------------------------------------------------- 1 | import {Page} from "../util/config"; 2 | import {motion} from "framer-motion"; 3 | 4 | export default function Slider(props) { 5 | 6 | const additionalClasses = "text-[#A259FF]" 7 | 8 | function closeSlider() { 9 | props.setSlider(false) 10 | } 11 | 12 | function setPage(page) { 13 | props.setPage(page) 14 | closeSlider() 15 | } 16 | 17 | function logout() { 18 | props.setUserInfo({username: "", email: ""}) 19 | props.setLoggedIn(false) 20 | setPage(Page.HOME_PAGE) 21 | } 22 | 23 | return ( 24 |
25 | 26 | 27 | 28 |
29 | 30 |
31 | 32 | {!props.loggedIn &&
33 | 34 | 35 |
} 36 | 37 | {props.loggedIn &&
38 |

{props.userInfo.username}

39 | 40 |
} 41 | 42 |
43 |

setPage(Page.HOME_PAGE)} className={`mb-6 ${props.currentPage === Page.HOME_PAGE ? additionalClasses : ""}`}>Home

44 |

setPage(Page.CONTACT)} className={`${props.currentPage === Page.CONTACT ? additionalClasses : ""}`}>Contact

45 |
46 |
47 |
48 | ) 49 | } -------------------------------------------------------------------------------- /client/src/components/Navbar.js: -------------------------------------------------------------------------------- 1 | import {Page} from "../util/config"; 2 | 3 | export default function Navbar(props) { 4 | 5 | const additionalClasses = "text-[#A259FF]" 6 | 7 | function setPage(property) { 8 | props.setPage(property) 9 | } 10 | 11 | function logout() { 12 | props.setUserInfo({username: "", email: ""}) 13 | props.setLoggedIn(false) 14 | setPage(Page.HOME_PAGE) 15 | } 16 | 17 | return ( 18 |
19 | 20 | {/*logo and text*/} 21 |
window.location.reload()}> 22 | 23 |

Graphical Password Auth

24 |
25 | 26 | {/*nav element list*/} 27 |
28 |

setPage(Page.HOME_PAGE)}>Home

29 | {/*

About Us

*/} 30 |

setPage(Page.CONTACT)}>Contact

31 | 32 | {!props.loggedIn &&
33 | 34 | 35 |
} 36 | 37 | {props.loggedIn &&
38 |

{props.userInfo.username}

39 | 40 |
} 41 |
42 | 43 |
44 | props.setSlider(true)} className="ml-2" width="32px" src="https://img.icons8.com/fluency-systems-regular/48/A259FF/menu--v1.png" alt=""/> 45 |
46 | 47 |
48 | ) 49 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Graphical Password Authentication

2 | 3 |
4 | 5 | 6 | 7 | 8 |

9 | 10 | 11 | Graphical Password Authentication is a GitHub project that provides an additional layer of security to alphanumeric passwords by using images as passwords. With this project, users can create a unique and personalized image password by selecting images from a pre-defined set. This password can then be used in combination with a traditional alphanumeric password for enhanced security. This project is ideal for applications where password protection is critical, such as online banking, e-commerce, or social media. By providing an additional layer of authentication, this project can significantly reduce the risk of unauthorized access to sensitive information. This project uses the MERN stack (MongoDB, Express, React, Node.js) to build a server and a client for the application. 12 | 13 | ## Screenshots 14 | To view screenshots go to SCREENSHOTS. 15 | 16 | ## Installation 17 | To install this project, follow these steps:
18 | 19 |

1. Clone the repository to your local machine.

20 | 21 | ```bash 22 | git clone https://github.com/prathamesh-a/graphical-password-authentication.git 23 | ``` 24 | 25 |

2. Navigate to the root folder of the project.

26 | 27 | ```bash 28 | git clone https://github.com/prathamesh-a/graphical-password-authentication.git 29 | ``` 30 | 31 |

3. Installation of Server

32 |

4. Installation of Client

33 | 34 | ## Usage 35 | To use the application, follow these steps: 36 | 37 | 1. Setup Server 38 | 2. Setup Client 39 | 40 | ## Contributing 41 | If you would like to contribute to this project, please follow these steps: 42 | 43 | 1. Fork the repository to your own account. 44 | 2. Create a new branch from the **`develop`** branch. 45 | 3. Make your changes and test them thoroughly. 46 | 4. Submit a pull request to the **`develop`** branch. 47 | 48 | ## License 49 | This project is licensed under the MIT License. See the LICENSE file for details. 50 | -------------------------------------------------------------------------------- /server/controllers/users_login.js: -------------------------------------------------------------------------------- 1 | //import * as dotenv from 'dotenv' 2 | import { usertModel as User } from '../models/user.js' 3 | import bcrypt from "bcryptjs" 4 | import { login_messages as msg, commons} from '../static/message.js' 5 | import jwt from 'jsonwebtoken' 6 | import { checkArray, sendEmail } from '../util/util.js' 7 | import { userAttemptsModel } from '../models/user_attempts.js' 8 | import { nanoid } from 'nanoid' 9 | 10 | 11 | const login = async (req, res, next) => { 12 | 13 | //dotenv.config() 14 | 15 | let token 16 | let existingUser 17 | let isValidPassword = false 18 | var isValidPattern = false 19 | var { username, password, pattern } = req.body 20 | username = username.toLowerCase() 21 | 22 | if (typeof username === 'undefined' || typeof password === 'undefined' || typeof pattern === 'undefined') { 23 | res.status(406).json({ 24 | message: commons.invalid_params, 25 | format: msg.format 26 | }) 27 | return next() 28 | } 29 | 30 | try { existingUser = await User.findOne({username: username}) } 31 | catch(err) { 32 | res.status(401).json({message: msg.db_user_failed}) 33 | return next() 34 | } 35 | 36 | if (!existingUser) { 37 | res.status(401).json({message: msg.user_not_exist}) 38 | return next() 39 | } 40 | 41 | const currentAttempts = await userAttemptsModel.findOne({username: username}) 42 | 43 | if (currentAttempts.attempts > process.env.MAX_ATTEMPTS) { 44 | res.status(500).json({status: "blocked", message: "Your account has been blocked, please check email."}) 45 | return next() 46 | } 47 | 48 | try { isValidPassword = await bcrypt.compare(password, existingUser.password) } 49 | catch(err) { 50 | console.log(err) 51 | res.status(500).json({message: msg.db_pass_failed}) 52 | return next() 53 | } 54 | 55 | isValidPattern = checkArray(existingUser.pattern, pattern, true) 56 | 57 | if (!isValidPassword || !isValidPattern) { 58 | if (currentAttempts.attempts === Number(process.env.MAX_ATTEMPTS)) { 59 | await userAttemptsModel.findOneAndUpdate({username: username}, {attempts: currentAttempts.attempts+1, token: nanoid(32)}).catch(err => console.log(err)) 60 | //console.log("sending email entered") 61 | sendEmail(currentAttempts.email) 62 | } 63 | await userAttemptsModel.findOneAndUpdate({username: username}, {attempts: currentAttempts.attempts+1}).catch(err => console.log(err)) 64 | res.status(500).json({message: msg.invalid_credentials}) 65 | return next() 66 | } 67 | 68 | try { token = jwt.sign({userId: existingUser.id, email: existingUser.email}, process.env.TOKEN_KEY) } 69 | catch (err) { 70 | console.log(err) 71 | res.status(500).json({message: commons.token_failed}) 72 | return next() 73 | } 74 | await userAttemptsModel.findOneAndUpdate({username: username}, {attempts: 0}).catch(err => console.log(err)) 75 | res.status(200).json({username: existingUser.username, userId: existingUser.id, email: existingUser.email, token: token}) 76 | } 77 | 78 | export {login as loginController} -------------------------------------------------------------------------------- /client/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import {Page} from "../util/config"; 2 | import {useState} from "react"; 3 | import validator from "validator/es"; 4 | import {successToast, Toast} from "../util/toast"; 5 | import axios from "axios"; 6 | import {api} from "../static/config"; 7 | 8 | export default function Footer(props) { 9 | 10 | const[email, setEMail] = useState("") 11 | 12 | function handleChange(event) { 13 | setEMail(event.target.value) 14 | } 15 | 16 | function handleSubmit() { 17 | if (validator.isEmail(email)) { 18 | axios.post(`${api.url}/api/digest`, {email: email}) 19 | .then(() => { 20 | successToast("Thank You For Subscribing!") 21 | clearData() 22 | }) 23 | .catch(err => { 24 | Toast(err.response.data.message) 25 | clearData() 26 | }) 27 | } 28 | else Toast("Invalid Email") 29 | } 30 | 31 | function clearData() { 32 | setEMail("") 33 | } 34 | 35 | return ( 36 |
37 |
38 | 39 |
40 |
41 | 42 |

Graphical Password Auth

43 |
44 |

A Novel Approach For Security

45 |
46 | 47 |
48 |

Explore

49 |

props.setPage(Page.ABOUT)} className="text-gray-300 font-['Work_Sans'] mt-2 sm:mt-4 cursor-pointer">About Us

50 |

props.setPage(Page.CONTACT)} className="text-gray-300 font-['Work_Sans'] cursor-pointer">Contact

51 |
52 | 53 |
54 |

Join Our Monthly Digest

55 |

Get Exclusive Promotions & Updates.

56 | 57 |
58 | 59 | 62 |
63 |
64 |
65 |
66 |

github.com/prathamesh-a

67 |
68 | 69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /client/src/components/Contact.js: -------------------------------------------------------------------------------- 1 | import {useState} from "react"; 2 | import validator from "validator/es"; 3 | import {successToast, Toast} from "../util/toast"; 4 | import axios from "axios"; 5 | import {api} from "../static/config"; 6 | 7 | export default function Contact(props) { 8 | 9 | const [data, setData] = useState({ 10 | name: "", 11 | email: "", 12 | message: "" 13 | }) 14 | 15 | function handleChange(event) { 16 | setData(prev => { 17 | return { 18 | ...prev, 19 | [event.target.name]: event.target.value 20 | } 21 | }) 22 | } 23 | 24 | function handleSubmit() { 25 | if (!validateData()) return 26 | props.setLoading(true) 27 | axios.post(`${api.url}/api/contact`, data) 28 | .then(res => { 29 | props.setLoading(false) 30 | successToast("Message Sent") 31 | clearData() 32 | }) 33 | .catch(err => Toast(err.response.data.message)) 34 | } 35 | 36 | function clearData() { 37 | setData({ 38 | name: "", 39 | email: "", 40 | message: "" 41 | }) 42 | } 43 | 44 | function validateData() { 45 | if (data.name.length < 3) { 46 | Toast("Invalid Name") 47 | return false 48 | } 49 | if (!validator.isEmail(data.email)) { 50 | Toast("Invalid Email") 51 | return false 52 | } 53 | if (data.message.length < 3) { 54 | Toast("Enter a valid message") 55 | return false 56 | } 57 | return true 58 | } 59 | 60 | return ( 61 |
62 | 63 |
64 |

Connect With Us

65 |

We would love to respond to your queries.

66 |

Feel free to get in touch with us.

67 |
68 |
69 |

Full Name

70 | 71 |
72 |
73 |

Email

74 | 75 |
76 |
77 |

Message

78 |