User Tools

Site Tools


authentication:send_sms

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
authentication:send_sms [2026/03/09 07:23] sonaliauthentication:send_sms [2026/03/09 07:27] (current) – [Upload server.js from local machine (run on Windows):] sonali
Line 324: Line 324:
 <code> <code>
 nginx -s reload nginx -s reload
- 
-</code> 
- 
-==== Upload server.js from local machine (run on Windows): ==== 
- 
-<code> 
-scp d:/keycloak-test/server.js root@64.227.190.56:/root/keycloak-test/server.js 
- 
-</code> 
- 
-==== Start Node.js server: ==== 
- 
-<code> 
-cd /root/keycloak-test 
-nohup node server.js>> app.log 2>&1 & 
-# Check it started 
-tail -5 app.log 
- 
-</code> 
- 
-**Step 7: Update Nginx Config** 
- 
-<code> 
-cat> /etc/nginx/conf.d/keycloak.conf <<'EOF' 
-server { 
-  listen 443 ssl; 
-  server_name 64.227.190.56; 
-    ssl_certificate     /etc/nginx/ssl/keycloak.crt; 
-  ssl_certificate_key /etc/nginx/ssl/keycloak.key; 
- 
-    add_header Strict-Transport-Security "max-age=31536000" always; 
-  add_header X-Frame-Options SAMEORIGIN; 
-  add_header X-Content-Type-Options nosniff; 
- 
-    # Node.js API 
-  location /api/ { 
-   proxy_pass http://localhost:3000; 
-      proxy_set_header Host $host; 
-      proxy_set_header X-Real-IP $remote_addr; 
-      proxy_set_header X-Forwarded-Proto $scheme; 
-  } 
- 
-    # Webhook 
-  location /webhook/ { 
-      proxy_pass http://localhost:3000; 
-      proxy_set_header Host $host; 
-      proxy_set_header X-Real-IP $remote_addr; 
-      proxy_set_header X-Forwarded-Proto $scheme; 
-  } 
- 
-    # Keycloak\ 
-  location / { 
-      proxy_pass http://localhost:8080; 
-      proxy_set_header Host $host; 
-      proxy_set_header X-Real-IP $remote_addr; 
-      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
-      proxy_set_header X-Forwarded-Proto $scheme; 
-      proxy_buffer_size 128k; 
-      proxy_buffers 4 256k; 
-      proxy_busy_buffers_size 256k; 
-  } 
-} 
- 
-server { 
-  listen 80; 
-  server_name 64.227.190.56; 
-  return 301 https://$host$request_uri; 
-} 
-EOF 
- 
-</code> 
- 
-**# Test and reload Nginx** 
- 
-<code> 
-nginx -t && nginx -s reload 
- 
-</code> 
- <font 16px/inherit;;inherit;;inherit>**Step 8:Exotel SMS Configuration**</font> 
- 
-  * **Provider:**  Exotel 
-  * **Account SID:**''novuslogic1'' 
-  * **Sender ID:**''COTRAV''  (DLT approved) 
-  * **API endpoint:**''[[https://novuslogic1|https://novuslogic1]]:{token}@twilix.exotel.in/v1/Accounts/novuslogic1/Sms/send'' 
-  * **Approved message template:** 
-<code> 
- 
-Dear {name}, 
-OTP for login to your Cotrav Hotel Agent App Is {otp} 
-Regards, 
-Cotrav 
  
 </code> </code>
Line 420: Line 329:
 sample server.js sample server.js
  
-<code> +<code>## Complete server.js Code 
- +javascript
-## Complete server.js Code +
- +
-```javascript+
  
 const express = require('express'); const express = require('express');
Line 460: Line 366:
   for (const [id, session] of otpSessions) {   for (const [id, session] of otpSessions) {
  
-    if (session.createdAt < cutoff) otpSessions.delete(id);+    if (session.createdAt <cutoff) otpSessions.delete(id);
  
   }   }
Line 515: Line 421:
  
 } }
- 
-// ── Email OTP Login ─────────────────────────────────────────────────────────── 
- 
-app.post('/api/login', async (req, res) => { 
- 
-  const { kcUrl, realm, clientId, username } = req.body; 
- 
-  if (!kcUrl || !realm || !clientId || !username) 
- 
-    return res.status(400).json({ error: 'All fields are required.' }); 
- 
-  const redirectUri = `${req.protocol}://${req.get('host')}/callback`; 
- 
-  const authUrl = `${kcUrl}/realms/${realm}/protocol/openid-connect/auth`; 
- 
-  try { 
- 
-    const authRes = await axios.get(authUrl, { 
- 
-      params: { client_id: clientId, response_type: 'code', scope: 'openid', redirect_uri: redirectUri }, 
- 
-      httpsAgent, maxRedirects: 5, validateStatus: s => s < 500, 
- 
-    }); 
- 
-    if (authRes.status !== 200) { 
- 
-      let kcDetail = ''; 
- 
-      try { 
- 
-        const body = typeof authRes.data === 'string' ? authRes.data : JSON.stringify(authRes.data); 
- 
-        const errMatch = body.match(/error[^<]{0,200}/i); 
- 
-        kcDetail = errMatch ? ' | Keycloak says: ' + errMatch[0].replace(/<[^>]+>/g, '').trim().substring(0, 150) : ''; 
- 
-      } catch (_) {} 
- 
-      return res.status(400).json({ error: `Keycloak HTTP ${authRes.status} at ${authUrl}.${kcDetail}` }); 
- 
-    } 
- 
-    const loginActionUrl = authRes.data.match(/action="([^"]+)"/)[1].replace(/&amp;/g, '&'); 
- 
-    const loginCookies = parseCookies(authRes.headers['set-cookie']); 
- 
-    const credRes = await axios.post(loginActionUrl, qs.stringify({ username }), { 
- 
-      headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Cookie': loginCookies }, 
- 
-      httpsAgent, maxRedirects: 0, validateStatus: s => s < 500, 
- 
-    }); 
- 
-    if (credRes.status === 302 && credRes.headers.location?.includes('code=')) { 
- 
-      const code = new URL(credRes.headers.location).searchParams.get('code'); 
- 
-      const tokens = await exchangeCodeForTokens(kcUrl, realm, clientId, code, redirectUri); 
- 
-      return res.json({ success: true, tokens }); 
- 
-    } 
- 
-    if (credRes.status === 200 && credRes.data?.includes('Invalid username or password')) 
- 
-      return res.status(401).json({ error: 'Invalid username or password.' }); 
- 
-    let otpHtml = credRes.data; 
- 
-    let otpCookies = mergeCookies(loginCookies, parseCookies(credRes.headers['set-cookie'])); 
- 
-    if (credRes.status === 302 && credRes.headers.location && !credRes.headers.location.includes('code=')) { 
- 
-      const otpPageRes = await axios.get(credRes.headers.location, { 
- 
-        headers: { 'Cookie': otpCookies }, httpsAgent, maxRedirects: 5, validateStatus: s => s < 500, 
- 
-      }); 
- 
-      otpHtml = otpPageRes.data; 
- 
-      otpCookies = mergeCookies(otpCookies, parseCookies(otpPageRes.headers['set-cookie'])); 
- 
-    } 
- 
-    const otpActionMatch = otpHtml?.match(/action="([^"]+)"/); 
- 
-    if (!otpActionMatch) 
- 
-      return res.status(500).json({ error: 'Email OTP form not found. Check Keycloak flow configuration.' }); 
- 
-    const sessionId = generateSessionId(); 
- 
-    otpSessions.set(sessionId, { 
- 
-      otpActionUrl: otpActionMatch[1].replace(/&amp;/g, '&'), 
- 
-      cookies: otpCookies, kcUrl, realm, clientId, redirectUri, createdAt: Date.now(), 
- 
-    }); 
- 
-    return res.json({ requiresOTP: true, sessionId }); 
- 
-  } catch (err) { 
- 
-    return res.status(500).json({ error: `Login failed: ${err.response?.data || err.message}` }); 
- 
-  } 
- 
-}); 
- 
-// ── Email OTP Verify ────────────────────────────────────────────────────────── 
- 
-app.post('/api/verify-otp', async (req, res) => { 
- 
-  const { sessionId, otp } = req.body; 
- 
-  if (!sessionId || !otp) return res.status(400).json({ error: 'sessionId and otp are required.' }); 
- 
-  const session = otpSessions.get(sessionId); 
- 
-  if (!session) return res.status(400).json({ error: 'Session not found or expired.' }); 
- 
-  try { 
- 
-    const otpRes = await axios.post(session.otpActionUrl, qs.stringify({ 'email-otp': otp }), { 
- 
-      headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Cookie': session.cookies }, 
- 
-      httpsAgent, maxRedirects: 0, validateStatus: s => s < 500, 
- 
-    }); 
- 
-    if (otpRes.status === 302 && otpRes.headers.location?.includes('code=')) { 
- 
-      const code = new URL(otpRes.headers.location).searchParams.get('code'); 
- 
-      const { kcUrl, realm, clientId, redirectUri } = session; 
- 
-      const tokens = await exchangeCodeForTokens(kcUrl, realm, clientId, code, redirectUri); 
- 
-      otpSessions.delete(sessionId); 
- 
-      return res.json({ success: true, tokens }); 
- 
-    } 
- 
-    if (otpRes.status === 200) return res.status(401).json({ error: 'Invalid OTP. Please try again.' }); 
- 
-    return res.status(400).json({ error: `Unexpected response: HTTP ${otpRes.status}` }); 
- 
-  } catch (err) { 
- 
-    return res.status(500).json({ error: `OTP verification failed: ${err.message}` }); 
- 
-  } 
- 
-}); 
- 
-// ── Password Login ──────────────────────────────────────────────────────────── 
- 
-app.post('/api/login-password', async (req, res) => { 
- 
-  const { kcUrl, realm, clientId, username, password } = req.body; 
- 
-  if (!kcUrl || !realm || !clientId || !username || !password) 
- 
-    return res.status(400).json({ error: 'All fields are required.' }); 
- 
-  try { 
- 
-    const response = await axios.post( 
- 
-      `${kcUrl}/realms/${realm}/protocol/openid-connect/token`, 
- 
-      qs.stringify({ grant_type: 'password', client_id: clientId, username, password }), 
- 
-      { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, httpsAgent } 
- 
-    ); 
- 
-    return res.json({ success: true, tokens: response.data }); 
- 
-  } catch (err) { 
- 
-    const msg = err.response?.data?.error_description || err.response?.data?.error || err.message; 
- 
-    return res.status(401).json({ error: `Login failed: ${msg}` }); 
- 
-  } 
- 
-}); 
  
 // ── SMS OTP Login ───────────────────────────────────────────────────────────── // ── SMS OTP Login ─────────────────────────────────────────────────────────────
Line 730: Line 442:
     const tokenRes = await axios.post(     const tokenRes = await axios.post(
  
-      `${kcUrl}/realms/master/protocol/openid-connect/token`,+   `${kcUrl}/realms/master/protocol/openid-connect/token`,
  
       qs.stringify({ grant_type: 'password', client_id: 'admin-cli', username: 'super.admin', password: 'SuperAdmin@26' }),       qs.stringify({ grant_type: 'password', client_id: 'admin-cli', username: 'super.admin', password: 'SuperAdmin@26' }),
Line 780: Line 492:
       params: { client_id: smsClientId, response_type: 'code', scope: 'openid', redirect_uri: redirectUri },       params: { client_id: smsClientId, response_type: 'code', scope: 'openid', redirect_uri: redirectUri },
  
-      httpsAgent, maxRedirects: 5, validateStatus: s => s < 500,+      httpsAgent, maxRedirects: 5, validateStatus: s => s <500,
  
     });     });
Line 792: Line 504:
     if (!actionMatch) return res.status(500).json({ error: 'Could not find login form.' });     if (!actionMatch) return res.status(500).json({ error: 'Could not find login form.' });
  
-    const loginActionUrl = actionMatch[1].replace(/&amp;/g, '&');+    const loginActionUrl = actionMatch[1].replace(/&/g, '&');
  
     const loginCookies = parseCookies(authRes.headers['set-cookie']);     const loginCookies = parseCookies(authRes.headers['set-cookie']);
Line 800: Line 512:
       headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Cookie': loginCookies },       headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Cookie': loginCookies },
  
-      httpsAgent, maxRedirects: 0, validateStatus: s => s < 500,+      httpsAgent, maxRedirects: 0, validateStatus: s => s <500,
  
     });     });
Line 822: Line 534:
       const otpPageRes = await axios.get(credRes.headers.location, {       const otpPageRes = await axios.get(credRes.headers.location, {
  
-        headers: { 'Cookie': otpCookies }, httpsAgent, maxRedirects: 5, validateStatus: s => s < 500,+        headers: { 'Cookie': otpCookies }, httpsAgent, maxRedirects: 5, validateStatus: s => s <500,
  
       });       });
Line 842: Line 554:
     otpSessions.set(sessionId, {     otpSessions.set(sessionId, {
  
-      otpActionUrl: otpActionMatch[1].replace(/&amp;/g, '&'),+      otpActionUrl: otpActionMatch[1].replace(/&/g, '&'),
  
       cookies: otpCookies, kcUrl, realm, clientId: smsClientId, redirectUri, createdAt: Date.now(),       cookies: otpCookies, kcUrl, realm, clientId: smsClientId, redirectUri, createdAt: Date.now(),
Line 876: Line 588:
       headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Cookie': session.cookies },       headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Cookie': session.cookies },
  
-      httpsAgent, maxRedirects: 0, validateStatus: s => s < 500,+      httpsAgent, maxRedirects: 0, validateStatus: s => s <500,
  
     });     });
