From 72f381232bcb6e4e5e0d93e3b7ac019f8c8eece0 Mon Sep 17 00:00:00 2001 From: thecookingsenpai Date: Mon, 25 Dec 2023 13:28:38 +0100 Subject: [PATCH] Initial commit --- .gitignore | 8 ++ README.md | 16 ++++ client/auth_client.js | 46 ++++++++++ client/index.html | 25 ++++++ client/style.css | 0 server/auth.js | 189 ++++++++++++++++++++++++++++++++++++++++++ server/package.json | 17 ++++ 7 files changed, 301 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 client/auth_client.js create mode 100644 client/index.html create mode 100644 client/style.css create mode 100644 server/auth.js create mode 100644 server/package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f0127f --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ + +.DS_Store + +server/node_modules/.bin/ + +server/node_modules/ + +server/package-lock.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..73079e6 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# OCSA + +![image](https://user-images.githubusercontent.com/67682496/210122012-9cee1a06-ca3d-43b1-bef3-20dc22835705.png) + +## A blockchain based gas free (aka without costs) authentication module based on data signing through Metamask enabled browsers + +This module is a work in progress and should be used with a grain of salt + +### Usage + +Host auth.js on a server and run it: + + npm install + node auth.js + +Use the code provided in client folder to build your app. diff --git a/client/auth_client.js b/client/auth_client.js new file mode 100644 index 0000000..ec39b25 --- /dev/null +++ b/client/auth_client.js @@ -0,0 +1,46 @@ +// INFO This library relies on ethers.js + +var provider; +var signer; +var address; + +var session_token_storage; + +const server = "http://193.187.129.116:9000" + +async function connect() { + provider = new ethers.providers.Web3Provider(window.ethereum, "any"); + // Prompt user for account connections + await provider.send("eth_requestAccounts", []); + signer = provider.getSigner(); + address = await signer.getAddress(); + console.log("Connected to: " + address); + return address +} + +async function login() { + // Fetching our public ip + var response = await fetch("https://api.ipify.org?format=json"); + var data = await response.json(); + var ip = data.ip; + // Get the message from the server + var HEADERS = new Headers ({"User-Agent": "Auther"}) + var response = await fetch(server + "/auth/hello/" + ip, {headers: HEADERS }); + var data = await response.json(); + var session_token = data.session_token; + console.log("Session token: " + session_token); + var message = data.message; + console.log("Message: " + message); + // Sign the message + var signature = await signer.signMessage(message); + // Send the signature to the server + var response = await fetch(server + "/auth/response/", { headers: HEADERS, method: "POST", body: JSON.stringify({ session_token: session_token, message: message, signature: signature, address: address, ip: ip }) }); + var data = await response.json(); + console.log(data); + session_token_storage = session_token; + if (data.verified) { + return "Authenticated" + } else { + return "NOPE" + } +} \ No newline at end of file diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..d448625 --- /dev/null +++ b/client/index.html @@ -0,0 +1,25 @@ + + + + + + Auth Client + + +

Auth Client

+ +

Waiting

