Implement OTP page in config

a
This commit is contained in:
Yukino Song
2024-08-28 03:04:50 +08:00
parent 86e707618f
commit b8d3ebb248
6 changed files with 87 additions and 13 deletions

View File

@@ -766,8 +766,8 @@ namespace confighttp {
}
outputTree.put("otp", nvhttp::request_otp(it->second));
outputTree.put("statue", true);
outputTree.put("message", "OTP created, effective within 1 minute.");
outputTree.put("status", true);
outputTree.put("message", "OTP created, effective within 3 minutes.");
}
catch (std::exception &e) {
BOOST_LOG(warning) << "OTP creation failed: "sv << e.what();

View File

@@ -619,10 +619,10 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 503);
tree.put("root.<xmlattr>.status_message", "OTP auth not available.");
} else {
auto hash = util::hex(crypto::hash(one_time_pin + ptr->second.async_insert_pin.salt + otp_passphrase));
auto hash = util::hex(crypto::hash(one_time_pin + ptr->second.async_insert_pin.salt + otp_passphrase), true);
if (hash.to_string_view() == it->second) {
getservercert(ptr->second, tree, one_time_pin);
pin(one_time_pin, deviceName);
one_time_pin.clear();
otp_passphrase.clear();
return;

View File

@@ -44,7 +44,7 @@ namespace nvhttp {
*/
constexpr auto PORT_HTTPS = -5;
constexpr auto OTP_EXPIRE_DURATION = 60s;
constexpr auto OTP_EXPIRE_DURATION = 180s;
/**
* @brief Start the nvhttp server.

View File

@@ -3,23 +3,49 @@
<head>
<%- header %>
<style scoped type="text/css">
.content-container {
padding-top: 2em;
}
.pin-tab-bar {
margin-bottom: 2em !important;
}
</style>
</head>
<body id="app" v-cloak>
<Navbar></Navbar>
<div id="content" class="container">
<h1 class="my-4 text-center">{{ $t('pin.pin_pairing') }}</h1>
<form class="form d-flex flex-column align-items-center" id="form" @submit.prevent="registerDevice">
<div id="content" class="container content-container">
<ul class="nav nav-pills nav-fill pin-tab-bar">
<li class="nav-item">
<a class="nav-link" :class="{active: currentTab === 'OTP'}" @click="currentTab = 'OTP'">{{ $t('pin.otp_pairing') }}</a>
</li>
<li class="nav-item">
<a class="nav-link" :class="{active: currentTab === 'PIN'}" @click="currentTab = 'PIN'">{{ $t('pin.pin_pairing') }}</a>
</li>
</ul>
<form v-if="currentTab === 'OTP'" class="form d-flex flex-column align-items-center" @submit.prevent="requestOTP">
<div class="card flex-column d-flex p-4 mb-4">
<h1 class="my-4 text-center">{{ otp && otp || '????' }}</h1>
<input type="text" pattern="[0-9a-zA-Z]{4,}" :placeholder="`${$t('pin.otp_passphrase')}`" v-model="passphrase" required autofocus class="form-control my-4" />
<button class="btn btn-primary">{{ $t('pin.generate_pin') }}</button>
</div>
<div v-if="otpMessage" class="alert" :class="['alert-' + otpStatus]">{{ otpMessage }}</div>
<div class="alert alert-info">{{ $t('pin.otp_msg') }}</div>
</form>
<form v-if="currentTab === 'PIN'" class="form d-flex flex-column align-items-center" id="form" @submit.prevent="registerDevice">
<div class="card flex-column d-flex p-4 mb-4">
<input type="text" pattern="\d*" :placeholder="`${$t('navbar.pin')}`" autofocus id="pin-input" class="form-control mt-2" required />
<input type="text" :placeholder="`${$t('pin.device_name')}`" id="name-input" class="form-control my-4" />
<button class="btn btn-primary">{{ $t('pin.send') }}</button>
</div>
<div class="alert alert-warning">
<b>{{ $t('_common.warning') }}</b> {{ $t('pin.warning_msg') }}
</div>
<div id="status"></div>
</form>
<div class="alert alert-warning">
<b>{{ $t('_common.warning') }}</b> {{ $t('pin.warning_msg') }}
</div>
</div>
</body>
@@ -28,11 +54,22 @@
import { initApp } from './init'
import Navbar from './Navbar.vue'
let resetOTPTimeout = null;
let app = createApp({
components: {
Navbar
},
inject: ['i18n'],
data() {
return {
currentTab: 'OTP',
otp: '',
passphrase: '',
otpMessage: '',
otpStatus: 'warning'
}
},
methods: {
registerDevice(e) {
let pin = document.querySelector("#pin-input").value;
@@ -54,6 +91,29 @@
).innerHTML = `<div class="alert alert-danger" role="alert">${this.i18n.t('pin.pair_failure')}</div>`;
}
});
},
requestOTP() {
fetch(`/api/otp?passphrase=${this.passphrase}`)
.then(resp => resp.json())
.then(resp => {
if (resp.status !== 'true') {
this.otpMessage = resp.message
this.otpStatus = 'danger'
return
}
this.otp = resp.otp
this.otpStatus = 'success'
this.otpMessage = this.i18n.t('pin.otp_success')
if (resetOTPTimeout !== null) clearTimeout(resetOTPTimeout)
resetOTPTimeout = setTimeout(() => {
this.otp = this.i18n.t('pin.otp_expired')
this.otpMessage = this.i18n.t('pin.otp_expired_msg')
this.otpStatus = 'warning'
resetOTPTimeout = null
}, 3 * 60 * 1000)
})
}
}
});

View File

@@ -373,7 +373,14 @@
"pair_success": "Success! Please check Moonlight to continue",
"pin_pairing": "PIN Pairing",
"send": "Send",
"warning_msg": "Make sure you have access to the client you are pairing with. This software can give total control to your computer, so be careful!"
"warning_msg": "Make sure you have access to the client you are pairing with. This software can give total control to your computer, so be careful!",
"otp_pairing": "OTP Pairing",
"generate_pin": "Generate PIN",
"otp_passphrase": "One Time Passphrase",
"otp_expired": "EXPIRED",
"otp_expired_msg": "OTP expired. Please request a new one.",
"otp_success": "PIN request success, the PIN is available within 3 minutes.",
"otp_msg": "OTP pairing is only available for Artemis clients. Please use legacy pairing method for other clients."
},
"resource_card": {
"github_discussions": "GitHub Discussions",

View File

@@ -374,7 +374,14 @@
"pair_success": "成功!请检查 Moonlight 以继续",
"pin_pairing": "PIN 码配对",
"send": "发送",
"warning_msg": "请确保您可以掌控您正在配对的客户端。该软件可以完全控制您的计算机,请务必小心!"
"warning_msg": "请确保您可以掌控您正在配对的客户端。该软件可以完全控制您的计算机,请务必小心!",
"otp_pairing": "OTP 配对",
"generate_pin": "生成 PIN",
"otp_passphrase": "一次性口令",
"otp_expired": "已过期",
"otp_expired_msg": "口令已过期,请重新请求。",
"otp_success": "一次性 PIN 请求成功3分钟内有效。",
"otp_msg": "一次性口令目前仅支持 Artemis 客户端使用。其他客户端请使用传统配对方式。"
},
"resource_card": {
"github_discussions": "Github 讨论区",