mirror of
https://github.com/bitwarden/clients.git
synced 2025-12-05 19:17:06 -06:00
Compare commits
9 Commits
b294d5778f
...
4e26b8e8ec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e26b8e8ec | ||
|
|
d32365fbba | ||
|
|
2bf9e3f6df | ||
|
|
cf806dcac4 | ||
|
|
474ffa2ce1 | ||
|
|
ad12704c21 | ||
|
|
4155e26c28 | ||
|
|
5386b58f23 | ||
|
|
b9cb19a98e |
@@ -627,11 +627,11 @@ export default class NotificationBackground {
|
||||
}
|
||||
|
||||
const username: string | null = data.username || null;
|
||||
const currentPassword = data.password || null;
|
||||
const newPassword = data.newPassword || null;
|
||||
const currentPasswordFieldValue = data.password || null;
|
||||
const newPasswordFieldValue = data.newPassword || null;
|
||||
|
||||
if (authStatus === AuthenticationStatus.Locked && newPassword !== null) {
|
||||
await this.pushChangePasswordToQueue(null, loginDomain, newPassword, tab, true);
|
||||
if (authStatus === AuthenticationStatus.Locked && newPasswordFieldValue !== null) {
|
||||
await this.pushChangePasswordToQueue(null, loginDomain, newPasswordFieldValue, tab, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -657,35 +657,49 @@ export default class NotificationBackground {
|
||||
const [cipher] = ciphers;
|
||||
if (
|
||||
username !== null &&
|
||||
newPassword === null &&
|
||||
newPasswordFieldValue === null &&
|
||||
cipher.login.username.toLowerCase() === normalizedUsername &&
|
||||
cipher.login.password === currentPassword
|
||||
cipher.login.password === currentPasswordFieldValue
|
||||
) {
|
||||
// Assumed to be a login
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentPassword && !newPassword) {
|
||||
if (
|
||||
ciphers.length > 0 &&
|
||||
currentPasswordFieldValue?.length &&
|
||||
// Only use current password for change if no new password present.
|
||||
if (ciphers.length > 0) {
|
||||
await this.pushChangePasswordToQueue(
|
||||
ciphers.map((cipher) => cipher.id),
|
||||
loginDomain,
|
||||
currentPassword,
|
||||
tab,
|
||||
);
|
||||
return true;
|
||||
!newPasswordFieldValue
|
||||
) {
|
||||
const currentPasswordMatchesAnExistingValue = ciphers.some(
|
||||
(cipher) =>
|
||||
cipher.login?.password?.length && cipher.login.password === currentPasswordFieldValue,
|
||||
);
|
||||
|
||||
// The password entered matched a stored cipher value with
|
||||
// the same username (no change)
|
||||
if (currentPasswordMatchesAnExistingValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.pushChangePasswordToQueue(
|
||||
ciphers.map((cipher) => cipher.id),
|
||||
loginDomain,
|
||||
currentPasswordFieldValue,
|
||||
tab,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (newPassword) {
|
||||
if (newPasswordFieldValue) {
|
||||
// Otherwise include all known ciphers.
|
||||
if (ciphers.length > 0) {
|
||||
await this.pushChangePasswordToQueue(
|
||||
ciphers.map((cipher) => cipher.id),
|
||||
loginDomain,
|
||||
newPassword,
|
||||
newPasswordFieldValue,
|
||||
tab,
|
||||
);
|
||||
|
||||
|
||||
@@ -61,8 +61,8 @@ impl InstalledBrowserRetriever for DefaultInstalledBrowserRetriever {
|
||||
let mut browsers = Vec::with_capacity(SUPPORTED_BROWSER_MAP.len());
|
||||
|
||||
for (browser, config) in SUPPORTED_BROWSER_MAP.iter() {
|
||||
let data_dir = get_browser_data_dir(config)?;
|
||||
if data_dir.exists() {
|
||||
let data_dir = get_and_validate_data_dir(config);
|
||||
if data_dir.is_ok() {
|
||||
browsers.push((*browser).to_string());
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ pub async fn import_logins(
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct BrowserConfig {
|
||||
pub name: &'static str,
|
||||
pub data_dir: &'static str,
|
||||
pub data_dir: &'static [&'static str],
|
||||
}
|
||||
|
||||
pub(crate) static SUPPORTED_BROWSER_MAP: LazyLock<
|
||||
@@ -126,11 +126,19 @@ pub(crate) static SUPPORTED_BROWSER_MAP: LazyLock<
|
||||
.collect::<std::collections::HashMap<_, _>>()
|
||||
});
|
||||
|
||||
fn get_browser_data_dir(config: &BrowserConfig) -> Result<PathBuf> {
|
||||
let dir = dirs::home_dir()
|
||||
.ok_or_else(|| anyhow!("Home directory not found"))?
|
||||
.join(config.data_dir);
|
||||
Ok(dir)
|
||||
fn get_and_validate_data_dir(config: &BrowserConfig) -> Result<PathBuf> {
|
||||
for data_dir in config.data_dir.iter() {
|
||||
let dir = dirs::home_dir()
|
||||
.ok_or_else(|| anyhow!("Home directory not found"))?
|
||||
.join(data_dir);
|
||||
if dir.exists() {
|
||||
return Ok(dir);
|
||||
}
|
||||
}
|
||||
Err(anyhow!(
|
||||
"Browser user data directory '{:?}' not found",
|
||||
config.data_dir
|
||||
))
|
||||
}
|
||||
|
||||
//
|
||||
@@ -174,13 +182,7 @@ fn load_local_state_for_browser(browser_name: &String) -> Result<(PathBuf, Local
|
||||
.get(browser_name.as_str())
|
||||
.ok_or_else(|| anyhow!("Unsupported browser: {}", browser_name))?;
|
||||
|
||||
let data_dir = get_browser_data_dir(config)?;
|
||||
if !data_dir.exists() {
|
||||
return Err(anyhow!(
|
||||
"Browser user data directory '{}' not found",
|
||||
data_dir.display()
|
||||
));
|
||||
}
|
||||
let data_dir = get_and_validate_data_dir(config)?;
|
||||
|
||||
let local_state = load_local_state(&data_dir)?;
|
||||
|
||||
|
||||
@@ -18,19 +18,22 @@ use crate::{
|
||||
pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
|
||||
BrowserConfig {
|
||||
name: "Chrome",
|
||||
data_dir: ".config/google-chrome",
|
||||
data_dir: &[".config/google-chrome"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Chromium",
|
||||
data_dir: "snap/chromium/common/chromium",
|
||||
data_dir: &["snap/chromium/common/chromium"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Brave",
|
||||
data_dir: "snap/brave/current/.config/BraveSoftware/Brave-Browser",
|
||||
data_dir: &[
|
||||
"snap/brave/current/.config/BraveSoftware/Brave-Browser",
|
||||
".config/BraveSoftware/Brave-Browser",
|
||||
],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Opera",
|
||||
data_dir: "snap/opera/current/.config/opera",
|
||||
data_dir: &["snap/opera/current/.config/opera", ".config/opera"],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -14,31 +14,31 @@ use crate::{
|
||||
pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
|
||||
BrowserConfig {
|
||||
name: "Chrome",
|
||||
data_dir: "Library/Application Support/Google/Chrome",
|
||||
data_dir: &["Library/Application Support/Google/Chrome"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Chromium",
|
||||
data_dir: "Library/Application Support/Chromium",
|
||||
data_dir: &["Library/Application Support/Chromium"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Microsoft Edge",
|
||||
data_dir: "Library/Application Support/Microsoft Edge",
|
||||
data_dir: &["Library/Application Support/Microsoft Edge"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Brave",
|
||||
data_dir: "Library/Application Support/BraveSoftware/Brave-Browser",
|
||||
data_dir: &["Library/Application Support/BraveSoftware/Brave-Browser"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Arc",
|
||||
data_dir: "Library/Application Support/Arc/User Data",
|
||||
data_dir: &["Library/Application Support/Arc/User Data"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Opera",
|
||||
data_dir: "Library/Application Support/com.operasoftware.Opera",
|
||||
data_dir: &["Library/Application Support/com.operasoftware.Opera"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Vivaldi",
|
||||
data_dir: "Library/Application Support/Vivaldi",
|
||||
data_dir: &["Library/Application Support/Vivaldi"],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -25,27 +25,27 @@ pub use signature::*;
|
||||
pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
|
||||
BrowserConfig {
|
||||
name: "Brave",
|
||||
data_dir: "AppData/Local/BraveSoftware/Brave-Browser/User Data",
|
||||
data_dir: &["AppData/Local/BraveSoftware/Brave-Browser/User Data"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Chrome",
|
||||
data_dir: "AppData/Local/Google/Chrome/User Data",
|
||||
data_dir: &["AppData/Local/Google/Chrome/User Data"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Chromium",
|
||||
data_dir: "AppData/Local/Chromium/User Data",
|
||||
data_dir: &["AppData/Local/Chromium/User Data"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Microsoft Edge",
|
||||
data_dir: "AppData/Local/Microsoft/Edge/User Data",
|
||||
data_dir: &["AppData/Local/Microsoft/Edge/User Data"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Opera",
|
||||
data_dir: "AppData/Roaming/Opera Software/Opera Stable",
|
||||
data_dir: &["AppData/Roaming/Opera Software/Opera Stable"],
|
||||
},
|
||||
BrowserConfig {
|
||||
name: "Vivaldi",
|
||||
data_dir: "AppData/Local/Vivaldi/User Data",
|
||||
data_dir: &["AppData/Local/Vivaldi/User Data"],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"build": "node scripts/build.js",
|
||||
"build": "napi build --platform --no-js",
|
||||
"test": "cargo test"
|
||||
},
|
||||
"author": "",
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const isRelease = args.includes('--release');
|
||||
|
||||
if (isRelease) {
|
||||
console.log('Building release mode.');
|
||||
} else {
|
||||
console.log('Building debug mode.');
|
||||
process.env.RUST_LOG = 'debug';
|
||||
}
|
||||
|
||||
execSync(`napi build --platform --no-js false ${isRelease ? '--release' : ''}`, { stdio: 'inherit', env: process.env });
|
||||
@@ -966,7 +966,7 @@ pub mod logging {
|
||||
};
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::{
|
||||
filter::EnvFilter,
|
||||
filter::{EnvFilter, LevelFilter},
|
||||
fmt::format::{DefaultVisitor, Writer},
|
||||
layer::SubscriberExt,
|
||||
util::SubscriberInitExt,
|
||||
@@ -1054,17 +1054,9 @@ pub mod logging {
|
||||
pub fn init_napi_log(js_log_fn: ThreadsafeFunction<FnArgs<(LogLevel, String)>>) {
|
||||
let _ = JS_LOGGER.0.set(js_log_fn);
|
||||
|
||||
// the log level hierarchy is determined by:
|
||||
// - if RUST_LOG is detected at runtime
|
||||
// - if RUST_LOG is provided at compile time
|
||||
// - default to INFO
|
||||
let filter = EnvFilter::builder()
|
||||
.with_default_directive(
|
||||
option_env!("RUST_LOG")
|
||||
.unwrap_or("info")
|
||||
.parse()
|
||||
.expect("should provide valid log level at compile time."),
|
||||
)
|
||||
// set the default log level to INFO.
|
||||
.with_default_directive(LevelFilter::INFO.into())
|
||||
// parse directives from the RUST_LOG environment variable,
|
||||
// overriding the default directive for matching targets.
|
||||
.from_env_lossy();
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
<app-side-nav variant="secondary" *ngIf="organization$ | async as organization">
|
||||
<bit-nav-logo [openIcon]="logo" route="." [label]="'adminConsole' | i18n"></bit-nav-logo>
|
||||
<org-switcher [filter]="orgFilter" [hideNewButton]="hideNewOrgButton$ | async"></org-switcher>
|
||||
<bit-nav-item
|
||||
icon="bwi-dashboard"
|
||||
*ngIf="organization.canAccessReports"
|
||||
[text]="'accessIntelligence' | i18n"
|
||||
route="access-intelligence"
|
||||
></bit-nav-item>
|
||||
|
||||
@if (canShowAccessIntelligenceTab(organization)) {
|
||||
<bit-nav-item
|
||||
icon="bwi-dashboard"
|
||||
[text]="'accessIntelligence' | i18n"
|
||||
route="access-intelligence"
|
||||
></bit-nav-item>
|
||||
}
|
||||
|
||||
<bit-nav-item
|
||||
icon="bwi-collection-shared"
|
||||
[text]="'collections' | i18n"
|
||||
|
||||
@@ -8,6 +8,7 @@ import { combineLatest, filter, map, Observable, switchMap, withLatestFrom } fro
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AdminConsoleLogo } from "@bitwarden/assets/svg";
|
||||
import {
|
||||
canAccessAccessIntelligence,
|
||||
canAccessBillingTab,
|
||||
canAccessGroupsTab,
|
||||
canAccessMembersTab,
|
||||
@@ -172,6 +173,10 @@ export class OrganizationLayoutComponent implements OnInit {
|
||||
return canAccessBillingTab(organization);
|
||||
}
|
||||
|
||||
canShowAccessIntelligenceTab(organization: Organization): boolean {
|
||||
return canAccessAccessIntelligence(organization);
|
||||
}
|
||||
|
||||
getReportTabLabel(organization: Organization): string {
|
||||
return organization.useEvents ? "reporting" : "reports";
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { authGuard } from "@bitwarden/angular/auth/guards";
|
||||
import { canAccessSettingsTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import {
|
||||
canAccessAccessIntelligence,
|
||||
canAccessSettingsTab,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { isEnterpriseOrgGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/is-enterprise-org.guard";
|
||||
import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard";
|
||||
import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/admin-console/organizations/layouts/organization-layout.component";
|
||||
@@ -79,7 +82,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: "access-intelligence",
|
||||
canActivate: [organizationPermissionsGuard((org) => org.canAccessReports)],
|
||||
canActivate: [organizationPermissionsGuard(canAccessAccessIntelligence)],
|
||||
loadChildren: () =>
|
||||
import("../../dirt/access-intelligence/access-intelligence.module").then(
|
||||
(m) => m.AccessIntelligenceModule,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { canAccessAccessIntelligence } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard";
|
||||
|
||||
import { RiskInsightsComponent } from "./risk-insights.component";
|
||||
@@ -8,7 +9,7 @@ import { RiskInsightsComponent } from "./risk-insights.component";
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
canActivate: [organizationPermissionsGuard((org) => org.canAccessReports)],
|
||||
canActivate: [organizationPermissionsGuard(canAccessAccessIntelligence)],
|
||||
component: RiskInsightsComponent,
|
||||
data: {
|
||||
titleId: "accessIntelligence",
|
||||
|
||||
@@ -41,6 +41,18 @@ export function canAccessBillingTab(org: Organization): boolean {
|
||||
return org.isOwner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access Intelligence is only available to:
|
||||
* - Enterprise organizations
|
||||
* - Users in those organizations with report access
|
||||
*
|
||||
* @param org The organization to verify access
|
||||
* @returns If true can access the Access Intelligence feature
|
||||
*/
|
||||
export function canAccessAccessIntelligence(org: Organization): boolean {
|
||||
return org.canUseAccessIntelligence && org.canAccessReports;
|
||||
}
|
||||
|
||||
export function canAccessOrgAdmin(org: Organization): boolean {
|
||||
// Admin console can only be accessed by Owners for disabled organizations
|
||||
if (!org.enabled && !org.isOwner) {
|
||||
|
||||
@@ -402,4 +402,8 @@ export class Organization {
|
||||
this.permissions.accessEventLogs)
|
||||
);
|
||||
}
|
||||
|
||||
get canUseAccessIntelligence() {
|
||||
return this.productTierType === ProductTierType.Enterprise;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,8 +335,10 @@ export class SearchService implements SearchServiceAbstraction {
|
||||
|
||||
if (
|
||||
login &&
|
||||
login.uris.length &&
|
||||
login.uris.some((loginUri) => loginUri?.uri?.toLowerCase().indexOf(query) > -1)
|
||||
login.uris?.length &&
|
||||
login.uris?.some(
|
||||
(loginUri) => loginUri?.uri && loginUri.uri.toLowerCase().indexOf(query) > -1,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,10 +51,10 @@ describe("Default task service", () => {
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
useAccessIntelligence: false,
|
||||
canUseAccessIntelligence: false,
|
||||
},
|
||||
{
|
||||
useAccessIntelligence: true,
|
||||
canUseAccessIntelligence: true,
|
||||
},
|
||||
] as Organization[]),
|
||||
);
|
||||
@@ -70,10 +70,10 @@ describe("Default task service", () => {
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
useAccessIntelligence: false,
|
||||
canUseAccessIntelligence: false,
|
||||
},
|
||||
{
|
||||
useAccessIntelligence: false,
|
||||
canUseAccessIntelligence: false,
|
||||
},
|
||||
] as Organization[]),
|
||||
);
|
||||
@@ -91,17 +91,17 @@ describe("Default task service", () => {
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
useAccessIntelligence: true,
|
||||
canUseAccessIntelligence: true,
|
||||
},
|
||||
] as Organization[]),
|
||||
);
|
||||
});
|
||||
|
||||
it("should return an empty array if tasks are not enabled", async () => {
|
||||
it("should return no tasks if not present and canUserAccessIntelligence is false", async () => {
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
useAccessIntelligence: false,
|
||||
canUseAccessIntelligence: false,
|
||||
},
|
||||
] as Organization[]),
|
||||
);
|
||||
@@ -111,7 +111,6 @@ describe("Default task service", () => {
|
||||
const result = await firstValueFrom(tasks$("user-id" as UserId));
|
||||
|
||||
expect(result.length).toBe(0);
|
||||
expect(mockApiSend).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should fetch tasks from the API when the state is null", async () => {
|
||||
@@ -163,17 +162,17 @@ describe("Default task service", () => {
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
useAccessIntelligence: true,
|
||||
canUseAccessIntelligence: true,
|
||||
},
|
||||
] as Organization[]),
|
||||
);
|
||||
});
|
||||
|
||||
it("should return an empty array if tasks are not enabled", async () => {
|
||||
it("should return no tasks if not present and canUserAccessIntelligence is false", async () => {
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
useAccessIntelligence: false,
|
||||
canUseAccessIntelligence: false,
|
||||
},
|
||||
] as Organization[]),
|
||||
);
|
||||
@@ -183,7 +182,6 @@ describe("Default task service", () => {
|
||||
const result = await firstValueFrom(pendingTasks$("user-id" as UserId));
|
||||
|
||||
expect(result.length).toBe(0);
|
||||
expect(mockApiSend).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should filter tasks to only pending tasks", async () => {
|
||||
|
||||
@@ -48,7 +48,7 @@ export class DefaultTaskService implements TaskService {
|
||||
|
||||
tasksEnabled$ = perUserCache$((userId) => {
|
||||
return this.organizationService.organizations$(userId).pipe(
|
||||
map((orgs) => orgs.some((o) => o.useAccessIntelligence)),
|
||||
map((orgs) => orgs.some((o) => o.canUseAccessIntelligence)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ type CharacterType = "letter" | "emoji" | "special" | "number";
|
||||
@Component({
|
||||
selector: "bit-color-password",
|
||||
template: `@for (character of passwordCharArray(); track $index; let i = $index) {
|
||||
<span [class]="getCharacterClass(character)">
|
||||
<span [class]="getCharacterClass(character)" class="tw-font-mono">
|
||||
<span>{{ character }}</span>
|
||||
@if (showCount()) {
|
||||
<span class="tw-whitespace-nowrap tw-text-xs tw-leading-5 tw-text-main">{{ i + 1 }}</span>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for
|
||||
|
||||
import { ColorPasswordComponent } from "./color-password.component";
|
||||
|
||||
const examplePassword = "Wq$Jk😀7jlI DX#rS5Sdi!z ";
|
||||
const examplePassword = "Wq$Jk😀7jlI DX#rS5Sdi!z0O ";
|
||||
|
||||
export default {
|
||||
title: "Component Library/Color Password",
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
(keydown)="handleKeyDown($event)"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
<div class="tw-sticky tw-bottom-0 tw-left-0 tw-z-20 tw-mt-auto tw-w-full tw-bg-background-alt3">
|
||||
<!-- 53rem = ~850px -->
|
||||
<!-- This is a magic number. This number was selected by going to the UI and finding the number that felt the best to me and design. No real rhyme or reason :) -->
|
||||
<div
|
||||
class="[@media(min-height:53rem)]:tw-sticky tw-bottom-0 tw-left-0 tw-z-20 tw-mt-auto tw-w-full tw-bg-background-alt3"
|
||||
>
|
||||
<bit-nav-divider></bit-nav-divider>
|
||||
@if (data.open) {
|
||||
<ng-content select="[slot=footer]"></ng-content>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="tw-flex tw-items-center tw-justify-center" style="width: 40px; height: 40px">
|
||||
<app-vault-icon [cipher]="cipher()" [coloredIcon]="true"></app-vault-icon>
|
||||
</div>
|
||||
<h2 bitTypography="h4" class="tw-ml-2 tw-mt-2" data-testid="item-name">
|
||||
<h2 bitTypography="h4" class="tw-ml-2 tw-mt-2 tw-select-auto" data-testid="item-name">
|
||||
{{ cipher().name }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user