diff --git a/frontend/auth/admin/index.html b/frontend/auth/admin/index.html index 5d38a85..9e11041 100644 --- a/frontend/auth/admin/index.html +++ b/frontend/auth/admin/index.html @@ -1,5 +1,5 @@ - + diff --git a/frontend/auth/index.html b/frontend/auth/index.html index 3e90ccb..b5bddab 100644 --- a/frontend/auth/index.html +++ b/frontend/auth/index.html @@ -1,5 +1,5 @@ - + diff --git a/frontend/auth/restricted/index.html b/frontend/auth/restricted/index.html index f06d835..a7d03bf 100644 --- a/frontend/auth/restricted/index.html +++ b/frontend/auth/restricted/index.html @@ -1,4 +1,11 @@ - - -
- + + + + + + + +
+ + + diff --git a/frontend/src/assets/style.css b/frontend/src/assets/style.css index d66b3c8..20ec734 100644 --- a/frontend/src/assets/style.css +++ b/frontend/src/assets/style.css @@ -37,6 +37,7 @@ --layout-max-width: 1400px; --layout-padding: clamp(1.5rem, 3vw + 1rem, 3.25rem); --transition-base: 160ms ease; + --focus-ring: 0 0 0 2px var(--color-accent); } @media (prefers-color-scheme: dark) { @@ -84,6 +85,10 @@ body { color: var(--color-text); line-height: 1.55; -webkit-font-smoothing: antialiased; + text-align: justify; + hyphens: auto; + -webkit-hyphens: auto; + -moz-hyphens: auto; } body, @@ -108,13 +113,6 @@ a:visited { a:hover, a:focus-visible { color: var(--color-link-hover); - text-decoration: underline; -} - -a:focus-visible { - outline: 2px solid var(--color-accent); - outline-offset: 2px; - border-radius: var(--radius-sm); } .app-shell { @@ -187,6 +185,11 @@ a:focus-visible { gap: 1rem; } +.section-description { + margin: 0; + color: var(--color-text-muted); +} + .button-row { display: flex; flex-wrap: wrap; @@ -214,7 +217,6 @@ button { border: 1px solid transparent; padding: 0.65rem 1.1rem; cursor: pointer; - transition: all var(--transition-base); display: inline-flex; align-items: center; justify-content: center; @@ -223,11 +225,6 @@ button { color: var(--color-text); } -button:focus-visible { - outline: 2px solid var(--color-accent); - outline-offset: 2px; -} - button:disabled { cursor: not-allowed; filter: opacity(0.6); @@ -437,7 +434,7 @@ th { color: var(--color-text); } -:root { --card-width: 22rem; } +:root { --card-width: 16rem; } .record-list, .credential-list, @@ -447,7 +444,7 @@ th { grid-auto-flow: row; grid-template-columns: repeat(auto-fill, minmax(var(--card-width), 1fr)); justify-content: start; - gap: 1rem 1.25rem; + gap: 0.5rem; align-items: stretch; margin: 0 auto; } @@ -461,9 +458,7 @@ th { .session-item { display: flex; flex-direction: column; - gap: 0.75rem; - padding: 1rem; - border: 1px solid var(--color-border); + padding: 0.5rem; border-radius: var(--radius-md); background: var(--color-surface); height: 100%; @@ -484,7 +479,7 @@ th { .credential-item.current-session, .credential-item.is-hovered, .session-item.is-current, -.session-item.is-hovered { border-color: var(--color-accent); } +.session-item.is-hovered { border-color: var(--color-accent); background-color: var(--color-surface-subtle); } .credential-item.is-linked-session, .session-item.is-linked-credential { border-color: var(--color-accent); background-color: var(--color-surface-subtle); } @@ -492,7 +487,7 @@ th { .item-top { display: flex; align-items: center; - gap: 1rem; + height: 2.5rem; } .item-icon { @@ -500,9 +495,6 @@ th { height: 40px; display: grid; place-items: center; - background: var(--color-surface-subtle, transparent); - border-radius: var(--radius-sm); - border: 1px solid var(--color-border); flex-shrink: 0; } @@ -516,12 +508,14 @@ th { font-size: 1rem; font-weight: 600; color: var(--color-heading); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .item-actions { flex-shrink: 0; display: flex; - gap: 0.5rem; align-items: center; } @@ -529,39 +523,30 @@ th { .item-actions .badge + .badge { margin-left: 0.25rem; } .item-details { - margin-left: calc(40px + 1rem); - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.credential-dates { - display: grid; - grid-auto-flow: row; - grid-template-columns: 7rem 1fr; - gap: 0.35rem 0.5rem; - font-size: 0.75rem; - color: var(--color-text-muted); - align-items: center; + margin-left: 40px; } +.credential-dates, .session-dates { display: grid; grid-auto-flow: row; - grid-template-columns: 7rem 1fr; - gap: 0.35rem 0.5rem; + grid-template-columns: 8em 1fr; font-size: 0.75rem; - color: var(--color-text-muted); align-items: center; } .date-label { font-weight: 500; - color: inherit; + color: var(--color-text-muted); + overflow: hidden; + text-overflow: ellipsis; } .date-value { color: var(--color-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .btn-card-delete { background: transparent; border: none; color: var(--color-danger); padding: 0.35rem 0.5rem; font-size: 1.05rem; line-height: 1; border-radius: var(--radius-sm); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; } @@ -578,6 +563,7 @@ th { border-radius: var(--radius-sm); font-size: 0.8rem; font-weight: 500; + white-space: nowrap; } .badge-current { @@ -610,28 +596,18 @@ th { } .user-info { - background: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: var(--radius-sm); - padding: 1.1rem 1.25rem; display: grid; - grid-template-columns: auto 1fr; - gap: 0.75rem 1.25rem; + border-radius: var(--radius-md); + background: var(--color-surface); + padding: 1.1rem 1.25rem; } -.user-info h3 { - margin: 0; - grid-column: span 2; - display: flex; - align-items: center; - gap: 0.5rem; - font-size: 1.15rem; - font-weight: 600; -} - -.user-info span { - text-align: left; - color: var(--color-text); +.user-details { + display: grid; + grid-template-columns: 7em 1fr; + gap: 0 0.5rem; + padding-left: 40px; + font-size: 0.75rem; } .toggle-link { @@ -653,11 +629,6 @@ th { gap: 1.75rem; } - .credential-dates { - grid-auto-flow: row; - grid-template-columns: auto auto; - } - .global-status { top: 1rem; } @@ -738,3 +709,27 @@ body:has(#auth-iframe) { backdrop-filter: blur(.1rem) brightness(0.7); -webkit-backdrop-filter: blur(.1rem) brightness(0.7); } + +.slot-machine { + padding: 0.875rem 1rem; + background: var(--color-surface-hover, rgba(0, 0, 0, 0.03)); + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; + display: flex; + align-items: center; + user-select: none; + pointer-events: none; + white-space: nowrap; + overflow: hidden; +} + +.slot-reel { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 1; + min-width: 0; + height: 1.8em; + position: relative; +} diff --git a/frontend/src/components/Breadcrumbs.vue b/frontend/src/components/Breadcrumbs.vue index 1c5bece..ed6206b 100644 --- a/frontend/src/components/Breadcrumbs.vue +++ b/frontend/src/components/Breadcrumbs.vue @@ -33,6 +33,5 @@ const crumbs = computed(() => { .breadcrumbs ol { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: .25rem; } .breadcrumbs li { display: inline-flex; align-items: center; gap: .25rem; font-size: .9rem; } .breadcrumbs a { text-decoration: none; color: var(--color-link); padding: 0 .25rem; border-radius:4px; transition: color 0.2s ease, background 0.2s ease; } -.breadcrumbs a:hover, .breadcrumbs a:focus-visible { text-decoration: underline; color: var(--color-link-hover); outline: none; } .breadcrumbs .sep { color: var(--color-text-muted); margin: 0; } diff --git a/frontend/src/components/CredentialList.vue b/frontend/src/components/CredentialList.vue index 6420932..07605e1 100644 --- a/frontend/src/components/CredentialList.vue +++ b/frontend/src/components/CredentialList.vue @@ -12,6 +12,8 @@ 'is-linked-session': hoveredSessionCredentialUuid === credential.credential_uuid }]" tabindex="0" + @mousedown.prevent + @click.capture="handleCardClick" @focusin="handleCredentialFocus(credential.credential_uuid)" @focusout="handleCredentialBlur($event)" @keydown="handleDelete($event, credential)" @@ -59,7 +61,6 @@ + + diff --git a/frontend/src/components/ProfileView.vue b/frontend/src/components/ProfileView.vue index e70329c..7537800 100644 --- a/frontend/src/components/ProfileView.vue +++ b/frontend/src/components/ProfileView.vue @@ -19,24 +19,25 @@ @edit-name="openNameDialog" >
- - Code words: +
+

Provided by another device requesting remote auth.

Your Passkeys

-

Keep at least one trusted passkey so you can always sign in.

+

Ideally have at least two passkeys in case you lose one. More than one user can be registered on the same device, giving you a choice at login.

+

Bitwarden can sync one passkey to all your devices. Other secure options include local passkeys, as well as hardware keys such as YubiKey. Cloud sync via Google, Microsoft or iCloud is discouraged.

- - + +
@@ -62,7 +63,7 @@ :hovered-credential-uuid="hoveredCredentialUuid" @terminate="terminateSession" @session-hover="hoveredSession = $event" - section-description="Review where you're signed in and end any sessions you no longer recognize." + section-description="You are currently signed in to the following sessions. If you don't recognize something, consider deleting not only the session but the associated passkey you suspect is compromised, as only this terminates all linked sessions and prevents logging in again." /> @@ -112,7 +113,7 @@ import Modal from '@/components/Modal.vue' import NameEditForm from '@/components/NameEditForm.vue' import SessionList from '@/components/SessionList.vue' import RegistrationLinkModal from '@/components/RegistrationLinkModal.vue' -import RemoteAuth from '@/components/RemoteAuthPermit.vue' +import RemoteAuthPermit from '@/components/RemoteAuthPermit.vue' import { useAuthStore } from '@/stores/auth' import { adminUiPath, makeUiHref } from '@/utils/settings' import passkey from '@/utils/passkey' @@ -167,10 +168,9 @@ const handlePairingError = (message) => { const handleDelete = async (credential) => { const credentialId = credential?.credential_uuid if (!credentialId) return - if (!confirm('Are you sure you want to delete this passkey?')) return try { await authStore.deleteCredential(credentialId) - authStore.showMessage('Passkey deleted successfully!', 'success', 3000) + authStore.showMessage('Passkey deleted! You should also remove it from your password manager or device.', 'success', 3000) } catch (error) { authStore.showMessage(`Failed to delete passkey: ${error.message}`, 'error') } } @@ -219,9 +219,12 @@ const saveName = async () => { diff --git a/frontend/src/components/RemoteAuthPermit.vue b/frontend/src/components/RemoteAuthPermit.vue index 06ab3af..6758f48 100644 --- a/frontend/src/components/RemoteAuthPermit.vue +++ b/frontend/src/components/RemoteAuthPermit.vue @@ -95,7 +95,7 @@ const props = defineProps({ title: { type: String, default: 'Help Another Device Sign In' }, description: { type: String, default: 'Enter the code shown on the device that needs to sign in.' }, placeholder: { type: String, default: 'Enter three words' }, - action: { type: String, default: 'login' } // 'login' or 'register' + action: { type: String, default: 'login' }, // 'login' or 'register' }) const emit = defineEmits(['completed', 'error', 'cancelled', 'back', 'register', 'deviceInfoVisible']) @@ -654,7 +654,6 @@ function reset() { onMounted(async () => { await fetchSettings() - inputRef.value?.focus() // Initialize cursor position nextTick(() => { cursorPos.value = inputRef.value?.selectionStart ?? 0 @@ -696,25 +695,14 @@ defineExpose({ reset, deny, code, handleInput, loading, error }) max-width: 100%; } -/* Slot machine visual display (matches RemoteAuthInline) */ +/* Slot machine visual display (matches RemoteAuthRequest) */ .slot-machine { position: absolute; left: 0; top: 0; width: 100%; height: 100%; - padding: 0.875rem 1rem; - background: var(--color-surface-hover, rgba(0, 0, 0, 0.03)); - border: 2px solid var(--color-border); - border-radius: var(--radius-sm, 6px); - font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; - display: flex; gap: 0; - align-items: center; - user-select: none; - pointer-events: none; - white-space: nowrap; - overflow: hidden; box-sizing: border-box; z-index: 1; } @@ -729,15 +717,8 @@ defineExpose({ reset, deny, code, handleInput, loading, error }) } .slot-reel { - display: inline-flex; - align-items: center; - justify-content: center; flex: 1 1 33.333%; - min-width: 0; - height: 1.8em; overflow: visible; - position: relative; - border-radius: 3px; } .slot-reel:not(:last-child) { diff --git a/frontend/src/components/RemoteAuthRequest.vue b/frontend/src/components/RemoteAuthRequest.vue index 38d07cc..b5acd6a 100644 --- a/frontend/src/components/RemoteAuthRequest.vue +++ b/frontend/src/components/RemoteAuthRequest.vue @@ -360,16 +360,8 @@ defineExpose({ retry, cancel }) } .slot-reel { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 1; - min-width: 0; - height: 1.8em; overflow: hidden; - position: relative; background: var(--color-surface, rgba(255, 255, 255, 0.5)); - border-radius: 3px; } .slot-machine:not(.stopped) .slot-reel:nth-child(1) { diff --git a/frontend/src/components/RestrictedAuth.vue b/frontend/src/components/RestrictedAuth.vue index 420dd43..40bc2e0 100644 --- a/frontend/src/components/RestrictedAuth.vue +++ b/frontend/src/components/RestrictedAuth.vue @@ -39,7 +39,7 @@
-
-

Active Sessions

+

Login Sessions

{{ sectionDescription }}

-
+