diff --git a/index.js b/index.js index d267418..db3b0d9 100644 --- a/index.js +++ b/index.js @@ -1,66 +1,51 @@ -const express = require('express'); -const mongoose = require('mongoose'); -const cors = require('cors'); -const morgan = require('morgan'); +const express = require("express"); +const mongoose = require("mongoose"); +const cors = require("cors"); +const morgan = require("morgan"); -const appRoute = require('./routes/app.route.js'); +const appRoute = require("./routes/app.route.js"); -require('dotenv').config() +require("dotenv").config(); const PORT = process.env.PORT || 3000; const logger = (req, res, next) => { console.log( - '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", ); - console.log('----------BODY----------'); + console.log("----------BODY----------"); console.log(req.body); console.log(); - console.log('----------HEAD----------'); + console.log("----------HEAD----------"); console.log(req.headers); console.log( - '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", ); next(); -} +}; const app = express(); -app.enable('trust proxy'); -app.disable('x-powered-by'); -app.use(express.json({ limit: '64mb' } )); +app.enable("trust proxy"); +app.disable("x-powered-by"); +app.use(express.json({ limit: "64mb" })); app.use(cors()); app.use( morgan( - '[ :method :url ] ~:status | :date[web] | :total-time[digits] ms | IP :remote-addr | :user-agent' - ) + "[ :method :url ] ~:status | :date[web] | :total-time[digits] ms | IP :remote-addr | :user-agent", + ), ); app.use(logger, appRoute); -mongoose.connect(process.env.MONGODB_URI).then(() => { console.info('Database connected'); }); +mongoose.connect(process.env.MONGODB_URI).then(() => { + console.info("Database connected"); +}); -const verifyToken = async (req, res, next) => { - const authHeader = req.headers['authorization']; - const token = authHeader && authHeader.split(' ')[1]; - - if (token == null) return res.sendStatus(401); - - try { - const authRecord = await AuthToken.findOne({ auth_token: token }); - if (!authRecord) return res.sendStatus(403); - - req.address = authRecord.address; - req.authType = authRecord.type; - next(); - } catch (error) { - return res.sendStatus(403); - } -}; - -app.get('/api/hello', (req, res) => { +app.get("/api/hello", (req, res) => { res.status(200).json({ - message: 'Close the world, .txen eht nepO', - author: 'Yigid BALABAN ', - authorHomepage: 'https://fybx.dev/' - }) + message: "Close the world, .txen eht nepO", + author: "Yigid BALABAN ", + authorHomepage: "https://fybx.dev/", + thanks: "to Abdullah VELISOY, login.xyz, my family", + }); }); app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); diff --git a/middlewares/auth.middleware.js b/middlewares/auth.middleware.js new file mode 100644 index 0000000..44ed6a3 --- /dev/null +++ b/middlewares/auth.middleware.js @@ -0,0 +1,40 @@ +const { AuthTokens } = require("../models/index.js"); + +const verifyToken = async (req, res, next) => { + const authHeader = req.headers["authorization"]; + + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return res.status(401).json({ error: "Unauthorized" }); + } + + const authToken = authHeader.split(" ")[1]; + + try { + const tokenRecord = await AuthTokens.findOne({ authToken }); + + if (!tokenRecord) { + return res.status(401).json({ error: "Unauthorized" }); + } + + req.tokenRecord = tokenRecord; + next(); + } catch (err) { + res.status(500).json({ error: "Internal server error" }); + } +}; + +const editProfile = async (req, res, next) => { + const { walletAddress } = req.body; + const { tokenRecord } = req; + + if (tokenRecord.walletAddress !== walletAddress) { + return res.status(403).json({ + error: + "Forbidden: You are not allowed to edit this account's information.", + }); + } + + next(); +}; + +module.exports = { verifyToken, editProfile }; diff --git a/models/index.js b/models/index.js index 28dd555..3e19cfa 100644 --- a/models/index.js +++ b/models/index.js @@ -1,29 +1,29 @@ -const mongoose = require('mongoose'); +const mongoose = require("mongoose"); const NonceSchema = new mongoose.Schema({ - address: String, + walletAddress: String, nonce: String, - type: String, - createdAt: { type: Date, expires: 300, default: Date.now } + networkType: String, + createdAt: { type: Date, expires: 300, default: Date.now }, }); const AuthTokenSchema = new mongoose.Schema({ - address: String, - auth_token: String, - type: String, - createdAt: { type: Date, expires: 3600, default: Date.now } + walletAddress: String, + authToken: String, + networkType: String, + createdAt: { type: Date, expires: 3600, default: Date.now }, }); const UserSchema = new mongoose.Schema({ - address: { type: String, required: true }, + walletAddress: { type: String, required: true }, networkType: { type: String, required: true }, - profileName: { type: String, required: true, default: 'Anonymous' }, - publicKey: { type: String, required: true, unique: true } + profileName: { type: String, required: true, default: "Anonymous" }, + publicKey: { type: String, required: true, unique: true }, }); -const Nonce = mongoose.model('Nonce', NonceSchema); -const AuthToken = mongoose.model('AuthToken', AuthTokenSchema); -const User = mongoose.model('User', UserSchema); -User.collection.createIndex({ publicKey: 1 }, { unique: true }); +const Nonces = mongoose.model("Nonce", NonceSchema); +const AuthTokens = mongoose.model("AuthToken", AuthTokenSchema); +const Users = mongoose.model("User", UserSchema); +Users.collection.createIndex({ publicKey: 1 }, { unique: true }); -module.exports = { Nonce, AuthToken, User }; +module.exports = { Nonces, AuthTokens, Users }; diff --git a/routes/auth.route.js b/routes/auth.route.js index fb827ba..3711f02 100644 --- a/routes/auth.route.js +++ b/routes/auth.route.js @@ -1,100 +1,113 @@ -const express = require('express'); +const express = require("express"); const router = express.Router({ mergeParams: true }); -const siwe = require('siwe'); -const bs58 = require('bs58'); -const nacl = require('tweetnacl'); +const siwe = require("siwe"); +const bs58 = require("bs58"); +const nacl = require("tweetnacl"); -const { Nonce, AuthToken } = require('../models/index.js'); -const { generateNonce, generateBearerToken } = require('../utils/index.js'); +const { verifyToken } = require("../middlewares/auth.middleware.js"); +const { Nonces, AuthTokens } = require("../models/index.js"); +const { generateNonce, generateBearerToken } = require("../utils/index.js"); -router.get('/nonce', async (req, res) => { - const { address, type } = req.query; +router.get("/nonce", async (req, res) => { + const { address: walletAddress, type: networkType } = req.query; - if (!address || !type) { - return res.status(400).json({ error: 'Missing address or type' }); + if (!walletAddress || !networkType) { + return res + .status(400) + .json({ error: "Missing walletAddress or networkType" }); } - if (type !== 'ethereum' && type !== 'solana') { - return res.status(400).json({ error: 'Invalid type. Must be "ethereum" or "solana"' }); + if (networkType !== "ethereum" && type !== "solana") { + return res + .status(400) + .json({ error: 'Invalid networkType. Must be "ethereum" or "solana"' }); } const nonce = generateNonce(); try { - await Nonce.findOneAndUpdate( - { address, type }, - { address, nonce, type }, - { upsert: true, new: true } + await Nonces.findOneAndUpdate( + { walletAddress, networkType }, + { walletAddress, nonce, networkType }, + { upsert: true, new: true }, ); res.json({ nonce }); } catch (error) { - res.status(500).json({ error: 'Server error' }); + res.status(500).json({ error: "Server error" }); } }); -router.post('/verify', async (req, res) => { - const { message, address, signature } = req.body; +router.post("/verify", async (req, res) => { + const { message, walletAddress, signature } = req.body; - if (!address || !signature) { - return res.status(400).json({error: 'Missing address or signature'}); + if (!walletAddress || !signature) { + return res + .status(400) + .json({ error: "Missing walletAddress or signature" }); } - const nonce = await Nonce.findOne({ address }).lean().select('-__v -updatedAt'); - const type = nonce.type; + const nonce = await Nonces.findOne({ walletAddress }) + .lean() + .select("-__v -updatedAt"); + const networkType = nonce.networkType; try { - if (type === 'ethereum') { + if (networkType === "ethereum") { if (!message) { - return res.status(400).json({ error: 'Missing SiweMessage' }); + return res.status(400).json({ error: "Missing SiweMessage" }); } const siweMessage = new siwe.SiweMessage(message); - const fields = await siweMessage.verify({ signature, nonce: nonce.nonce }); - if (address == fields.address) - throw new Error('Invalid signature'); - } else if (type === 'solana') { + const fields = await siweMessage.verify({ + signature, + nonce: nonce.nonce, + }); + if (walletAddress == fields.walletAddress) + throw new Error("Invalid signature"); + } else if (networkType === "solana") { const signatureUint8 = bs58.default.decode(signature); const messageUint8 = new TextEncoder().encode(nonce.nonce); - const publicKeyUint8 = bs58.default.decode(address); + const publicKeyUint8 = bs58.default.decode(walletAddress); - const isValid = nacl.sign.detached.verify(messageUint8, signatureUint8, publicKeyUint8); + const isValid = nacl.sign.detached.verify( + messageUint8, + signatureUint8, + publicKeyUint8, + ); if (!isValid) { - throw new Error('Invalid signature'); + throw new Error("Invalid signature"); } } else { - return res.status(400).json({ error: 'Invalid type. Must be "ethereum" or "solana"' }); + return res + .status(400) + .json({ error: 'Invalid networkType. Must be "ethereum" or "solana"' }); } - const auth_token = generateBearerToken(); + const authToken = generateBearerToken(); - await AuthToken.findOneAndUpdate( - { address, type }, - { auth_token, createdAt: new Date() }, - { upsert: true, new: true, setDefaultsOnInsert: true } + await AuthTokens.findOneAndUpdate( + { walletAddress, networkType }, + { authToken, createdAt: new Date() }, + { upsert: true, new: true, setDefaultsOnInsert: true }, ); - await Nonce.deleteOne({ address, type }); + await Nonces.deleteOne({ walletAddress, networkType }); - res.json({ auth_token }); + res.json({ authToken }); } catch (error) { res.status(400).json({ error: `${error.message}, ${error.stack}` }); } }); -router.delete('/auth/signoff', async (req, res) => { - const authHeader = req.headers['authorization']; - const token = authHeader && authHeader.split(' ')[1]; - - if (!token) { - return res.status(400).json({ error: 'Missing authorization token in header'}); - } - - const deletedAuth = AuthToken.findOneAndDelete({ auth_token: token }); +router.delete("/signoff", verifyToken, async (req, res) => { + const deletedAuth = AuthTokens.findOneAndDelete({ + authToken: tokenRecord.authToken, + }); if (!deletedAuth) { - return res.status(400).json({ error: 'Can\'t log out, not logged in?' }); + return res.status(400).json({ error: "Can't log out, not logged in?" }); } - return res.status(204).json({ error: 'Logged out successfully.' }); + return res.status(204).json({ error: "Logged out successfully." }); }); module.exports = router; diff --git a/routes/users.route.js b/routes/users.route.js index 577b14f..c13cd75 100644 --- a/routes/users.route.js +++ b/routes/users.route.js @@ -1,9 +1,13 @@ -const express = require('express'); +const express = require("express"); const router = express.Router({ mergeParams: true }); -const { User } = require('../models/index.js'); +const { Users } = require("../models/index.js"); +const { + verifyToken, + editProfile, +} = require("../middlewares/auth.middleware.js"); -router.get('/users', async (req, res) => { +router.get("/", async (req, res) => { const { publicKey, address } = req.query; try { @@ -16,59 +20,71 @@ router.get('/users', async (req, res) => { } if (!publicKey && !address) { - return res.status(400).json({ error: 'Please provide a publicKey or address to search.' }); + return res + .status(400) + .json({ error: "Please provide a publicKey or address to search." }); } - const users = await User.find(query); + const users = await Users.find(query); if (users.length === 0) { - return res.status(404).json({ error: 'No users found matching the criteria.' }); + return res + .status(404) + .json({ error: "No users found matching the criteria." }); } res.json(users); } catch (err) { - res.status(500).json({ error: 'Server error' }); + res.status(500).json({ error: "Server error" }); } }); -router.post('/users', async (req, res) => { +router.post("/", verifyToken, editProfile, async (req, res) => { + const { walletAddress, networkType, profileName, publicKey } = req.body; + try { - const user = new User({ - address: req.body.address, - networkType: req.body.networkType, - profileName: req.body.profileName || 'Anonymous', - publicKey: req.body.publicKey + const user = new Users({ + walletAddress, + networkType, + profileName, + publicKey, }); - + await user.save(); res.status(201).json(user); - } catch (err) { - if (err.code === 11000) { - return res.status(400).json({ error: 'Public key already exists, are you trying to impersonate someone?' }); + } catch (error) { + if (error.code === 11000) { + return res.status(400).json({ + error: + "Public key already exists, are you trying to impersonate someone?", + }); } - res.status(500).json({ error: 'Server error' }); + console.error(error); + res.status(500).json({ error }); } }); -router.put('/users/:publicKey', async (req, res) => { +router.put("/:publicKey", verifyToken, editProfile, async (req, res) => { + const { walletAddress, networkType, profileName, publicKey } = req.body; + try { - const updatedUser = await User.findOneAndUpdate( - { publicKey: req.params.publicKey }, + const updatedUser = await Users.findOneAndUpdate( + { publicKey }, { - address: req.body.address, - networkType: req.body.networkType, - profileName: req.body.profileName + walletAddress, + networkType, + profileName, }, - { new: true } + { new: true }, ); if (!updatedUser) { - return res.status(404).json({ error: 'User not found' }); + return res.status(404).json({ error: "User not found" }); } res.json(updatedUser); - } catch (err) { - res.status(500).json({ error: 'Server error' }); + } catch (error) { + res.status(500).json({ error }); } });