mirror of
https://github.com/jumpserver/lina.git
synced 2025-09-21 02:59:07 +00:00
feat: 添加人脸识别 MFA
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
:visible.sync="visible"
|
:visible.sync="visible"
|
||||||
class="dialog-content"
|
class="dialog-content"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
width="600px"
|
width="740px"
|
||||||
@confirm="visible = false"
|
@confirm="visible = false"
|
||||||
v-on="$listeners"
|
v-on="$listeners"
|
||||||
>
|
>
|
||||||
@@ -52,11 +52,21 @@
|
|||||||
<el-row :gutter="24" style="margin: 0 auto;">
|
<el-row :gutter="24" style="margin: 0 auto;">
|
||||||
<el-col :md="24" :sm="24" style="display: flex; align-items: center; margin-bottom: 20px;">
|
<el-col :md="24" :sm="24" style="display: flex; align-items: center; margin-bottom: 20px;">
|
||||||
<el-input
|
<el-input
|
||||||
|
v-if="subTypeSelected !== 'face'"
|
||||||
v-model="secretValue"
|
v-model="secretValue"
|
||||||
:placeholder="inputPlaceholder"
|
:placeholder="inputPlaceholder"
|
||||||
:show-password="showPassword"
|
:show-password="showPassword"
|
||||||
@keyup.enter.native="handleConfirm"
|
@keyup.enter.native="handleConfirm"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<iframe
|
||||||
|
v-if="isFaceCaptureVisible && subTypeSelected ==='face' && faceCaptureUrl"
|
||||||
|
:src="faceCaptureUrl"
|
||||||
|
allow="camera"
|
||||||
|
sandbox="allow-scripts allow-same-origin"
|
||||||
|
style="width: 100%; height: 800px;border: none;"
|
||||||
|
/>
|
||||||
|
|
||||||
<span v-if="subTypeSelected === 'sms'" style="margin: -1px 0 0 20px;">
|
<span v-if="subTypeSelected === 'sms'" style="margin: -1px 0 0 20px;">
|
||||||
<el-button
|
<el-button
|
||||||
:disabled="smsBtnDisabled"
|
:disabled="smsBtnDisabled"
|
||||||
@@ -72,9 +82,24 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="24" style="margin: 10px auto;">
|
<el-row :gutter="24" style="margin: 10px auto;">
|
||||||
<el-col :md="24" :sm="24">
|
<el-col :md="24" :sm="24">
|
||||||
<el-button class="confirm-btn" size="mini" type="primary" @click="handleConfirm">
|
<el-button
|
||||||
|
v-if="subTypeSelected!=='face'"
|
||||||
|
class="confirm-btn"
|
||||||
|
size="mini"
|
||||||
|
type="primary"
|
||||||
|
@click="handleConfirm"
|
||||||
|
>
|
||||||
{{ this.$t('Confirm') }}
|
{{ this.$t('Confirm') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="subTypeSelected==='face'&&!isFaceCaptureVisible"
|
||||||
|
class="confirm-btn"
|
||||||
|
size="mini"
|
||||||
|
type="primary"
|
||||||
|
@click="handleFaceCapture"
|
||||||
|
>
|
||||||
|
开始人脸识别
|
||||||
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
@@ -113,7 +138,10 @@ export default {
|
|||||||
visible: false,
|
visible: false,
|
||||||
callback: null,
|
callback: null,
|
||||||
cancel: null,
|
cancel: null,
|
||||||
processing: false
|
processing: false,
|
||||||
|
isFaceCaptureVisible: false,
|
||||||
|
faceToken: null,
|
||||||
|
faceCaptureUrl: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -129,6 +157,10 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleSubTypeChange(val) {
|
handleSubTypeChange(val) {
|
||||||
|
if (val !== 'face') {
|
||||||
|
this.isFaceCaptureVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
this.inputPlaceholder = this.subTypeChoices.filter(item => item.name === val)[0]?.placeholder
|
this.inputPlaceholder = this.subTypeChoices.filter(item => item.name === val)[0]?.placeholder
|
||||||
this.smsWidth = val === 'sms' ? 6 : 0
|
this.smsWidth = val === 'sms' ? 6 : 0
|
||||||
},
|
},
|
||||||
@@ -192,6 +224,29 @@ export default {
|
|||||||
this.$message.error(this.$tc('FailedToSendVerificationCode'))
|
this.$message.error(this.$tc('FailedToSendVerificationCode'))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
startFaceCapture() {
|
||||||
|
const url = '/api/v1/authentication/mfa/face/context/'
|
||||||
|
this.$axios.post(url).then(data => {
|
||||||
|
const token = data['token']
|
||||||
|
this.faceCaptureUrl = '/facelive/capture?token=' + token
|
||||||
|
this.isFaceCaptureVisible = true
|
||||||
|
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
this.$axios.get(url + `?token=${token}`).then(data => {
|
||||||
|
if (data['is_finished']) {
|
||||||
|
clearInterval(timer)
|
||||||
|
this.isFaceCaptureVisible = false
|
||||||
|
this.handleConfirm()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
}).catch(() => {
|
||||||
|
this.$message.error(this.$tc('FailedToStartFaceCapture'))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleFaceCapture() {
|
||||||
|
this.startFaceCapture()
|
||||||
|
},
|
||||||
handleConfirm() {
|
handleConfirm() {
|
||||||
if (this.confirmTypeRequired === 'relogin') {
|
if (this.confirmTypeRequired === 'relogin') {
|
||||||
return this.logout()
|
return this.logout()
|
||||||
@@ -214,6 +269,8 @@ export default {
|
|||||||
})
|
})
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.$message.error(err.message || this.$tc('ConfirmFailed'))
|
this.$message.error(err.message || this.$tc('ConfirmFailed'))
|
||||||
|
this.faceCaptureUrl = null
|
||||||
|
this.isFaceCaptureVisible = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -74,6 +74,11 @@ module.exports = {
|
|||||||
target: 'http://127.0.0.1:4200',
|
target: 'http://127.0.0.1:4200',
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
},
|
},
|
||||||
|
'/facelive/': {
|
||||||
|
target: 'http://localhost:9999',
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true
|
||||||
|
},
|
||||||
'^/(core|static|media)/': {
|
'^/(core|static|media)/': {
|
||||||
target: process.env.VUE_APP_CORE_HOST || 'http://127.0.0.1:8080',
|
target: process.env.VUE_APP_CORE_HOST || 'http://127.0.0.1:8080',
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
@@ -81,8 +86,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
after: require('./mock/mock-server.js')
|
after: require('./mock/mock-server.js')
|
||||||
},
|
},
|
||||||
css: {
|
css: {},
|
||||||
},
|
|
||||||
configureWebpack: {
|
configureWebpack: {
|
||||||
// provide the app's title in webpack's name field, so that
|
// provide the app's title in webpack's name field, so that
|
||||||
// it can be accessed in index.html to inject the correct title.
|
// it can be accessed in index.html to inject the correct title.
|
||||||
@@ -90,7 +94,8 @@ module.exports = {
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': resolve('src'),
|
'@': resolve('src'),
|
||||||
elementCss: resolve('node_modules/element-ui/lib/theme-chalk/index.css'),
|
elementCss: resolve(
|
||||||
|
'node_modules/element-ui/lib/theme-chalk/index.css'),
|
||||||
elementLocale: resolve('node_modules/element-ui/lib/locale/lang/en.js')
|
elementLocale: resolve('node_modules/element-ui/lib/locale/lang/en.js')
|
||||||
},
|
},
|
||||||
extensions: ['.vue', '.js', '.json']
|
extensions: ['.vue', '.js', '.json']
|
||||||
@@ -159,7 +164,8 @@ module.exports = {
|
|||||||
config
|
config
|
||||||
.plugin('ScriptExtHtmlWebpackPlugin')
|
.plugin('ScriptExtHtmlWebpackPlugin')
|
||||||
.after('html')
|
.after('html')
|
||||||
.use('script-ext-html-webpack-plugin', [{
|
.use('script-ext-html-webpack-plugin', [
|
||||||
|
{
|
||||||
// `runtime` must same as runtimeChunk name. default is `runtime`
|
// `runtime` must same as runtimeChunk name. default is `runtime`
|
||||||
inline: /runtime\..*\.js$/
|
inline: /runtime\..*\.js$/
|
||||||
}])
|
}])
|
||||||
|
Reference in New Issue
Block a user