Line 918: Line 630:
   if (!phone.startsWith('+')) phone = '+91' + phone.replace(/^0+/, '');   if (!phone.startsWith('+')) phone = '+91' + phone.replace(/^0+/, '');
  
-  const sid   = process.env.EXOTEL_SID       || 'novuslogic1';+  const sid   = process.env.EXOTEL_SID       || 'actual sid';
  
-  const token = process.env.EXOTEL_API_TOKEN  || '94bfed570a17cd98d466175e1c893ad3cf5aef03';+  const token = process.env.EXOTEL_API_TOKEN  || 'actual token';
  
-  const from  = process.env.EXOTEL_FROM       || 'COTRAV';+  const from  = process.env.EXOTEL_FROM       || 'actual id';
  
   const message = `Dear ${name || 'User'},\n\nOTP for login to your Cotrav Hotel Agent App Is ${otp}\n\nRegards,\nCotrav`;   const message = `Dear ${name || 'User'},\n\nOTP for login to your Cotrav Hotel Agent App Is ${otp}\n\nRegards,\nCotrav`;
Line 934: Line 646:
       headers: { 'Content-Type': 'application/x-www-form-urlencoded' },       headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  
-      validateStatus: s => s < 600,+      validateStatus: s => s <600,
  
     });     });
Line 958: Line 670:
 app.listen(3000, () => console.log('Server running on http://localhost:3000')); app.listen(3000, () => console.log('Server running on http://localhost:3000'));
  
-```+</code> 
 +==== Upload server.js from local machine (run on Windows): ==== 
 +<code>
  
 +scp d:/keycloak-test/server.js root@64.227.190.56:/root/keycloak-test/server.js
  
 </code> </code>
 +
 +
 +==== Start Node.js server: ====
 +
 +<code>
 +cd /root/keycloak-test
 +nohup node server.js>> app.log 2>&1 &
 +# Check it started
 +tail -5 app.log
 +
 +</code>
 +
 +**Step 7: Update Nginx Config**
 +<code>
 +cat> /etc/nginx/conf.d/keycloak.conf <<'EOF'
 +server {
 +  listen 443 ssl;
 +  server_name 64.227.190.56;
 +    ssl_certificate     /etc/nginx/ssl/keycloak.crt;
 +  ssl_certificate_key /etc/nginx/ssl/keycloak.key;
 +
 +    add_header Strict-Transport-Security "max-age=31536000" always;
 +  add_header X-Frame-Options SAMEORIGIN;
 +  add_header X-Content-Type-Options nosniff;
 +
 +    # Node.js API
 +  location /api/ {
 +   proxy_pass http://localhost:3000;
 +      proxy_set_header Host $host;
 +      proxy_set_header X-Real-IP $remote_addr;
 +      proxy_set_header X-Forwarded-Proto $scheme;
 +  }
 +
 +    # Webhook
 +  location /webhook/ {
 +      proxy_pass http://localhost:3000;
 +      proxy_set_header Host $host;
 +      proxy_set_header X-Real-IP $remote_addr;
 +      proxy_set_header X-Forwarded-Proto $scheme;
 +  }
 +
 +    # Keycloak\
 +  location / {
 +      proxy_pass http://localhost:8080;
 +      proxy_set_header Host $host;
 +      proxy_set_header X-Real-IP $remote_addr;
 +      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 +      proxy_set_header X-Forwarded-Proto $scheme;
 +      proxy_buffer_size 128k;
 +      proxy_buffers 4 256k;
 +      proxy_busy_buffers_size 256k;
 +  }
 +}
 +
 +server {
 +  listen 80;
 +  server_name 64.227.190.56;
 +  return 301 https://$host$request_uri;
 +}
 +EOF
 +
 +</code>
 +
 +**# Test and reload Nginx**
 +<code>
 +nginx -t && nginx -s reload
 +
 +</code>
 + <font 16px/inherit;;inherit;;inherit>**Step 8:Exotel SMS Configuration**</font>
 +
 +  * **Provider:**  Exotel
 +  * **Account SID:**''novuslogic1''
 +  * **Sender ID:**''COTRAV''  (DLT approved)
 +  * **API endpoint:**''[[https://novuslogic1|https://novuslogic1]]:{token}@twilix.exotel.in/v1/Accounts/novuslogic1/Sms/send''
 +  * **Approved message template:**
 +<code>
 +
 +Dear {name},
 +OTP for login to your Cotrav Hotel Agent App Is {otp}
 +Regards,
 +Cotrav
 +
 +</code>
 +
 + 
  
  
authentication/send_sms.1773041036.txt.gz · Last modified: by sonali