From 60d72c5d43861781b48dac6e139cab5d865134fd Mon Sep 17 00:00:00 2001 From: f-w Date: Mon, 26 Nov 2018 09:37:42 -0800 Subject: [PATCH] support voice prompt translation in 4 languages --- README.md | 38 +++++++++++++++++++++++++++++++------- app.code-workspace | 3 +++ package.json | 4 ++-- src/index.ts | 34 ++++++++++++++++++++++++++++------ 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c3ee27a..aec69a7 100644 --- a/README.md +++ b/README.md @@ -94,13 +94,37 @@ The tests cover the following cases: ###### API Specification: -Request Type | API Endpoint | Parameters | Returns | Purpose ------------- | ------------- | ------------- | ------------- | ------------- -HTTP GET | / or /status | | OK | Returns "OK" if the service is running -HTTP POST | /captcha | request body: { nonce: string } | { "nonce": string, "captcha": string, validation": JSON}| Retrieve a captcha to be displayed to a user -HTTP POST | /captcha/audio | request body: { validation: JSON } | { "audio": dataUri}| Retrieve the audio for a given captcha validation object, returns MP3 in DataUri format -HTTP POST | /verify/captcha | request body: { nonce: string, answer: string, validation: JSON } | { valid: true/false, jwt: JWT } | Compare the answer to the encryptedAnswer, return a signed JWT if successful -HTTP POST | /verify/jwt | request body: { nonce: string, token: JWT } | { valid: true/false } | Validate a signed JWT by resource server + +* GET / or /status + * returns: "OK" if the service is running +* POST /captcha + * input: request body: { nonce: \ } + * returns: {nonce: \, captcha: \, validation: \} + + Retrieve a captcha to be displayed to a user +* POST /captcha/audio + * input: request body: { validation: \ [, translation: \\|true] } + * returns: {audio: \} + + Retrieve the audio for a given captcha validation object, returns MP3 in DataUri format. The audio prompt is spoken in English unless *transaltion* is specified. *transaltion* can be either a string of one of following supported language acronyms + + * *en* (English) + * *fr* (French) + * *pa* (Punjabi) + * *zh* (Mandarin Chinese) + + or the boolean value of *true*. If *transaltion* is set to *true*, then the first language set in the http *Accept-Language* request header matching the above list is chosen. If there is no match, then fall backs to English. + +* POST /verify/captcha + * input: request body: { nonce: \, answer: \, validation: \ } + * returns: { valid: true\|false, jwt: \ } + + Compare the answer to the encryptedAnswer, return a signed JWT if the answer if valid +* POST /verify/jwt + * input: request body: { nonce: \, token: \ } + * returns: { valid: true\|false } + + Validate a signed JWT by resource server #### API Demo You can try it out the API for yourself at our demo environment by following the above API specs: diff --git a/app.code-workspace b/app.code-workspace index d34c4fc..965e11e 100644 --- a/app.code-workspace +++ b/app.code-workspace @@ -5,6 +5,9 @@ }, { "path": "../text2wav.node.js" + }, + { + "path": "../MyGovBC-CAPTCHA-Widget" } ], "settings": {} diff --git a/package.json b/package.json index 57ad3b8..cacfe8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mygovbc-captcha-service", - "version": "7.1.0", + "version": "7.2.0", "description": "A reusable, secure and reliable CAPTCHA microservice for service providers to use within online digital services.", "main": "lib/index.js", "scripts": { @@ -24,7 +24,7 @@ "os": "^0.1.1", "streamifier": "^0.1.1", "svg-captcha": "^1.3.1", - "text2wav": "^0.0.7", + "text2wav": "^0.0.10", "ts-node": "^7.0.1", "typescript": "^3.1.3", "wav": "^1.0.1", diff --git a/src/index.ts b/src/index.ts index 3939753..66af3be 100644 --- a/src/index.ts +++ b/src/index.ts @@ -340,9 +340,17 @@ app.post('/verify/captcha', async function (req: Request, res: Response) { //////////////////////////////////////////////////////// interface GetAudioRequestBody { validation: object, + translation?: string | boolean, } -var getAudio = async function (body: GetAudioRequestBody) { +const voicePromptLanguageMap: { [index: string]: string } = { + en: 'Please type in following letters or numbers', // english + fr: 'Veuillez saisir les lettres ou les chiffres suivants', // french + pa: 'ਕਿਰਪਾ ਕਰਕੇ ਹੇਠ ਲਿਖੇ ਅੱਖਰ ਜਾਂ ਨੰਬਰ ਟਾਈਪ ਕਰੋ', // punjabi + zh: '请输入以下英文字母或数字', // mandarin chinese +} + +var getAudio = async function (body: GetAudioRequestBody, req?: Request) { winston.debug(`getting audio for`, body) try { // Ensure audio is enabled. @@ -362,11 +370,25 @@ var getAudio = async function (body: GetAudioRequestBody) { // Insert leading text and commas to slow down reader var captchaCharArray = decryptedBody.answer.toString().split("") - var spokenCatpcha = "Please type in following letters or numbers: " + let language = 'en' + if (body.translation) { + if (typeof body.translation == 'string') { + if (voicePromptLanguageMap.hasOwnProperty(body.translation)) { + language = body.translation + } + } + else if (body.translation === true && req && req.headers['accept-language']) { + let lang = (req.headers['accept-language']).split(',').map(e => e.split(';')[0].split('-')[0]).find(e => voicePromptLanguageMap.hasOwnProperty(e)) + if (lang) { + language = lang + } + } + } + var spokenCatpcha = voicePromptLanguageMap[language] + ": " for (var i = 0; i < captchaCharArray.length; i++) { spokenCatpcha += captchaCharArray[i] + ", " } - let audioDataUri = await getMp3DataUriFromText(spokenCatpcha) + let audioDataUri = await getMp3DataUriFromText(spokenCatpcha, language) // Now pass back the full payload , return { audio: audioDataUri @@ -380,7 +402,7 @@ var getAudio = async function (body: GetAudioRequestBody) { } app.post('/captcha/audio', async function (req: Request, res: Response) { - let ret = await getAudio(req.body) + let ret = await getAudio(req.body, req) return res.send(ret) }) @@ -440,7 +462,7 @@ app.post('/verify/jwt', async function (req: Request, res: Response) { * Audio routines */ //////////////////////////////////////////////////////// -function getMp3DataUriFromText(text: string) { +function getMp3DataUriFromText(text: string, language: string = 'en') { winston.debug("Starting audio generation...") return new Promise(async function (resolve) { @@ -480,7 +502,7 @@ function getMp3DataUriFromText(text: string) { // Generate audio, Base64 encoded WAV in DataUri format including mime type header winston.debug("Generate speach as WAV in ArrayBuffer") - var audioArrayBuffer = await text2wav(text) + let audioArrayBuffer = await text2wav(text, { voice: language }) // convert to buffer winston.debug("Convert arraybuffer to buffer")