Remove the ability to set a plain text secret

This commit is contained in:
Bubka 2022-12-13 09:05:56 +01:00
parent 6f56c84928
commit b6e4cf50a4
8 changed files with 17 additions and 66 deletions

View File

@ -28,4 +28,15 @@ class Helpers
// We use the regex for semver detection (see https://semver.org/) // We use the regex for semver detection (see https://semver.org/)
return preg_match('/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?/', $release, $version) ? $version[0] : false; return preg_match('/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?/', $release, $version) ? $version[0] : false;
} }
/**
* Format a string to comply with Base32 format
*
* @param string $str
* @return string The filename
*/
public static function PadToBase32Format(?string $str): string
{
return blank($str) ? '' : strtoupper(str_pad($str, (int) ceil(strlen($str) / 8) * 8, '='));
}
} }

View File

@ -242,7 +242,7 @@ class TwoFAccount extends Model implements Sortable
public function setSecretAttribute($value) public function setSecretAttribute($value)
{ {
// Encrypt when needed // Encrypt when needed
$this->attributes['secret'] = $this->encryptOrReturn($value); $this->attributes['secret'] = $this->encryptOrReturn(Helpers::PadToBase32Format($value));
} }
/** /**

View File

@ -2,6 +2,7 @@
namespace App\Rules; namespace App\Rules;
use App\Helpers\Helpers;
use Illuminate\Contracts\Validation\Rule; use Illuminate\Contracts\Validation\Rule;
use ParagonIE\ConstantTime\Base32; use ParagonIE\ConstantTime\Base32;
@ -27,7 +28,7 @@ class IsBase32Encoded implements Rule
public function passes($attribute, $value) public function passes($attribute, $value)
{ {
try { try {
$secret = Base32::decodeUpper($value); $secret = Base32::decodeUpper(Helpers::PadToBase32Format($value));
} catch (\Exception $e) { } catch (\Exception $e) {
return false; return false;
} }

13
package-lock.json generated
View File

@ -9,11 +9,10 @@
"@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^2.0.6", "@fortawesome/vue-fontawesome": "^2.0.6",
"axios": "^1.0.0", "axios": "^1.1.0",
"bulma": "^0.9.3", "bulma": "^0.9.3",
"bulma-checkradio": "^2.1.2", "bulma-checkradio": "^2.1.2",
"bulma-switch": "^2.0.0", "bulma-switch": "^2.0.0",
"hi-base32": "^0.5.1",
"object-equals": "^0.3.0", "object-equals": "^0.3.0",
"v-clipboard": "^2.2.3", "v-clipboard": "^2.2.3",
"vue": "^2.6.14", "vue": "^2.6.14",
@ -5116,11 +5115,6 @@
"he": "bin/he" "he": "bin/he"
} }
}, },
"node_modules/hi-base32": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz",
"integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA=="
},
"node_modules/hmac-drbg": { "node_modules/hmac-drbg": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@ -13647,11 +13641,6 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true "dev": true
}, },
"hi-base32": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz",
"integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA=="
},
"hmac-drbg": { "hmac-drbg": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",

View File

@ -30,7 +30,6 @@
"bulma": "^0.9.3", "bulma": "^0.9.3",
"bulma-checkradio": "^2.1.2", "bulma-checkradio": "^2.1.2",
"bulma-switch": "^2.0.0", "bulma-switch": "^2.0.0",
"hi-base32": "^0.5.1",
"object-equals": "^0.3.0", "object-equals": "^0.3.0",
"v-clipboard": "^2.2.3", "v-clipboard": "^2.2.3",
"vue": "^2.6.14", "vue": "^2.6.14",

View File

@ -101,14 +101,7 @@
<div v-if="form.otp_type"> <div v-if="form.otp_type">
<!-- secret --> <!-- secret -->
<label :for="this.inputId('text','secret')" class="label" v-html="$t('twofaccounts.forms.secret.label')"></label> <label :for="this.inputId('text','secret')" class="label" v-html="$t('twofaccounts.forms.secret.label')"></label>
<div class="field has-addons"> <div class="field">
<p class="control">
<span class="select">
<select @change="form.secret=''" v-model="secretIsBase32Encoded">
<option v-for="(format) in secretFormats" :key="format.value" :value="format.value">{{ format.text }}</option>
</select>
</span>
</p>
<p class="control is-expanded"> <p class="control is-expanded">
<input :id="this.inputId('text','secret')" class="input" type="text" v-model="form.secret"> <input :id="this.inputId('text','secret')" class="input" type="text" v-model="form.secret">
</p> </p>
@ -146,7 +139,7 @@
</form> </form>
<!-- modal --> <!-- modal -->
<modal v-model="ShowTwofaccountInModal"> <modal v-model="ShowTwofaccountInModal">
<otp-displayer ref="AdvancedFormOtpDisplayer" v-bind="otpdisplayerData" @increment-hotp="incrementHotp" @validation-error="mapDisplayerErrors"> <otp-displayer ref="AdvancedFormOtpDisplayer" v-bind="form.data()" @increment-hotp="incrementHotp" @validation-error="mapDisplayerErrors">
</otp-displayer> </otp-displayer>
</modal> </modal>
</form-wrapper> </form-wrapper>
@ -198,7 +191,6 @@
import Modal from '../../components/Modal' import Modal from '../../components/Modal'
import Form from './../../components/Form' import Form from './../../components/Form'
import OtpDisplayer from '../../components/OtpDisplayer' import OtpDisplayer from '../../components/OtpDisplayer'
import Base32 from "hi-base32"
export default { export default {
data() { data() {
@ -209,7 +201,6 @@
showAlternatives : false, showAlternatives : false,
tempIcon: '', tempIcon: '',
uri: '', uri: '',
secretIsBase32Encoded: 0,
form: new Form({ form: new Form({
service: '', service: '',
account: '', account: '',
@ -235,10 +226,6 @@
{ text: 9, value: 9 }, { text: 9, value: 9 },
{ text: 10, value: 10 }, { text: 10, value: 10 },
], ],
secretFormats: [
{ text: this.$t('twofaccounts.forms.plain_text'), value: 0 },
{ text: 'Base32', value: 1 }
],
algorithms: [ algorithms: [
{ text: 'sha1', value: 'sha1' }, { text: 'sha1', value: 'sha1' },
{ text: 'sha256', value: 'sha256' }, { text: 'sha256', value: 'sha256' },
@ -260,17 +247,6 @@
}, },
}, },
computed: {
otpdisplayerData: function() {
let o = this.form.data()
o.secret = this.secretIsBase32Encoded
? o.secret
: Base32.encode(o.secret).toString();
return o
}
},
mounted: function () { mounted: function () {
if( this.$route.params.decodedUri ) { if( this.$route.params.decodedUri ) {
this.uri = this.$route.params.decodedUri this.uri = this.$route.params.decodedUri
@ -279,7 +255,6 @@
this.axios.post('/api/v1/twofaccounts/preview', { uri: this.uri }).then(response => { this.axios.post('/api/v1/twofaccounts/preview', { uri: this.uri }).then(response => {
this.form.fill(response.data) this.form.fill(response.data)
this.secretIsBase32Encoded = 1
this.tempIcon = response.data.icon ? response.data.icon : null this.tempIcon = response.data.icon ? response.data.icon : null
this.showQuickForm = true this.showQuickForm = true
}) })
@ -315,9 +290,6 @@
// set current temp icon as account icon // set current temp icon as account icon
this.form.icon = this.tempIcon this.form.icon = this.tempIcon
// Secret to base32 if necessary
this.form.secret = this.secretIsBase32Encoded ? this.form.secret : Base32.encode(this.form.secret).toString();
await this.form.post('/api/v1/twofaccounts') await this.form.post('/api/v1/twofaccounts')
if( this.form.errors.any() === false ) { if( this.form.errors.any() === false ) {
@ -359,7 +331,6 @@
// Then the otp described by the uri // Then the otp described by the uri
this.axios.post('/api/v1/twofaccounts/preview', { uri: this.uri }).then(response => { this.axios.post('/api/v1/twofaccounts/preview', { uri: this.uri }).then(response => {
this.form.fill(response.data) this.form.fill(response.data)
this.secretIsBase32Encoded = 1
this.tempIcon = response.data.icon ? response.data.icon : null this.tempIcon = response.data.icon ? response.data.icon : null
}) })
.catch(error => { .catch(error => {
@ -441,9 +412,7 @@
this.form.otp_type = to this.form.otp_type = to
if (to === 'steamtotp') { if (to === 'steamtotp') {
this.secretIsBase32Encoded = 1
this.form.service = 'Steam' this.form.service = 'Steam'
this.form.secret = ''
this.fetchLogo() this.fetchLogo()
} }
else if (from === 'steamtotp') { else if (from === 'steamtotp') {

View File

@ -46,13 +46,6 @@
<!-- secret --> <!-- secret -->
<label :for="this.inputId('text','secret')" class="label" v-html="$t('twofaccounts.forms.secret.label')"></label> <label :for="this.inputId('text','secret')" class="label" v-html="$t('twofaccounts.forms.secret.label')"></label>
<div class="field has-addons"> <div class="field has-addons">
<p v-if="!secretIsLocked" class="control">
<span class="select">
<select @change="form.secret=''" v-model="secretIsBase32Encoded">
<option v-for="(format) in secretFormats" :key="format.value" :value="format.value">{{ format.text }}</option>
</select>
</span>
</p>
<p class="control is-expanded"> <p class="control is-expanded">
<input :id="this.inputId('text','secret')" class="input" type="text" v-model="form.secret" :disabled="secretIsLocked"> <input :id="this.inputId('text','secret')" class="input" type="text" v-model="form.secret" :disabled="secretIsLocked">
</p> </p>
@ -141,7 +134,6 @@
import Modal from '../../components/Modal' import Modal from '../../components/Modal'
import Form from './../../components/Form' import Form from './../../components/Form'
import OtpDisplayer from '../../components/OtpDisplayer' import OtpDisplayer from '../../components/OtpDisplayer'
import Base32 from "hi-base32"
export default { export default {
data() { data() {
@ -150,7 +142,6 @@
counterIsLocked: true, counterIsLocked: true,
twofaccountExists: false, twofaccountExists: false,
tempIcon: '', tempIcon: '',
secretIsBase32Encoded: null,
form: new Form({ form: new Form({
service: '', service: '',
account: '', account: '',
@ -176,10 +167,6 @@
{ text: 9, value: 9 }, { text: 9, value: 9 },
{ text: 10, value: 10 }, { text: 10, value: 10 },
], ],
secretFormats: [
{ text: this.$t('twofaccounts.forms.plain_text'), value: 0 },
{ text: 'Base32', value: 1 }
],
algorithms: [ algorithms: [
{ text: 'sha1', value: 'sha1' }, { text: 'sha1', value: 'sha1' },
{ text: 'sha256', value: 'sha256' }, { text: 'sha256', value: 'sha256' },
@ -214,7 +201,6 @@
const { data } = await this.axios.get('/api/v1/twofaccounts/' + this.$route.params.twofaccountId) const { data } = await this.axios.get('/api/v1/twofaccounts/' + this.$route.params.twofaccountId)
this.form.fill(data) this.form.fill(data)
this.secretIsBase32Encoded = 1
this.twofaccountExists = true this.twofaccountExists = true
// set account icon as temp icon // set account icon as temp icon
@ -235,9 +221,6 @@
this.deleteIcon() this.deleteIcon()
} }
// Secret to base32 if necessary
this.form.secret = this.secretIsBase32Encoded ? this.form.secret : Base32.encode(this.form.secret).toString();
await this.form.put('/api/v1/twofaccounts/' + this.$route.params.twofaccountId) await this.form.put('/api/v1/twofaccounts/' + this.$route.params.twofaccountId)
if( this.form.errors.any() === false ) { if( this.form.errors.any() === false ) {

View File

@ -171,7 +171,6 @@
otp_type: '', otp_type: '',
icon: '', icon: '',
secret: '', secret: '',
secretIsBase32Encoded: 1,
algorithm: '', algorithm: '',
digits: null, digits: null,
counter: null, counter: null,