Created
February 28, 2024 03:57
-
-
Save kiler129/5d49ab331b91976e0b4da0d9461379aa to your computer and use it in GitHub Desktop.
CloudFlare worker to redirect SMS to Telegram
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Receives Twilio SMS WebHook and sends message via Telegram | |
// | |
//Define the following as variables on CF: | |
// - ACCESS_USER (encrypt): long unpredictible string, shared with Twilio | |
// - ACCESS_PASS (encrypt): long unpredictible string, shared with Twilio | |
// - TELEGRAM_CHAT_ID: chat id for Telegram conversation | |
// - TELEGRAM_API_TOKEN (encrypt): API token for Telegram intragration | |
// | |
// Configure Twilio with SMS WebHook to call https://<ACCESS_USER>:<ACCESS_PASS>@<worker name>.<cf login>.workers.dev/notify | |
// e.g. https://foo:[email protected]/notify | |
const DEFAULT_HEADERS = { | |
'Content-Type': 'application/xml', | |
'Cache-Control': 'no-store', | |
}; | |
async function handleRequest(request) { | |
const {protocol, pathname} = new URL(request.url); | |
if ('https:' !== protocol || 'https' !== request.headers.get('x-forwarded-proto')) { | |
throw new RejectRequest(400, 'Not HTTPS'); | |
} | |
if (pathname !== '/notify') { | |
throw new RejectRequest(400, 'Not /notify'); | |
} | |
if (!request.headers.has('Authorization')) { | |
//This must happen on production as per flow docks: https://www.twilio.com/docs/usage/security#http-authentication | |
//Without first 401-ing it will pass sandbox testing but it will fail on production! | |
throw new RejectRequest(401, 'No Authorization header (yet?)', {'WWW-Authenticate': 'Basic realm="worker"'}); | |
} | |
const {user, pass} = basicAuthentication(request); | |
if (user !== ACCESS_USER || pass !== ACCESS_PASS) { | |
throw new RejectRequest(403, 'Wrong credentials received'); | |
} | |
const contentType = request.headers.get('content-type') || ''; | |
if (!contentType.includes('application/x-www-form-urlencoded')) { | |
throw new RejectRequest(400, 'Expected x-www-form-urlencoded'); | |
} | |
//See https://www.twilio.com/docs/messaging/guides/webhook-request for details | |
const formData = await request.formData(); | |
const body = {}; | |
for (const entry of formData.entries()) { | |
body[entry[0]] = entry[1]; | |
} | |
if (!body.hasOwnProperty('From') || !body.hasOwnProperty('To') || !body.hasOwnProperty('Body')) { | |
console.error(body); | |
throw new RejectRequest(400, 'Expected to get "From", "To", and "Body" - one of them was missing?!'); | |
} | |
await sendTelegramMessage(body['From'], body['To'], body['Body']); | |
return new Response('<Response></Response>', { status: 200, headers: DEFAULT_HEADERS }); | |
} | |
async function sendTelegramMessage(from, to, contents) { | |
const tMessage = { | |
chat_id: TELEGRAM_CHAT_ID, | |
text: '<b>From:</b> ' + from + '\n<b>To:</b> ' + to + '\n<b>Contents:</b>\n' + contents, | |
parse_mode: 'HTML', | |
no_webpage: true, | |
noforwards: true, | |
}; | |
const tReq = new Request( | |
'https://api.telegram.org/bot' + TELEGRAM_API_TOKEN + '/sendMessage', | |
{ | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify(tMessage), | |
} | |
); | |
return fetch(tReq); | |
} | |
function RejectRequest(code, reason, extraHeaders) { | |
this.status = code; | |
this.reason = reason; | |
this.extraHeaders = extraHeaders; | |
} | |
function basicAuthentication(request) { | |
const Authorization = request.headers.get('Authorization'); | |
const [scheme, encoded] = Authorization.split(' '); | |
if (!encoded || scheme !== 'Basic') { | |
throw new RejectRequest(400, 'Invalid encoding or scheme != Basic'); | |
} | |
const buffer = Uint8Array.from(atob(encoded), character => character.charCodeAt(0)); | |
const decoded = new TextDecoder().decode(buffer).normalize(); | |
const index = decoded.indexOf(':'); | |
if (index === -1 || /[\0-\x1F\x7F]/.test(decoded)) { | |
throw new RejectRequest(400, 'Malformed authorization value'); | |
} | |
return {user: decoded.substring(0, index), pass: decoded.substring(index + 1)}; | |
} | |
addEventListener('fetch', event => { | |
event.respondWith( | |
handleRequest(event.request).catch(err => { | |
console.error('handleRequest reject: ' + err.reason); | |
return new Response('', { | |
status: err.status || 500, | |
statusText: null, | |
headers: (err.extraHeaders) ? {...DEFAULT_HEADERS, ...err.extraHeaders} : DEFAULT_HEADERS, | |
}); | |
}) | |
) | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment