TT#122015 Add QR-Code to login to sip:phone mobile app from CSC

- TT#128156 Add QR-Code button to the header
- TT#128157 Add QR-Code render library
- TT#128158 Implement QR-Code generation
- TT#129205 Render QR-Code in the popup
- TT#129224 Create store test and api test (including endpoint mockup)

NOTE
You need to enable sip_phone.show_qr_csc in /etc/ngcp-config/config.yml of your environment to be able to see the QR code icon

Change-Id: Ifa065ef057549696387026c5a62cf0f5297ffb05
(cherry picked from commit 389a6bcb9d)
mr9.5.2
Carlo Venusino 4 years ago committed by Hans-Peter Herzog
parent b26dd3a9f8
commit ccdf8971bb

2
env/Dockerfile vendored

@ -5,7 +5,7 @@ FROM docker.mgm.sipwise.com/sipwise-buster:latest
# is updated with the current date. It will force refresh of all
# of the base images and things like `apt-get update` won't be using
# old cached versions when the Dockerfile is built.
ENV REFRESHED_AT 2020-08-27
ENV REFRESHED_AT 2021-07-07
# files that get-code generates
COPY env/sources.list.d/builddeps.list /etc/apt/sources.list.d/

@ -35,6 +35,7 @@
"load-script": "^1.0.0",
"lodash": "4.17.21",
"moment": "^2.27.0",
"qrcode": "1.4.4",
"quasar": "^1.15.2",
"vue-i18n": "^8.0.0",
"vue-password-strength-meter": "^1.7.2",

@ -3,6 +3,7 @@ import _ from 'lodash'
import Vue from 'vue'
import {
get,
post,
getList,
patchReplace
} from './common'
@ -135,3 +136,14 @@ export async function getResellerBranding () {
resource: 'resellerbrandings'
})
}
export async function createAuthToken (tokenExpiringTime) {
const response = await post({
resource: 'authtokens',
body: {
type: 'onetime',
expires: tokenExpiringTime
}
})
return response.token
}

@ -0,0 +1,56 @@
<template>
<csc-dialog
ref="dialog"
:title="$t('Scan to login sip:phone')"
:opened="true"
@cancel="hide"
>
<q-img
v-if="qrCode"
class="full-width justify-center"
:src="qrCode"
spinner-color="primary"
:ratio="1"
width="300px"
/>
<div
v-else
class="full-width text-center text-negative "
>
{{ $t('QR code unavailable. Please retry later') }}
</div>
</csc-dialog>
</template>
<script>
import CscDialog from './CscDialog'
import { mapState } from 'vuex'
export default {
name: 'CscDialogQrCode',
components: {
CscDialog
},
data () {
return {
dataImg: null
}
},
computed: {
...mapState('user', [
'qrCode',
'qrExpiringTime'
])
},
methods: {
show () {
this.$refs.dialog.show()
setTimeout(() => {
this.$emit('hide')
}, Number(this.qrExpiringTime) * 1000)
},
hide () {
this.$refs.dialog.hide()
}
}
}
</script>

@ -0,0 +1,3 @@
export function qrPayload ({ subscriber, server, token }) {
return `username=${subscriber}&server=${server}&token=${token}`
}

@ -385,6 +385,7 @@
"PBX Settings": "PBX Settings",
"PIN": "PIN",
"Page Header": "Page Header",
"Page not found": "Page not found",
"Parallel Ringing": "Parallel Ringing",
"Password": "Password",
"Password Retype": "Password Retype",
@ -412,6 +413,7 @@
"Privacy": "Privacy",
"Private": "Private",
"Q-Value": "Q-Value",
"QR code unavailable. Please retry later": "QR code unavailable. Please retry later",
"Quality": "Quality",
"Queue Length": "Queue Length",
"Random Ringing": "Random Ringing",
@ -469,6 +471,7 @@
"Renew Notify Email": "Renew Notify Email",
"Reset": "Reset",
"Reset Filters": "Reset Filters",
"Reset Password": "Reset Password",
"Reset filters": "Reset filters",
"Reset greeting sound": "Reset greeting sound",
"Reset of timesets completed": "Reset of timesets completed",
@ -484,6 +487,8 @@
"Saturday": "Saturday",
"Save": "Save",
"Save new password": "Save new password",
"Scan QR code": "Scan QR code",
"Scan to login sip:phone": "Scan to login sip:phone",
"Screen Share": "Screen Share",
"Search": "Search",
"Seat": "Seat",

@ -70,6 +70,15 @@
/>
</q-menu>
</q-btn>
<q-btn
v-if="showQrBtn"
flat
dense
class="q-ml-sm"
icon="qr_code"
color="primary"
@click="showQrDialog"
/>
<q-space />
<csc-logo
v-if="isLogoRequested && !customLogo"
@ -226,6 +235,7 @@ import CscMainMenuTop from 'components/CscMainMenuTop'
import CscPopupMenu from 'components/CscPopupMenu'
import CscPopupMenuItem from 'components/CscPopupMenuItem'
import AuiMobileAppBadges from 'components/AuiMobileAppBadges'
import CscDialogQrCode from 'components/CscDialogQrCode'
export default {
name: 'CscMainLayout',
@ -312,12 +322,16 @@ export default {
]),
...mapState('user', [
'resellerBranding',
'defaultBranding'
'defaultBranding',
'platformInfo'
]),
...mapGetters('communication', [
'createFaxState',
'createFaxError'
]),
showQrBtn () {
return this.platformInfo?.app?.show_qr
},
hasCommunicationCapabilities () {
return (this.hasSmsCapability && this.hasSendSmsFeature) ||
(this.hasFaxCapabilityAndFaxActive && this.hasSendFaxFeature)
@ -449,7 +463,8 @@ export default {
},
methods: {
...mapActions('user', [
'forwardHome'
'forwardHome',
'fetchAuthToken'
]),
layoutResized () {
this.$refs.call.fitMedia()
@ -535,6 +550,13 @@ export default {
if (secondaryColor) {
colors.setBrand('secondary', secondaryColor)
}
},
async showQrDialog () {
await this.fetchAuthToken()
this.$q.dialog({
component: CscDialogQrCode,
parent: this
})
}
}
}

@ -11,7 +11,8 @@ import {
} from './common'
import {
login,
getUserData
getUserData,
createAuthToken
} from '../api/user'
import {
changePassword,
@ -24,6 +25,10 @@ import {
import { deleteJwt, getJwt, getSubscriberId, setJwt, setSubscriberId } from 'src/auth'
import { setSession } from 'src/storage'
import { get } from 'src/api/common'
import QRCode from 'qrcode'
import {
qrPayload
} from 'src/helpers/qr'
export default {
namespaced: true,
@ -58,7 +63,9 @@ export default {
resellerBranding: null,
defaultBranding: {},
subscriberRegistrations: [],
platformInfo: null
platformInfo: null,
qrCode: null,
qrExpiringTime: null
},
getters: {
isLogged (state) {
@ -307,6 +314,12 @@ export default {
},
platformInfo (state, payload) {
state.platformInfo = payload
},
setQrCode (state, qrCode) {
state.qrCode = qrCode
},
setQrExpiringTime (state, qrExpiringTime) {
state.qrExpiringTime = qrExpiringTime
}
},
actions: {
@ -435,6 +448,22 @@ export default {
commit('setSubscriberRegistrations', [])
throw err
}
},
async fetchAuthToken ({ commit, state, getters }, expiringTime = 300) {
const subscriber = state.subscriber
commit('setQrExpiringTime', expiringTime)
try {
const authToken = await createAuthToken(expiringTime)
const data = qrPayload({
subscriber: subscriber.username,
server: subscriber.domain,
token: authToken
})
const qrCode = await QRCode.toDataURL(data)
commit('setQrCode', qrCode)
} catch (err) {
commit('setQrCode', null)
}
}
}
}

@ -5,7 +5,7 @@ FROM docker.mgm.sipwise.com/sipwise-bullseye:latest
# is updated with the current date. It will force refresh of all
# of the base images and things like `apt-get update` won't be using
# old cached versions when the Dockerfile is built.
ENV REFRESHED_AT 2021-05-03
ENV REFRESHED_AT 2021-07-07
ENV DEBIAN_FRONTEND noninteractive
ENV DISPLAY=:0

@ -0,0 +1,25 @@
/* eslint-disable */
import Vue from 'vue'
import VueResource from 'vue-resource'
import { createAuthToken } from 'src/api/user'
Vue.use(VueResource)
describe('User API', () => {
beforeEach( () => {
Vue.http.interceptors = []
})
it('should fetch an authtoken', async () => {
const authToken = 'd73ddf3a-0bf3-47bd-bee9-13bd972b37ec'
Vue.http.interceptors.unshift((request, next) => {
next(request.respondWith(JSON.stringify({
token: authToken
}), {
status: 201
}))
})
const response = await createAuthToken(300)
expect(response).toEqual(authToken)
})
})

@ -0,0 +1,22 @@
/* eslint-disable */
import {
qrPayload
} from 'src/helpers/qr'
describe('QR helpers', function () {
it('checks the format of QR payload', () => {
const subscriber = '43991002'
const server = 'sipwise.com'
const token = 'e7cd5253-79fc-4aec-bb1b-4b86eff96c7d'
const payload = `username=${subscriber}&server=${server}&token=${token}`
const result = qrPayload({
subscriber: subscriber,
server: server,
token: token
})
expect(result).toBe(payload)
})
})

@ -24,4 +24,12 @@ describe('UserStore', () => {
expect(state.jwt).toBe('1234')
expect(state.subscriberId).toBe('5678')
})
it('should successfully store a qrcode ',() => {
const dataImg = 'data:image;123456789'
const state = {}
UserStore.mutations.setQrCode(state, dataImg)
expect(state.qrCode).toBe(dataImg)
})
})

@ -2488,7 +2488,7 @@ base64-js@^1.0.2:
resolved "https://npm-registry.sipwise.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
base64-js@^1.5.1:
base64-js@^1.3.1, base64-js@^1.5.1:
version "1.5.1"
resolved "https://npm-registry.sipwise.com/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@ -2788,7 +2788,7 @@ buffer-fill@^1.0.0:
resolved "https://npm-registry.sipwise.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
buffer-from@^1.0.0:
buffer-from@^1.0.0, buffer-from@^1.1.1:
version "1.1.1"
resolved "https://npm-registry.sipwise.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
@ -2820,6 +2820,14 @@ buffer@^5.1.0, buffer@^5.2.1, buffer@^5.5.0:
base64-js "^1.0.2"
ieee754 "^1.1.4"
buffer@^5.4.3:
version "5.7.1"
resolved "https://npm-registry.sipwise.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.1.13"
builtin-status-codes@^3.0.0:
version "3.0.0"
resolved "https://npm-registry.sipwise.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@ -4221,6 +4229,11 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
dijkstrajs@^1.0.1:
version "1.0.2"
resolved "https://npm-registry.sipwise.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257"
integrity sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://npm-registry.sipwise.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@ -6106,6 +6119,11 @@ icss-utils@^4.0.0, icss-utils@^4.1.1:
dependencies:
postcss "^7.0.14"
ieee754@^1.1.13:
version "1.2.1"
resolved "https://npm-registry.sipwise.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ieee754@^1.1.4:
version "1.1.13"
resolved "https://npm-registry.sipwise.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
@ -6675,6 +6693,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
resolved "https://npm-registry.sipwise.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
isarray@^2.0.1:
version "2.0.5"
resolved "https://npm-registry.sipwise.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
isbinaryfile@4.0.6:
version "4.0.6"
resolved "https://npm-registry.sipwise.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b"
@ -9004,6 +9027,11 @@ pn@^1.1.0:
resolved "https://npm-registry.sipwise.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
pngjs@^3.3.0:
version "3.4.0"
resolved "https://npm-registry.sipwise.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
portfinder@^1.0.26:
version "1.0.28"
resolved "https://npm-registry.sipwise.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
@ -9550,6 +9578,19 @@ q@^1.1.2:
resolved "https://npm-registry.sipwise.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
qrcode@1.4.4:
version "1.4.4"
resolved "https://npm-registry.sipwise.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83"
integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==
dependencies:
buffer "^5.4.3"
buffer-alloc "^1.2.0"
buffer-from "^1.1.1"
dijkstrajs "^1.0.1"
isarray "^2.0.1"
pngjs "^3.3.0"
yargs "^13.2.4"
qs@6.7.0:
version "6.7.0"
resolved "https://npm-registry.sipwise.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@ -12410,7 +12451,7 @@ yargs@^12.0.5:
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^11.1.1"
yargs@^13.3.0, yargs@^13.3.2:
yargs@^13.2.4, yargs@^13.3.0, yargs@^13.3.2:
version "13.3.2"
resolved "https://npm-registry.sipwise.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==

Loading…
Cancel
Save