Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const {
LIGHTWEIGHT_CLIENT = 'true',
MAX_EVENTS_AFTER = '',
NODE_ENV = 'development',
UNSAFE_HARDCODED_TEST_PASSWORD = 'false',
EXPOSE_SBP = '',
ENABLE_UNSAFE_NULL_CRYPTO = 'false',
UNSAFE_TRUST_ALL_MANIFEST_SIGNING_KEYS = 'false'
Expand Down Expand Up @@ -227,6 +228,7 @@ module.exports = (grunt) => {
'process.env.LIGHTWEIGHT_CLIENT': `'${LIGHTWEIGHT_CLIENT}'`,
'process.env.MAX_EVENTS_AFTER': `'${MAX_EVENTS_AFTER}'`,
'process.env.NODE_ENV': `'${NODE_ENV}'`,
'process.env.UNSAFE_HARDCODED_TEST_PASSWORD': `'${UNSAFE_HARDCODED_TEST_PASSWORD}'`,
'process.env.EXPOSE_SBP': `'${EXPOSE_SBP}'`,
'process.env.ENABLE_UNSAFE_NULL_CRYPTO': `'${ENABLE_UNSAFE_NULL_CRYPTO}'`,
'process.env.UNSAFE_TRUST_ALL_MANIFEST_SIGNING_KEYS': `'${UNSAFE_TRUST_ALL_MANIFEST_SIGNING_KEYS}'`
Expand Down
15 changes: 15 additions & 0 deletions frontend/assets/style/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ $payment-table-desktop-bp: 1290px;
width: $dimensions;
}

@mixin tooltip-style-common {
position: absolute;
top: 0;
left: 0;
min-width: 3rem;
max-width: 12rem;
border-radius: 3px;
padding: 0.5rem;
z-index: 50; // $zindex-tooltip: 50;
pointer-events: none;
background-color: var(--text_0);
opacity: 0.95;
color: var(--background_0);
}

@mixin overflow-touch {
-webkit-overflow-scrolling: touch;
}
Expand Down
13 changes: 1 addition & 12 deletions frontend/views/components/Tooltip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -375,18 +375,7 @@ export default ({

.c-tooltip,
.c-anchored-tooltip {
position: absolute;
top: 0;
left: 0;
min-width: 3rem;
max-width: 12rem;
border-radius: $radius;
padding: 0.5rem;
z-index: $zindex-tooltip;
pointer-events: none;
background-color: $text_0;
opacity: 0.95;
color: $background_0;
@include tooltip-style-common;

&.has-text-center {
text-align: center;
Expand Down
162 changes: 150 additions & 12 deletions frontend/views/containers/access/PasswordForm.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
<template lang='pug'>
label.field
component.field(:is='mode === "manual" ? "label" : "div"')
.label(v-if='label') {{ label }}

.inputgroup.c-mode-auto(
v-if='mode === "auto"'
:class='{ "password-copied": ephemeral.showCopyFeedback }'
v-error:[name]=''
)
input.input.with-single-addon.c-auto-password(
:class='{ "blurred-text has-ellipsis": ephemeral.pwMode === "text" }'
:type='ephemeral.pwMode'
:data-test='name'
:value='ephemeral.randomPassword'
:disabled='true'
)

.addons
button.is-success.c-copy-btn(
type='button'
data-test='copyPassword'
@click.stop='copyPassword'
)
span.c-copied(v-if='ephemeral.showCopyFeedback')
i.icon-check-circle
i18n Copied
i18n.c-copy(v-else) Copy

.inputgroup(
v-error:[name]='{ attrs: { "data-test": "badPassword" }}'
v-else
v-error:[name]=''
)
input.input.with-single-addon(
:type='isLock ? "password" : "text"'
Expand All @@ -17,7 +43,6 @@ label.field
.addons
button.is-icon(
type='button'
v-if='hasIconRight'
:aria-label='L("Toggle password visibility")'
:aria-pressed='!isLock'
@click.prevent='isLock = !isLock'
Expand All @@ -26,13 +51,33 @@ label.field
</template>

<script>
import { base58btc } from '@chelonia/multiformats/bases/base58'
import Tooltip from '@components/Tooltip.vue'
import validationsDebouncedMixins from '@view-utils/validationsDebouncedMixins.js'
import { L } from '@common/common.js'

function generateBase58Password (length = 32) {
const bytes = crypto.getRandomValues(new Uint8Array((length)))
const encoded = base58btc.baseEncode(bytes)

// Truncate to desired length
return encoded.slice(0, length)
}

export default ({
name: 'PasswordForm',
components: {
Tooltip
},
data () {
return {
isLock: true
isLock: true,
pwCopyTimeoutId: null,
ephemeral: {
randomPassword: '',
showCopyFeedback: false,
pwMode: 'text'
}
}
},
mixins: [validationsDebouncedMixins],
Expand All @@ -42,6 +87,11 @@ export default ({
required: false,
default: 'password'
},
mode: {
type: String,
required: false,
default: 'manual' // 'manual' | 'auto'
},
label: {
type: String,
required: false
Expand All @@ -50,10 +100,6 @@ export default ({
type: Object,
required: true
},
hasIconRight: {
type: Boolean,
default: true
},
showPlaceholder: {
type: Boolean,
default: false
Expand All @@ -67,15 +113,107 @@ export default ({
required: false
}
},
methods: {
generateRandomPassword (pwLen = 32) {
let genPassword = ''

if (process.env.NODE_ENV !== 'production' && process.env.UNSAFE_HARDCODED_TEST_PASSWORD === 'true') {
// We can optionally use a hardcoded test password for easier debugging/development
// so there is no need to memorize random passwords generated for each new account.
genPassword = '123456789'
} else {
genPassword = generateBase58Password(pwLen)
}

this.ephemeral.randomPassword = genPassword
this.$v.form[this.name].$model = genPassword
},
copyPassword () {
const pw = this.ephemeral.randomPassword
const copyToClipBoard = () => {
navigator.clipboard.writeText(pw)
this.ephemeral.showCopyFeedback = true
this.ephemeral.pwMode = 'password'
this.$emit('password-copied')

clearTimeout(this.pwCopyTimeoutId)
this.pwCopyTimeoutId = setTimeout(() => {
this.ephemeral.showCopyFeedback = false
}, 2000)
}

if (navigator.share) {
navigator.share({
title: L('Your password'),
text: pw
}).catch((error) => {
console.error('navigator.share failed with:', error)
copyToClipBoard()
})
} else {
copyToClipBoard()
}
}
},
created () {
this.isLock = !this.showPassword
if (this.mode === 'auto') {
this.generateRandomPassword()
} else {
this.isLock = !this.showPassword
}
}
}: Object)
</script>

<style lang="scss" scoped>
.icon {
cursor: pointer;
pointer-events: initial !important;
@import "@assets/style/_variables.scss";

.c-mode-auto {
position: relative;

.c-auto-password {
display: block;
line-height: 2.75rem;
padding-right: 5rem;

&.blurred-text {
color: transparent;
text-shadow: 0 0 4px rgba(54, 54, 54, 0.725);
}
}

.addons {
align-items: center;
right: 0.5rem;
}
}

button.c-copy-btn {
min-height: unset;
height: 1.75rem;
border-radius: 3px;
padding-left: 0.75rem;
padding-right: 0.75rem;
overflow: hidden;

.c-copied {
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
}

.password-copied {
.c-auto-password {
padding-right: 7rem;
}

button.c-copy-btn {
padding-left: 0.75rem;
}
}

.is-dark-theme .c-auto-password.blurred-text {
text-shadow: 0 0 3px rgba(255, 255, 255, 0.685);
}
</style>
3 changes: 0 additions & 3 deletions frontend/views/containers/access/PasswordModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ modal-template(class='is-centered is-left-aligned' back-on-mobile=true ref='moda
:value='form'
:$v='$v'
@enter='changePassword'
:hasIconRight='true'
:showPlaceholder='false'
:showPassword='false'
size='is-large'
Expand All @@ -26,7 +25,6 @@ modal-template(class='is-centered is-left-aligned' back-on-mobile=true ref='moda
:value='form'
:$v='$v'
@enter='changePassword'
:hasIconRight='true'
:showPlaceholder='false'
:showPassword='false'
size='is-large'
Expand All @@ -38,7 +36,6 @@ modal-template(class='is-centered is-left-aligned' back-on-mobile=true ref='moda
:value='form'
:$v='$v'
@enter='changePassword'
:hasIconRight='true'
:showPlaceholder='false'
:showPassword='false'
size='is-large'
Expand Down
Loading
Loading