अपने Node.js अनुप्रयोगों के लिए एक तेज़, सहज और सुव्यवस्थित प्रमाणीकरण समाधान की तलाश में, मुझे ऐसे परिदृश्यों का सामना करना पड़ा जो कार्यक्षमता से समझौता किए बिना तेजी से कार्यान्वयन की मांग करते थे।
उपयोगकर्ता साइनअप और लॉगिन से लेकर भूले हुए पासवर्ड को प्रबंधित करने, उपयोगकर्ता डेटा अपडेट करने और यहां तक कि खाता हटाने तक, मैंने एक व्यापक समाधान की तलाश की जो इन आवश्यक उपयोगकर्ता इंटरैक्शन के माध्यम से निर्बाध रूप से नेविगेट करता हो।
इस प्रकार, मेरे लेख का उद्देश्य सटीक रूप से प्रस्तुत करना है - प्रमाणीकरण और कैशिंग को लागू करने के लिए स्पष्ट पद्धतियों को एकीकृत करने वाला एक समेकित दृष्टिकोण, एक मजबूत और कुशल उपयोगकर्ता प्रवाह सुनिश्चित करना।
यहां, हम प्रमाणीकरण और उपयोगकर्ता प्रवाह की जटिलताओं पर सीधे ध्यान देते हुए मूलभूत स्थापना प्रक्रियाओं और मॉडल निर्माण को दरकिनार कर देंगे। हम पूरे आलेख में कॉन्फ़िगरेशन फ़ाइलें प्राप्त करने के लिए सभी आवश्यक लिंक शामिल करेंगे, जिससे सेटअप के लिए आवश्यक संसाधनों तक निर्बाध पहुंच सुनिश्चित होगी।
इस कार्यान्वयन के लिए, हम Knex, Express और Redis के साथ-साथ Node.js संस्करण 20.11.1 का लाभ उठाएंगे। इसके अतिरिक्त, हम अपने डेटाबेस के रूप में PostgreSQL का उपयोग करेंगे, जिसे निर्बाध प्रबंधन के लिए डॉकर का उपयोग करके कंटेनरीकृत और व्यवस्थित किया जाएगा।
हमारे एप्लिकेशन का नाम user-flow-boilerplate
होगा। आइए वह फ़ोल्डर बनाएं और मूल package.json
जेनरेट करने के लिए npm init -y
चलाएं
{ "name": "user-flow-boilerplate", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
प्रारंभिक पैकेज.json
अगला कदम आवश्यक निर्भरताएँ जोड़ना है:
निर्भरताएँ : npm i -S bcrypt body-parser cors dotenv express jsonwebtoken knex pg redis validator
निर्भरताएँ :
npm i -D @babel/core @babel/eslint-parser @babel/plugin-transform-class-properties @babel/plugin-transform-runtime @babel/preset-env @babel/preset-typescript @faker-js/faker @types/bcrypt @types/body-parser @types/cors @types/express @types/jest @types/jsonwebtoken @types/node @types/node-cron @types/validator @typescript-eslint/eslint-plugin @typescript-eslint/parser babel-jest cross-env eslint eslint-config-prettier eslint-plugin-prettier jest nodemon npm-run-all prettier ts-jest ts-loader ts-node tsconfig-paths tslint typescript webpack webpack-cli webpack-node-externals
और स्क्रिप्ट जोड़ें जो हमारे एप्लिकेशन को बनाएगी और चलाएगी:
"scripts": { "start": "NODE_ENV=production node dist/bundle.js", "build": "NODE_ENV=production webpack --config webpack.config.js", "dev": "cross-env NODE_ENV=development && npm-run-all -p dev:*", "dev:build": "webpack --config webpack.config.js --watch", "dev:start": "nodemon --watch dist --exec node dist/bundle.js", "test": "NODE_ENV=test jest --config ./jest.config.js", "lint": "eslint ./src -c .eslintrc.json" },
हमारे एप्लिकेशन के सुचारू लॉन्च को सुनिश्चित करने के लिए, एक src
फ़ोल्डर बनाना और हमारी प्रारंभिक प्रविष्टि बिंदु फ़ाइल, index.ts
को उसके भीतर रखना आवश्यक है।
require('dotenv').config(); import process from 'process'; import express from 'express'; import bodyParser from 'body-parser'; import cors from 'cors'; const app = express(); const PORT = process.env.PORT || 9999; app.use(bodyParser.json()); app.use(cors()); app.get('/api/v1/health', (req, res) => res.status(200).json({ message: 'OK' })); (async () => { try { app.listen(PORT, async () => { console.log(`Server is running on port ${PORT}`); }); } catch (error) { console.error('Failed to start server:', error); process.exit(1); } })();
एंट्रीपॉइंट फ़ाइल
विकास के लिए, हमें typscript
, lint
, jest
, bable
, prettier
, nodemon
के लिए सेटिंग्स की आवश्यकता है। उन सभी फ़ाइलों का वर्णन मैंने निम्नलिखित आलेख में किया है: एक्सप्रेस पर पोस्टग्रेज़ और क्नेक्स के साथ एक Node.js सर्वर बनाना ।
सभी सेटिंग्स को कॉन्फ़िगर करने और प्रवेश बिंदु बनाने के बाद, npm run dev
निष्पादित करने से सर्वर शुरू होना चाहिए, और आपको निम्न के समान आउटपुट देखने की उम्मीद करनी चाहिए:
./src/index.ts 1.7 KiB [built] [code generated] external "dotenv" 42 bytes [built] [code generated] external "process" 42 bytes [built] [code generated] external "express" 42 bytes [built] [code generated] external "body-parser" 42 bytes [built] [code generated] external "cors" 42 bytes [built] [code generated] webpack 5.90.3 compiled successfully in 751 ms [nodemon] restarting due to changes... [nodemon] starting `node dist/bundle.js` Server is running on port 9999
इसके बाद, नेविगेट करेंGET
अनुरोध जोड़ें, cmd + E
दबाएँ (Mac पर, लेकिन कुंजियाँ आपके OS पर निर्भर करती हैं), और इसे health
नाम दें।
URL के लिए एंटर जोड़ें: {{BASE_URI}}/health
। BASE_URI
के लिए, एक नया वेरिएबल जोड़ें जिसे आप संग्रह में उपयोग करने जा रहे हैं: http://localhost:9999/api/v1
इसके बाद, बस 'भेजें' बटन पर क्लिक करें, और आपको प्रतिक्रिया का मुख्य भाग देखना चाहिए:
{ "message": "OK" }
आगे बढ़ने से पहले, हमारे डेटाबेस को तैयार और चालू रखना महत्वपूर्ण है। हम इसे docker-compose
के साथ लॉन्च करके पूरा करेंगे। डेटाबेस तक पहुंचने और प्रबंधित करने के लिए, आप विभिन्न विकास प्लेटफार्मों का उपयोग कर सकते हैं
व्यक्तिगत रूप से, मैं उपयोग करना पसंद करता हूँ
हमें आवश्यक कुंजी, पासवर्ड और परीक्षण नामों के साथ .env
फ़ाइल की आवश्यकता है:
PORT=9999 WEB_HOST="localhost" # DB DB_HOST="localhost" DB_PORT=5432 DB_NAME="user_flow_boilerplate" DB_USER="username_123" DB_PASSWORD="SomeParole999" # User DEFAULT_PASSWORD="SomeParole999" JWT_SECRET="6f1d7e9b9ba56476ae2f4bdebf667d88eeee6e6c98c68f392ed39f7cf6e51c5a" # Test User TEST_EMAIL="[email protected]" TEST_USERNAME="test_username" TEST_PASSWORD="SomeParole999" # Redis REDIS_HOST="localhost" REDIS_PORT=6379 REDIS_DB=0 REDIS_PASSWORD="SomeParole999"
डेटाबेस, रेडिस और बीजों के परीक्षण मूल्यों से कनेक्शन के लिए .env
डरो मत, मैंने इसे अधिक प्रामाणिक तरीके से चित्रित करने के लिए यादृच्छिक रूप से JWT_SECRET
उत्पन्न किया। तो, आइए प्रोजेक्ट के मूल में एक docker-compose.yml
फ़ाइल बनाएं:
version: '3.6' volumes: data: services: database: build: context: . dockerfile: postgres.dockerfile image: postgres:latest container_name: postgres environment: TZ: Europe/Madrid POSTGRES_DB: ${DB_NAME} POSTGRES_USER: ${DB_USER} POSTGRES_PASSWORD: ${DB_PASSWORD} networks: - default volumes: - data:/var/lib/postgresql/data ports: - "5432:5432" restart: unless-stopped redis: image: redis:latest container_name: redis command: redis-server --requirepass ${REDIS_PASSWORD} networks: - default ports: - "6379:6379" restart: unless-stopped
सेवाओं के साथ docker-compose फ़ाइल
हम तेजी से कनेक्टिविटी के लिए डॉकर में दो सेवाएं शुरू करने जा रहे हैं। मैंने डेटाबेस या रेडिस तक त्वरित पहुंच की सुविधा के लिए इस प्रक्रिया को सुव्यवस्थित किया है, जिससे हमें कुशलतापूर्वक डेटा पुनर्प्राप्त करने की अनुमति मिलती है। तो, चलिए उन सेवाओं को docker-compose up
चलाते हैं, और हमें निम्नलिखित आउटपुट के बाद docker ps
के बाद आउटपुट देखने में सक्षम होना होगा:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e4bef95de1dd postgres:latest "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:5432->5432/tcp postgres 365e3a68351a redis:latest "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:6379->6379/tcp redis
अब, हमें src/@types/index.ts
फ़ाइल बनाने की आवश्यकता है जहां हम एप्लिकेशन के लिए अपने प्रकार संग्रहीत करते हैं:
export enum Role { Admin = 'admin', Blogger = 'blogger', } export type UserSession = { id: number; }; export type DatabaseDate = { created_at: Date; updated_at: Date; }; export type DefaultUserData = { role: Role; }; export interface User extends DatabaseDate { id: number; email: string; username: string; password: string; role: Role; }
सेवा के लिए प्रकार
इस समय, आपको कनेक्शन, माइग्रेशन और सीड्स के लिए प्रोजेक्ट और डेटाबेस फ़ोल्डर के रूट में knexfile.ts
की आवश्यकता है।
मैंने एक्सप्रेस लेख पर पोस्टग्रेज़ और क्नेक्स के साथ एक नोड.जेएस सर्वर बनाने में एक विस्तृत विवरण छोड़ा है कि उपयोगकर्ताओं को उस डेटाबेस में कैसे स्थानांतरित और सीड किया जाए जहां हम उन एनवी वेरिएबल्स का उपयोग कर रहे हैं।
मैं यह सुनिश्चित करने के लिए विशेष रूप से माइग्रेशन की जांच करना चाहूंगा कि हम एक ही पृष्ठ पर हैं। हमने पहले ही अपनी सेवाएँ लॉन्च कर दी हैं, और हमें डेटाबेस से कनेक्शन की जाँच करने में सक्षम होना होगा।
docker exec -it postgres psql -U username_123 user_flow_boilerplate
यदि कनेक्शन अच्छा है, तो आप psql
कंसोल में होंगे। ठीक है, यदि कनेक्शन में कोई समस्या नहीं है, तो हमें अपनी तालिकाओं को वहां स्थानांतरित करने में सक्षम होना चाहिए। knex migrate:latest
। फिर आपको डेटाबेस के भीतर अपनी users
तालिका में नए जोड़े गए कॉलम का निरीक्षण करना चाहिए।
आइए इसे नकली डेटा के साथ सीड करें knex seed:run
, और तालिका को फिर से जांचें।
इसलिए, अब हम डेटाबेस में हेरफेर करने में सक्षम हैं, जिससे हम आवश्यकतानुसार उपयोगकर्ताओं को जोड़, हटा या अपडेट कर सकते हैं।
अंत में, हम सेटिंग्स और तैयारी के बारे में भूल सकते हैं और विशेष रूप से उपयोगकर्ता प्रवाह पर ध्यान केंद्रित कर सकते हैं। इसके लिए हमें एक राउटर बनाना होगा। हमें उस राउटर द्वारा निम्नलिखित परिचालनों को संभालने की आवश्यकता है: login
, logout
, signup
, delete_user
, update_user
।
उसके लिए, src/routes/index.ts
पर, निम्नलिखित कोड जोड़ें:
import { Router } from 'express'; import { authRouter } from 'src/routes/authRouter'; import { healthController } from 'src/controllers/healthController'; import { sessionController } from 'src/controllers/sessionController'; import { authMiddleware } from 'src/middlewares/authMiddleware'; import { userRouter } from 'src/routes/userRouter'; export const router = Router({ mergeParams: true }); router.get('/health', healthController); router.use('/auth', authRouter); router.get('/session', authMiddleware, sessionController); router.use('/user', authMiddleware, userRouter); router.use((_, res) => { return res.status(404).json({ message: 'Not Found' }); });
रूट फ़ाइल
जैसा कि आप देख सकते हैं, शुरुआत में, हमने /health
मार्ग जोड़ा था जिसे हमने पहले ही जांच लिया था। तो फिर, आइए उन मार्गों को वहां लागू करने के लिए प्रवेश बिंदु को अपडेट करें। सबसे पहले, पिछले get
हटा दें।
-> REMOVE -> app.get('/api/v1/health', (req, res) => res.status(200).json({ message: 'OK' }));
और फ़ाइल के शीर्ष पर जोड़ें:
import { router } from 'src/routes'; // ... app.use(cors()); app.use('/api/v1', router);
और कोड के साथ health
जांच के लिए पहला नियंत्रक src/controllers/healthController.ts
बनाएं:
import { Request, Response } from 'express'; export const healthController = (_: Request, res: Response) => res.status(200).send('ok');
स्वास्थ्य नियंत्रक
अब, राउटर पर वापस आते हैं, और देखते हैं कि हमें रूट में और क्या जोड़ना है। हमें दो और फ़ाइलें जोड़ने की आवश्यकता है: authRouter.ts
और userRouter.ts
import { Router } from 'express'; import { signUpController } from 'src/controllers/auth/signUpController'; import { loginController } from 'src/controllers/auth/loginController'; export const authRouter = Router(); authRouter.post('/signup', signUpController); authRouter.post('/login', loginController);
प्रामाणिक राउटर
import { Router } from 'express'; import { updateUserController } from 'src/controllers/user/updateUserController'; import { deleteUserController } from 'src/controllers/user/deleteUserController'; import { logoutController } from 'src/controllers/user/logoutController'; import { updatePasswordController } from 'src/controllers/user/updatePasswordController'; export const userRouter = Router(); userRouter.patch('/', updateUserController); userRouter.delete('/', deleteUserController); userRouter.post('/logout', logoutController); userRouter.post('/update-password', updatePasswordController);
उपयोगकर्ता राउटर
मैंने इस तर्क को पठनीयता और पृथक कार्यक्षमता बनाए रखने की जिम्मेदारी के लिए विभाजित किया है। उन सभी मार्गों को नियंत्रकों की आवश्यकता है जहां हम तर्क को संभालने जा रहे हैं।
प्रामाणिक और स्वास्थ्य मार्गों को प्रमाणीकरण मिडलवेयर की आवश्यकता नहीं है, इसलिए वे मार्ग सुरक्षित नहीं हैं, लेकिन यदि कोई मिलान नहीं है, तो हमें स्थिति 404 प्राप्त होगी।
router.get('/health', healthController); router.use('/auth', authRouter);
अब, चूँकि हमने सभी रूट तय कर लिए हैं, हमें उपयोगकर्ता मॉडल सेट करना होगा।
मैं उपयोगकर्ता मॉडल के लिए एक बेस मॉडल का उपयोग करूंगा, जिससे मैं सीआरयूडी विधियों का पुन: उपयोग करूंगा। जबकि मैंने पहले मॉडल निर्माण को दूसरे में कवर किया हैsrc/models/Model.ts
में बनाएं
import { database } from 'root/database'; export abstract class Model { protected static tableName?: string; protected static get table() { if (!this.tableName) { throw new Error('The table name must be defined for the model.'); } return database(this.tableName); } public static async insert<Payload>(data: Payload): Promise<{ id: number; }> { const [result] = await this.table.insert(data).returning('id'); return result; } public static async updateOneById<Payload>( id: number, data: Payload ): Promise<{ id: number; }> { const [result] = await this.table.where({ id }).update(data).returning('id'); return result; } public static async delete(id: number): Promise<number> { return this.table.where({ id }).del(); } public static async findOneById<Result>(id: number): Promise<Result> { return this.table.where('id', id).first(); } public static async findOneBy<Payload, Result>(data: Payload): Promise<Result> { return this.table.where(data as string).first(); } }
आधार मॉडल
बेस मॉडल के साथ, हमें उसी फ़ोल्डर में UserModel.ts
बनाने में सक्षम होना होगा:
import { Model } from 'src/models/Model'; import { Role, User, DefaultUserData } from 'src/@types'; export class UserModel extends Model { static tableName = 'users'; public static async create<Payload>(data: Payload) { return super.insert<Payload & DefaultUserData>({ ...data, role: data.role || Role.Blogger, }); } public static findByEmail(email: string): Promise<User | null> { return this.findOneBy< { email: string; }, User >({ email }); } public static findByUsername(username: string): Promise<User | null> { return this.findOneBy< { username: string; }, User >({ username }); } }
उपयोगकर्ता मॉडल
उपयोगकर्ता के मॉडल में, यदि पेलोड से प्रदान नहीं किया गया है तो मैं डिफ़ॉल्ट रूप से role
निर्धारित करता हूं। और अब जब हमारे मॉडल तैयार हैं, तो हम उन्हें अपने नियंत्रकों और मिडलवेयर में उपयोग करने के लिए आगे बढ़ सकते हैं।
Node.js एप्लिकेशन में ऑथ मिडलवेयर आने वाले अनुरोधों को प्रमाणित करने के लिए जिम्मेदार है, यह सुनिश्चित करते हुए कि वे वैध और अधिकृत उपयोगकर्ताओं से आ रहे हैं।
यह आम तौर पर आने वाले अनुरोधों को रोकता है, प्रमाणीकरण टोकन या क्रेडेंशियल निकालता है, और इस मामले में JWT (JSON वेब टोकन) जैसे पूर्वनिर्धारित प्रमाणीकरण तंत्र के खिलाफ उनकी वैधता की पुष्टि करता है।
यदि प्रमाणीकरण प्रक्रिया सफल हो जाती है, तो मिडलवेयर अनुरोध को अनुरोध-प्रतिक्रिया चक्र में अगले हैंडलर पर आगे बढ़ने की अनुमति देता है। हालाँकि, यदि प्रमाणीकरण विफल हो जाता है, तो यह उचित HTTP स्थिति कोड (उदाहरण के लिए, 401 अनधिकृत) के साथ प्रतिक्रिया करता है और वैकल्पिक रूप से एक त्रुटि संदेश प्रदान करता है।
फ़ोल्डर src/middlewares
बनाएं, और वहां निम्नलिखित कोड के साथ एक फ़ाइल authMiddleware.ts
जोड़ें:
import { jwt } from 'src/utils/jwt'; import { Redis } from 'src/redis'; import type { Request, Response, NextFunction } from 'express'; import type { UserSession } from 'src/@types'; export async function authMiddleware(req: Request, res: Response, next: NextFunction) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; const JWT_SECRET = process.env.JWT_SECRET; if (!token) return res.sendStatus(401); if (!JWT_SECRET) { console.error('JWT_SECRET Not Found'); return res.sendStatus(500); } if (!token) return res.status(401).json({ error: 'Token not provided' }); try { const userSession = await jwt.verify<UserSession>(token); if (!userSession) { return res.sendStatus(401); } const storedToken = await Redis.getSession(userSession.id); if (!storedToken || storedToken !== token) { return res.sendStatus(401); } req.user = userSession; next(); } catch (error) { console.error('JWT_ERROR', error); return res.sendStatus(401); } }
प्रामाणिक मिडलवेयर फ़ाइल
ऑथ मिडलवेयर अनुरोध हेडर से जेडब्ल्यूटी टोकन निकालता है, जेडब्ल्यूटी लाइब्रेरी का उपयोग करके इसकी वैधता की पुष्टि करता है, और जांचता है कि टोकन रेडिस में संग्रहीत टोकन से मेल खाता है या नहीं।
यदि टोकन वैध है और संग्रहीत टोकन से मेल खाता है, तो मिडलवेयर अनुरोध ऑब्जेक्ट ( req.user
) पर प्रमाणित उपयोगकर्ता सत्र सेट करता है और अगले मिडलवेयर या रूट हैंडलर को नियंत्रण पास करने के लिए next()
फ़ंक्शन को कॉल करता है। अन्यथा, यह प्रमाणीकरण विफलता का संकेत देने वाले 401 स्थिति कोड के साथ प्रतिक्रिया करता है।
आइए jwt के उपयोग की समीक्षा करें। निम्नलिखित कोड के साथ src/utils/jwt.ts
फ़ाइल बनाएं:
require('dotenv').config(); import jsonwebtoken from 'jsonwebtoken'; const JWT_SECRET = process.env.JWT_SECRET as string; export const jwt = { verify: <Result>(token: string): Promise<Result> => { if (!JWT_SECRET) { throw new Error('JWT_SECRET not found in environment variables!'); } return new Promise((resolve, reject) => { jsonwebtoken.verify(token, JWT_SECRET, (error, decoded) => { if (error) { reject(error); } else { resolve(decoded as Result); } }); }); }, sign: (payload: string | object | Buffer): Promise<string> => { if (!JWT_SECRET) { throw new Error('JWT_SECRET not found in environment variables!'); } return new Promise((resolve, reject) => { try { resolve(jsonwebtoken.sign(payload, JWT_SECRET)); } catch (error) { reject(error); } }); }, };
JWT उपयोगिता फ़ाइल
यह उपयोगिता Node.js एप्लिकेशन के भीतर JSON वेब टोकन को संभालने में महत्वपूर्ण भूमिका निभाती है। jwt
ऑब्जेक्ट jsonwebtoken
लाइब्रेरी का लाभ उठाते हुए, JWT पर हस्ताक्षर करने और सत्यापित करने दोनों के लिए कार्य निर्यात करता है। ये फ़ंक्शन जेडब्ल्यूटी के निर्माण और सत्यापन की सुविधा प्रदान करते हैं, जो एप्लिकेशन में प्रमाणीकरण तंत्र को लागू करने के लिए आवश्यक है।
यूटिलिटी पर्यावरण परिवर्तनीय प्रबंधन के लिए सर्वोत्तम प्रथाओं का पालन करते हुए, Node.js एप्लिकेशन के भीतर सुरक्षित प्रमाणीकरण तंत्र सुनिश्चित करते हुए, JWTs को संभालने के लिए कार्यक्षमता को समाहित करती है।
डेटाबेस, कैश और संदेश ब्रोकर के रूप में उपयोग किया जाता है। आमतौर पर कैशिंग, सत्र प्रबंधन, रीयल-टाइम एनालिटिक्स, मैसेजिंग कतार, लीडरबोर्ड और बहुत कुछ सहित विभिन्न प्रकार के उपयोग के मामलों में उपयोग किया जाता है।
Redis से टोकन की जाँच करना JWT टोकन के लिए सुरक्षा और सत्यापन की एक अतिरिक्त परत के रूप में कार्य करता है। आइए सेटिंग्स में गोता लगाएँ। उसके लिए, निम्नलिखित कोड के साथ फ़ाइल src/redis/index.ts
बनाएं:
require('dotenv').config({ path: '../../.env', }); import process from 'process'; import * as redis from 'redis'; const client = redis.createClient({ url: `redis://:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, }); client.on('error', error => console.error('Redis Client Error', error)); const connect = async () => { try { await client.connect(); console.log('Connected to Redis'); } catch (err) { console.error(`Could not connect to Redis: ${err}`); process.exit(1); } }; class Redis { public static setSession(userId: number, token: string) { if (!userId) throw new Error('userId is required'); if (!token) throw new Error('token is required'); try { return client.set(`session:${userId}`, token); } catch (error) { console.error(error); } } public static getSession(userId: number) { if (!userId) throw new Error('userId is required'); return client.get(`session:${userId}`); } public static deleteSession(userId: string) { if (!userId) throw new Error('userId is required'); try { return client.del(`session:${userId}`); } catch (error) { console.error(error); } } } export { client, connect, Redis };
रेडिस सेशन स्टोर
रेडिस द्वारा, हम उपयोगकर्ता सत्र टोकन को संग्रहीत और प्रबंधित करने जा रहे हैं। ऑथ मिडलवेयर में, जेडब्ल्यूटी टोकन की प्रामाणिकता को सत्यापित करने के बाद, मिडलवेयर जांच करता है कि टोकन मौजूद है या नहीं और संबंधित उपयोगकर्ता सत्र के लिए रेडिस में संग्रहीत टोकन से मेल खाता है। इससे यह सुनिश्चित करने में मदद मिलती है कि केवल वैध और अधिकृत उपयोगकर्ता ही संरक्षित मार्गों तक पहुंच सकते हैं।
रेडिस का उपयोग उपयोगकर्ता सत्र टोकन को बनाए रखने के लिए कुंजी-मूल्य स्टोर के रूप में किया जाता है। जब कोई उपयोगकर्ता लॉग इन करता है या प्रमाणित करता है, तो उनका सत्र टोकन रेडिस में संग्रहीत होता है। यह बाद की प्रमाणीकरण जांच के दौरान सत्र टोकन की कुशल और तेज़ पुनर्प्राप्ति की अनुमति देता है।
रेडिस का उपयोग कुशल सत्र प्रबंधन के लिए ऑथ मिडलवेयर में किया जाता है, जबकि रेडिस-संबंधित फ़ाइल रेडिस सर्वर से कॉन्फ़िगरेशन और कनेक्शन को संभालती है और एप्लिकेशन के अन्य हिस्सों में रेडिस के साथ इंटरैक्ट करने के लिए फ़ंक्शन प्रदान करती है।
यह सेटअप सुरक्षित और विश्वसनीय प्रमाणीकरण तंत्र सुनिश्चित करता है, जिसमें उपयोगकर्ता सत्र टोकन रेडिस में संग्रहीत और प्रबंधित होते हैं।
अंतिम भाग यह है कि हमें अपने प्रवेश बिंदु में रेडिस से जुड़ना होगा:
// all imports import * as Redis from 'src/redis'; const app = express(); const PORT = process.env.PORT || 9999; // middlewares (async () => { try { await Redis.connect(); app.listen(PORT, async () => { console.log(`Server is running on port ${PORT}`); }); } catch (error) { console.error('Failed to start server:', error); process.exit(1); } })();
रेडिस से कनेक्ट करें
प्रमाणीकरण की तैयारी पूरी करने के बाद, अब हम अपना ध्यान नियंत्रकों पर केंद्रित कर सकते हैं।
मार्गों में नियंत्रक चिंताओं को अलग करके और कोड रखरखाव को बढ़ावा देकर एप्लिकेशन के तर्क को व्यवस्थित करने में मदद करते हैं। हमने स्वास्थ्य जांच के लिए नियंत्रक पहले ही बना लिया है। इसके बाद, हम उपयोगकर्ता के साथ परिचालन को संभालने के लिए नियंत्रक बनाने के लिए आगे बढ़ेंगे।
पहला नियंत्रक जो हम लेने जा रहे हैं वह sessionController.ts
है जिसे निम्नलिखित कोड के साथ src/controllers
में होना चाहिए:
import { Request, Response } from 'express'; import { UserModel } from 'src/models/UserModel'; import type { User } from 'src/@types'; export const sessionController = async (req: Request, res: Response) => { if (!req.user) return res.sendStatus(401); try { const user = await UserModel.findOneById<User>(req.user.id); if (user) { return res.status(200).json(user); } else { return res.sendStatus(401); } } catch (error) { return res.sendStatus(500); } };
सत्र नियंत्रक
यह नियंत्रक सत्र-संबंधित समापन बिंदु को संभालने के उद्देश्य से कार्य करता है, जो संभवतः वर्तमान में प्रमाणित उपयोगकर्ता के बारे में जानकारी प्राप्त करने के लिए जिम्मेदार है। हमें निम्नलिखित कारणों से इस नियंत्रक की आवश्यकता है:
उपयोगकर्ता सत्र जानकारी: यह नियंत्रक एप्लिकेशन को उपयोगकर्ता के सत्र, जैसे कि उनकी उपयोगकर्ता प्रोफ़ाइल या अन्य प्रासंगिक डेटा के बारे में जानकारी पुनर्प्राप्त करने की अनुमति देता है। यह जानकारी उपयोगकर्ता अनुभव को अनुकूलित करने या उपयोगकर्ता की प्रोफ़ाइल के आधार पर वैयक्तिकृत सामग्री प्रदान करने के लिए उपयोगी हो सकती है।
प्रमाणीकरण और प्राधिकरण: यह जांच कर कि क्या req.user
मौजूद है, नियंत्रक यह सुनिश्चित करता है कि केवल प्रमाणित उपयोगकर्ता ही समापन बिंदु तक पहुंच सकते हैं। यह प्रमाणीकरण और प्राधिकरण नियमों को लागू करने में मदद करता है, यह सुनिश्चित करते हुए कि संवेदनशील उपयोगकर्ता डेटा केवल अधिकृत उपयोगकर्ताओं के लिए ही पहुंच योग्य है।
उपयोगकर्ता प्रोफ़ाइल पुनर्प्राप्ति: नियंत्रक अपने सत्र आईडी के आधार पर उपयोगकर्ता की जानकारी पुनर्प्राप्त करने के लिए डेटाबेस से पूछताछ करता है ( UserModel
उपयोग करके)। यह एप्लिकेशन को उपयोगकर्ता-विशिष्ट डेटा को गतिशील रूप से लाने की अनुमति देता है, जो प्रत्येक उपयोगकर्ता के लिए एक अनुरूप अनुभव प्रदान करता है। रेडिस कैश द्वारा इस भाग को निश्चित रूप से बेहतर बनाया जा सकता है:
import { Request, Response } from 'express'; import { UserModel } from 'src/models/UserModel'; import { Redis } from 'src/redis'; import type { User } from 'src/@types'; export const sessionController = async (req: Request, res: Response) => { if (!req.user) return res.sendStatus(401); try { const cachedProfile = await Redis.getSession(req.user.id); if (cachedProfile) { return res.status(200).json(JSON.parse(cachedProfile)); } else { const user = await UserModel.findOneById<User>(req.user.id); if (user) { await Redis.setSession(req.user.id, JSON.stringify(user), CACHE_EXPIRATION); return res.status(200).json(user); } else { return res.sendStatus(401); } } } catch (error) { console.error('Error retrieving user profile:', error); return res.sendStatus(500); } };
रेडिस सेट सत्र के साथ सत्र नियंत्रक फ़ाइल
हम सेकंड में कैश समाप्ति समय निर्दिष्ट करने के लिए एक स्थिर CACHE_EXPIRATION
परिभाषित करते हैं। इस उदाहरण में, यह 3600 सेकंड (1 घंटा) पर सेट है। कैश्ड डेटा को समय-समय पर ताज़ा किया जाता है, जिससे पुराने डेटा को उपयोगकर्ताओं को परोसे जाने से रोका जा सकता है और कैश के भीतर डेटा अखंडता बनाए रखी जा सकती है।
signUpController
बनाने के लिए आगे बढ़ने से पहले, जो हमारे एप्लिकेशन में नए उपयोगकर्ताओं के लिए साइन-अप प्रक्रिया का प्रबंधन करता है, आइए स्कीमा की समीक्षा करें:
हमारे मामले में, डेटाबेस में मौजूदा ईमेल के साथ साइन अप करने का प्रयास करते समय, हम स्पष्ट रूप से यह प्रकट न करके उपयोगकर्ता की गोपनीयता को प्राथमिकता देते हैं कि उपयोगकर्ता मौजूद है या नहीं। इसके बजाय, हम क्लाइंट को Invalid email or password
बताते हुए एक सामान्य संदेश के साथ सूचित करते हैं।
यह दृष्टिकोण ग्राहक को मौजूदा उपयोगकर्ताओं के बारे में अनावश्यक जानकारी का खुलासा किए बिना वैध क्रेडेंशियल प्रस्तुत करने के लिए प्रोत्साहित करता है।
अब आइए src/controllers/auth/signUpController.ts
बनाएं, और निम्नलिखित कोड जोड़ें:
import bcrypt from 'bcrypt'; import { jwt } from 'src/utils/jwt'; import { Request, Response } from 'express'; import { validate } from 'src/helpers/validation/validate'; import { userSchema } from 'src/helpers/validation/schemas/userSchema'; import { UserModel } from 'src/models/UserModel'; import { Redis } from 'src/redis'; import type { User } from 'src/@types'; import { getRandomString } from 'src/utils/getRandomString'; type Payload = Omit<User, 'id' | 'created_at' | 'updated_at' | 'role'>; export async function signUpController(req: Request, res: Response) { const { email, password }: Payload = req.body; const validation = validate<Payload>(req.body, userSchema); if (!validation.isValid) { return res.status(400).send(`Invalid ${validation.invalidKey}`); } try { const user = await UserModel.findOneBy({ email }); if (user) { return res.status(400).json({ message: 'Invalid email or password' }); } const hashedPassword = (await bcrypt.hash(password, 10)) as string; const username = `${email.split('@')[0]}${getRandomString(5)}`; const createdUser = await UserModel.create<Payload>({ email, password: hashedPassword, username, }); const token = await jwt.sign({ id: createdUser.id, }); await Redis.setSession(createdUser.id, token); res.status(200).json({ token, }); } catch (error) { return res.sendStatus(500); } }
साइन अप नियंत्रक
नियंत्रक को आमतौर पर साइन-अप फॉर्म से उपयोगकर्ता के ईमेल और पासवर्ड वाला एक अनुरोध प्राप्त होता है। यह आने वाले डेटा को पूर्वनिर्धारित userSchema
के विरुद्ध सत्यापित करता है ताकि यह सुनिश्चित हो सके कि यह आवश्यक प्रारूप को पूरा करता है।
यदि सत्यापन सफलतापूर्वक पारित हो जाता है, कोई मौजूदा उपयोगकर्ता और वैध फ़ील्ड नहीं दर्शाता है, तो नियंत्रक bcrypt.hash
का उपयोग करके पासवर्ड हैश करने के लिए आगे बढ़ता है, एक username
उत्पन्न करता है, और UserModel.create
का उपयोग करके उपयोगकर्ता बनाता है।
अंत में, यह jwt
उपयोग करके एक token
उत्पन्न करता है, Redis
में session
डेटा सेट करता है, और उपयोगकर्ता को token
वापस भेजता है।
अब, आइए एक लॉगिन नियंत्रक के निर्माण पर ध्यान केंद्रित करें। फ़ाइल बनाएं src/controllers/auth/loginController.ts
:
require('dotenv').config({ path: '../../.env', }); import bcrypt from 'bcrypt'; import { Request, Response } from 'express'; import { jwt } from 'src/utils/jwt'; import { UserModel } from 'src/models/UserModel'; import { Redis } from 'src/redis'; export async function loginController(req: Request, res: Response) { const { email, password } = req.body; if (!email || !password) { return res.status(400).json({ message: 'Invalid email or password' }); } try { const user = await UserModel.findByEmail(email); if (user) { const isValidPassword = await bcrypt.compare(password, user.password); if (!isValidPassword) { return res.status(400).json({ message: 'Invalid email or password' }); } const token: string = await jwt.sign({ id: user.id, }); await Redis.setSession(user.id, token); res.status(200).json({ token }); } else { return res.status(400).json({ message: 'Invalid email or password' }); } } catch (error) { console.error(error); return res.sendStatus(500); } }
लॉगिन नियंत्रक
अनिवार्य रूप से, हम प्रदान किए गए फ़ील्ड को मान्य करने और फिर उपयोगकर्ता के अस्तित्व की जांच करने से शुरू करते हैं। यदि कोई उपयोगकर्ता नहीं मिलता है, तो हम Invalid email or password
संदेश के साथ 400 स्टेटस कोड के साथ जवाब देते हैं, जो signupController
के व्यवहार के समान है।
यदि कोई उपयोगकर्ता मौजूद है, तो हम bcrypt.compare
उपयोग करके डेटाबेस में संग्रहीत हैशेड पासवर्ड के साथ दिए गए पासवर्ड की तुलना करने के लिए आगे बढ़ते हैं।
यदि पासवर्ड मेल नहीं खाते हैं, तो हम परिचित संदेश 'अमान्य ईमेल या पासवर्ड' के साथ जवाब देते हैं। अंत में, सफल प्रमाणीकरण पर, हम एक टोकन उत्पन्न करते हैं, रेडिस में सत्र सेट करते हैं, और क्लाइंट को टोकन वापस भेजते हैं।
आइए हमारे संरक्षित नियंत्रकों की समीक्षा करें, जो मिडलवेयर से प्राप्त उपयोगकर्ता_आईडी की उपस्थिति पर निर्भर करते हैं। हम इन नियंत्रकों के भीतर संचालन के लिए लगातार इस user_id पर भरोसा करते हैं। ऐसे मामलों में जहां अनुरोध में authorization
हेडर का अभाव है, हमें 401
स्थिति कोड के साथ जवाब देना होगा।
const authHeader = req.headers['authorization'];
निम्नलिखित कोड के साथ फ़ाइल src/controllers/user/logoutController.ts
बनाएँ:
import type { Request, Response } from 'express'; import { Redis } from 'src/redis'; export async function logoutController(req: Request, res: Response) { try { await Redis.deleteSession(req.user.id); return res.sendStatus(200); } catch (error) { return res.sendStatus(500); } }
लॉगआउट नियंत्रक
यह logoutController
, किसी उपयोगकर्ता को सिस्टम से लॉग आउट करने के लिए जिम्मेदार है। अनुरोध प्राप्त होने पर, यह user.id
से जुड़े सत्र को हटाने के लिए Redis क्लाइंट के साथ इंटरैक्ट करता है। यदि ऑपरेशन सफल होता है, तो यह सफल लॉगआउट को इंगित करने के लिए 200
स्थिति कोड के साथ प्रतिक्रिया करता है।
हालाँकि, यदि प्रक्रिया के दौरान कोई त्रुटि होती है, तो यह आंतरिक सर्वर त्रुटि का संकेत देने के लिए 500
स्थिति कोड के साथ प्रतिक्रिया करता है।
इसके बाद, आइए उपयोगकर्ता डेटा को हटाने पर चर्चा करें।
src/controllers/user/deleteUserController.ts
बनाएं और यह कोड जोड़ें:
import { Request, Response } from 'express'; import { UserModel } from 'src/models/UserModel'; import { Redis } from 'src/redis'; export const deleteUserController = async (req: Request, res: Response) => { const user_id = req.user.id; try { await Redis.deleteSession(user_id); await UserModel.delete(user_id); return res.sendStatus(200); } catch (error) { return res.sendStatus(500); } };
उपयोगकर्ता नियंत्रक हटाएँ
जब कोई अनुरोध प्राप्त होता है, तो यह अनुरोध ऑब्जेक्ट से उपयोगकर्ता आईडी निकालता है, जो आमतौर पर प्रमाणीकरण मिडलवेयर से प्राप्त होता है।
इसके बाद, यह Redis क्लाइंट का उपयोग करके Redis से इस user_id
से जुड़े सत्र को हटाने के लिए आगे बढ़ता है। बाद में, यह डेटाबेस से उपयोगकर्ता के डेटा को हटाने के लिए UserModel
की delete
विधि को लागू करता है।
सत्र और उपयोगकर्ता डेटा दोनों के सफल विलोपन पर, यह सफल विलोपन को इंगित करने के लिए 200
स्थिति कोड के साथ प्रतिक्रिया करता है। हटाने की प्रक्रिया के दौरान किसी त्रुटि की स्थिति में, यह आंतरिक सर्वर त्रुटि को इंगित करने के लिए 500
स्थिति कोड के साथ प्रतिक्रिया करता है।
सिस्टम में उपयोगकर्ता डेटा को अपडेट करने के लिए src/controllers/user/updateUserController.ts
बनाएं, और फ़ाइल में निम्नलिखित कोड जोड़ें:
import { Request, Response } from 'express'; import { UserModel } from 'src/models/UserModel'; import { filterObject } from 'src/utils/filterObject'; type Payload = { first_name?: string; last_name?: string; username?: string; }; export const updateUserController = async (req: Request, res: Response) => { const { first_name, last_name, username } = req.body; const payload: Payload = filterObject({ first_name, last_name, username, }); try { const existingUserName = await UserModel.findByUsername(username); if (existingUserName) { return res.status(400).json({ error: 'Invalid username', }); } const updatedUser = await UserModel.updateOneById<typeof payload>(req.user.id, payload); res.status(200).json(updatedUser); } catch (error) { res.sendStatus(500); } };
उपयोगकर्ता नियंत्रक को अद्यतन करें
अनुरोध प्राप्त होने पर, यह अनुरोध निकाय से फ़ील्ड first_name
, last_name
और username
निकालता है। इसके बाद, यह filterObject
उपयोगिता फ़ंक्शन का उपयोग करके इन फ़ील्ड को फ़िल्टर करता है ताकि यह सुनिश्चित किया जा सके कि पेलोड में केवल वैध फ़ील्ड शामिल हैं।
इसके बाद, यह जांच करता है कि क्या प्रदान किया गया username
डेटाबेस में पहले से मौजूद है। यदि ऐसा होता है, तो नियंत्रक 400
स्टेटस कोड और एक अमान्य username
दर्शाते हुए एक त्रुटि संदेश के साथ प्रतिक्रिया करता है। यदि username
अद्वितीय है, तो नियंत्रक UserModel
की updateOneById
विधि का उपयोग करके डेटाबेस में उपयोगकर्ता डेटा को अपडेट करने के लिए आगे बढ़ता है।
सफल अद्यतन पर, यह 200
स्थिति कोड और अद्यतन उपयोगकर्ता डेटा के साथ प्रतिक्रिया करता है। अद्यतन प्रक्रिया के दौरान किसी भी त्रुटि के मामले में, नियंत्रक आंतरिक सर्वर त्रुटि को इंगित करने के लिए 500
स्थिति कोड के साथ प्रतिक्रिया करता है।
अंतिम चरण पासवर्ड को अपडेट करना होगा, यह विचार काफी हद तक उपयोगकर्ता डेटा को अपडेट करने जैसा ही है, लेकिन नए पासवर्ड को हैश करने के साथ। हमारी सूची src/controllers/user/updatePasswordController.ts
से अंतिम नियंत्रक बनाएं, और कोड जोड़ें:
import { Request, Response } from 'express'; import { UserModel } from 'src/models/UserModel'; import bcrypt from 'bcrypt'; export const updatePasswordController = async (req: Request, res: Response) => { try { const { password } = req.body; if (!password) return res.sendStatus(400); const hashedPassword = (await bcrypt.hash(password, 10)) as string; const user = await UserModel.updateOneById(req.user.id, { password: hashedPassword }); return res.status(200).json({ id: user.id }); } catch (error) { return res.sendStatus(500); } };
पासवर्ड नियंत्रक अद्यतन करें
अनुरोध प्राप्त होने पर, यह अनुरोध निकाय से नया पासवर्ड निकालता है। इसके बाद यह जांचता है कि अनुरोध निकाय में पासवर्ड प्रदान किया गया है या नहीं। यदि नहीं, तो यह 400
स्थिति कोड के साथ प्रतिक्रिया करता है, जो एक खराब अनुरोध का संकेत देता है। इसके बाद, यह 10 के नमक कारक के साथ bcrypt
लाइब्रेरी का उपयोग करके नया पासवर्ड हैश करता है।
इसके बाद हैश किए गए पासवर्ड को UserModel
की updateOneById
विधि का उपयोग करके डेटाबेस में सुरक्षित रूप से संग्रहीत किया जाता है, इसे user.id
के साथ जोड़ा जाता है। सफल पासवर्ड अपडेट पर, नियंत्रक 200
स्थिति कोड और उपयोगकर्ता की आईडी वाले JSON ऑब्जेक्ट के साथ प्रतिक्रिया करता है।
पासवर्ड अपडेट प्रक्रिया के दौरान किसी भी त्रुटि के मामले में, नियंत्रक अन्य नियंत्रकों की तरह आंतरिक सर्वर त्रुटि को इंगित करने के लिए 500
स्थिति कोड के साथ प्रतिक्रिया करता है।
से सत्यापन सहायक और उपयोगिताओं की समीक्षा करना और उन्हें स्थापित करना सुनिश्चित करें
आइए साइनअप समापन बिंदु की जाँच करें:
जैसा कि स्पष्ट है, हमें एक टोकन प्राप्त हुआ है, जिसका उपयोग सत्र को पुनः प्राप्त करने के लिए हेडर में किया जाएगा।
हमने हेडर में प्राधिकरण टोकन सर्वर को भेजा, और जवाब में, सर्वर ने हमें डेटाबेस से पुनर्प्राप्त उपयोगकर्ता डेटा प्रदान किया।
सुरक्षा सुविधाओं और रेडिस कैशिंग के बारे में जानने और प्रयोग करने के लिए स्वतंत्र महसूस करें। मूलभूत मॉडल के साथ, आप अतिरिक्त कार्यक्षमताओं में तल्लीन हो सकते हैं, जैसे कि उन उपयोगकर्ताओं के लिए खाता पुनर्प्राप्ति जो अपने पासवर्ड भूल जाते हैं। हालाँकि, यह विषय भविष्य के लेख के लिए आरक्षित रहेगा।
रूटिंग और उपयोगकर्ता प्रमाणीकरण प्रवाह को स्केलेबल तरीके से प्रबंधित करना चुनौतीपूर्ण हो सकता है। हालाँकि हमने मार्गों की सुरक्षा के लिए मिडलवेयर लागू किया है, लेकिन सेवा के प्रदर्शन और विश्वसनीयता को बढ़ाने के लिए अतिरिक्त रणनीतियाँ भी उपलब्ध हैं।
स्पष्ट त्रुटि संदेश प्रदान करके उपयोगकर्ता अनुभव को और बढ़ाया गया है, क्योंकि त्रुटि प्रबंधन एक महत्वपूर्ण पहलू है जिसके लिए अधिक व्यापक कवरेज की आवश्यकता होती है। हालाँकि, हमने प्राथमिक प्रमाणीकरण प्रवाह को सफलतापूर्वक कार्यान्वित किया है, जिससे उपयोगकर्ता साइन अप कर सकते हैं, अपने खातों तक पहुंच सकते हैं, सत्र डेटा पुनर्प्राप्त कर सकते हैं, उपयोगकर्ता जानकारी अपडेट कर सकते हैं और खाते हटा सकते हैं।
मुझे आशा है कि आपको यह यात्रा ज्ञानवर्धक लगी होगी और उपयोगकर्ता प्रमाणीकरण में बहुमूल्य ज्ञान प्राप्त हुआ होगा।
यहाँ भी प्रकाशित किया गया