authentication:send_sms
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| authentication:send_sms [2026/03/09 07:23] – sonali | authentication:send_sms [2026/03/09 07:27] (current) – [Upload server.js from local machine (run on Windows):] sonali | ||
|---|---|---|---|
| Line 324: | Line 324: | ||
| < | < | ||
| nginx -s reload | nginx -s reload | ||
| - | |||
| - | </ | ||
| - | |||
| - | ==== Upload server.js from local machine (run on Windows): ==== | ||
| - | |||
| - | < | ||
| - | scp d:/ | ||
| - | |||
| - | </ | ||
| - | |||
| - | ==== Start Node.js server: ==== | ||
| - | |||
| - | < | ||
| - | cd / | ||
| - | nohup node server.js>> | ||
| - | # Check it started | ||
| - | tail -5 app.log | ||
| - | |||
| - | </ | ||
| - | |||
| - | **Step 7: Update Nginx Config** | ||
| - | |||
| - | < | ||
| - | cat> / | ||
| - | server { | ||
| - | listen 443 ssl; | ||
| - | server_name 64.227.190.56; | ||
| - | ssl_certificate | ||
| - | ssl_certificate_key / | ||
| - | |||
| - | add_header Strict-Transport-Security " | ||
| - | add_header X-Frame-Options SAMEORIGIN; | ||
| - | add_header X-Content-Type-Options nosniff; | ||
| - | |||
| - | # Node.js API | ||
| - | location /api/ { | ||
| - | | ||
| - | 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:// | ||
| - | 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:// | ||
| - | 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:// | ||
| - | } | ||
| - | EOF | ||
| - | |||
| - | </ | ||
| - | |||
| - | **# Test and reload Nginx** | ||
| - | |||
| - | < | ||
| - | nginx -t && nginx -s reload | ||
| - | |||
| - | </ | ||
| - | < | ||
| - | |||
| - | * **Provider: | ||
| - | * **Account SID: | ||
| - | * **Sender ID: | ||
| - | * **API endpoint: | ||
| - | * **Approved message template:** | ||
| - | < | ||
| - | |||
| - | Dear {name}, | ||
| - | OTP for login to your Cotrav Hotel Agent App Is {otp} | ||
| - | Regards, | ||
| - | Cotrav | ||
| </ | </ | ||
| Line 420: | Line 329: | ||
| sample server.js | sample server.js | ||
| - | < | + | < |
| - | + | javascript | |
| - | ## Complete server.js Code | + | |
| - | + | ||
| - | ```javascript | + | |
| const express = require(' | const express = require(' | ||
| 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('/ | ||
| - | |||
| - | 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}:// | ||
| - | |||
| - | const authUrl = `${kcUrl}/ | ||
| - | |||
| - | try { | ||
| - | |||
| - | const authRes = await axios.get(authUrl, | ||
| - | |||
| - | params: { client_id: clientId, response_type: | ||
| - | |||
| - | httpsAgent, maxRedirects: | ||
| - | |||
| - | }); | ||
| - | |||
| - | if (authRes.status !== 200) { | ||
| - | |||
| - | let kcDetail = ''; | ||
| - | |||
| - | try { | ||
| - | |||
| - | const body = typeof authRes.data === ' | ||
| - | |||
| - | const errMatch = body.match(/ | ||
| - | |||
| - | kcDetail = errMatch ? ' | Keycloak says: ' + errMatch[0].replace(/< | ||
| - | |||
| - | } catch (_) {} | ||
| - | |||
| - | return res.status(400).json({ error: `Keycloak HTTP ${authRes.status} at ${authUrl}.${kcDetail}` }); | ||
| - | |||
| - | } | ||
| - | |||
| - | const loginActionUrl = authRes.data.match(/ | ||
| - | |||
| - | const loginCookies = parseCookies(authRes.headers[' | ||
| - | |||
| - | const credRes = await axios.post(loginActionUrl, | ||
| - | |||
| - | headers: { ' | ||
| - | |||
| - | httpsAgent, maxRedirects: | ||
| - | |||
| - | }); | ||
| - | |||
| - | if (credRes.status === 302 && credRes.headers.location? | ||
| - | |||
| - | const code = new URL(credRes.headers.location).searchParams.get(' | ||
| - | |||
| - | const tokens = await exchangeCodeForTokens(kcUrl, | ||
| - | |||
| - | return res.json({ success: true, tokens }); | ||
| - | |||
| - | } | ||
| - | |||
| - | if (credRes.status === 200 && credRes.data? | ||
| - | |||
| - | return res.status(401).json({ error: ' | ||
| - | |||
| - | let otpHtml = credRes.data; | ||
| - | |||
| - | let otpCookies = mergeCookies(loginCookies, | ||
| - | |||
| - | if (credRes.status === 302 && credRes.headers.location && !credRes.headers.location.includes(' | ||
| - | |||
| - | const otpPageRes = await axios.get(credRes.headers.location, | ||
| - | |||
| - | headers: { ' | ||
| - | |||
| - | }); | ||
| - | |||
| - | otpHtml = otpPageRes.data; | ||
| - | |||
| - | otpCookies = mergeCookies(otpCookies, | ||
| - | |||
| - | } | ||
| - | |||
| - | const otpActionMatch = otpHtml? | ||
| - | |||
| - | if (!otpActionMatch) | ||
| - | |||
| - | return res.status(500).json({ error: 'Email OTP form not found. Check Keycloak flow configuration.' | ||
| - | |||
| - | const sessionId = generateSessionId(); | ||
| - | |||
| - | otpSessions.set(sessionId, | ||
| - | |||
| - | otpActionUrl: | ||
| - | |||
| - | cookies: otpCookies, kcUrl, realm, clientId, redirectUri, | ||
| - | |||
| - | }); | ||
| - | |||
| - | return res.json({ requiresOTP: | ||
| - | |||
| - | } catch (err) { | ||
| - | |||
| - | return res.status(500).json({ error: `Login failed: ${err.response? | ||
| - | |||
| - | } | ||
| - | |||
| - | }); | ||
| - | |||
| - | // ── Email OTP Verify ────────────────────────────────────────────────────────── | ||
| - | |||
| - | app.post('/ | ||
| - | |||
| - | const { sessionId, otp } = req.body; | ||
| - | |||
| - | if (!sessionId || !otp) return res.status(400).json({ error: ' | ||
| - | |||
| - | const session = otpSessions.get(sessionId); | ||
| - | |||
| - | if (!session) return res.status(400).json({ error: ' | ||
| - | |||
| - | try { | ||
| - | |||
| - | const otpRes = await axios.post(session.otpActionUrl, | ||
| - | |||
| - | headers: { ' | ||
| - | |||
| - | httpsAgent, maxRedirects: | ||
| - | |||
| - | }); | ||
| - | |||
| - | if (otpRes.status === 302 && otpRes.headers.location? | ||
| - | |||
| - | const code = new URL(otpRes.headers.location).searchParams.get(' | ||
| - | |||
| - | const { kcUrl, realm, clientId, redirectUri } = session; | ||
| - | |||
| - | const tokens = await exchangeCodeForTokens(kcUrl, | ||
| - | |||
| - | otpSessions.delete(sessionId); | ||
| - | |||
| - | return res.json({ success: true, tokens }); | ||
| - | |||
| - | } | ||
| - | |||
| - | if (otpRes.status === 200) return res.status(401).json({ error: ' | ||
| - | |||
| - | 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('/ | ||
| - | |||
| - | 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}/ | ||
| - | |||
| - | qs.stringify({ grant_type: ' | ||
| - | |||
| - | { headers: { ' | ||
| - | |||
| - | ); | ||
| - | |||
| - | return res.json({ success: true, tokens: response.data }); | ||
| - | |||
| - | } catch (err) { | ||
| - | |||
| - | const msg = err.response? | ||
| - | |||
| - | 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}/ |
| qs.stringify({ grant_type: ' | qs.stringify({ grant_type: ' | ||
| Line 780: | Line 492: | ||
| params: { client_id: smsClientId, | params: { client_id: smsClientId, | ||
| - | httpsAgent, maxRedirects: | + | httpsAgent, maxRedirects: |
| }); | }); | ||
| 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(/& | + | const loginActionUrl = actionMatch[1].replace(/&/ |
| const loginCookies = parseCookies(authRes.headers[' | const loginCookies = parseCookies(authRes.headers[' | ||
| Line 800: | Line 512: | ||
| headers: { ' | headers: { ' | ||
| - | httpsAgent, maxRedirects: | + | httpsAgent, maxRedirects: |
| }); | }); | ||
| Line 822: | Line 534: | ||
| const otpPageRes = await axios.get(credRes.headers.location, | const otpPageRes = await axios.get(credRes.headers.location, | ||
| - | headers: { ' | + | headers: { ' |
| }); | }); | ||
| Line 842: | Line 554: | ||
| otpSessions.set(sessionId, | otpSessions.set(sessionId, | ||
| - | otpActionUrl: | + | otpActionUrl: |
| cookies: otpCookies, kcUrl, realm, clientId: smsClientId, | cookies: otpCookies, kcUrl, realm, clientId: smsClientId, | ||
| Line 876: | Line 588: | ||
| headers: { ' | headers: { ' | ||
| - | httpsAgent, maxRedirects: | + | httpsAgent, maxRedirects: |
| }); | }); | ||
| Line 918: | Line 630: | ||
| if (!phone.startsWith(' | if (!phone.startsWith(' | ||
| - | const sid = process.env.EXOTEL_SID | + | const sid = process.env.EXOTEL_SID |
| - | const token = process.env.EXOTEL_API_TOKEN | + | const token = process.env.EXOTEL_API_TOKEN |
| - | const from = process.env.EXOTEL_FROM | + | const from = process.env.EXOTEL_FROM |
| const message = `Dear ${name || ' | const message = `Dear ${name || ' | ||
| Line 934: | Line 646: | ||
| headers: { ' | headers: { ' | ||
| - | validateStatus: | + | validateStatus: |
| }); | }); | ||
| Line 958: | Line 670: | ||
| app.listen(3000, | app.listen(3000, | ||
| - | ``` | + | </ |
| + | ==== Upload server.js from local machine (run on Windows): ==== | ||
| + | < | ||
| + | scp d:/ | ||
| </ | </ | ||
| + | |||
| + | |||
| + | ==== Start Node.js server: ==== | ||
| + | |||
| + | < | ||
| + | cd / | ||
| + | nohup node server.js>> | ||
| + | # Check it started | ||
| + | tail -5 app.log | ||
| + | |||
| + | </ | ||
| + | |||
| + | **Step 7: Update Nginx Config** | ||
| + | < | ||
| + | cat> / | ||
| + | server { | ||
| + | listen 443 ssl; | ||
| + | server_name 64.227.190.56; | ||
| + | ssl_certificate | ||
| + | ssl_certificate_key / | ||
| + | |||
| + | add_header Strict-Transport-Security " | ||
| + | add_header X-Frame-Options SAMEORIGIN; | ||
| + | add_header X-Content-Type-Options nosniff; | ||
| + | |||
| + | # Node.js API | ||
| + | location /api/ { | ||
| + | | ||
| + | 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:// | ||
| + | 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:// | ||
| + | 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:// | ||
| + | } | ||
| + | EOF | ||
| + | |||
| + | </ | ||
| + | |||
| + | **# Test and reload Nginx** | ||
| + | < | ||
| + | nginx -t && nginx -s reload | ||
| + | |||
| + | </ | ||
| + | < | ||
| + | |||
| + | * **Provider: | ||
| + | * **Account SID: | ||
| + | * **Sender ID: | ||
| + | * **API endpoint: | ||
| + | * **Approved message template:** | ||
| + | < | ||
| + | |||
| + | Dear {name}, | ||
| + | OTP for login to your Cotrav Hotel Agent App Is {otp} | ||
| + | Regards, | ||
| + | Cotrav | ||
| + | |||
| + | </ | ||
| + | |||
| + | |||
authentication/send_sms.1773041036.txt.gz · Last modified: by sonali