+ + + \ No newline at end of file diff --git a/client/style.css b/client/style.css new file mode 100644 index 0000000..e69de29 diff --git a/server/auth.js b/server/auth.js new file mode 100644 index 0000000..84108da --- /dev/null +++ b/server/auth.js @@ -0,0 +1,189 @@ +// Require the framework and instantiate it +const fastify = require('fastify')({ logger: true }) +const ethers = require('ethers') +const { hashMessage } = require("@ethersproject/hash"); + +var timeout = 3 // In minutes +var sessions = {} + +// NOTE Verify signature method +async function verifySignature(message, signature, address) { + var derived = ethers.utils.recoverAddress(hashMessage(message),signature); + console.log("Derived: " + derived) + console.log("Address: " + address) + if (derived == address) { + return true; + } + return false; +} + +function generateUID() { + // I generate the UID from two parts here + // to ensure the random number provide enough bits. + var uid = ""; + for (var i = 0; i < 12; i++) { + var part = (Math.random() * 46656) | 0; + part = (part.toString(36)).slice(-3); + uid += part; + } + return uid; +} + +function getSessions() { + return sessions +} + +// Declare a route +fastify.get('/', async (request, reply) => { + // Allow cross origin requests + reply.header("Access-Control-Allow-Origin", "*"); + console.log('request', request) + return { hello: 'world' } +}) + +fastify.get("/auth/hello/:ip", async (request, reply) => { + // Allow cross origin requests + reply.header("Access-Control-Allow-Origin", "*"); + // Get the ip from the request + var derived_ip = request.raw.connection.remoteAddress + console.log("Derived IP: " + derived_ip) + var ip = request.params.ip + console.log(ip) + // Ip checking + if (derived_ip == "127.0.0.1" || derived_ip == "::ffff:" || derived_ip == '::1') { + console.log("Localhost") + } else { + console.log("Not localhost") + if (derived_ip != ip) { + console.log("IP mismatch") + return { error: "IP mismatch" } + } + } + // Create a uid + var session_token = generateUID() + // Generate a random message based on timestamp + var message = "Hello_" + Date.now() + "_" + session_token + "_" + Math.random() + message = message.replace(/[^a-z0-9áéíóúñü \.,_-]/gim,""); + message = message.trim() + // Store the message in the session overwriting any previous message + sessions[session_token.toString()] = { message: message, timeout: Date.now() + 1000 * 60 * timeout, ip: ip, verified: false } // x minutes timeout + console.log("Message: " + message + + " Timeout: " + sessions[session_token.toString()].timeout + + " Session token: " + session_token) + console.log(sessions) + return { message: message, session_token: session_token } +}) + +// Sign based authentication +fastify.post("/auth/response/", async (request, reply) => { + var local_sessions = getSessions() + console.log(local_sessions) + // Allow cross origin requests + reply.header("Access-Control-Allow-Origin", "*"); + // Parse the json + var data = request.body + data = JSON.parse(data) + console.log(data) + var session_token = data.session_token + console.log("Session token: " + session_token) + var message = data.message + console.log("Message: " + message) + var signature = data.signature + console.log("Signature: " + signature) + var address = data.address + console.log("Address: " + address) + var derived_ip = request.raw.connection.remoteAddress + console.log("Derived IP: " + derived_ip) + var ip = data.ip + console.log("IP: " + ip) + // Ip checking + if (derived_ip == "127.0.0.1" || derived_ip == "::ffff:" || derived_ip == '::1') { + console.log("Localhost") + } else { + console.log("Not localhost") + if (derived_ip != ip) { + console.log("IP mismatch") + return { error: "IP mismatch" } + } + } + // Check if the session_token is in the session + if (!(session_token in local_sessions)) { + return { verified: false, error: "No session found" } + } + // Check if the message is the same as the one we sent and clear the session + if (message != local_sessions[session_token].message) { + local_sessions[session_token] = null + return { verified: false, error: "Message does not match" } + } + // Check for timeout and clear the session + if (local_sessions[session_token].timeout < Date.now()) { + local_sessions[session_token] = null + return { verified: false, error: "Session timed out" } + } + // Ensure ip is the same + if (local_sessions[session_token].ip != ip) { + local_sessions[session_token] = null + return { verified: false, error: "IP does not match" } + } + // Verify signature + var verified = await verifySignature(message, signature, address) + console.log("Verified: " + verified) + local_sessions[session_token].verified = verified + return { verified: verified, error: null } +}) + +fastify.post("/check", async (request, reply) => { + // Allow cross origin requests + reply.header("Access-Control-Allow-Origin", "*"); + // Parse the json + var data = request.body + data = JSON.parse(data) + console.log(data) + var session_token = data.session_token + console.log("Session token: " + session_token) + var derived_ip = request.raw.connection.remoteAddress + console.log("Derived IP: " + derived_ip) + var ip = data.ip + console.log("IP: " + ip) + // Ip checking + if (derived_ip == "127.0.0.1" || derived_ip == "::ffff:" || derived_ip == '::1') { + console.log("Localhost") + } else { + console.log("Not localhost") + if (derived_ip != ip) { + console.log("IP mismatch") + return { error: "IP mismatch" } + } + } + // Check if the session_token is in the session + if (!(session_token in sessions)) { + return { authorized: false, error: "No session found" } + } + // Check for timeout and clear the session + if (sessions[session_token].timeout < Date.now()) { + sessions[session_token] = null + return { authorized: false, error: "Session timed out" } + } + // Ensure ip is the same + if (sessions[session_token].ip != ip) { + sessions[session_token] = null + return { authorized: false, error: "IP does not match" } + } + // Check if the session is verified + if (sessions[session_token].verified == false) { + return { authorized: false, error: "Session not verified" } + } + return { authorized: true, error: null, timeout: sessions[session_token].timeout} +}) + +const start = async () => { + + // Run the api server + try { + await fastify.listen({ host: "0.0.0.0", port: 9000 }) + } catch (err) { + fastify.log.error(err) + process.exit(1) + } +} +start() \ No newline at end of file diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..a0eaaac --- /dev/null +++ b/server/package.json @@ -0,0 +1,17 @@ +{ + "name": "web3auth_server", + "version": "1.0.0", + "description": "Server side web3 based authenticator", + "main": "auth.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "tcsenpai", + "license": "ISC", + "devDependencies": { + "@fastify/cors": "^8.2.0", + "ethers": "^5.7.2", + "fastify": "^4.10.2", + "fastify-cors": "^6.1.0" + } +}