diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index a8ce05f180e..5dd8cd621ba 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -36,7 +36,6 @@ ./libs/angular/src/interfaces/selectOptions.ts ./libs/components/src/stories/Introduction.stories.mdx ./libs/common/spec/misc/logInStrategies/logIn.strategy.spec.ts -./libs/common/spec/misc/logInStrategies/apiLogIn.strategy.spec.ts ./libs/common/spec/misc/logInStrategies/passwordLogIn.strategy.spec.ts ./libs/common/spec/misc/logInStrategies/ssoLogIn.strategy.spec.ts ./libs/common/spec/web/services/webCryptoFunction.service.spec.ts @@ -60,7 +59,6 @@ ./libs/common/spec/services/consoleLog.service.spec.ts ./libs/common/src/misc/logInStrategies/ssoLogin.strategy.ts ./libs/common/src/misc/logInStrategies/passwordLogin.strategy.ts -./libs/common/src/misc/logInStrategies/apiLogin.strategy.ts ./libs/common/src/misc/logInStrategies/passwordlessLogin.strategy.ts ./libs/common/src/misc/logInStrategies/logIn.strategy.ts ./libs/common/src/misc/nodeUtils.ts @@ -164,19 +162,6 @@ ./libs/common/src/services/bitwardenFileUpload.service.ts ./libs/common/src/services/webCryptoFunction.service.ts ./libs/common/src/interfaces/IEncrypted.ts -./libs/node/spec/cli/consoleLog.service.spec.ts -./libs/node/spec/services/nodeCryptoFunction.service.spec.ts -./libs/node/src/cli/models/response/baseResponse.ts -./libs/node/src/cli/models/response/stringResponse.ts -./libs/node/src/cli/models/response/fileResponse.ts -./libs/node/src/cli/models/response/messageResponse.ts -./libs/node/src/cli/models/response/listResponse.ts -./libs/node/src/cli/baseProgram.ts -./libs/node/src/cli/services/consoleLog.service.ts -./libs/node/src/cli/services/cliPlatformUtils.service.ts -./libs/node/src/services/nodeApi.service.ts -./libs/node/src/services/nodeCryptoFunction.service.ts -./libs/node/src/services/lowdbStorage.service.ts ./libs/electron/spec/services/electronLog.service.spec.ts ./libs/electron/src/baseMenu.ts ./libs/electron/src/services/electronLog.service.ts @@ -239,26 +224,6 @@ ./apps/desktop/src/services/nativeMessageHandler.service.ts ./apps/cli/stores/chocolatey/tools/VERIFICATION.txt ./apps/cli/README.md -./apps/cli/src/models/response/sendFileResponse.ts -./apps/cli/src/models/response/organizationCollectionResponse.ts -./apps/cli/src/models/response/collectionResponse.ts -./apps/cli/src/models/response/templateResponse.ts -./apps/cli/src/models/response/passwordHistoryResponse.ts -./apps/cli/src/models/response/folderResponse.ts -./apps/cli/src/models/response/loginResponse.ts -./apps/cli/src/models/response/sendAccessResponse.ts -./apps/cli/src/models/response/sendResponse.ts -./apps/cli/src/models/response/cipherResponse.ts -./apps/cli/src/models/response/organizationResponse.ts -./apps/cli/src/models/response/organizationUserResponse.ts -./apps/cli/src/models/response/sendTextResponse.ts -./apps/cli/src/models/response/attachmentResponse.ts -./apps/cli/src/models/selectionReadOnly.ts -./apps/cli/src/models/request/organizationCollectionRequest.ts -./apps/cli/src/commands/convertToKeyConnector.command.ts -./apps/cli/src/commands/send/removePassword.command.ts -./apps/cli/src/services/lowdbStorage.service.ts -./apps/cli/src/services/nodeEnvSecureStorage.service.ts ./apps/browser/README.md ./apps/browser/store/windows/AppxManifest.xml ./apps/browser/src/background/nativeMessaging.background.ts diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 1ce19d5f377..b3a8a004a29 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -149,11 +149,11 @@ "message": "Změnit hlavní heslo" }, "fingerprintPhrase": { - "message": "Unikátní přístupová fráze", + "message": "Fráze otisku účtu", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "yourAccountsFingerprint": { - "message": "Fráze otisku prstu vašeho účtu", + "message": "Fráze otisku vašeho účtu", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "twoStepLogin": { diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 75624fd4f56..bc362924d83 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -1485,7 +1485,7 @@ "message": "Uusi pääsalasanasi ei täytä käytännön määrittämiä vaatimuksia." }, "acceptPolicies": { - "message": "Valitsemalla tämän ruudun hyväksyt seuraavat:" + "message": "Valitsemalla tämän hyväksyt seuraavat:" }, "acceptPoliciesRequired": { "message": "Palveluehtoja ja tietosuojakäytäntöä ei ole vahvistettu." diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index d6a3ddacc48..2d370c91bb7 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -17,7 +17,7 @@ import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarde import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service"; import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service"; -import { OrganizationService as OrganizationServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; +import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction"; @@ -153,7 +153,7 @@ export default class MainBackground { popupUtilsService: PopupUtilsService; sendService: SendServiceAbstraction; fileUploadService: FileUploadServiceAbstraction; - organizationService: OrganizationServiceAbstraction; + organizationService: InternalOrganizationServiceAbstraction; providerService: ProviderServiceAbstraction; keyConnectorService: KeyConnectorServiceAbstraction; userVerificationService: UserVerificationServiceAbstraction; @@ -317,10 +317,7 @@ export default class MainBackground { this.stateService ); this.syncNotifierService = new SyncNotifierService(); - this.organizationService = new BrowserOrganizationService( - this.stateService, - this.syncNotifierService - ); + this.organizationService = new BrowserOrganizationService(this.stateService); this.policyService = new BrowserPolicyService(this.stateService, this.organizationService); this.policyApiService = new PolicyApiService( this.policyService, @@ -412,7 +409,7 @@ export default class MainBackground { this.stateService, this.providerService, this.folderApiService, - this.syncNotifierService, + this.organizationService, logoutCallback ); this.eventService = new EventService( diff --git a/apps/browser/src/background/service_factories/organization-service.factory.ts b/apps/browser/src/background/service_factories/organization-service.factory.ts index 2e7c31b596e..ea11d32e26a 100644 --- a/apps/browser/src/background/service_factories/organization-service.factory.ts +++ b/apps/browser/src/background/service_factories/organization-service.factory.ts @@ -3,15 +3,10 @@ import { OrganizationService } from "@bitwarden/common/services/organization/org import { FactoryOptions, CachedServices, factory } from "./factory-options"; import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory"; -import { - syncNotifierServiceFactory, - SyncNotifierServiceInitOptions, -} from "./sync-notifier-service.factory"; type OrganizationServiceFactoryOptions = FactoryOptions; export type OrganizationServiceInitOptions = OrganizationServiceFactoryOptions & - SyncNotifierServiceInitOptions & StateServiceInitOptions; export function organizationServiceFactory( @@ -22,10 +17,6 @@ export function organizationServiceFactory( cache, "organizationService", opts, - async () => - new OrganizationService( - await stateServiceFactory(cache, opts), - await syncNotifierServiceFactory(cache, opts) - ) + async () => new OrganizationService(await stateServiceFactory(cache, opts)) ); } diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 3dc4085d3d0..e9224a8919a 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -6,6 +6,7 @@ const CopyWebpackPlugin = require("copy-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const { AngularWebpackPlugin } = require("@ngtools/webpack"); const TerserPlugin = require("terser-webpack-plugin"); +const { TsconfigPathsPlugin } = require("tsconfig-paths-webpack-plugin"); const configurator = require("./config/config"); if (process.env.NODE_ENV == null) { @@ -67,17 +68,24 @@ const moduleRules = [ }, ]; +const requiredPlugins = [ + new webpack.DefinePlugin({ + "process.env": { + ENV: JSON.stringify(ENV), + }, + }), + new webpack.EnvironmentPlugin({ + FLAGS: envConfig.flags, + DEV_FLAGS: ENV === "development" ? envConfig.devFlags : {}, + }), +]; + const plugins = [ new HtmlWebpackPlugin({ template: "./src/popup/index.html", filename: "popup/index.html", chunks: ["popup/polyfills", "popup/vendor-angular", "popup/vendor", "popup/main"], }), - new HtmlWebpackPlugin({ - template: "./src/background.html", - filename: "background.html", - chunks: ["vendor", "background"], - }), new HtmlWebpackPlugin({ template: "./src/notification/bar.html", filename: "notification/bar.html", @@ -99,11 +107,6 @@ const plugins = [ filename: "[name].css", chunkFilename: "chunk-[id].css", }), - new webpack.DefinePlugin({ - "process.env": { - ENV: JSON.stringify(ENV), - }, - }), new AngularWebpackPlugin({ tsConfigPath: "tsconfig.json", entryModule: "src/popup/app.module#AppModule", @@ -119,19 +122,20 @@ const plugins = [ exclude: [/content\/.*/, /notification\/.*/], filename: "[file].map", }), - new webpack.EnvironmentPlugin({ - FLAGS: envConfig.flags, - DEV_FLAGS: ENV === "development" ? envConfig.devFlags : {}, - }), + ...requiredPlugins, ]; -const config = { +/** + * @type {import("webpack").Configuration} + * This config compiles everything but the background + */ +const mainConfig = { + name: "main", mode: ENV, devtool: false, entry: { "popup/polyfills": "./src/popup/polyfills.ts", "popup/main": "./src/popup/main.ts", - background: "./src/background.ts", "content/autofill": "./src/content/autofill.js", "content/autofiller": "./src/content/autofiller.ts", "content/notificationBar": "./src/content/notificationBar.ts", @@ -209,18 +213,72 @@ const config = { plugins: plugins, }; +/** + * @type {import("webpack").Configuration[]} + */ +const configs = []; + if (manifestVersion == 2) { - // We can't use this in manifest v3 - // Ideally we understand why this breaks it and we don't have to do this - config.optimization.splitChunks.cacheGroups.commons2 = { + mainConfig.optimization.splitChunks.cacheGroups.commons2 = { test: /[\\/]node_modules[\\/]/, name: "vendor", chunks: (chunk) => { return chunk.name === "background"; }, }; + + // Manifest V2 uses Background Pages which requires a html page. + mainConfig.plugins.push( + new HtmlWebpackPlugin({ + template: "./src/background.html", + filename: "background.html", + chunks: ["vendor", "background"], + }) + ); + + // Manifest V2 background pages can be run through the regular build pipeline. + // Since it's a standard webpage. + mainConfig.entry.background = "./src/background.ts"; + + configs.push(mainConfig); } else { - config.entry["content/misc-utils"] = "./src/content/misc-utils.ts"; + // Manifest v3 needs an extra helper for utilities in the content script. + // The javascript output of this should be added to manifest.v3.json + mainConfig.entry["content/misc-utils"] = "./src/content/misc-utils.ts"; + + /** + * @type {import("webpack").Configuration} + */ + const backgroundConfig = { + name: "background", + mode: ENV, + devtool: false, + entry: "./src/background.ts", + target: "webworker", + output: { + filename: "background.js", + path: path.resolve(__dirname, "build"), + }, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: "ts-loader", + }, + ], + }, + resolve: { + extensions: [".ts", ".js"], + symlinks: false, + modules: [path.resolve("../../node_modules")], + plugins: [new TsconfigPathsPlugin()], + }, + dependencies: ["main"], + plugins: [...requiredPlugins], + }; + + configs.push(mainConfig); + configs.push(backgroundConfig); } -module.exports = config; +module.exports = configs; diff --git a/apps/cli/jest.config.js b/apps/cli/jest.config.js index 4f0dab845ec..67116d394b3 100644 --- a/apps/cli/jest.config.js +++ b/apps/cli/jest.config.js @@ -1,11 +1,12 @@ const { pathsToModuleNameMapper } = require("ts-jest"); - -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("./tsconfig.json"); const sharedConfig = require("../../libs/shared/jest.config.base"); module.exports = { + ...sharedConfig, preset: "ts-jest", + testEnvironment: "node", setupFilesAfterEnv: ["/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { prefix: "/", diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 42738b7dfa7..1cb03db67bc 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -49,16 +49,16 @@ import { UserVerificationApiService } from "@bitwarden/common/services/userVerif import { UserVerificationService } from "@bitwarden/common/services/userVerification/userVerification.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout/vaultTimeout.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service"; -import { CliPlatformUtilsService } from "@bitwarden/node/cli/services/cliPlatformUtils.service"; -import { ConsoleLogService } from "@bitwarden/node/cli/services/consoleLog.service"; -import { NodeApiService } from "@bitwarden/node/services/nodeApi.service"; -import { NodeCryptoFunctionService } from "@bitwarden/node/services/nodeCryptoFunction.service"; +import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; import { Program } from "./program"; import { SendProgram } from "./send.program"; +import { CliPlatformUtilsService } from "./services/cli-platform-utils.service"; +import { ConsoleLogService } from "./services/console-log.service"; import { I18nService } from "./services/i18n.service"; -import { LowdbStorageService } from "./services/lowdbStorage.service"; -import { NodeEnvSecureStorageService } from "./services/nodeEnvSecureStorage.service"; +import { LowdbStorageService } from "./services/lowdb-storage.service"; +import { NodeApiService } from "./services/node-api.service"; +import { NodeEnvSecureStorageService } from "./services/node-env-secure-storage.service"; import { VaultProgram } from "./vault.program"; // Polyfills @@ -240,7 +240,7 @@ export class Main { this.providerService = new ProviderService(this.stateService); - this.organizationService = new OrganizationService(this.stateService, this.syncNotifierService); + this.organizationService = new OrganizationService(this.stateService); this.policyService = new PolicyService(this.stateService, this.organizationService); @@ -322,7 +322,7 @@ export class Main { this.stateService, this.providerService, this.folderApiService, - this.syncNotifierService, + this.organizationService, async (expired: boolean) => await this.logout() ); diff --git a/apps/cli/src/commands/completion.command.ts b/apps/cli/src/commands/completion.command.ts index c175f9b6a13..17a3eff5a83 100644 --- a/apps/cli/src/commands/completion.command.ts +++ b/apps/cli/src/commands/completion.command.ts @@ -1,7 +1,7 @@ import * as program from "commander"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; interface IOption { long?: string; diff --git a/apps/cli/src/commands/config.command.ts b/apps/cli/src/commands/config.command.ts index 2fa33c9edc5..9110b46bf29 100644 --- a/apps/cli/src/commands/config.command.ts +++ b/apps/cli/src/commands/config.command.ts @@ -1,9 +1,10 @@ import * as program from "commander"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; + +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; +import { StringResponse } from "../models/response/string.response"; export class ConfigCommand { constructor(private environmentService: EnvironmentService) {} diff --git a/apps/cli/src/commands/confirm.command.ts b/apps/cli/src/commands/confirm.command.ts index 79d30dfec65..824bd5c8e85 100644 --- a/apps/cli/src/commands/confirm.command.ts +++ b/apps/cli/src/commands/confirm.command.ts @@ -2,7 +2,8 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { Utils } from "@bitwarden/common/misc/utils"; import { OrganizationUserConfirmRequest } from "@bitwarden/common/models/request/organization-user-confirm.request"; -import { Response } from "@bitwarden/node/cli/models/response"; + +import { Response } from "../models/response"; export class ConfirmCommand { constructor(private apiService: ApiService, private cryptoService: CryptoService) {} diff --git a/apps/cli/src/commands/convertToKeyConnector.command.ts b/apps/cli/src/commands/convert-to-key-connector.command.ts similarity index 95% rename from apps/cli/src/commands/convertToKeyConnector.command.ts rename to apps/cli/src/commands/convert-to-key-connector.command.ts index 00fb2f72910..82756edc985 100644 --- a/apps/cli/src/commands/convertToKeyConnector.command.ts +++ b/apps/cli/src/commands/convert-to-key-connector.command.ts @@ -4,8 +4,9 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; + +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; export class ConvertToKeyConnectorCommand { constructor( diff --git a/apps/cli/src/commands/create.command.ts b/apps/cli/src/commands/create.command.ts index 334573b3dfc..92855ee6f79 100644 --- a/apps/cli/src/commands/create.command.ts +++ b/apps/cli/src/commands/create.command.ts @@ -13,12 +13,12 @@ import { CollectionExport } from "@bitwarden/common/models/export/collection.exp import { FolderExport } from "@bitwarden/common/models/export/folder.export"; import { CollectionRequest } from "@bitwarden/common/models/request/collection.request"; import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { OrganizationCollectionRequest } from "../models/request/organizationCollectionRequest"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { OrganizationCollectionResponse } from "../models/response/organizationCollectionResponse"; +import { OrganizationCollectionRequest } from "../models/request/organization-collection.request"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; +import { FolderResponse } from "../models/response/folder.response"; +import { OrganizationCollectionResponse } from "../models/response/organization-collection.response"; import { CliUtils } from "../utils"; export class CreateCommand { diff --git a/apps/cli/src/commands/delete.command.ts b/apps/cli/src/commands/delete.command.ts index 9a4480df687..86bfc8cbbc2 100644 --- a/apps/cli/src/commands/delete.command.ts +++ b/apps/cli/src/commands/delete.command.ts @@ -4,8 +4,8 @@ import { FolderApiServiceAbstraction } from "@bitwarden/common/abstractions/fold import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { Utils } from "@bitwarden/common/misc/utils"; -import { Response } from "@bitwarden/node/cli/models/response"; +import { Response } from "../models/response"; import { CliUtils } from "../utils"; export class DeleteCommand { @@ -88,7 +88,7 @@ export class DeleteCommand { } private async deleteFolder(id: string) { - const folder = await this.folderService.get(id); + const folder = await this.folderService.getFromState(id); if (folder == null) { return Response.notFound(); } diff --git a/apps/cli/src/commands/download.command.ts b/apps/cli/src/commands/download.command.ts index eb40cf5a2d7..8d7a6130c0d 100644 --- a/apps/cli/src/commands/download.command.ts +++ b/apps/cli/src/commands/download.command.ts @@ -3,9 +3,9 @@ import * as fet from "node-fetch"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { EncArrayBuffer } from "@bitwarden/common/models/domain/enc-array-buffer"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { FileResponse } from "@bitwarden/node/cli/models/response/fileResponse"; +import { Response } from "../models/response"; +import { FileResponse } from "../models/response/file.response"; import { CliUtils } from "../utils"; export abstract class DownloadCommand { diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index a8ed1b0f281..d6cd120da0d 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -9,12 +9,12 @@ import { CollectionExport } from "@bitwarden/common/models/export/collection.exp import { FolderExport } from "@bitwarden/common/models/export/folder.export"; import { CollectionRequest } from "@bitwarden/common/models/request/collection.request"; import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { OrganizationCollectionRequest } from "../models/request/organizationCollectionRequest"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { OrganizationCollectionResponse } from "../models/response/organizationCollectionResponse"; +import { OrganizationCollectionRequest } from "../models/request/organization-collection.request"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; +import { FolderResponse } from "../models/response/folder.response"; +import { OrganizationCollectionResponse } from "../models/response/organization-collection.response"; import { CliUtils } from "../utils"; export class EditCommand { @@ -118,7 +118,7 @@ export class EditCommand { } private async editFolder(id: string, req: FolderExport) { - const folder = await this.folderService.get(id); + const folder = await this.folderService.getFromState(id); if (folder == null) { return Response.notFound(); } diff --git a/apps/cli/src/commands/encode.command.ts b/apps/cli/src/commands/encode.command.ts index 1c957d26fa6..4598e1b230c 100644 --- a/apps/cli/src/commands/encode.command.ts +++ b/apps/cli/src/commands/encode.command.ts @@ -1,6 +1,5 @@ -import { Response } from "@bitwarden/node/cli/models/response"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; - +import { Response } from "../models/response"; +import { StringResponse } from "../models/response/string.response"; import { CliUtils } from "../utils"; export class EncodeCommand { diff --git a/apps/cli/src/commands/export.command.ts b/apps/cli/src/commands/export.command.ts index 50446d7e027..9e9594c6ca0 100644 --- a/apps/cli/src/commands/export.command.ts +++ b/apps/cli/src/commands/export.command.ts @@ -5,8 +5,8 @@ import { ExportFormat, ExportService } from "@bitwarden/common/abstractions/expo import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/enums/policyType"; import { Utils } from "@bitwarden/common/misc/utils"; -import { Response } from "@bitwarden/node/cli/models/response"; +import { Response } from "../models/response"; import { CliUtils } from "../utils"; export class ExportCommand { diff --git a/apps/cli/src/commands/generate.command.ts b/apps/cli/src/commands/generate.command.ts index 3fb8a7fd8b9..aebc084a06b 100644 --- a/apps/cli/src/commands/generate.command.ts +++ b/apps/cli/src/commands/generate.command.ts @@ -1,8 +1,8 @@ import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; +import { Response } from "../models/response"; +import { StringResponse } from "../models/response/string.response"; import { CliUtils } from "../utils"; export class GenerateCommand { diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index d2ca9edbd48..6cd57fe7cc1 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -26,18 +26,18 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { CipherView } from "@bitwarden/common/models/view/cipher.view"; import { CollectionView } from "@bitwarden/common/models/view/collection.view"; import { FolderView } from "@bitwarden/common/models/view/folder.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; -import { OrganizationCollectionRequest } from "../models/request/organizationCollectionRequest"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { CollectionResponse } from "../models/response/collectionResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { OrganizationCollectionResponse } from "../models/response/organizationCollectionResponse"; -import { OrganizationResponse } from "../models/response/organizationResponse"; -import { SendResponse } from "../models/response/sendResponse"; -import { TemplateResponse } from "../models/response/templateResponse"; -import { SelectionReadOnly } from "../models/selectionReadOnly"; +import { OrganizationCollectionRequest } from "../models/request/organization-collection.request"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; +import { CollectionResponse } from "../models/response/collection.response"; +import { FolderResponse } from "../models/response/folder.response"; +import { OrganizationCollectionResponse } from "../models/response/organization-collection.response"; +import { OrganizationResponse } from "../models/response/organization.response"; +import { SendResponse } from "../models/response/send.response"; +import { StringResponse } from "../models/response/string.response"; +import { TemplateResponse } from "../models/response/template.response"; +import { SelectionReadOnly } from "../models/selection-read-only"; import { CliUtils } from "../utils"; import { DownloadCommand } from "./download.command"; @@ -353,7 +353,7 @@ export class GetCommand extends DownloadCommand { private async getFolder(id: string) { let decFolder: FolderView = null; if (Utils.isGuid(id)) { - const folder = await this.folderService.get(id); + const folder = await this.folderService.getFromState(id); if (folder != null) { decFolder = await folder.decrypt(); } diff --git a/apps/cli/src/commands/import.command.ts b/apps/cli/src/commands/import.command.ts index fcb2e215bbc..97145771ef1 100644 --- a/apps/cli/src/commands/import.command.ts +++ b/apps/cli/src/commands/import.command.ts @@ -5,9 +5,9 @@ import { ImportService } from "@bitwarden/common/abstractions/import.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { ImportType } from "@bitwarden/common/enums/importOptions"; import { Importer } from "@bitwarden/common/importers/importer"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; import { CliUtils } from "../utils"; export class ImportCommand { diff --git a/apps/cli/src/commands/list.command.ts b/apps/cli/src/commands/list.command.ts index 5322026284d..cc40c5bc79f 100644 --- a/apps/cli/src/commands/list.command.ts +++ b/apps/cli/src/commands/list.command.ts @@ -13,14 +13,14 @@ import { } from "@bitwarden/common/models/response/collection.response"; import { ListResponse as ApiListResponse } from "@bitwarden/common/models/response/list.response"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { ListResponse } from "@bitwarden/node/cli/models/response/listResponse"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { CollectionResponse } from "../models/response/collectionResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { OrganizationResponse } from "../models/response/organizationResponse"; -import { OrganizationUserResponse } from "../models/response/organizationUserResponse"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; +import { CollectionResponse } from "../models/response/collection.response"; +import { FolderResponse } from "../models/response/folder.response"; +import { ListResponse } from "../models/response/list.response"; +import { OrganizationUserResponse } from "../models/response/organization-user.response"; +import { OrganizationResponse } from "../models/response/organization.response"; import { CliUtils } from "../utils"; export class ListCommand { diff --git a/apps/cli/src/commands/lock.command.ts b/apps/cli/src/commands/lock.command.ts index ee993376fd8..f9f136a6b92 100644 --- a/apps/cli/src/commands/lock.command.ts +++ b/apps/cli/src/commands/lock.command.ts @@ -1,6 +1,7 @@ import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; + +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; export class LockCommand { constructor(private vaultTimeoutService: VaultTimeoutService) {} diff --git a/apps/cli/src/commands/login.command.ts b/apps/cli/src/commands/login.command.ts index d88a4d6d2f0..6284f8b94b5 100644 --- a/apps/cli/src/commands/login.command.ts +++ b/apps/cli/src/commands/login.command.ts @@ -1,11 +1,15 @@ +import * as http from "http"; + import * as program from "commander"; +import * as inquirer from "inquirer"; +import Separator from "inquirer/lib/objects/separator"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; @@ -13,87 +17,644 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.serv import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service"; +import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; +import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; import { Utils } from "@bitwarden/common/misc/utils"; -import { LoginCommand as BaseLoginCommand } from "@bitwarden/node/cli/commands/login.command"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; +import { AuthResult } from "@bitwarden/common/models/domain/auth-result"; +import { + UserApiLogInCredentials, + PasswordLogInCredentials, + SsoLogInCredentials, +} from "@bitwarden/common/models/domain/log-in-credentials"; +import { TokenTwoFactorRequest } from "@bitwarden/common/models/request/identity-token/token-two-factor.request"; +import { TwoFactorEmailRequest } from "@bitwarden/common/models/request/two-factor-email.request"; +import { UpdateTempPasswordRequest } from "@bitwarden/common/models/request/update-temp-password.request"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -export class LoginCommand extends BaseLoginCommand { +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; + +export class LoginCommand { + protected canInteract: boolean; + protected clientSecret: string; + protected email: string; + + private ssoRedirectUri: string = null; private options: program.OptionValues; constructor( - authService: AuthService, - apiService: ApiService, - cryptoFunctionService: CryptoFunctionService, - i18nService: I18nService, - environmentService: EnvironmentService, - passwordGenerationService: PasswordGenerationService, - platformUtilsService: PlatformUtilsService, - stateService: StateService, - cryptoService: CryptoService, - policyService: PolicyService, - twoFactorService: TwoFactorService, - private syncService: SyncService, - private keyConnectorService: KeyConnectorService, - private logoutCallback: () => Promise - ) { - super( - authService, - apiService, - i18nService, - environmentService, - passwordGenerationService, - cryptoFunctionService, - platformUtilsService, - stateService, - cryptoService, - policyService, - twoFactorService, - "cli" - ); - this.logout = this.logoutCallback; - this.validatedParams = async () => { - const key = await cryptoFunctionService.randomBytes(64); - process.env.BW_SESSION = Utils.fromBufferToB64(key); - }; - this.success = async () => { - await this.syncService.fullSync(true); + protected authService: AuthService, + protected apiService: ApiService, + protected cryptoFunctionService: CryptoFunctionService, + protected environmentService: EnvironmentService, + protected passwordGenerationService: PasswordGenerationService, + protected platformUtilsService: PlatformUtilsService, + protected stateService: StateService, + protected cryptoService: CryptoService, + protected policyService: PolicyService, + protected twoFactorService: TwoFactorService, + protected syncService: SyncService, + protected keyConnectorService: KeyConnectorService, + protected logoutCallback: () => Promise + ) {} - const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); - - if ( - (this.options.sso != null || this.options.apikey != null) && - this.canInteract && - !usesKeyConnector - ) { - const res = new MessageResponse( - "You are logged in!", - "\n" + "To unlock your vault, use the `unlock` command. ex:\n" + "$ bw unlock" - ); - return res; - } else { - const res = new MessageResponse( - "You are logged in!", - "\n" + - "To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n" + - '$ export BW_SESSION="' + - process.env.BW_SESSION + - '"\n' + - '> $env:BW_SESSION="' + - process.env.BW_SESSION + - '"\n\n' + - "You can also pass the session key to any command with the `--session` option. ex:\n" + - "$ bw list items --session " + - process.env.BW_SESSION - ); - res.raw = process.env.BW_SESSION; - return res; - } - }; - } - - run(email: string, password: string, options: program.OptionValues) { + async run(email: string, password: string, options: program.OptionValues) { this.options = options; this.email = email; - return super.run(email, password, options); + + this.canInteract = process.env.BW_NOINTERACTION !== "true"; + + let ssoCodeVerifier: string = null; + let ssoCode: string = null; + let orgIdentifier: string = null; + + let clientId: string = null; + let clientSecret: string = null; + + let selectedProvider: any = null; + + if (options.apikey != null) { + const apiIdentifiers = await this.apiIdentifiers(); + clientId = apiIdentifiers.clientId; + clientSecret = apiIdentifiers.clientSecret; + } else if (options.sso != null && this.canInteract) { + const passwordOptions: any = { + type: "password", + length: 64, + uppercase: true, + lowercase: true, + numbers: true, + special: false, + }; + const state = await this.passwordGenerationService.generatePassword(passwordOptions); + ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); + const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); + const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); + try { + const ssoParams = await this.openSsoPrompt(codeChallenge, state); + ssoCode = ssoParams.ssoCode; + orgIdentifier = ssoParams.orgIdentifier; + } catch { + return Response.badRequest("Something went wrong. Try again."); + } + } else { + if ((email == null || email === "") && this.canInteract) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "input", + name: "email", + message: "Email address:", + }); + email = answer.email; + } + if (email == null || email.trim() === "") { + return Response.badRequest("Email address is required."); + } + if (email.indexOf("@") === -1) { + return Response.badRequest("Email address is invalid."); + } + this.email = email; + + if (password == null || password === "") { + if (options.passwordfile) { + password = await NodeUtils.readFirstLine(options.passwordfile); + } else if (options.passwordenv && process.env[options.passwordenv]) { + password = process.env[options.passwordenv]; + } else if (this.canInteract) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "password", + name: "password", + message: "Master password:", + }); + password = answer.password; + } + } + + if (password == null || password === "") { + return Response.badRequest("Master password is required."); + } + } + + let twoFactorToken: string = options.code; + let twoFactorMethod: TwoFactorProviderType = null; + try { + if (options.method != null) { + twoFactorMethod = parseInt(options.method, null); + } + } catch (e) { + return Response.error("Invalid two-step login method."); + } + + const twoFactor = + twoFactorToken == null + ? null + : new TokenTwoFactorRequest(twoFactorMethod, twoFactorToken, false); + + try { + await this.validatedParams(); + + let response: AuthResult = null; + if (clientId != null && clientSecret != null) { + if (!clientId.startsWith("user")) { + return Response.error("Invalid API Key; Organization API Key currently not supported"); + } + response = await this.authService.logIn( + new UserApiLogInCredentials(clientId, clientSecret) + ); + } else if (ssoCode != null && ssoCodeVerifier != null) { + response = await this.authService.logIn( + new SsoLogInCredentials( + ssoCode, + ssoCodeVerifier, + this.ssoRedirectUri, + orgIdentifier, + twoFactor + ) + ); + } else { + response = await this.authService.logIn( + new PasswordLogInCredentials(email, password, null, twoFactor) + ); + } + if (response.captchaSiteKey) { + const credentials = new PasswordLogInCredentials(email, password); + const handledResponse = await this.handleCaptchaRequired(twoFactor, credentials); + + // Error Response + if (handledResponse instanceof Response) { + return handledResponse; + } else { + response = handledResponse; + } + } + if (response.requiresTwoFactor) { + const twoFactorProviders = this.twoFactorService.getSupportedProviders(null); + if (twoFactorProviders.length === 0) { + return Response.badRequest("No providers available for this client."); + } + + if (twoFactorMethod != null) { + try { + selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0]; + } catch (e) { + return Response.error("Invalid two-step login method."); + } + } + + if (selectedProvider == null) { + if (twoFactorProviders.length === 1) { + selectedProvider = twoFactorProviders[0]; + } else if (this.canInteract) { + const twoFactorOptions: (string | Separator)[] = twoFactorProviders.map((p) => p.name); + twoFactorOptions.push(new inquirer.Separator()); + twoFactorOptions.push("Cancel"); + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "list", + name: "method", + message: "Two-step login method:", + choices: twoFactorOptions, + }); + const i = twoFactorOptions.indexOf(answer.method); + if (i === twoFactorOptions.length - 1) { + return Response.error("Login failed."); + } + selectedProvider = twoFactorProviders[i]; + } + if (selectedProvider == null) { + return Response.error("Login failed. No provider selected."); + } + } + + if ( + twoFactorToken == null && + response.twoFactorProviders.size > 1 && + selectedProvider.type === TwoFactorProviderType.Email + ) { + const emailReq = new TwoFactorEmailRequest(); + emailReq.email = this.authService.email; + emailReq.masterPasswordHash = this.authService.masterPasswordHash; + await this.apiService.postTwoFactorEmail(emailReq); + } + + if (twoFactorToken == null) { + if (this.canInteract) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "input", + name: "token", + message: "Two-step login code:", + }); + twoFactorToken = answer.token; + } + if (twoFactorToken == null || twoFactorToken === "") { + return Response.badRequest("Code is required."); + } + } + + response = await this.authService.logInTwoFactor( + new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken), + null + ); + } + + if (response.captchaSiteKey) { + const twoFactorRequest = new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken); + const handledResponse = await this.handleCaptchaRequired(twoFactorRequest); + + // Error Response + if (handledResponse instanceof Response) { + return handledResponse; + } else { + response = handledResponse; + } + } + + if (response.requiresTwoFactor) { + return Response.error("Login failed."); + } + + if (response.resetMasterPassword) { + return Response.error( + "In order to log in with SSO from the CLI, you must first log in" + + " through the web vault to set your master password." + ); + } + + // Handle Updating Temp Password if NOT using an API Key for authentication + if (response.forcePasswordReset && clientId == null && clientSecret == null) { + return await this.updateTempPassword(); + } + + return await this.handleSuccessResponse(); + } catch (e) { + return Response.error(e); + } + } + + private async validatedParams() { + const key = await this.cryptoFunctionService.randomBytes(64); + process.env.BW_SESSION = Utils.fromBufferToB64(key); + } + + private async handleSuccessResponse(): Promise { + await this.syncService.fullSync(true); + + const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); + + if ( + (this.options.sso != null || this.options.apikey != null) && + this.canInteract && + !usesKeyConnector + ) { + const res = new MessageResponse( + "You are logged in!", + "\n" + "To unlock your vault, use the `unlock` command. ex:\n" + "$ bw unlock" + ); + return Response.success(res); + } + + const res = new MessageResponse( + "You are logged in!", + "\n" + + "To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n" + + '$ export BW_SESSION="' + + process.env.BW_SESSION + + '"\n' + + '> $env:BW_SESSION="' + + process.env.BW_SESSION + + '"\n\n' + + "You can also pass the session key to any command with the `--session` option. ex:\n" + + "$ bw list items --session " + + process.env.BW_SESSION + ); + res.raw = process.env.BW_SESSION; + return Response.success(res); + } + + private async updateTempPassword(error?: string): Promise { + // If no interaction available, alert user to use web vault + if (!this.canInteract) { + await this.logoutCallback(); + this.authService.logOut(() => { + /* Do nothing */ + }); + return Response.error( + new MessageResponse( + "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now via the web vault. You have been logged out.", + null + ) + ); + } + + if (this.email == null || this.email === "undefined") { + this.email = await this.stateService.getEmail(); + } + + // Get New Master Password + const baseMessage = + "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now.\n" + + "Master password: "; + const firstMessage = error != null ? error + baseMessage : baseMessage; + const mp: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ + type: "password", + name: "password", + message: firstMessage, + }); + const masterPassword = mp.password; + + // Master Password Validation + if (masterPassword == null || masterPassword === "") { + return this.updateTempPassword("Master password is required.\n"); + } + + if (masterPassword.length < 8) { + return this.updateTempPassword("Master password must be at least 8 characters long.\n"); + } + + // Strength & Policy Validation + const strengthResult = this.passwordGenerationService.passwordStrength( + masterPassword, + this.getPasswordStrengthUserInput() + ); + + // Get New Master Password Re-type + const reTypeMessage = "Re-type New Master password (Strength: " + strengthResult.score + ")"; + const retype: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ + type: "password", + name: "password", + message: reTypeMessage, + }); + const masterPasswordRetype = retype.password; + + // Re-type Validation + if (masterPassword !== masterPasswordRetype) { + return this.updateTempPassword("Master password confirmation does not match.\n"); + } + + // Get Hint (optional) + const hint: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ + type: "input", + name: "input", + message: "Master Password Hint (optional):", + }); + const masterPasswordHint = hint.input; + + // Retrieve details for key generation + const enforcedPolicyOptions = await firstValueFrom( + this.policyService.masterPasswordPolicyOptions$() + ); + const kdf = await this.stateService.getKdfType(); + const kdfIterations = await this.stateService.getKdfIterations(); + + if ( + enforcedPolicyOptions != null && + !this.policyService.evaluateMasterPassword( + strengthResult.score, + masterPassword, + enforcedPolicyOptions + ) + ) { + return this.updateTempPassword( + "Your new master password does not meet the policy requirements.\n" + ); + } + + try { + // Create new key and hash new password + const newKey = await this.cryptoService.makeKey( + masterPassword, + this.email.trim().toLowerCase(), + kdf, + kdfIterations + ); + const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey); + + // Grab user's current enc key + const userEncKey = await this.cryptoService.getEncKey(); + + // Create new encKey for the User + const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey); + + // Create request + const request = new UpdateTempPasswordRequest(); + request.key = newEncKey[1].encryptedString; + request.newMasterPasswordHash = newPasswordHash; + request.masterPasswordHint = masterPasswordHint; + + // Update user's password + await this.apiService.putUpdateTempPassword(request); + return this.handleSuccessResponse(); + } catch (e) { + await this.logoutCallback(); + this.authService.logOut(() => { + /* Do nothing */ + }); + return Response.error(e); + } + } + + private async handleCaptchaRequired( + twoFactorRequest: TokenTwoFactorRequest, + credentials: PasswordLogInCredentials = null + ): Promise { + const badCaptcha = Response.badRequest( + "Your authentication request has been flagged and will require user interaction to proceed.\n" + + "Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n" + + "(https://bitwarden.com/help/cli-auth-challenges)" + ); + + try { + const captchaClientSecret = await this.apiClientSecret(true); + if (Utils.isNullOrWhitespace(captchaClientSecret)) { + return badCaptcha; + } + + let authResultResponse: AuthResult = null; + if (credentials != null) { + credentials.captchaToken = captchaClientSecret; + credentials.twoFactor = twoFactorRequest; + authResultResponse = await this.authService.logIn(credentials); + } else { + authResultResponse = await this.authService.logInTwoFactor( + twoFactorRequest, + captchaClientSecret + ); + } + + return authResultResponse; + } catch (e) { + if ( + e instanceof ErrorResponse || + (e.constructor.name === ErrorResponse.name && + (e as ErrorResponse).message.includes("Captcha is invalid")) + ) { + return badCaptcha; + } else { + return Response.error(e); + } + } + } + + private getPasswordStrengthUserInput() { + let userInput: string[] = []; + const atPosition = this.email.indexOf("@"); + if (atPosition > -1) { + userInput = userInput.concat( + this.email + .substr(0, atPosition) + .trim() + .toLowerCase() + .split(/[^A-Za-z0-9]/) + ); + } + return userInput; + } + + private async apiClientId(): Promise { + let clientId: string = null; + + const storedClientId: string = process.env.BW_CLIENTID; + if (storedClientId == null) { + if (this.canInteract) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "input", + name: "clientId", + message: "client_id:", + }); + clientId = answer.clientId; + } else { + clientId = null; + } + } else { + clientId = storedClientId; + } + + return clientId; + } + + private async apiClientSecret(isAdditionalAuthentication = false): Promise { + const additionalAuthenticationMessage = "Additional authentication required.\nAPI key "; + let clientSecret: string = null; + + const storedClientSecret: string = this.clientSecret || process.env.BW_CLIENTSECRET; + if (this.canInteract && storedClientSecret == null) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "input", + name: "clientSecret", + message: + (isAdditionalAuthentication ? additionalAuthenticationMessage : "") + "client_secret:", + }); + clientSecret = answer.clientSecret; + } else { + clientSecret = storedClientSecret; + } + + return clientSecret; + } + + private async apiIdentifiers(): Promise<{ clientId: string; clientSecret: string }> { + return { + clientId: await this.apiClientId(), + clientSecret: await this.apiClientSecret(), + }; + } + + private async openSsoPrompt( + codeChallenge: string, + state: string + ): Promise<{ ssoCode: string; orgIdentifier: string }> { + return new Promise((resolve, reject) => { + const callbackServer = http.createServer((req, res) => { + const urlString = "http://localhost" + req.url; + const url = new URL(urlString); + const code = url.searchParams.get("code"); + const receivedState = url.searchParams.get("state"); + const orgIdentifier = this.getOrgIdentifierFromState(receivedState); + res.setHeader("Content-Type", "text/html"); + if (code != null && receivedState != null && this.checkState(receivedState, state)) { + res.writeHead(200); + res.end( + "Success | Bitwarden CLI" + + "

Successfully authenticated with the Bitwarden CLI

" + + "

You may now close this tab and return to the terminal.

" + + "" + ); + callbackServer.close(() => + resolve({ + ssoCode: code, + orgIdentifier: orgIdentifier, + }) + ); + } else { + res.writeHead(400); + res.end( + "Failed | Bitwarden CLI" + + "

Something went wrong logging into the Bitwarden CLI

" + + "

You may now close this tab and return to the terminal.

" + + "" + ); + callbackServer.close(() => reject()); + } + }); + let foundPort = false; + const webUrl = this.environmentService.getWebVaultUrl(); + for (let port = 8065; port <= 8070; port++) { + try { + this.ssoRedirectUri = "http://localhost:" + port; + callbackServer.listen(port, () => { + this.platformUtilsService.launchUri( + webUrl + + "/#/sso?clientId=" + + "cli" + + "&redirectUri=" + + encodeURIComponent(this.ssoRedirectUri) + + "&state=" + + state + + "&codeChallenge=" + + codeChallenge + ); + }); + foundPort = true; + break; + } catch { + // Ignore error since we run the same command up to 5 times. + } + } + if (!foundPort) { + reject(); + } + }); + } + + private getOrgIdentifierFromState(state: string): string { + if (state === null || state === undefined) { + return null; + } + + const stateSplit = state.split("_identifier="); + return stateSplit.length > 1 ? stateSplit[1] : null; + } + + private checkState(state: string, checkState: string): boolean { + if (state === null || state === undefined) { + return false; + } + if (checkState === null || checkState === undefined) { + return false; + } + + const stateSplit = state.split("_identifier="); + const checkStateSplit = checkState.split("_identifier="); + return stateSplit[0] === checkStateSplit[0]; } } diff --git a/libs/node/src/cli/commands/logout.command.ts b/apps/cli/src/commands/logout.command.ts similarity index 89% rename from libs/node/src/cli/commands/logout.command.ts rename to apps/cli/src/commands/logout.command.ts index 9f8a544410a..3de76275761 100644 --- a/libs/node/src/cli/commands/logout.command.ts +++ b/apps/cli/src/commands/logout.command.ts @@ -2,7 +2,7 @@ import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { Response } from "../models/response"; -import { MessageResponse } from "../models/response/messageResponse"; +import { MessageResponse } from "../models/response/message.response"; export class LogoutCommand { constructor( diff --git a/apps/cli/src/commands/restore.command.ts b/apps/cli/src/commands/restore.command.ts index 75e50dabfd1..48e6b2aa9cc 100644 --- a/apps/cli/src/commands/restore.command.ts +++ b/apps/cli/src/commands/restore.command.ts @@ -1,5 +1,6 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; -import { Response } from "@bitwarden/node/cli/models/response"; + +import { Response } from "../models/response"; export class RestoreCommand { constructor(private cipherService: CipherService) {} diff --git a/apps/cli/src/commands/send/create.command.ts b/apps/cli/src/commands/send/create.command.ts index cfea2e863cf..2fab074f443 100644 --- a/apps/cli/src/commands/send/create.command.ts +++ b/apps/cli/src/commands/send/create.command.ts @@ -6,10 +6,10 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SendType } from "@bitwarden/common/enums/sendType"; import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendResponse } from "../../models/response/sendResponse"; -import { SendTextResponse } from "../../models/response/sendTextResponse"; +import { Response } from "../../models/response"; +import { SendTextResponse } from "../../models/response/send-text.response"; +import { SendResponse } from "../../models/response/send.response"; import { CliUtils } from "../../utils"; export class SendCreateCommand { diff --git a/apps/cli/src/commands/send/delete.command.ts b/apps/cli/src/commands/send/delete.command.ts index 71778a6fc8c..6138382565c 100644 --- a/apps/cli/src/commands/send/delete.command.ts +++ b/apps/cli/src/commands/send/delete.command.ts @@ -1,5 +1,6 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; -import { Response } from "@bitwarden/node/cli/models/response"; + +import { Response } from "../../models/response"; export class SendDeleteCommand { constructor(private sendService: SendService) {} diff --git a/apps/cli/src/commands/send/edit.command.ts b/apps/cli/src/commands/send/edit.command.ts index bb4d467eafe..4fc92fb89a1 100644 --- a/apps/cli/src/commands/send/edit.command.ts +++ b/apps/cli/src/commands/send/edit.command.ts @@ -1,9 +1,9 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SendType } from "@bitwarden/common/enums/sendType"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendResponse } from "../../models/response/sendResponse"; +import { Response } from "../../models/response"; +import { SendResponse } from "../../models/response/send.response"; import { CliUtils } from "../../utils"; import { SendGetCommand } from "./get.command"; diff --git a/apps/cli/src/commands/send/get.command.ts b/apps/cli/src/commands/send/get.command.ts index aeae1203a61..37367276a8d 100644 --- a/apps/cli/src/commands/send/get.command.ts +++ b/apps/cli/src/commands/send/get.command.ts @@ -6,9 +6,9 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SendService } from "@bitwarden/common/abstractions/send.service"; import { Utils } from "@bitwarden/common/misc/utils"; import { SendView } from "@bitwarden/common/models/view/send.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendResponse } from "../../models/response/sendResponse"; +import { Response } from "../../models/response"; +import { SendResponse } from "../../models/response/send.response"; import { DownloadCommand } from "../download.command"; export class SendGetCommand extends DownloadCommand { diff --git a/apps/cli/src/commands/send/list.command.ts b/apps/cli/src/commands/send/list.command.ts index 149c34d69d1..c37040b2550 100644 --- a/apps/cli/src/commands/send/list.command.ts +++ b/apps/cli/src/commands/send/list.command.ts @@ -1,10 +1,10 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SendService } from "@bitwarden/common/abstractions/send.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { ListResponse } from "@bitwarden/node/cli/models/response/listResponse"; -import { SendResponse } from "../..//models/response/sendResponse"; +import { Response } from "../../models/response"; +import { ListResponse } from "../../models/response/list.response"; +import { SendResponse } from "../../models/response/send.response"; export class SendListCommand { constructor( diff --git a/apps/cli/src/commands/send/receive.command.ts b/apps/cli/src/commands/send/receive.command.ts index 9a9803e57f5..824f7d1a930 100644 --- a/apps/cli/src/commands/send/receive.command.ts +++ b/apps/cli/src/commands/send/receive.command.ts @@ -14,9 +14,9 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-cr import { SendAccessRequest } from "@bitwarden/common/models/request/send-access.request"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { SendAccessView } from "@bitwarden/common/models/view/send-access.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendAccessResponse } from "../../models/response/sendAccessResponse"; +import { Response } from "../../models/response"; +import { SendAccessResponse } from "../../models/response/send-access.response"; import { DownloadCommand } from "../download.command"; export class SendReceiveCommand extends DownloadCommand { diff --git a/apps/cli/src/commands/send/removePassword.command.ts b/apps/cli/src/commands/send/remove-password.command.ts similarity index 79% rename from apps/cli/src/commands/send/removePassword.command.ts rename to apps/cli/src/commands/send/remove-password.command.ts index 53e82b7d722..53805fa003c 100644 --- a/apps/cli/src/commands/send/removePassword.command.ts +++ b/apps/cli/src/commands/send/remove-password.command.ts @@ -1,7 +1,7 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendResponse } from "../../models/response/sendResponse"; +import { Response } from "../../models/response"; +import { SendResponse } from "../../models/response/send.response"; export class SendRemovePasswordCommand { constructor(private sendService: SendService) {} diff --git a/apps/cli/src/commands/serve.command.ts b/apps/cli/src/commands/serve.command.ts index 9b2ea77784c..ddba5acbb7a 100644 --- a/apps/cli/src/commands/serve.command.ts +++ b/apps/cli/src/commands/serve.command.ts @@ -7,10 +7,10 @@ import * as koaJson from "koa-json"; import { KeySuffixOptions } from "@bitwarden/common/enums/keySuffixOptions"; import { Utils } from "@bitwarden/common/misc/utils"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { FileResponse } from "@bitwarden/node/cli/models/response/fileResponse"; import { Main } from "../bw"; +import { Response } from "../models/response"; +import { FileResponse } from "../models/response/file.response"; import { ConfirmCommand } from "./confirm.command"; import { CreateCommand } from "./create.command"; @@ -26,7 +26,7 @@ import { SendDeleteCommand } from "./send/delete.command"; import { SendEditCommand } from "./send/edit.command"; import { SendGetCommand } from "./send/get.command"; import { SendListCommand } from "./send/list.command"; -import { SendRemovePasswordCommand } from "./send/removePassword.command"; +import { SendRemovePasswordCommand } from "./send/remove-password.command"; import { ShareCommand } from "./share.command"; import { StatusCommand } from "./status.command"; import { SyncCommand } from "./sync.command"; diff --git a/apps/cli/src/commands/share.command.ts b/apps/cli/src/commands/share.command.ts index 8766baea8f7..c04760b9d93 100644 --- a/apps/cli/src/commands/share.command.ts +++ b/apps/cli/src/commands/share.command.ts @@ -1,7 +1,7 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { CipherResponse } from "../models/response/cipherResponse"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; import { CliUtils } from "../utils"; export class ShareCommand { diff --git a/apps/cli/src/commands/status.command.ts b/apps/cli/src/commands/status.command.ts index 1b8d5fa3f56..7cd109309c1 100644 --- a/apps/cli/src/commands/status.command.ts +++ b/apps/cli/src/commands/status.command.ts @@ -3,9 +3,9 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { TemplateResponse } from "../models/response/templateResponse"; +import { Response } from "../models/response"; +import { TemplateResponse } from "../models/response/template.response"; export class StatusCommand { constructor( diff --git a/apps/cli/src/commands/sync.command.ts b/apps/cli/src/commands/sync.command.ts index 6ba4166826f..3d72b6b89ea 100644 --- a/apps/cli/src/commands/sync.command.ts +++ b/apps/cli/src/commands/sync.command.ts @@ -1,8 +1,8 @@ import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; +import { StringResponse } from "../models/response/string.response"; import { CliUtils } from "../utils"; export class SyncCommand { diff --git a/apps/cli/src/commands/unlock.command.ts b/apps/cli/src/commands/unlock.command.ts index 83de7a0b1ce..7e2136e6e3e 100644 --- a/apps/cli/src/commands/unlock.command.ts +++ b/apps/cli/src/commands/unlock.command.ts @@ -10,12 +10,12 @@ import { HashPurpose } from "@bitwarden/common/enums/hashPurpose"; import { Utils } from "@bitwarden/common/misc/utils"; import { SecretVerificationRequest } from "@bitwarden/common/models/request/secret-verification.request"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; import { CliUtils } from "../utils"; -import { ConvertToKeyConnectorCommand } from "./convertToKeyConnector.command"; +import { ConvertToKeyConnectorCommand } from "./convert-to-key-connector.command"; export class UnlockCommand { constructor( diff --git a/libs/node/src/cli/commands/update.command.ts b/apps/cli/src/commands/update.command.ts similarity index 97% rename from libs/node/src/cli/commands/update.command.ts rename to apps/cli/src/commands/update.command.ts index 534e11de47d..b058feb72b8 100644 --- a/libs/node/src/cli/commands/update.command.ts +++ b/apps/cli/src/commands/update.command.ts @@ -4,7 +4,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { Response } from "../models/response"; -import { MessageResponse } from "../models/response/messageResponse"; +import { MessageResponse } from "../models/response/message.response"; export class UpdateCommand { inPkg = false; diff --git a/apps/cli/src/models/request/organizationCollectionRequest.ts b/apps/cli/src/models/request/organization-collection.request.ts similarity index 89% rename from apps/cli/src/models/request/organizationCollectionRequest.ts rename to apps/cli/src/models/request/organization-collection.request.ts index 7b9f184d855..7546d116092 100644 --- a/apps/cli/src/models/request/organizationCollectionRequest.ts +++ b/apps/cli/src/models/request/organization-collection.request.ts @@ -1,6 +1,6 @@ import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; -import { SelectionReadOnly } from "../selectionReadOnly"; +import { SelectionReadOnly } from "../selection-read-only"; export class OrganizationCollectionRequest extends CollectionExport { static template(): OrganizationCollectionRequest { diff --git a/libs/node/src/cli/models/response.ts b/apps/cli/src/models/response.ts similarity index 95% rename from libs/node/src/cli/models/response.ts rename to apps/cli/src/models/response.ts index d768c51fdeb..ad70c4c8df1 100644 --- a/libs/node/src/cli/models/response.ts +++ b/apps/cli/src/models/response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./response/baseResponse"; +import { BaseResponse } from "./response/base.response"; export class Response { static error(error: any, data?: any): Response { diff --git a/apps/cli/src/models/response/attachmentResponse.ts b/apps/cli/src/models/response/attachment.response.ts similarity index 100% rename from apps/cli/src/models/response/attachmentResponse.ts rename to apps/cli/src/models/response/attachment.response.ts diff --git a/libs/node/src/cli/models/response/baseResponse.ts b/apps/cli/src/models/response/base.response.ts similarity index 100% rename from libs/node/src/cli/models/response/baseResponse.ts rename to apps/cli/src/models/response/base.response.ts diff --git a/apps/cli/src/models/response/cipherResponse.ts b/apps/cli/src/models/response/cipher.response.ts similarity index 80% rename from apps/cli/src/models/response/cipherResponse.ts rename to apps/cli/src/models/response/cipher.response.ts index f1e83ac30e2..182bff6e7f2 100644 --- a/apps/cli/src/models/response/cipherResponse.ts +++ b/apps/cli/src/models/response/cipher.response.ts @@ -1,11 +1,11 @@ import { CipherType } from "@bitwarden/common/enums/cipherType"; import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; -import { AttachmentResponse } from "./attachmentResponse"; -import { LoginResponse } from "./loginResponse"; -import { PasswordHistoryResponse } from "./passwordHistoryResponse"; +import { AttachmentResponse } from "./attachment.response"; +import { BaseResponse } from "./base.response"; +import { LoginResponse } from "./login.response"; +import { PasswordHistoryResponse } from "./password-history.response"; export class CipherResponse extends CipherWithIdExport implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/collectionResponse.ts b/apps/cli/src/models/response/collection.response.ts similarity index 82% rename from apps/cli/src/models/response/collectionResponse.ts rename to apps/cli/src/models/response/collection.response.ts index 1818c946e35..4275415233f 100644 --- a/apps/cli/src/models/response/collectionResponse.ts +++ b/apps/cli/src/models/response/collection.response.ts @@ -1,6 +1,7 @@ import { CollectionWithIdExport } from "@bitwarden/common/models/export/collection-with-id.export"; import { CollectionView } from "@bitwarden/common/models/view/collection.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; + +import { BaseResponse } from "./base.response"; export class CollectionResponse extends CollectionWithIdExport implements BaseResponse { object: string; diff --git a/libs/node/src/cli/models/response/fileResponse.ts b/apps/cli/src/models/response/file.response.ts similarity index 83% rename from libs/node/src/cli/models/response/fileResponse.ts rename to apps/cli/src/models/response/file.response.ts index c16b56e0912..487cb270e2d 100644 --- a/libs/node/src/cli/models/response/fileResponse.ts +++ b/apps/cli/src/models/response/file.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./baseResponse"; +import { BaseResponse } from "./base.response"; export class FileResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/folderResponse.ts b/apps/cli/src/models/response/folder.response.ts similarity index 81% rename from apps/cli/src/models/response/folderResponse.ts rename to apps/cli/src/models/response/folder.response.ts index 196071fbab7..5a73d7feb28 100644 --- a/apps/cli/src/models/response/folderResponse.ts +++ b/apps/cli/src/models/response/folder.response.ts @@ -1,6 +1,7 @@ import { FolderWithIdExport } from "@bitwarden/common/models/export/folder-with-id.export"; import { FolderView } from "@bitwarden/common/models/view/folder.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; + +import { BaseResponse } from "./base.response"; export class FolderResponse extends FolderWithIdExport implements BaseResponse { object: string; diff --git a/libs/node/src/cli/models/response/listResponse.ts b/apps/cli/src/models/response/list.response.ts similarity index 79% rename from libs/node/src/cli/models/response/listResponse.ts rename to apps/cli/src/models/response/list.response.ts index db415bcdb30..da6e7f0999d 100644 --- a/libs/node/src/cli/models/response/listResponse.ts +++ b/apps/cli/src/models/response/list.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./baseResponse"; +import { BaseResponse } from "./base.response"; export class ListResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/loginResponse.ts b/apps/cli/src/models/response/login.response.ts similarity index 100% rename from apps/cli/src/models/response/loginResponse.ts rename to apps/cli/src/models/response/login.response.ts diff --git a/libs/node/src/cli/models/response/messageResponse.ts b/apps/cli/src/models/response/message.response.ts similarity index 85% rename from libs/node/src/cli/models/response/messageResponse.ts rename to apps/cli/src/models/response/message.response.ts index 8612841ac1e..1440f97b646 100644 --- a/libs/node/src/cli/models/response/messageResponse.ts +++ b/apps/cli/src/models/response/message.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./baseResponse"; +import { BaseResponse } from "./base.response"; export class MessageResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/organizationCollectionResponse.ts b/apps/cli/src/models/response/organization-collection.response.ts similarity index 73% rename from apps/cli/src/models/response/organizationCollectionResponse.ts rename to apps/cli/src/models/response/organization-collection.response.ts index 811e4b2a57d..98b5b09c587 100644 --- a/apps/cli/src/models/response/organizationCollectionResponse.ts +++ b/apps/cli/src/models/response/organization-collection.response.ts @@ -1,8 +1,8 @@ import { CollectionView } from "@bitwarden/common/models/view/collection.view"; -import { SelectionReadOnly } from "../selectionReadOnly"; +import { SelectionReadOnly } from "../selection-read-only"; -import { CollectionResponse } from "./collectionResponse"; +import { CollectionResponse } from "./collection.response"; export class OrganizationCollectionResponse extends CollectionResponse { groups: SelectionReadOnly[]; diff --git a/apps/cli/src/models/response/organizationUserResponse.ts b/apps/cli/src/models/response/organization-user.response.ts similarity index 85% rename from apps/cli/src/models/response/organizationUserResponse.ts rename to apps/cli/src/models/response/organization-user.response.ts index 962fdb23bcd..92a8e63821a 100644 --- a/apps/cli/src/models/response/organizationUserResponse.ts +++ b/apps/cli/src/models/response/organization-user.response.ts @@ -1,6 +1,7 @@ import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; + +import { BaseResponse } from "./base.response"; export class OrganizationUserResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/organizationResponse.ts b/apps/cli/src/models/response/organization.response.ts similarity index 89% rename from apps/cli/src/models/response/organizationResponse.ts rename to apps/cli/src/models/response/organization.response.ts index 989623e35f0..1b100687f26 100644 --- a/apps/cli/src/models/response/organizationResponse.ts +++ b/apps/cli/src/models/response/organization.response.ts @@ -1,7 +1,8 @@ import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; import { Organization } from "@bitwarden/common/models/domain/organization"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; + +import { BaseResponse } from "./base.response"; export class OrganizationResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/passwordHistoryResponse.ts b/apps/cli/src/models/response/password-history.response.ts similarity index 100% rename from apps/cli/src/models/response/passwordHistoryResponse.ts rename to apps/cli/src/models/response/password-history.response.ts diff --git a/apps/cli/src/models/response/sendAccessResponse.ts b/apps/cli/src/models/response/send-access.response.ts similarity index 82% rename from apps/cli/src/models/response/sendAccessResponse.ts rename to apps/cli/src/models/response/send-access.response.ts index a37fd479b7e..18644d67492 100644 --- a/apps/cli/src/models/response/sendAccessResponse.ts +++ b/apps/cli/src/models/response/send-access.response.ts @@ -1,9 +1,9 @@ import { SendType } from "@bitwarden/common/enums/sendType"; import { SendAccessView } from "@bitwarden/common/models/view/send-access.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; -import { SendFileResponse } from "./sendFileResponse"; -import { SendTextResponse } from "./sendTextResponse"; +import { BaseResponse } from "./base.response"; +import { SendFileResponse } from "./send-file.response"; +import { SendTextResponse } from "./send-text.response"; export class SendAccessResponse implements BaseResponse { static template(): SendAccessResponse { diff --git a/apps/cli/src/models/response/sendFileResponse.ts b/apps/cli/src/models/response/send-file.response.ts similarity index 100% rename from apps/cli/src/models/response/sendFileResponse.ts rename to apps/cli/src/models/response/send-file.response.ts diff --git a/apps/cli/src/models/response/sendTextResponse.ts b/apps/cli/src/models/response/send-text.response.ts similarity index 100% rename from apps/cli/src/models/response/sendTextResponse.ts rename to apps/cli/src/models/response/send-text.response.ts diff --git a/apps/cli/src/models/response/sendResponse.ts b/apps/cli/src/models/response/send.response.ts similarity index 95% rename from apps/cli/src/models/response/sendResponse.ts rename to apps/cli/src/models/response/send.response.ts index c2e47fa740e..6d9f555312e 100644 --- a/apps/cli/src/models/response/sendResponse.ts +++ b/apps/cli/src/models/response/send.response.ts @@ -1,10 +1,10 @@ import { SendType } from "@bitwarden/common/enums/sendType"; import { Utils } from "@bitwarden/common/misc/utils"; import { SendView } from "@bitwarden/common/models/view/send.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; -import { SendFileResponse } from "./sendFileResponse"; -import { SendTextResponse } from "./sendTextResponse"; +import { BaseResponse } from "./base.response"; +import { SendFileResponse } from "./send-file.response"; +import { SendTextResponse } from "./send-text.response"; const dateProperties: string[] = [ Utils.nameOf("deletionDate"), diff --git a/libs/node/src/cli/models/response/stringResponse.ts b/apps/cli/src/models/response/string.response.ts similarity index 78% rename from libs/node/src/cli/models/response/stringResponse.ts rename to apps/cli/src/models/response/string.response.ts index f4becfe84ca..49bb299b669 100644 --- a/libs/node/src/cli/models/response/stringResponse.ts +++ b/apps/cli/src/models/response/string.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./baseResponse"; +import { BaseResponse } from "./base.response"; export class StringResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/templateResponse.ts b/apps/cli/src/models/response/template.response.ts similarity index 70% rename from apps/cli/src/models/response/templateResponse.ts rename to apps/cli/src/models/response/template.response.ts index 15944b0780e..86dc0b15886 100644 --- a/apps/cli/src/models/response/templateResponse.ts +++ b/apps/cli/src/models/response/template.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; +import { BaseResponse } from "./base.response"; export class TemplateResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/selectionReadOnly.ts b/apps/cli/src/models/selection-read-only.ts similarity index 100% rename from apps/cli/src/models/selectionReadOnly.ts rename to apps/cli/src/models/selection-read-only.ts diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index f80a87068b1..41a99dcd9bc 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -3,11 +3,6 @@ import * as program from "commander"; import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; import { KeySuffixOptions } from "@bitwarden/common/enums/keySuffixOptions"; -import { BaseProgram } from "@bitwarden/node/cli/baseProgram"; -import { LogoutCommand } from "@bitwarden/node/cli/commands/logout.command"; -import { UpdateCommand } from "@bitwarden/node/cli/commands/update.command"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; import { Main } from "./bw"; import { CompletionCommand } from "./commands/completion.command"; @@ -16,19 +11,23 @@ import { EncodeCommand } from "./commands/encode.command"; import { GenerateCommand } from "./commands/generate.command"; import { LockCommand } from "./commands/lock.command"; import { LoginCommand } from "./commands/login.command"; +import { LogoutCommand } from "./commands/logout.command"; import { ServeCommand } from "./commands/serve.command"; import { StatusCommand } from "./commands/status.command"; import { SyncCommand } from "./commands/sync.command"; import { UnlockCommand } from "./commands/unlock.command"; -import { TemplateResponse } from "./models/response/templateResponse"; +import { UpdateCommand } from "./commands/update.command"; +import { Response } from "./models/response"; +import { ListResponse } from "./models/response/list.response"; +import { MessageResponse } from "./models/response/message.response"; +import { StringResponse } from "./models/response/string.response"; +import { TemplateResponse } from "./models/response/template.response"; import { CliUtils } from "./utils"; const writeLn = CliUtils.writeLn; -export class Program extends BaseProgram { - constructor(protected main: Main) { - super(main.stateService, writeLn); - } +export class Program { + constructor(protected main: Main) {} async register() { program @@ -144,7 +143,6 @@ export class Program extends BaseProgram { this.main.authService, this.main.apiService, this.main.cryptoFunctionService, - this.main.i18nService, this.main.environmentService, this.main.passwordGenerationService, this.main.platformUtilsService, @@ -502,12 +500,102 @@ export class Program extends BaseProgram { } protected processResponse(response: Response, exitImmediately = false) { - super.processResponse(response, exitImmediately, () => { - if (response.data.object === "template") { - return this.getJson((response.data as TemplateResponse).template); + if (!response.success) { + if (process.env.BW_QUIET !== "true") { + if (process.env.BW_RESPONSE === "true") { + writeLn(this.getJson(response), true, false); + } else { + writeLn(chalk.redBright(response.message), true, true); + } } - return null; - }); + const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; + if (exitImmediately) { + process.exit(exitCode); + } else { + process.exitCode = exitCode; + } + return; + } + + if (process.env.BW_RESPONSE === "true") { + writeLn(this.getJson(response), true, false); + } else if (response.data != null) { + let out: string = null; + + if (response.data.object === "template") { + out = this.getJson((response.data as TemplateResponse).template); + } + + if (out == null) { + if (response.data.object === "string") { + const data = (response.data as StringResponse).data; + if (data != null) { + out = data; + } + } else if (response.data.object === "list") { + out = this.getJson((response.data as ListResponse).data); + } else if (response.data.object === "message") { + out = this.getMessage(response); + } else { + out = this.getJson(response.data); + } + } + + if (out != null && process.env.BW_QUIET !== "true") { + writeLn(out, true, false); + } + } + if (exitImmediately) { + process.exit(0); + } else { + process.exitCode = 0; + } + } + + private getJson(obj: any): string { + if (process.env.BW_PRETTY === "true") { + return JSON.stringify(obj, null, " "); + } else { + return JSON.stringify(obj); + } + } + + private getMessage(response: Response): string { + const message = response.data as MessageResponse; + if (process.env.BW_RAW === "true") { + return message.raw; + } + + let out = ""; + if (message.title != null) { + if (message.noColor) { + out = message.title; + } else { + out = chalk.greenBright(message.title); + } + } + if (message.message != null) { + if (message.title != null) { + out += "\n"; + } + out += message.message; + } + return out.trim() === "" ? null : out; + } + + private async exitIfAuthed() { + const authed = await this.main.stateService.getIsAuthenticated(); + if (authed) { + const email = await this.main.stateService.getEmail(); + this.processResponse(Response.error("You are already logged in as " + email + "."), true); + } + } + + private async exitIfNotAuthed() { + const authed = await this.main.stateService.getIsAuthenticated(); + if (!authed) { + this.processResponse(Response.error("You are not logged in."), true); + } } protected async exitIfLocked() { diff --git a/apps/cli/src/send.program.ts b/apps/cli/src/send.program.ts index f92e2a305e1..78c607f0419 100644 --- a/apps/cli/src/send.program.ts +++ b/apps/cli/src/send.program.ts @@ -6,7 +6,6 @@ import * as program from "commander"; import { SendType } from "@bitwarden/common/enums/sendType"; import { Utils } from "@bitwarden/common/misc/utils"; -import { Response } from "@bitwarden/node/cli/models/response"; import { Main } from "./bw"; import { GetCommand } from "./commands/get.command"; @@ -16,10 +15,11 @@ import { SendEditCommand } from "./commands/send/edit.command"; import { SendGetCommand } from "./commands/send/get.command"; import { SendListCommand } from "./commands/send/list.command"; import { SendReceiveCommand } from "./commands/send/receive.command"; -import { SendRemovePasswordCommand } from "./commands/send/removePassword.command"; -import { SendFileResponse } from "./models/response/sendFileResponse"; -import { SendResponse } from "./models/response/sendResponse"; -import { SendTextResponse } from "./models/response/sendTextResponse"; +import { SendRemovePasswordCommand } from "./commands/send/remove-password.command"; +import { Response } from "./models/response"; +import { SendFileResponse } from "./models/response/send-file.response"; +import { SendTextResponse } from "./models/response/send-text.response"; +import { SendResponse } from "./models/response/send.response"; import { Program } from "./program"; import { CliUtils } from "./utils"; diff --git a/libs/node/src/cli/services/cliPlatformUtils.service.ts b/apps/cli/src/services/cli-platform-utils.service.ts similarity index 100% rename from libs/node/src/cli/services/cliPlatformUtils.service.ts rename to apps/cli/src/services/cli-platform-utils.service.ts diff --git a/libs/node/spec/cli/consoleLog.service.spec.ts b/apps/cli/src/services/console-log.service.spec.ts similarity index 84% rename from libs/node/spec/cli/consoleLog.service.spec.ts rename to apps/cli/src/services/console-log.service.spec.ts index 656a74ca55d..d629b7c1c06 100644 --- a/libs/node/spec/cli/consoleLog.service.spec.ts +++ b/apps/cli/src/services/console-log.service.spec.ts @@ -1,6 +1,6 @@ -import { ConsoleLogService } from "@bitwarden/node/cli/services/consoleLog.service"; +import { interceptConsole, restoreConsole } from "@bitwarden/common/spec/shared/interceptConsole"; -import { interceptConsole, restoreConsole } from "../../../common/spec/shared/interceptConsole"; +import { ConsoleLogService } from "./console-log.service"; let caughtMessage: any = {}; diff --git a/libs/node/src/cli/services/consoleLog.service.ts b/apps/cli/src/services/console-log.service.ts similarity index 100% rename from libs/node/src/cli/services/consoleLog.service.ts rename to apps/cli/src/services/console-log.service.ts diff --git a/libs/node/src/services/lowdbStorage.service.ts b/apps/cli/src/services/lowdb-storage.service.ts similarity index 87% rename from libs/node/src/services/lowdbStorage.service.ts rename to apps/cli/src/services/lowdb-storage.service.ts index 102168f909b..6e096bd3c37 100644 --- a/libs/node/src/services/lowdbStorage.service.ts +++ b/apps/cli/src/services/lowdb-storage.service.ts @@ -3,6 +3,8 @@ import * as path from "path"; import * as lowdb from "lowdb"; import * as FileSync from "lowdb/adapters/FileSync"; +import * as lock from "proper-lockfile"; +import { OperationOptions } from "retry"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; @@ -10,6 +12,13 @@ import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; import { sequentialize } from "@bitwarden/common/misc/sequentialize"; import { Utils } from "@bitwarden/common/misc/utils"; +const retries: OperationOptions = { + retries: 50, + minTimeout: 100, + maxTimeout: 250, + factor: 2, +}; + export class LowdbStorageService implements AbstractStorageService { protected dataFilePath: string; private db: lowdb.LowdbSync; @@ -20,7 +29,8 @@ export class LowdbStorageService implements AbstractStorageService { protected logService: LogService, defaults?: any, private dir?: string, - private allowCache = false + private allowCache = false, + private requireLock = false ) { this.defaults = defaults; } @@ -130,8 +140,18 @@ export class LowdbStorageService implements AbstractStorageService { } protected async lockDbFile(action: () => T): Promise { - // Lock methods implemented in clients - return Promise.resolve(action()); + if (this.requireLock && !Utils.isNullOrWhitespace(this.dataFilePath)) { + this.logService.info("acquiring db file lock"); + return await lock.lock(this.dataFilePath, { retries: retries }).then((release) => { + try { + return action(); + } finally { + release(); + } + }); + } else { + return action(); + } } private readForNoCache() { diff --git a/apps/cli/src/services/lowdbStorage.service.ts b/apps/cli/src/services/lowdbStorage.service.ts deleted file mode 100644 index e01a161c518..00000000000 --- a/apps/cli/src/services/lowdbStorage.service.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as lock from "proper-lockfile"; -import { OperationOptions } from "retry"; - -import { LogService } from "@bitwarden/common/abstractions/log.service"; -import { Utils } from "@bitwarden/common/misc/utils"; -import { LowdbStorageService as LowdbStorageServiceBase } from "@bitwarden/node/services/lowdbStorage.service"; - -const retries: OperationOptions = { - retries: 50, - minTimeout: 100, - maxTimeout: 250, - factor: 2, -}; - -export class LowdbStorageService extends LowdbStorageServiceBase { - constructor( - logService: LogService, - defaults?: any, - dir?: string, - allowCache = false, - private requireLock = false - ) { - super(logService, defaults, dir, allowCache); - } - - protected async lockDbFile(action: () => T): Promise { - if (this.requireLock && !Utils.isNullOrWhitespace(this.dataFilePath)) { - this.logService.info("acquiring db file lock"); - return await lock.lock(this.dataFilePath, { retries: retries }).then((release) => { - try { - return action(); - } finally { - release(); - } - }); - } else { - return action(); - } - } -} diff --git a/libs/node/src/services/nodeApi.service.ts b/apps/cli/src/services/node-api.service.ts similarity index 100% rename from libs/node/src/services/nodeApi.service.ts rename to apps/cli/src/services/node-api.service.ts diff --git a/apps/cli/src/services/nodeEnvSecureStorage.service.ts b/apps/cli/src/services/node-env-secure-storage.service.ts similarity index 100% rename from apps/cli/src/services/nodeEnvSecureStorage.service.ts rename to apps/cli/src/services/node-env-secure-storage.service.ts diff --git a/apps/cli/src/utils.ts b/apps/cli/src/utils.ts index 440d02c25d2..e41c1c7896a 100644 --- a/apps/cli/src/utils.ts +++ b/apps/cli/src/utils.ts @@ -10,8 +10,9 @@ import { Utils } from "@bitwarden/common/misc/utils"; import { Organization } from "@bitwarden/common/models/domain/organization"; import { CollectionView } from "@bitwarden/common/models/view/collection.view"; import { FolderView } from "@bitwarden/common/models/view/folder.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; + +import { Response } from "./models/response"; +import { MessageResponse } from "./models/response/message.response"; export class CliUtils { static writeLn(s: string, finalLine = false, error = false) { diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 616568fe248..a3edf1a9132 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -1,7 +1,5 @@ import * as program from "commander"; -import { Response } from "@bitwarden/node/cli/models/response"; - import { Main } from "./bw"; import { ConfirmCommand } from "./commands/confirm.command"; import { CreateCommand } from "./commands/create.command"; @@ -13,6 +11,7 @@ import { ImportCommand } from "./commands/import.command"; import { ListCommand } from "./commands/list.command"; import { RestoreCommand } from "./commands/restore.command"; import { ShareCommand } from "./commands/share.command"; +import { Response } from "./models/response"; import { Program } from "./program"; import { CliUtils } from "./utils"; diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index da47ac52d8b..845c6b5afcf 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -12,9 +12,10 @@ "sourceMap": true, "baseUrl": ".", "paths": { + "@bitwarden/common/spec/*": ["../../libs/common/spec/*"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/node/*": ["../../libs/node/src/*"] } }, - "include": ["src"] + "include": ["src", "src/**/*.spec.ts"] } diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 85d4a1ab6a6..b9c437f591d 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -30,6 +30,6 @@ }, "_moduleAliases": { "@bitwarden/common": "dist/libs/common/src", - "@bitwarden/node/services/nodeCryptoFunction.service": "dist/libs/node/src/services/nodeCryptoFunction.service" + "@bitwarden/node/services/node-crypto-function.service": "dist/libs/node/src/services/node-crypto-function.service" } } diff --git a/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts b/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts index 916ec3c286e..d1f29493fc0 100644 --- a/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts +++ b/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts @@ -7,7 +7,7 @@ import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; -import { NodeCryptoFunctionService } from "@bitwarden/node/services/nodeCryptoFunction.service"; +import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; import { DecryptedCommandData } from "../../src/models/nativeMessaging/decryptedCommandData"; import { EncryptedMessage } from "../../src/models/nativeMessaging/encryptedMessage"; diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index aba5e702467..cca2d86a35a 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -782,11 +782,11 @@ "message": "Hlavní heslo si můžete změnit na webové stránce bitwarden.com. Chcete tuto stránku nyní otevřít?" }, "fingerprintPhrase": { - "message": "Fráze otisku prstu", + "message": "Fráze otisku účtu", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "yourAccountsFingerprint": { - "message": "Fráze otisku prstu vašeho účtu", + "message": "Fráze otisku vašeho účtu", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "goToWebVault": { diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 85c3045db29..d0a0bd56cf2 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -1563,7 +1563,7 @@ "message": "Uusi pääsalasanasi ei täytä käytännön määrittämiä vaatimuksia." }, "acceptPolicies": { - "message": "Valitsemalla tämän ruudun hyväksyt seuraavat:" + "message": "Valitsemalla tämän hyväksyt seuraavat:" }, "acceptPoliciesRequired": { "message": "Palveluehtoja ja tietosuojakäytäntöä ei ole vahvistettu." diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index acb6259e065..b2329fe2cf3 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -1766,7 +1766,7 @@ "message": "Скопировать ссылку в буфер обмена после сохранения, чтобы поделиться этой Send." }, "sendDisabled": { - "message": "Send отключена", + "message": "Send удалена", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 308c87584c3..645e47bbbc0 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -582,7 +582,7 @@ "message": "Създаване на абонамент" }, "newAroundHere": { - "message": "New around here?" + "message": "За пръв път ли сте тук?" }, "startTrial": { "message": "Стартиране на пробния период" diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index e3e6c3f8f85..4c18613ed17 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -4420,7 +4420,7 @@ "message": "La resposta de les claus d’organització és nul·la" }, "resetPasswordDetailsError": { - "message": "La resposta Restableix detalls de contrasenya és nul·la" + "message": "La resposta dels detalls de restabliment de la contrasenya és nul·la" }, "trashCleanupWarning": { "message": "Els elements que porten més de 30 dies a la paperera se suprimiran automàticament." @@ -4516,7 +4516,7 @@ "message": "Configuració del proveïdor" }, "setupProviderLoginDesc": { - "message": "T'han convidat a configurar un proveïdor nou. Per continuar, heu d'iniciar sessió o crear un nou compte de Bitwarden." + "message": "Us han convidat a configurar un proveïdor nou. Per continuar, heu d'iniciar sessió o crear un nou compte de Bitwarden." }, "setupProviderDesc": { "message": "Introduïu els detalls a continuació per completar la configuració del proveïdor. Poseu-vos en contacte amb el servei d'atenció al client si teniu cap pregunta." @@ -4547,7 +4547,7 @@ "message": "Els usuaris del servei poden accedir i gestionar totes les organitzacions client." }, "providerInviteUserDesc": { - "message": "Convida un nou usuari a la vostra organització introduint l'adreça electrònica del compte de Bitwarden a continuació. Si encara no tenen un compte de Bitwarden, se us demanarà que creeu un compte nou." + "message": "Convideu un nou usuari a la vostra organització introduint l'adreça electrònica del compte de Bitwarden a continuació. Si encara no tenen un compte de Bitwarden, se us demanarà que creeu un compte nou." }, "joinProvider": { "message": "Uniu-vos al proveïdor" @@ -4556,7 +4556,7 @@ "message": "Heu estat convidat a unir-vos al proveïdor llistat més amunt. Per acceptar la invitació, heu d'iniciar sessió o crear un compte nou a Bitwarden." }, "providerInviteAcceptFailed": { - "message": "No es pot acceptar la invitació. Demaneu a l'administrador d'una organització que envie una invitació nova." + "message": "No es pot acceptar la invitació. Demaneu a un administrador del proveïdor que envie una nova invitació." }, "providerInviteAcceptedDesc": { "message": "Podeu accedir a aquesta organització una vegada que un administrador confirme la vostra pertinença. Rebreu un correu electrònic quan això passe." @@ -4608,10 +4608,10 @@ "message": "S'ha inhabilitat el proveïdor." }, "providerUpdated": { - "message": "S'ha actualitzat el proveïdor" + "message": "S'ha guardat el proveïdor" }, "yourProviderIs": { - "message": "El vostre proveïdor és $PROVIDER$. Tenen privilegis administratius i de facturació en la vostra organització.", + "message": "El vostre proveïdor és $PROVIDER$. Té privilegis administratius i de facturació en la vostra organització.", "placeholders": { "provider": { "content": "$1", @@ -4635,7 +4635,7 @@ "message": "Afig" }, "updatedMasterPassword": { - "message": "Contrasenya mestra actualitzada" + "message": "Contrasenya mestra guardada" }, "updateMasterPassword": { "message": "Actualitza contrasenya mestra" diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 2da886dc61a..cbdd04f18aa 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -936,10 +936,10 @@ "message": "Set a password to encrypt the export and import it to any Bitwarden account using the password for decryption." }, "fileTypeHeading": { - "message": "File type" + "message": "Typ souboru" }, "accountBackup": { - "message": "Account backup" + "message": "Zálohování účtu" }, "passwordProtected": { "message": "Password protected" @@ -1199,7 +1199,7 @@ "message": "Choose File" }, "noFileChosen": { - "message": "No file chosen" + "message": "Není vybrán žádný soubor" }, "orCopyPasteFileContents": { "message": "nebo zkopírujte a vložte obsah souboru" @@ -2897,7 +2897,7 @@ "message": "Znovu poslat pozvánku" }, "resendEmail": { - "message": "Resend email" + "message": "Znovu poslat e-mail" }, "hasBeenReinvited": { "message": "Uživatel $USER$ byl znovu pozván.", @@ -3345,7 +3345,7 @@ "description": "ex. Date this item was updated" }, "dateCreated": { - "message": "Created", + "message": "Vytvořeno", "description": "ex. Date this item was created" }, "datePasswordUpdated": { @@ -3418,7 +3418,7 @@ "message": "Ve vašem trezoru jsou staré přílohy vyžadující opravu před změnou šifrovacího klíče k vašemu účtu." }, "yourAccountsFingerprint": { - "message": "Fráze otisku prstu vašeho účtu", + "message": "Fráze otisku vašeho účtu", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "fingerprintEnsureIntegrityVerify": { @@ -3432,7 +3432,7 @@ "message": "Fingerprint phrase" }, "dontAskFingerprintAgain": { - "message": "Již se neptat na ověření fráze otisku prstu", + "message": "Již se neptat na ověření fráze otisku účtu (nedoporučeno)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "free": { @@ -4123,10 +4123,10 @@ "message": "Oprávnění" }, "permission": { - "message": "Permission" + "message": "Oprávnění" }, "managerPermissions": { - "message": "Manager Permissions" + "message": "Spravovat oprávnění" }, "adminPermissions": { "message": "Admin Permissions" @@ -5216,7 +5216,7 @@ "description": "This is used by screen readers to indicate the organization that is currently being shown to the user." }, "accountSettings": { - "message": "Account settings" + "message": "Nastavení účtu" }, "generator": { "message": "Generator" @@ -5443,7 +5443,7 @@ "message": "Zapnuto" }, "members": { - "message": "Members" + "message": "Členové" }, "reporting": { "message": "Reporting" @@ -5479,7 +5479,7 @@ "message": "To" }, "member": { - "message": "Member" + "message": "Člen" }, "update": { "message": "Update" diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 5ac7fabfef6..bd53b87cb75 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -1085,7 +1085,7 @@ } }, "kdfIterationsWarning": { - "message": "Hvis du indstiller dine KDF iterationer for højt, kan det resultere i dårlig ydeevne, når du logger ind på (og låser op for) Bitwarden på enheder med langsomme CPU'er. Vi anbefaler at du øger værdien i trin på $INCREMENT$ og derefter tester alle dine enheder.", + "message": "Indstilles KDF-iterationer for højt, kan det resultere i dårlig ydeevne under indlogning på (og oplåsning af) Bitwarden på enheder med langsomme CPU'er. En værdiforøgelse i trin á $INCREMENT$ anbefales med efterfølgende test på alle dine enheder.", "placeholders": { "increment": { "content": "$1", @@ -1094,7 +1094,7 @@ } }, "changeKdf": { - "message": "Ændre KDF" + "message": "Ændr KDF" }, "encKeySettingsChanged": { "message": "Indstillinger for krypteringsnøgle gemt" @@ -1103,7 +1103,7 @@ "message": "Farezone" }, "dangerZoneDesc": { - "message": "Pas på, disse handlinger kan ikke fortrydes!" + "message": "Pas på, disse handlinger er irreversible!" }, "deauthorizeSessions": { "message": "Fjern sessionsgodkendelser" @@ -1112,7 +1112,7 @@ "message": "Bekymret for om kontoen er logget ind på en anden enhed? Fortsæt nedenfor for at fjerne godkendelsen for alle tidligere anvendte enhedstyper. Dette sikkerhedstrin tilrådes, hvis man tidligere har anvendt en offentlig computer eller ved et uheld har gemt sin adgangskode på en enhed, der ikke er ens egen. Dette trin rydder også alle tidligere lagrede totrins-login sessioner." }, "deauthorizeSessionsWarning": { - "message": "Fortsættes, logges man også ud af den nuværende session og vil skulle logge ind igen. Der anmodes også om totrins-login igen, såfremt funktionen er opsat. Aktive sessioner på andre enheder vil kunne forblive aktive i op til én time." + "message": "Fortsættes, logges man også ud af denne session og vil skulle logge ind igen. Der anmodes også om totrins-login igen, såfremt funktionen er opsat. Aktive sessioner på andre enheder kan forblive aktive i op til én time." }, "sessionsDeauthorized": { "message": "Godkendelser for alle sessioner fjernet" @@ -1127,13 +1127,13 @@ "message": "Boks tilgået af udbyder." }, "purgeVaultDesc": { - "message": "Fortsæt nedenfor for at slette alle elementer og mapper i din boks. Elementer, der tilhører en organisation du deler med, slettes ikke." + "message": "Fortsæt nedenfor for at slette alle emner og mapper i din boks. Emner, tilhørende en organisation du deler med, slettes ikke." }, "purgeOrgVaultDesc": { - "message": "Fortsæt nedenfor for at slette alle elementer i organisationens boks." + "message": "Fortsæt nedenfor for at slette alle emner i organisationens boks." }, "purgeVaultWarning": { - "message": "Tømning af din boks er permanent. Det kan ikke fortrydes." + "message": "Tømning af din boks er permanent og irreversibel." }, "vaultPurged": { "message": "Boks er tømt." @@ -1145,13 +1145,13 @@ "message": "Fortsæt nedenfor for at slette din konto og alle boks-data." }, "deleteAccountWarning": { - "message": "Sletning af din konto er permanent. Det kan ikke fortrydes." + "message": "Sletning af din konto er permanent og irreversibel." }, "accountDeleted": { "message": "Konto slettet" }, "accountDeletedDesc": { - "message": "Din konto er blevet lukket, og alle tilknyttede data er blevet slettet." + "message": "Din konto er blevet lukket og alle tilknyttede data slettet." }, "myAccount": { "message": "Min konto" @@ -1172,7 +1172,7 @@ "message": "Data er nu importeret" }, "importWarning": { - "message": "Du importerer data til $ORGANIZATION$. Dine data kan blive delt med medlemmer af denne organisation. Vil du fortsætte?", + "message": "Du er ved at importere data til $ORGANIZATION$, som kan blive delt med medlemmer af denne organisation. Fortsæt?", "placeholders": { "organization": { "content": "$1", @@ -1181,13 +1181,13 @@ } }, "importFormatError": { - "message": "Data er ikke formateret korrekt. Kontroller din importfil og prøv igen." + "message": "Data er ikke korrekt formateret. Tjek importfilen og forsøg igen." }, "importNothingError": { "message": "Intet blev importeret." }, "importEncKeyError": { - "message": "Fejl ved dekryptering af den eksporterede fil. Din krypteringsnøglen matcher ikke den krypteringsnøgle, som blev anvendt til dataeksporten." + "message": "Fejl under dekryptering af den eksporterede fil. Krypteringsnøglen matcher ikke den til dataeksporten anvendte krypteringsnøgle." }, "selectFormat": { "message": "Vælg formatet for importfilen" @@ -1205,7 +1205,7 @@ "message": "eller kopiér/indsæt importfilens indhold" }, "instructionsFor": { - "message": "$NAME$ instruktioner", + "message": "$NAME$-vejledning", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -1230,7 +1230,7 @@ "message": "Sprog" }, "languageDesc": { - "message": "Skift det sprog, der bruges af web-boksen." + "message": "Skift sproget brugt af web-boksen." }, "enableFavicon": { "message": "Vis webstedsikoner" @@ -1239,11 +1239,11 @@ "message": "Vis et genkendeligt billede ud for hvert login." }, "enableFullWidth": { - "message": "Vis layout med fuld bredde", + "message": "Vis layout i fuld bredde", "description": "Allows scaling the web vault UI's width" }, "enableFullWidthDesc": { - "message": "Lad web-boksen udfylde browservinduets fulde bredde." + "message": "Lad web-boksen udnytte browservinduets fulde bredde." }, "default": { "message": "Standard" @@ -1252,7 +1252,7 @@ "message": "Domæneregler" }, "domainRulesDesc": { - "message": "Hvis du har samme login på tværs af flere forskellige website-domæner, kan du markere hjemmesiden som \"ækvivalent\". \"Globale\" domæner er dem, der allerede er oprettet til dig af Bitwarden." + "message": "Har man samme login på tværs af flere forskellige website-domæner, kan webstedet markeres som \"ækvivalent\". \"Globale\" domæner er dem, som allerede er oprettet til dig af Bitwarden." }, "globalEqDomains": { "message": "Globale ækvivalente domæner" @@ -1264,7 +1264,7 @@ "message": "Udeluk" }, "include": { - "message": "Inkluder" + "message": "Inkludér" }, "customize": { "message": "Tilpas" @@ -1273,7 +1273,7 @@ "message": "Nyt tilpasset domæne" }, "newCustomDomainDesc": { - "message": "Indtast en liste over domæner adskilt af kommaer. Kun \"grund\"-domæner er tilladt. Indtast ikke underdomæner. F.eks. Indtast \"google.com\" i stedet for \"www.google.com\". Du kan også indtaste \"androidapp://package.name\" for at knytte en androidapp til andre website-domæner." + "message": "Opret en liste ved at angive domæner adskilt af kommaer. Kun domæner er tilladt, ingen underdomæner (f.eks. \"google.com\", men ikke \"www.google.com\"). Du kan også angive \"androidapp://package.name \"for at knytte en Android-app til andre webstedsdomæner." }, "customDomainX": { "message": "Tilpasset domæne $INDEX$", @@ -1294,7 +1294,7 @@ "message": "Totrins-login håndhævelse" }, "twoStepLoginDesc": { - "message": "Gør din konto sikrere ved at kræve et ekstra trin, når du logger ind." + "message": "Beskyt kontoen ved at kræve et ekstra trin under indlogning." }, "twoStepLoginOrganizationDescStart": { "message": "Håndhæv Bitwarden totrins-login indstillinger for medlemmer vha. ", @@ -1310,7 +1310,7 @@ "message": "Er SSO opsat, eller planlægges opsat, håndhæves totrins-login muligvis allerede gennem identitetsudbyderen." }, "twoStepLoginRecoveryWarning": { - "message": "Opsætning af totrins-login kan permanent låse en bruger ude af sin Bitwarden-konto. En gendannelseskode muliggør kontoadgang, såfremt den normale totrins-loginudbyder ikke længere kan bruges (f.eks. ved tab af en enhed). Bitwarden-supporten kan ikke hjælpe ved mistet kontoadgang. Det anbefales at nedskrive/udskrive gendannelseskoden og gemmer denne et sikkert sted." + "message": "Opsætning af totrins-login kan permanent låse en bruger ude af sin Bitwarden-konto. En gendannelseskode muliggør kontoadgang, såfremt den normale totrins-loginudbyder ikke længere kan bruges (f.eks. ved tab af en enhed). Bitwarden-supporten kan ikke hjælpe ved mistet kontoadgang. Det anbefales at nedskrive/udskrive gendannelseskoden samt gemme denne et sikkert sted." }, "viewRecoveryCode": { "message": "Vis gendannelseskode" @@ -1336,16 +1336,16 @@ "message": "Premium-medlemskab" }, "premiumRequired": { - "message": "Premium påkrævet" + "message": "Premium kræves" }, "premiumRequiredDesc": { - "message": "Premium-medlemskab kræves for at anvende denne funktion." + "message": "Premium-medlemskab kræves for brug af denne funktion." }, "youHavePremiumAccess": { - "message": "Du har premium adgang" + "message": "Du har Premium-adgang" }, "alreadyPremiumFromOrg": { - "message": "Du har allerede adgang til premium-funktioner på grund af en organisation, du er medlem af." + "message": "Premium-funktioner er allerede tilgængelige via en organisation, du er medlem af." }, "manage": { "message": "Håndtér" @@ -1354,7 +1354,7 @@ "message": "Deaktivér" }, "revokeAccess": { - "message": "Fjern Adgang" + "message": "Ophæv adgang" }, "twoStepLoginProviderEnabled": { "message": "Denne totrins-loginudbyder er aktiveret på kontoen." @@ -1381,19 +1381,19 @@ "message": "Windows-enheder" }, "twoStepAuthenticatorAppsRecommended": { - "message": "Disse apps anbefales, men andre autentificeringsapps vil også fungere." + "message": "Disse apps anbefales, men andre godkendelses-apps vil også fungere." }, "twoStepAuthenticatorScanCode": { - "message": "Scan denne QR-kode med din autentiseringsapp" + "message": "Skan denne QR-kode med godkendelses-appen" }, "key": { "message": "Nøgle" }, "twoStepAuthenticatorEnterCode": { - "message": "Indtast den 6-cifrede verifikationskode fra appen" + "message": "Angiv den 6-cifrede bekræftelseskode fra appen" }, "twoStepAuthenticatorReaddDesc": { - "message": "Hvis du har brug for at tilføje den til en anden enhed, er nedenstående QR-koden (eller nøglen), der kræves af din autentificeringsapp." + "message": "Ønskes den føjet til en anden enhed, er nedenstående den QR-kode (eller nøgle), der kræves af godkendelses-appen." }, "twoStepDisableDesc": { "message": "Sikker på, at denne totrins-loginudbyder skal deaktiveres?" @@ -1405,13 +1405,13 @@ "message": "Tilføj en ny YubiKey til din konto" }, "twoFactorYubikeyPlugIn": { - "message": "Sæt YubiKey'en i din computers USB-port." + "message": "Isæt YubiKey'en i computerens USB-port." }, "twoFactorYubikeySelectKey": { "message": "Vælg det første tomme YubiKey-indtastningsfelt nedenfor." }, "twoFactorYubikeyTouchButton": { - "message": "Tryk på YubiKey knappen." + "message": "Tryk på YubiKey-knappen." }, "twoFactorYubikeySaveForm": { "message": "Gem formularen." @@ -1420,10 +1420,10 @@ "message": "Grundet platformsbegrænsninger kan YubiKeys ikke bruges i alle Bitwarden-applikationer. Man bør opsætter en anden totrins-loginudbyder, så man kan tilgå sin konto, når YubiKeys ikke kan benyttes. Understøttede platforme:" }, "twoFactorYubikeySupportUsb": { - "message": "Web-boks, desktop-program, CLI og alle browserudvidelser på en enhed med en USB-port, der kan benyttes af din YubiKey." + "message": "Web-boks, computerapplikation, CLI og alle browserudvidelser på en enhed med en USB-port, der accepterer din YubiKey." }, "twoFactorYubikeySupportMobile": { - "message": "Mobilapps på en enhed med NFC-understøttelse eller en data-port, der kan benyttes af din YubiKey." + "message": "Mobil-apps på en enhed med NFC-funktionalitet eller en dataport, der accepterer din YubiKey." }, "yubikeyX": { "message": "YubiKey $INDEX$", @@ -1435,7 +1435,7 @@ } }, "u2fkeyX": { - "message": "U2F nøgle $INDEX$", + "message": "U2F-nøgle $INDEX$", "placeholders": { "index": { "content": "$1", @@ -1453,13 +1453,13 @@ } }, "nfcSupport": { - "message": "NFC understøttelse" + "message": "NFC-understøttelse" }, "twoFactorYubikeySupportsNfc": { "message": "En af mine nøgler understøtter NFC." }, "twoFactorYubikeySupportsNfcDesc": { - "message": "Hvis en af dine YubiKeys understøtter NFC (f.eks. en YubiKey NEO), bliver du bedt om denne på mobilenheder, når NFC-tilgængelighed registreres." + "message": "Understøtter en YubiKey NFC (f.eks. YubiKey NEO), vises en prompt denne på mobilenheder, når NFC-tilgængelighed registreres." }, "yubikeysUpdated": { "message": "YubiKeys opdateret" @@ -1468,7 +1468,7 @@ "message": "Deaktivér alle nøgler" }, "twoFactorDuoDesc": { - "message": "Indtast Bitwarden-programoplysningerne fra dit Duo-administrationspanel." + "message": "Angiv Bitwarden-applikationsoplysningerne fra Duo Admin-panelet." }, "twoFactorDuoIntegrationKey": { "message": "Integrationsnøgle" @@ -1477,25 +1477,25 @@ "message": "Hemmelig nøgle" }, "twoFactorDuoApiHostname": { - "message": "API værtsnavn" + "message": "API-værtsnavn" }, "twoFactorEmailDesc": { "message": "Følg disse trin for at opsætte totrins-login vha. e-mail:" }, "twoFactorEmailEnterEmail": { - "message": "Indtast den e-mail, som du ønsker skal modtage verifikationskoder" + "message": "Angiv den e-mail, hvor bekræftelseskoder ønskes modtaget" }, "twoFactorEmailEnterCode": { - "message": "Indtast den 6-cifrede verifikationskode fra e-mailen" + "message": "Angiv den 6-cifrede bekræftelseskode fra e-mailen" }, "sendEmail": { - "message": "Send email" + "message": "Send e-mail" }, "twoFactorU2fAdd": { - "message": "Tilføj en FIDO U2F sikkerhedsnøgle til din konto" + "message": "Tilføj en FIDO U2F-sikkerhedsnøgle til din konto" }, "removeU2fConfirmation": { - "message": "Er du sikker på, at du vil fjerne denne sikkerhedsnøgle?" + "message": "Sikker på, at denne sikkerhedsnøgle skal fjernes?" }, "twoFactorWebAuthnAdd": { "message": "Føj en WebAuthn-sikkerhedsnøgle til din konto" @@ -1513,7 +1513,7 @@ "message": "Sæt sikkerhedsnøglen i computerens USB-port, og klik på knappen \"Læs nøgle\"." }, "twoFactorU2fTouchButton": { - "message": "Hvis sikkerhedsnøglen har en knap, skal du trykke på den." + "message": "Har sikkerhedsnøglen en knap, så tryk på denne." }, "twoFactorU2fSaveForm": { "message": "Gem formularen." @@ -1522,16 +1522,16 @@ "message": "Grundet platformsbegrænsninger kan FIDO U2F ikke bruges i alle Bitwarden-applikationer. Man bør opsætte en anden totrins-loginudbyder, så man kan tilgå sin konto, når FIDO U2F ikke kan benyttes. Understøttede platforme:" }, "twoFactorU2fSupportWeb": { - "message": "Web-boks og browserudvidelser på en desktop/laptop med en U2F-aktiveret browser (Chrome, Opera, Vivaldi eller Firefox med FIDO U2F aktiveret)." + "message": "Web-boks og browserudvidelser på en stationær/bærbar computer med en U2F-aktiveret browser (Chrome, Opera, Vivaldi eller Firefox med FIDO U2F aktiveret)." }, "twoFactorU2fWaiting": { - "message": "Venter på at du trykker på knappen på din sikkerhedsnøgle" + "message": "Afventer et tryk på knappen på din sikkerhedsnøgle" }, "twoFactorU2fClickSave": { "message": "Klik på knappen \"Gem\" nedenfor for at aktivere denne sikkerhedsnøgle til totrins-login." }, "twoFactorU2fProblemReadingTryAgain": { - "message": "Der opstod et problem med at læse sikkerhedsnøglen. Prøv igen." + "message": "Problem med at læse sikkerhedsnøglen. Forsøg igen." }, "twoFactorWebAuthnWarning": { "message": "Grundet platformsbegrænsninger kan WebAuthn ikke bruges i alle Bitwarden-applikationer. Man bør opsætte en anden totrins-loginudbyder, så man kan tilgå sin konto, når WebAuthn ikke kan benyttes. Understøttede platforme:" @@ -1540,10 +1540,10 @@ "message": "Web-boks og browserudvidelser på en stationær/bærbar computer med en WebAuthn-aktiveret browser (Chrome, Opera, Vivaldi eller Firefox med FIDO U2F aktiveret)." }, "twoFactorRecoveryYourCode": { - "message": "Bitwarden totrins-login gendannelseskoden" + "message": "Gendannelseskoden til Bitwarden totrins-login" }, "twoFactorRecoveryNoCode": { - "message": "Der er ikke opsat nogle totrins-loginudbydere endnu. Efter at have opsat én, kan man se sin gendannelseskode her." + "message": "Der er ikke opsat nogle totrins-loginudbydere endnu. Efter at have opsat én, kan du se din gendannelseskode her." }, "printCode": { "message": "Udskriv kode", @@ -1561,16 +1561,16 @@ "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization Vault." }, "unsecuredWebsitesReport": { - "message": "Usikre hjemmesider" + "message": "Usikre websteder" }, "unsecuredWebsitesReportDesc": { - "message": "URL'er, der starter med http://, bruger ikke den bedst tilgængelige kryptering. Skift login-URI'erne for disse konti til https:// for sikrere browsing." + "message": "URL'er startende med http:// bruger ikke den bedste tilgængelige kryptering. Skift login-URI'erne for disse konti til https:// for sikrere browsing." }, "unsecuredWebsitesFound": { - "message": "Ikke-sikrede hjemmesider fundet" + "message": "Ikke-sikrede websteder fundet" }, "unsecuredWebsitesFoundDesc": { - "message": "Vi fandt $COUNT$ elementer i din boks med ikke-sikrede URI'er. Du bør ændre deres URI-protokol til https:// hvis hjemmesiden tillader det.", + "message": "$COUNT$ emner fundet i din boks med ikke-sikrede URI'er. Deres URI-protokoller bør ændres til https://, såfremt webstedet tillader det.", "placeholders": { "count": { "content": "$1", @@ -1579,19 +1579,19 @@ } }, "noUnsecuredWebsites": { - "message": "Ingen elementer i din boks har ikke-sikrede URI'er." + "message": "Ingen emner i din boks med ikke-sikrede URI'er." }, "inactive2faReport": { - "message": "Inaktive totrins-login" + "message": "Inaktivt totrins-login" }, "inactive2faReportDesc": { "message": "Med totrins-login føjer man et beskyttelseslag til sine konti. Opsæt totrins-login vha. Bitwarden Authenticator for disse konti, eller brug en alternativ metode." }, "inactive2faFound": { - "message": "Logins uden totrinsopsætning fundet" + "message": "Logins uden totrins-login fundet" }, "inactive2faFoundDesc": { - "message": "$COUNT$ websted(er) fundet i boksen, som muligvis ikke er opsat med totrins-login (jf. 2fa.directory). For yderligere at beskytte disse konti, bør totrins-login aktiveres.", + "message": "$COUNT$ websted(er) fundet i boksen, som muligvis ikke er opsat med totrins-login (jf. 2fa.directory). For yderligere at beskytte disse konti, bør totrins-login opsættes.", "placeholders": { "count": { "content": "$1", @@ -1600,22 +1600,22 @@ } }, "noInactive2fa": { - "message": "Ingen websteder med manglende opsætning af totrins-login fundet i boksen." + "message": "Ingen websteder uden opsat totrins-login fundet i boksen." }, "instructions": { "message": "Instruktioner" }, "exposedPasswordsReport": { - "message": "Afslørede adgangskoder" + "message": "Kompromitterede adgangskoder" }, "exposedPasswordsReportDesc": { - "message": "Adgangskoder afsløret i et datalæk er nemme mål for angribere. Skift disse adgangskoder for at forhindre potentielle indbrud." + "message": "Adgangskoder kompromitteret i et datalæk er nemme mål for angribere. Skift disse adgangskoder for at forhindre potentielle indbrud." }, "exposedPasswordsFound": { - "message": "Afslørede adgangskoder fundet" + "message": "Kompromitterede adgangskoder fundet" }, "exposedPasswordsFoundDesc": { - "message": "Vi fandt $COUNT$ elementer i din boks, som har adgangskoder, der blev afsløret i kendte datalæk. Du bør ændre dem og bruge en ny adgangskode.", + "message": "$COUNT$ emner fundet i din boks med adgangskoder kompromitteret i kendte datalæk. En ny adgangskode bør opsættes for disse emner.", "placeholders": { "count": { "content": "$1", @@ -1624,13 +1624,13 @@ } }, "noExposedPasswords": { - "message": "Ingen elementer i din boks har adgangskoder, der har været afsløret i kendte datalæk." + "message": "Ingen emner i din boks med adgangskoder kompromitteret i kendte datalæk." }, "checkExposedPasswords": { - "message": "Tjek for afslørede adgangskoder" + "message": "Tjek for kompromitterede adgangskoder" }, "exposedXTimes": { - "message": "Afsløret $COUNT$ gang(e)", + "message": "$COUNT$ kompromittering(er)", "placeholders": { "count": { "content": "$1", @@ -1642,13 +1642,13 @@ "message": "Svage adgangskoder" }, "weakPasswordsReportDesc": { - "message": "Svage adgangskoder kan let gættes af angribere. Skift disse adgangskoder til stærke koder ved hjælp af adgangskodegeneratoren." + "message": "Svage adgangskoder kan let gættes af angribere. Skift disse adgangskoder til stærke koder vha. adgangskodegeneratoren." }, "weakPasswordsFound": { "message": "Svage adgangskoder fundet" }, "weakPasswordsFoundDesc": { - "message": "Vi fandt $COUNT$ elementer i din boks med adgangskoder, der ikke er stærke. Du bør opdatere dem og bruge stærkere adgangskoder.", + "message": "$COUNT$ emner fundet i din boks med adgangskoder, som ikke er stærke. Disse bør opdateres med stærkere adgangskoder.", "placeholders": { "count": { "content": "$1", @@ -2597,7 +2597,7 @@ } }, "viewedCardNumberItemId": { - "message": "Viewed Card Number for item $ID$.", + "message": "Kortnummer for emne $ID$ blev kigget.", "placeholders": { "id": { "content": "$1", @@ -3422,7 +3422,7 @@ "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "fingerprintEnsureIntegrityVerify": { - "message": "For at sikre integriteten af dine krypteringsnøgler, bedes du bekræfte brugerens fingeraftrykssætning, inden du fortsætter.", + "message": "For at sikre integriteten af dine krypteringsnøgler, så bekræft brugerens fingeraftrykssætning, inden der fortsættes.", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "fingerprintMatchInfo": { @@ -3443,22 +3443,22 @@ "message": "API-nøgle" }, "apiKeyDesc": { - "message": "Din API-nøgle kan bruges til godkendelse mod det offentlige Bitwarden-API." + "message": "Din API-nøgle kan bruges til godkendelse i det offentlige Bitwarden-API." }, "apiKeyRotateDesc": { - "message": "Hvis du roterer API-nøglen, bliver den forrige nøgle ugyldig. Du kan rotere din API-nøgle, hvis du mener at den nuværende nøgle ikke længere er sikker at bruge." + "message": "Rotation af API-nøglen ugyldiggør den foregående nøgle. API-nøglen kan roteres, såfremt brug af den aktuelle nøgle ikke længere anses for sikker." }, "apiKeyWarning": { - "message": "Din API-nøgle har fuld adgang til organisationen. Den skal holdes hemmelig." + "message": "Din API-nøgle har fuld adgang til organisationen og skal holdes hemmelig." }, "userApiKeyDesc": { "message": "Din API-nøgle kan bruges til godkendelse i Bitwarden-CLI." }, "userApiKeyWarning": { - "message": "Din API-nøgle er en alternativ godkendelsesmekanisme. Den bør holdes hemmelig." + "message": "Din API-nøgle er en alternativ godkendelsesmekanisme, der skal holdes hemmelig." }, "oauth2ClientCredentials": { - "message": "OAuth 2.0 legitimationsoplysninger", + "message": "OAuth 2.0-legitimationsoplysninger", "description": "'OAuth 2.0' is a programming protocol. It should probably not be translated." }, "viewApiKey": { @@ -3471,16 +3471,16 @@ "message": "Du skal vælge minimum én samling." }, "couldNotChargeCardPayInvoice": { - "message": "Vi kunne ikke trække på dit kort. Se og betal den ubetalte faktura, der er anført nedenfor." + "message": "Dit betalingskort kunne ikke debiteres. Se og betal den ubetalte faktura vist nedenfor." }, "inAppPurchase": { - "message": "Køb i appen" + "message": "In-app køb" }, "cannotPerformInAppPurchase": { - "message": "Du kan ikke udføre denne handling, når du bruger en 'køb i appen'-betalingsmetode." + "message": "Denne handling kan ikke udføres under brug af 'in-app' betalingsmetoden." }, "manageSubscriptionFromStore": { - "message": "Du skal administrere dit abonnement fra den butik, hvor dit køb i appen blev foretaget." + "message": "Dit abonnement skal håndteres fra den butik, hvori in-app købet blev foretaget." }, "minLength": { "message": "Minimumslængde" @@ -3534,16 +3534,16 @@ } }, "policyInEffectUppercase": { - "message": "Indeholder ét eller flere store bogstaver" + "message": "Indeholdende én/flere majuskler" }, "policyInEffectLowercase": { - "message": "Indeholder ét eller flere små bogstaver" + "message": "Indeholdende én/flere minuskler" }, "policyInEffectNumbers": { - "message": "Indeholder ét eller flere tal" + "message": "Indeholdende ét/flere cifre" }, "policyInEffectSpecial": { - "message": "Indeholder ét eller flere af følgende specialtegn $CHARS$", + "message": "Indeholdende ét/flere af flg. specialtegn $CHARS$", "placeholders": { "chars": { "content": "$1", @@ -3561,7 +3561,7 @@ "message": "Standard type" }, "userPreference": { - "message": "Brugerindstilling" + "message": "Brugerpræference" }, "vaultTimeoutAction": { "message": "Boks timeout-handling" @@ -3587,22 +3587,22 @@ "message": "Slette permanent" }, "permanentlyDeleteSelected": { - "message": "Slet valgte elementer permanent" + "message": "Slet valgte emner permanent" }, "permanentlyDeleteItem": { - "message": "Slet element permanent" + "message": "Slet emner permanent" }, "permanentlyDeleteItemConfirmation": { - "message": "Er du sikker på, at du vil slette dette element permanent?" + "message": "Sikker på, at dette emne skal slettes permanent?" }, "permanentlyDeletedItem": { - "message": "Element slettet permanent" + "message": "Emne slettet permanent" }, "permanentlyDeletedItems": { - "message": "Elementer slettet permanent" + "message": "Emner slettet permanent" }, "permanentlyDeleteSelectedItemsDesc": { - "message": "Du har valgt $COUNT$ element(er), der skal slettes permanent. Er du sikker på, at du vil slette alle disse elementer permanent?", + "message": "$COUNT$ emne(r) valgt til permanent sletning. Sikker på, at de alle skal slettes permanent?", "placeholders": { "count": { "content": "$1", @@ -3611,7 +3611,7 @@ } }, "permanentlyDeletedItemId": { - "message": "Element $ID$ slettet permanent.", + "message": "Emnet $ID$ slettet permanent", "placeholders": { "id": { "content": "$1", @@ -3626,22 +3626,22 @@ "message": "Gendan valgte" }, "restoreItem": { - "message": "Gendan element" + "message": "Gendan emne" }, "restoredItem": { - "message": "Element gendannet" + "message": "Emne gendannet" }, "restoredItems": { - "message": "Elementer gendannet" + "message": "Emner gendannet" }, "restoreItemConfirmation": { - "message": "Er du sikker på, at du vil gendanne dette element?" + "message": "Sikker på, at dette emne skal gendannes?" }, "restoreItems": { - "message": "Gendan elementer" + "message": "Gendan emner" }, "restoreSelectedItemsDesc": { - "message": "Du har valgt $COUNT$ element(er), der skal gendannes. Er du sikker på, at du vil gendanne alle disse elementer?", + "message": "$COUNT$ emne(r) valgt til gendannelse. Sikker på, at alle de alle skal gendannes?", "placeholders": { "count": { "content": "$1", @@ -3650,7 +3650,7 @@ } }, "restoredItemId": { - "message": "Element $ID$ gendannet.", + "message": "Emnet $ID$ gendannet", "placeholders": { "id": { "content": "$1", @@ -3659,7 +3659,7 @@ } }, "vaultTimeoutLogOutConfirmation": { - "message": "Ved at logge ud fjernes al adgang til din boks og kræver online-godkendelse efter timeout-perioden. Er du sikker på, at du vil bruge denne indstilling?" + "message": "Udlogning fjerner al adgang til din boks og vil kræver online-godkendelse efter timeout-perioden. Sikker på, at denne indstilling skal anvendes?" }, "vaultTimeoutLogOutConfirmationTitle": { "message": "Bekræft timeout-handling" @@ -3692,10 +3692,10 @@ "message": "Organisationsidentifikator" }, "ssoLogInWithOrgIdentifier": { - "message": "Log ind vha. din organisations single sign-on portal. Angiv din organisations identifikator for at begynde." + "message": "Log ind via din organisations single sign-on portal. Angiv organisationens SSO-identifikator for at begynde." }, "enterpriseSingleSignOn": { - "message": "Virksomheds Single Sign On" + "message": "Virksomheds Single Sign-On" }, "ssoHandOff": { "message": "Du kan nu lukke denne fane og fortsætte i udvidelsen." @@ -3704,16 +3704,16 @@ "message": "Alle teamfunktioner, plus:" }, "includeSsoAuthentication": { - "message": "SSO godkendelse via SAML2.0 og OpenID Connect" + "message": "SSO-godkendelse via SAML2.0 og OpenID Connect" }, "includeEnterprisePolicies": { "message": "Virksomhedspolitikker" }, "ssoValidationFailed": { - "message": "SSO validering mislykkedes" + "message": "SSO-bekræftelse mislykkedes" }, "ssoIdentifierRequired": { - "message": "Organisationsidentifikator kræves." + "message": "Organisations-SSO kræves." }, "ssoIdentifier": { "message": "SSO-identifikator" @@ -3722,10 +3722,10 @@ "message": "Oplys dette ID til medlemmerne til brug for indlogning med SSO." }, "unlinkSso": { - "message": "Fjern SSO tilknytning" + "message": "Fjern SSO-tilknytning" }, "unlinkSsoConfirmation": { - "message": "Er du sikker på, at du vil fjerne SSO-tilknytningen for denne organisation?" + "message": "Sikker på, at SSO-tilknytningen for denne organisation skal fjernes?" }, "linkSso": { "message": "Tilknyt SSO" @@ -3737,16 +3737,16 @@ "message": "Begræns medlemmer fra at blive medlem af andre organisationer." }, "singleOrgBlockCreateMessage": { - "message": "Din nuværende organisation har en politik, der ikke tillader dig at deltage i mere end en organisation. Kontakt din organisations administratorer, eller tilmeld dig fra en anden Bitwarden-konto." + "message": "Den nuværende organisationspolitik tillader dig ikke at deltage i mere end én organisation. Kontakt organisationsadministratorerne eller benyt en anden Bitwarden-konto under tilmelding." }, "singleOrgPolicyWarning": { "message": "Organisationsmedlemmer, som ikke er ejere eller admins og allerede medlem af en anden organisation, fjernes fra organisationen." }, "requireSso": { - "message": "Kræv single sign-on-godkendelse" + "message": "Kræv single sign-on godkendelse" }, "requireSsoPolicyDesc": { - "message": "Kræv at medlemmer logger ind vha. virksomheds Single Sign-On metoden." + "message": "Kræv at medlemmer logger ind vha. Virksomheds single sign-on metoden." }, "prerequisite": { "message": "Forudsætning" diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 55c79f9b322..d5652b452ca 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -3299,7 +3299,7 @@ "message": "Et ole valinnut mitään." }, "acceptPolicies": { - "message": "Valitsemalla tämän ruudun hyväksyt seuraavat:" + "message": "Valitsemalla tämän hyväksyt seuraavat:" }, "acceptPoliciesRequired": { "message": "Palveluehtoja ja tietosuojakäytäntöä ei ole vahvistettu." diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index ebee7b503d3..7a2646497b1 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -1291,7 +1291,7 @@ "message": "Verifica in due passaggi" }, "twoStepLoginEnforcement": { - "message": "Attuazione accesso a due passaggi" + "message": "Forza accesso a due passaggi" }, "twoStepLoginDesc": { "message": "Proteggi il tuo account richiedendo un passaggio aggiuntivo all'accesso." @@ -1557,7 +1557,7 @@ "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "orgsReportsDesc": { - "message": "Identifica e chiudi i problemi di sicurezza negli account della tua organizzazione facendo clic sui rapporti qui sotto.", + "message": "Identifica e chiudi le falle di sicurezza negli account della tua organizzazione facendo click sui report in basso.", "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization Vault." }, "unsecuredWebsitesReport": { @@ -3017,7 +3017,7 @@ "message": "La mia organizzazione" }, "organizationInfo": { - "message": "Informazioni dell'Organizzazione" + "message": "Informazioni dell'organizzazione" }, "deleteOrganization": { "message": "Elimina organizzazione" @@ -4123,7 +4123,7 @@ "message": "Permessi" }, "permission": { - "message": "Permessi" + "message": "Permesso" }, "managerPermissions": { "message": "Permessi del Manager" @@ -4531,7 +4531,7 @@ "message": "Clienti" }, "client": { - "message": "Cliente", + "message": "Client", "description": "This is used as a table header to describe which client application created an event log." }, "providerAdmin": { @@ -5446,7 +5446,7 @@ "message": "Membri" }, "reporting": { - "message": "Relazioni" + "message": "Segnalazione" }, "cardBrandMir": { "message": "Mir" diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 21c00c2b49f..ab3d3013df9 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -1291,23 +1291,23 @@ "message": "Divpakāpju pierakstīšanās" }, "twoStepLoginEnforcement": { - "message": "Two-step Login Enforcement" + "message": "Divpakāpju pierakstīšanās piemērošana" }, "twoStepLoginDesc": { "message": "Nodrošināt kontu, pieprasot papildus darbību pierakstoties." }, "twoStepLoginOrganizationDescStart": { - "message": "Enforce Bitwarden Two-step Login options for members by using the ", + "message": "Piemērot Bitwarden divpakāpju pierakstīšanāš iespējas dalībniekiem ar ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" }, "twoStepLoginPolicy": { - "message": "Two-step Login Policy" + "message": "Divpakāpju pierakstīšanās nosacījumi" }, "twoStepLoginOrganizationDuoDesc": { - "message": "To enforce Two-step Login through Duo, use the options below." + "message": "Lai piemērotu divpakāpju pierakstīšanos ar Duo, jāizmanto zemāk esošās iespējas." }, "twoStepLoginOrganizationSsoDesc": { - "message": "If you have setup SSO or plan to, Two-step Login may already be enforced through your Identity Provider." + "message": "Ja ir uzstādīta vienotā pieteikšanās vai tas ir iecerēts, identitātes nodrošinātājs jau var būt piemērojis divpakāpju pierakstīšanos." }, "twoStepLoginRecoveryWarning": { "message": "Divpakāpju pierakstīšanās var pastāvīgi liegt piekļuvi Bitwarden kontam. Atkopšanas kods ļauj piekļūt tam gadījumā, kad vairs nav iespējams izmantot ierasto divpakāpju pierakstīšanās nodrošinātāju (piemēram, ir pazaudēta ierīce). Bitwarden atbalsts nevarēs palīdzēt, ja tiks pazaudēta piekļuve kontam. Ir ieteicams, ka atkopšanas kods tiek pierakstīts vai izdrukāts un turēts drošā vietā." @@ -1557,7 +1557,7 @@ "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "orgsReportsDesc": { - "message": "Identify and close security gaps in your organization's accounts by clicking the reports below.", + "message": "Noteikt un novērst drošības nepilnības apvienības kontos klikšķinot uz zemāk esošajām atskaitēm.", "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization Vault." }, "unsecuredWebsitesReport": { @@ -3017,7 +3017,7 @@ "message": "Mana apvienība" }, "organizationInfo": { - "message": "Organization info" + "message": "Apvienības informācija" }, "deleteOrganization": { "message": "Izdzēst apvienību" @@ -3716,10 +3716,10 @@ "message": "Ir nepieciešams apvienības identifikators." }, "ssoIdentifier": { - "message": "SSO identifier" + "message": "Vienotās pieteikšanās identifikators" }, "ssoIdentifierHint": { - "message": "Provide this ID to your members to login with SSO." + "message": "Šis Id jāsniedz dalībniekiem, lai pierakstītos ar vienoto pieteikšanos." }, "unlinkSso": { "message": "Atsaistīt SSO" @@ -4123,7 +4123,7 @@ "message": "Atļaujas" }, "permission": { - "message": "Permission" + "message": "Atļauja" }, "managerPermissions": { "message": "Pārvaldnieka atļaujas" @@ -4531,7 +4531,7 @@ "message": "Pasūtītāji" }, "client": { - "message": "Client", + "message": "Klients", "description": "This is used as a table header to describe which client application created an event log." }, "providerAdmin": { @@ -5419,7 +5419,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "Ievadītās vērtības garums nedrīkst pārsniegt $COUNT$ rakstzīmes.", "placeholders": { "count": { "content": "$1", @@ -5443,10 +5443,10 @@ "message": "Ieslēgts" }, "members": { - "message": "Members" + "message": "Dalībnieki" }, "reporting": { - "message": "Reporting" + "message": "Atskaišu sagatavošana" }, "cardBrandMir": { "message": "Mir" @@ -5473,39 +5473,39 @@ "message": "Notīrīt visu" }, "from": { - "message": "From" + "message": "No" }, "to": { - "message": "To" + "message": "Kam" }, "member": { - "message": "Member" + "message": "Dalībnieks" }, "update": { - "message": "Update" + "message": "Atjaunināt" }, "role": { - "message": "Role" + "message": "Loma" }, "canView": { - "message": "Can view" + "message": "Var skatīt" }, "canViewExceptPass": { - "message": "Can view, except passwords" + "message": "Var skatīt, izņemot paroles" }, "canEdit": { - "message": "Can edit" + "message": "Var labot" }, "canEditExceptPass": { - "message": "Can edit, except passwords" + "message": "Var labot, izņemot paroles" }, "group": { - "message": "Group" + "message": "Kopa" }, "groupAccessAll": { - "message": "This group can access and modify all items." + "message": "Šī kopa var piekļūt visiem vienumiem un mainīt tos." }, "memberAccessAll": { - "message": "This member can access and modify all items." + "message": "Šis dalībnieks var piekļūt visiem vienumiem un mainīt tos." } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 3994f458d63..72d610f22b7 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -1557,7 +1557,7 @@ "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "orgsReportsDesc": { - "message": "Выявите и устраните проблемы с безопасностью в аккаунтах вашей организации, перейдя по ссылкам на отчеты ниже.", + "message": "Выявите и устраните проблемы с безопасностью в аккаунтах вашей организации, просмотрев отчеты ниже.", "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization Vault." }, "unsecuredWebsitesReport": { @@ -1758,7 +1758,7 @@ "message": "Способ оплаты" }, "accountCredit": { - "message": "Кредит аккаунта", + "message": "Баланс аккаунта", "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." }, "accountBalance": { @@ -1787,7 +1787,7 @@ "description": "Another way of saying \"Get a Premium membership\"" }, "premiumUpdated": { - "message": "Вы перешли на Премиум." + "message": "Вы обновились до Премиум." }, "premiumUpgradeUnlockFeatures": { "message": "Обновите свой аккаунт до Премиум и разблокируйте дополнительные возможности." @@ -1911,7 +1911,7 @@ "message": "Кредитная карта" }, "paypalClickSubmit": { - "message": "Нажмите кнопку PayPal, чтобы войти в свой аккаунт PayPal, и нажмите кнопку \"Подтвердить\" для продолжения." + "message": "Нажмите кнопку PayPal, чтобы войти в свой аккаунт PayPal, и нажмите кнопку 'Подтвердить' для продолжения." }, "cancelSubscription": { "message": "Отменить подписку" @@ -1941,7 +1941,7 @@ "message": "Подписка отменена." }, "neverExpires": { - "message": "Не истекает" + "message": "Никогда не истекает" }, "status": { "message": "Статус" @@ -2058,7 +2058,7 @@ } }, "contactSupport": { - "message": "Обратиться в службу поддержки" + "message": "Связаться со службой поддержки" }, "updatedPaymentMethod": { "message": "Способ оплаты обновлен." @@ -2079,7 +2079,7 @@ } }, "uploadLicenseFilePremium": { - "message": "Чтобы обновить аккаунт до Премиум, необходимо отослать действительный файл лицензии." + "message": "Для обновления своей учетной записи до Премиум, необходимо загрузить действующий файл лицензии." }, "uploadLicenseFileOrg": { "message": "Для создания организации размещенной на локальном хостинге, необходимо загрузить действительный файл лицензии." @@ -2103,7 +2103,7 @@ "message": "Email для выставления счетов" }, "businessName": { - "message": "Наименование компании" + "message": "Название компании" }, "chooseYourPlan": { "message": "Выберите план" @@ -2115,7 +2115,7 @@ "message": "Лицензированных мест" }, "additionalUserSeats": { - "message": "Дополнительные пользовательские лицензии" + "message": "Дополнительные пользовательские места" }, "userSeatsDesc": { "message": "# пользовательских лицензий" @@ -2303,7 +2303,7 @@ "message": "Ваша новая организация готова к работе!" }, "organizationUpgraded": { - "message": "Ваша организация была обновлена." + "message": "Организация обновлена" }, "leave": { "message": "Покинуть" @@ -2366,7 +2366,7 @@ "message": "После отзыва члена организации, он теряет доступ к ее данным. Для быстрого восстановления доступа члена, перейдите на вкладку Отозвано." }, "removeUserConfirmationKeyConnector": { - "message": "Внимание! Этому пользователю необходим соединитель ключей для управления шифрованием. Удаление этого пользователя из вашей организации приведет к окончательному отключению его учетной записи. Это действие нельзя отменить. Вы хотите продолжить?" + "message": "Внимание! Этому пользователю необходим соединитель ключей для управления шифрованием. Удаление этого пользователя из вашей организации приведет к окончательной деактивации его учетной записи. Это действие нельзя отменить. Вы хотите продолжить?" }, "externalId": { "message": "Внешний ID" @@ -2501,7 +2501,7 @@ "message": "Веб-хранилище" }, "loggedIn": { - "message": "Вход выполнен." + "message": "Выполнен вход" }, "changedPassword": { "message": "Изменен пароль аккаунта." @@ -2597,7 +2597,7 @@ } }, "viewedCardNumberItemId": { - "message": "Просмотренный номер карты предмета $ID$.", + "message": "Просмотрен номер карты элемента $ID$.", "placeholders": { "id": { "content": "$1", @@ -2885,13 +2885,13 @@ "message": "Тип пользователя" }, "groupAccess": { - "message": "Доступ к группе" + "message": "Доступ группы" }, "groupAccessUserDesc": { "message": "Изменить группы, в которые входит этот пользователь." }, "invitedUsers": { - "message": "Приглашенные пользователи." + "message": "Пользователь(и) приглашен(ы)" }, "resendInvitation": { "message": "Пригласить повторно" @@ -2900,7 +2900,7 @@ "message": "Отправить email повторно" }, "hasBeenReinvited": { - "message": "$USER$ был приглашен повторно.", + "message": "$USER$ приглашен повторно", "placeholders": { "user": { "content": "$1", @@ -2915,7 +2915,7 @@ "message": "Подтвердить пользователя" }, "hasBeenConfirmed": { - "message": "$USER$ был подтвержден.", + "message": "$USER$ подтвержден.", "placeholders": { "user": { "content": "$1", @@ -2948,7 +2948,7 @@ "message": "Проверьте почту - вам должна прийти ссылка подтверждения." }, "emailVerified": { - "message": "Ваш адрес email подтвержден." + "message": "Адрес email аккаунта подтвержден" }, "emailVerifiedFailed": { "message": "Не удалось подтвердить ваш email. Попробуйте отправить новое письмо с подтверждением." @@ -3084,7 +3084,7 @@ "message": "Просмотр счета" }, "downloadInvoice": { - "message": "Загрузить счет" + "message": "Скачать счет" }, "verifyBankAccount": { "message": "Подтвердить банковский счет" @@ -3096,10 +3096,10 @@ "message": "Оплата с банковского счета доступна только клиентам из США. Вам будет необходимо подтвердить свой банковский счет. Мы сделаем два микро-депозита в течение ближайших 1-2 рабочих дней. Введите эти суммы на странице выставления счетов организации, чтобы подтвердить банковский счет." }, "verifyBankAccountFailureWarning": { - "message": "Невыполнение подтверждения банковского счета приведет к неудачной оплате и отключению подписки." + "message": "Если банковский счет не будет подтвержден, это приведет к пропуску платежа и приостановке подписки." }, "verifiedBankAccount": { - "message": "Банковский счет был подтвержден." + "message": "Банковский счет подтвержден" }, "bankAccount": { "message": "Банковский счет" @@ -3137,28 +3137,28 @@ "message": "Введите идентификатор установки" }, "limitSubscriptionDesc": { - "message": "Установите лимит мест для вашей подписки. По достижении этого лимита вы не сможете приглашать новых пользователей." + "message": "Установите лимит мест для вашей подписки. По достижении этого лимита вы не сможете приглашать новых членов." }, "maxSeatLimit": { - "message": "Максимальный предел мест (необязательно)", + "message": "Предел мест (необязательно)", "description": "Upper limit of seats to allow through autoscaling" }, "maxSeatCost": { "message": "Максимальная потенциальная стоимость места" }, "addSeats": { - "message": "Добавить лицензии", + "message": "Добавить места", "description": "Seat = User Seat" }, "removeSeats": { - "message": "Удалить лицензии", + "message": "Удалить места", "description": "Seat = User Seat" }, "subscriptionDesc": { "message": "Корректировка вашей подписки приведет к пропорциональному изменению суммарного счета. Если количество новых приглашенных пользователей превысит количество мест в вашей подписке, вы сразу же получите пропорциональную плату за дополнительных пользователей." }, "subscriptionUserSeats": { - "message": "Ваша подписка разрешает в общей сложности $COUNT$ пользователей.", + "message": "Ваша подписка допускает $COUNT$ пользователей.", "placeholders": { "count": { "content": "$1", @@ -3182,10 +3182,10 @@ "message": "За дополнительной помощью в управлении подпиской обращайтесь в службу поддержки клиентов." }, "subscriptionUserSeatsUnlimitedAutoscale": { - "message": "Корректировка вашей подписки приведет к пропорциональному изменению суммарного счета. Если количество новых приглашенных пользователей превысит количество мест в вашей подписке, вы сразу же получите пропорциональную плату за дополнительных пользователей." + "message": "Корректировка вашей подписки приведет к пропорциональным изменениям в сумме счетов. Если новых приглашенных пользователей будет больше, чем мест по подписке, вы сразу получите пропорциональную плату за дополнительных пользователей." }, "subscriptionUserSeatsLimitedAutoscale": { - "message": "Корректировка вашей подписки приведет к пропорциональному изменению суммарного счета. Если количество новых приглашенных пользователей превысит количество мест в вашей подписке, вы сразу же получите пропорциональную плату за дополнительных пользователей, пока не будет достигнут лимит мест $MAX$.", + "message": "Корректировка вашей подписки приведет к пропорциональным изменениям в сумме счетов. Если новых приглашенных пользователей будет больше, чем мест по подписке, вы сразу получите пропорциональную плату за дополнительных пользователей, пока не будет достигнут лимит мест ($MAX$).", "placeholders": { "max": { "content": "$1", @@ -3230,10 +3230,10 @@ } }, "seatsToAdd": { - "message": "Лицензии для добавления" + "message": "Места для добавления" }, "seatsToRemove": { - "message": "Лицензии для удаления" + "message": "Места для удаления" }, "seatsAddNote": { "message": "Добавление пользовательских лицензий приведет к корректировке итоговых счетов и немедленному взиманию платы с вашего метода оплаты. Первый платеж будет пропорционален оставшейся части текущего платежного периода." @@ -3281,7 +3281,7 @@ "message": "Обновить" }, "upgradeOrganization": { - "message": "Обновление организации" + "message": "Обновить организацию" }, "upgradeOrganizationDesc": { "message": "Эта функция недоступна для бесплатных организаций. Переключитесь на платный план, чтобы разблокировать дополнительные возможности." @@ -3353,7 +3353,7 @@ "description": "ex. Date this password was updated" }, "organizationIsDisabled": { - "message": "Организация отключена." + "message": "Организация приостановлена" }, "disabledOrganizationFilterError": { "message": "Доступ к элементам в отключенных организациях невозможен. Обратитесь за помощью к владельцу организации." @@ -3432,7 +3432,7 @@ "message": "Фраза отпечатка" }, "dontAskFingerprintAgain": { - "message": "Никогда не запрашивать проверку отпечатков фраз для приглашенных пользователей (не рекомендуется)", + "message": "Никогда не запрашивать верификацию фразы отпечатка для приглашенных пользователей (не рекомендуется)", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "free": { @@ -3462,7 +3462,7 @@ "description": "'OAuth 2.0' is a programming protocol. It should probably not be translated." }, "viewApiKey": { - "message": "Показать ключ API" + "message": "Показать API-ключ" }, "rotateApiKey": { "message": "Сменить API-ключ" @@ -3596,10 +3596,10 @@ "message": "Вы уверены, что хотите окончательно удалить этот элемент?" }, "permanentlyDeletedItem": { - "message": "Элемент удален навсегда" + "message": "Элемент удален окончательно" }, "permanentlyDeletedItems": { - "message": "Элементы удалены навсегда" + "message": "Элементы удалены окончательно" }, "permanentlyDeleteSelectedItemsDesc": { "message": "Выбрано элементов для удаления: $COUNT$. Вы действительно хотите окончательно удалить все эти элементы?", @@ -3611,7 +3611,7 @@ } }, "permanentlyDeletedItemId": { - "message": "Элемент $ID$ удален навсегда.", + "message": "Элемент $ID$ удален окончательно", "placeholders": { "id": { "content": "$1", @@ -3707,13 +3707,13 @@ "message": "SSO аутентификация через SAML2.0 и OpenID Connect" }, "includeEnterprisePolicies": { - "message": "Политик организации" + "message": "Политики организации" }, "ssoValidationFailed": { "message": "Проверка SSO не пройдена" }, "ssoIdentifierRequired": { - "message": "Требуется идентификатор организации." + "message": "Требуется идентификатор SSO организации." }, "ssoIdentifier": { "message": "SSO идентификатор" @@ -3755,10 +3755,10 @@ "message": "Перед активацией этой политики необходимо включить политику Единого входа (SSO)." }, "requireSsoPolicyReqError": { - "message": "Не включена политика 'Одна организация'." + "message": "Не включена политика единой организации." }, "requireSsoExemption": { - "message": "Владельцы и администраторы организации не подпадают под действие этой политики." + "message": "Владельцы и администраторы организаций освобождены от применения этой политики." }, "sendTypeFile": { "message": "Файл" @@ -3767,7 +3767,7 @@ "message": "Текст" }, "createSend": { - "message": "Создать новую Send", + "message": "Новая Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -3775,15 +3775,15 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Созданная Send", + "message": "Send сохранена", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Измененная Send", + "message": "Send сохранена", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "Удаленная Send", + "message": "Send удалена", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSend": { @@ -3799,14 +3799,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletionDate": { - "message": "Срок удаления" + "message": "Дата удаления" }, "deletionDateDesc": { "message": "Эта Send будет окончательно удалена в указанные дату и время.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { - "message": "Срок истечения" + "message": "Дата истечения" }, "expirationDateDesc": { "message": "Если задано, доступ к этой Send истечет в указанные дату и время.", @@ -3886,7 +3886,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendHiddenByDefault": { - "message": "Эта Send по умолчанию скрыта. Вы можете переключить ее видимость с помощью расположенной ниже кнопки.", + "message": "Эта Send по умолчанию скрыта. Вы можете переключить ее видимость с помощью кнопки ниже.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "downloadFile": { @@ -3911,7 +3911,7 @@ "message": "Предоставляйте экстренный доступ доверенным контактам и управляйте им. Доверенные контакты могут получить доступ для просмотра или передачи владения вашей учетной записью в экстренных ситуациях. Посетите нашу страницу помощи для детального ознакомления о том, как работает доступ нулевого уровня." }, "emergencyAccessOwnerWarning": { - "message": "Вы являетесь владельцем одной или нескольких организаций. Если передать владение контакту для экстренных ситуаций, он будет наделен всеми полномочиями в качестве полноправного владельца." + "message": "Вы является владельцем одной или нескольких организаций. Если предоставить право владения экстренному контакту, он будет наделен всеми полномочиями в качестве полноправного владельца после совершения операции." }, "trustedEmergencyContacts": { "message": "Надежные контакты для экстренных ситуаций" @@ -4053,7 +4053,7 @@ "message": "Требовать от пользователей сохранять элементы в организации, удалив опцию личного хранилища." }, "personalOwnershipExemption": { - "message": "Владельцы и администраторы организации не подпадают под действие этой политики." + "message": "Владельцы и администраторы организаций освобождены от применения этой политики." }, "personalOwnershipSubmitError": { "message": "В соответствии с корпоративной политикой вам запрещено сохранять элементы в личном хранилище. Измените владельца на организацию и выберите из доступных Коллекций." @@ -4062,14 +4062,14 @@ "message": "Отключить Send" }, "disableSendPolicyDesc": { - "message": "Не разрешать пользователям создавать или редактировать Bitwarden Send.", + "message": "Не разрешать пользователям создавать или редактировать Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "disableSendExemption": { "message": "Политики не распространяются на пользователей организации, которые могут ими управлять." }, "sendDisabled": { - "message": "Send отключена", + "message": "Send удалена", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -4123,13 +4123,13 @@ "message": "Разрешения" }, "permission": { - "message": "Разрешения" + "message": "Разрешение" }, "managerPermissions": { - "message": "Права менеджера" + "message": "Разрешения менеджера" }, "adminPermissions": { - "message": "Права администратора" + "message": "Разрешения администратора" }, "accessEventLogs": { "message": "Доступ к журналам событий" @@ -4144,43 +4144,43 @@ "message": "У вас нет необходимых разрешений для выполнения этого действия." }, "manageAllCollections": { - "message": "Управлять всеми коллекциями" + "message": "Управление всеми коллекциями" }, "createNewCollections": { - "message": "Создать новые коллекции" + "message": "Создавать новые коллекции" }, "editAnyCollection": { "message": "Редактировать любую коллекцию" }, "deleteAnyCollection": { - "message": "Удалить любую коллекцию" + "message": "Удалять любую коллекцию" }, "manageAssignedCollections": { - "message": "Управление назначенными коллекциями" + "message": "Управлять назначенными коллекциями" }, "editAssignedCollections": { - "message": "Изменить назначенные коллекции" + "message": "Изменять назначенные коллекции" }, "deleteAssignedCollections": { - "message": "Удалить назначенные коллекции" + "message": "Удалять назначенные коллекции" }, "manageGroups": { - "message": "Управление группами" + "message": "Управлять группами" }, "managePolicies": { - "message": "Управление политиками" + "message": "Управлятьполитиками" }, "manageSso": { "message": "Управление SSO" }, "manageUsers": { - "message": "Управление пользователями" + "message": "Управлять пользователями" }, "manageResetPassword": { - "message": "Управление сбросом пароля" + "message": "Управлять сбросом пароля" }, "disableRequiredError": { - "message": "Перед отключением этой политики необходимо сначала отключить политику $POLICYNAME$.", + "message": "Перед отключением этой политики необходимо вручную отключить политику $POLICYNAME$.", "placeholders": { "policyName": { "content": "$1", @@ -4206,10 +4206,10 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { - "message": "Текст, который вы хотите отправить." + "message": "Текст, который отправится вместе с Send." }, "sendFileDesc": { - "message": "Файл, который вы хотите отправить." + "message": "Файл, который отправится вместе с Send." }, "copySendLinkOnSave": { "message": "Скопировать ссылку в буфер обмена после сохранения, чтобы поделиться этой Send." @@ -4315,7 +4315,7 @@ "message": "Запрос на сброс пароля зарегистрирован" }, "withdrawPasswordReset": { - "message": "Отменить сброс пароля" + "message": "Отказаться от сброса пароля" }, "enrollPasswordResetSuccess": { "message": "Регистрация успешна!" @@ -4324,7 +4324,7 @@ "message": "Запись успешно отменена!" }, "eventEnrollPasswordReset": { - "message": "Пользователь $ID$ записался на помощь по сбросу пароля.", + "message": "Пользователь $ID$ зарегистрирован на сброс пароля.", "placeholders": { "id": { "content": "$1", @@ -4333,7 +4333,7 @@ } }, "eventWithdrawPasswordReset": { - "message": "Пользователь $ID$ отказался от помощи по сбросу пароля.", + "message": "Пользователь $ID$ отказался от сброса пароля.", "placeholders": { "id": { "content": "$1", @@ -4405,13 +4405,13 @@ "message": "Автоматическое развертывание" }, "resetPasswordPolicyAutoEnrollDescription": { - "message": "Все пользователи будут автоматически записаны на сброс пароля, после того как их приглашение будет принято, и им не будет разрешено отказаться от его выполнения." + "message": "Все пользователи будут автоматически зарегистрированы на сброс пароля, после того как их приглашение будет принято, и им не будет разрешено отказаться от его выполнения." }, "resetPasswordPolicyAutoEnrollWarning": { - "message": "Пользователи, уже входящие в организацию, не смогут записаться на сброс пароля задним числом. Они должны будут зарегистрироваться самостоятельно, прежде чем администраторы смогут сбросить их мастер-пароль." + "message": "Пользователи, уже входящие в организацию, не смогут зарегистрироваться на сброс пароля задним числом. Они должны будут зарегистрироваться самостоятельно, чтобы администраторы могли сбросить их мастер-пароль." }, "resetPasswordPolicyAutoEnrollCheckbox": { - "message": "Автоматически записывать новых пользователей" + "message": "Автоматически регистрировать новых пользователей" }, "resetPasswordAutoEnrollInviteWarning": { "message": "В этой организации действует корпоративная политика, которая автоматически зарегистрирует вас на сброс пароля. Регистрация позволит администраторам организации изменить ваш мастер-пароль." @@ -4423,10 +4423,10 @@ "message": "Ответ на сброс пароля пустой" }, "trashCleanupWarning": { - "message": "Элементы, которые находились в корзине более 30 дней, будут автоматически удалены." + "message": "Элементы, которые находились в корзине более 30 дней, будут удалены автоматически." }, "trashCleanupWarningSelfHosted": { - "message": "Элементы, которые находились в корзине в течение некоторого времени, будут автоматически удалены." + "message": "Элементы, которые находились в корзине в течение некоторого времени, будут удалены автоматически." }, "passwordPrompt": { "message": "Повторный запрос мастер-пароля" @@ -4477,10 +4477,10 @@ "message": "Статус массового действия" }, "bulkConfirmMessage": { - "message": "Подтверждено успешно." + "message": "Подтверждено успешно" }, "bulkReinviteMessage": { - "message": "Повторно приглашен успешно." + "message": "Повторно приглашен успешно" }, "bulkRemovedMessage": { "message": "Удален(-о) успешно" @@ -4501,16 +4501,16 @@ "message": "Удалить пользователей" }, "revokeUsers": { - "message": "Отзыв пользователей" + "message": "Отозвать пользователей" }, "restoreUsers": { - "message": "Восстановление пользователей" + "message": "Восстановить пользователей" }, "error": { "message": "Ошибка" }, "resetPasswordManageUsers": { - "message": "Управление пользователями также должно быть включено с разрешением Управление сбросом пароля" + "message": "Для управления пользователями необходимо также предоставить разрешение на управление сбросом пароля" }, "setupProvider": { "message": "Настройка поставщика" @@ -4547,13 +4547,13 @@ "message": "Пользователи сервиса могут получать доступ и управлять всеми клиентскими организациями." }, "providerInviteUserDesc": { - "message": "Пригласите нового пользователя в свою организацию, введя адрес email его учетной записи Bitwarden ниже. Если у него еще нет учетной записи, ему будет предложено ее создать." + "message": "Пригласите нового пользователя к вашему поставщику, введя ниже адрес email его учетной записи Bitwarden. Если у него еще нет учетной записи, ему будет предложено ее создать." }, "joinProvider": { "message": "Присоединиться к поставщику" }, "joinProviderDesc": { - "message": "Вас пригласили присоединиться к указанной выше организации. Чтобы принять приглашение, войдите или создайте новую учетную запись Bitwarden." + "message": "Вас пригласили присоединиться к указанному выше поставщику. Чтобы принять приглашение, войдите или создайте новую учетную запись Bitwarden." }, "providerInviteAcceptFailed": { "message": "Невозможно принять приглашение. Попросите администратора поставщика отправить новое приглашение." @@ -4571,7 +4571,7 @@ "message": "Организация нового клиента" }, "newClientOrganizationDesc": { - "message": "Создайте новую организацию клиента, которая будет связана с вами как с поставщиком услуг. Вы сможете получить доступ к этой организации и управлять ею." + "message": "Создайте новую организацию клиента, которая будет связана с вами как с поставщиком. Вы сможете получить доступ к этой организации и управлять ею." }, "addExistingOrganization": { "message": "Добавить существующую организацию" @@ -4596,7 +4596,7 @@ "message": "Организация была успешно добавлена к поставщику" }, "accessingUsingProvider": { - "message": "Доступ к организации с используя поставщика $PROVIDER$", + "message": "Доступ к организации с использованием поставщика $PROVIDER$", "placeholders": { "provider": { "content": "$1", @@ -4605,10 +4605,10 @@ } }, "providerIsDisabled": { - "message": "Поставщик отключен." + "message": "Поставщик отключен" }, "providerUpdated": { - "message": "Поставщик обновлен" + "message": "Поставщик сохранен" }, "yourProviderIs": { "message": "Ваш поставщик услуг - $PROVIDER$. Он обладает административными и биллинговыми привилегиями для вашей организации.", @@ -4620,7 +4620,7 @@ } }, "detachedOrganization": { - "message": "Организация $ORGANIZATION$ была отсоединена от вашего поставщика.", + "message": "Организация $ORGANIZATION$ была отключена от вашего поставщика.", "placeholders": { "organization": { "content": "$1", @@ -4629,22 +4629,22 @@ } }, "detachOrganizationConfirmation": { - "message": "Вы уверены, что хотите отсоединить эту организацию? Организация продолжит существовать, но больше не будет управляться поставщиком." + "message": "Вы уверены, что хотите отключить эту организацию? Организация продолжит существовать, но больше не будет управляться этим поставщиком." }, "add": { "message": "Добавить" }, "updatedMasterPassword": { - "message": "Мастер-пароль обновлен" + "message": "Мастер-пароль сохранен" }, "updateMasterPassword": { "message": "Обновить мастер-пароль" }, "updateMasterPasswordWarning": { - "message": "Мастер-пароль недавно был изменен администратором вашей организации. Чтобы получить доступ к хранилищу, вы должны обновить мастер-пароль сейчас. В результате текущая сессия будет завершена, потребуется повторный вход. Активные сессии на других устройствах могут оставаться активными в течение одного часа." + "message": "Мастер-пароль недавно был изменен администратором вашей организации. Чтобы получить доступ к хранилищу, вы должны обновить мастер-пароль сейчас. В результате текущий сеанс будет завершен, потребуется повторный вход. Активные сеансы на других устройствах могут оставаться активными в течение одного часа." }, "masterPasswordInvalidWarning": { - "message": "Мастер-пароль не соответствует требованиям политики этой организации. Чтобы присоединиться к организации, нужно обновить мастер-пароль. Текущая сессия будет завершена и потребуется повторный вход. Сессии на других устройствах могут оставаться активными в течение часа." + "message": "Мастер-пароль не соответствует требованиям политики этой организации. Чтобы присоединиться к организации, нужно обновить мастер-пароль. Текущий сеанс будет завершен и потребуется повторный вход. Сеансы на других устройствах могут оставаться активными в течение часа." }, "maximumVaultTimeout": { "message": "Тайм-аут хранилища" @@ -4699,7 +4699,7 @@ "message": "Экспорт хранилища отключен" }, "personalVaultExportPolicyInEffect": { - "message": "Экспорт вашего личного хранилища запрещен одной или несколькими политиками организации." + "message": "Одна или несколько политик организации запрещают вам экспортировать личное хранилище." }, "selectType": { "message": "Выберите тип SSO" @@ -4708,7 +4708,7 @@ "message": "Тип" }, "openIdConnectConfig": { - "message": "Конфигурация OpenID Connect" + "message": "Конфигурация подключения OpenID" }, "samlSpConfig": { "message": "Конфигурация поставщика услуг SAML" @@ -4717,10 +4717,10 @@ "message": "Конфигурация поставщика удостоверений SAML" }, "callbackPath": { - "message": "Резервный маршрут" + "message": "Маршрут обратного вызова" }, "signedOutCallbackPath": { - "message": "Выход с резервного маршрута" + "message": "Маршрут обратного вызова для выхода" }, "authority": { "message": "Сервер авторизации" @@ -4768,7 +4768,7 @@ "message": "URL службы подтверждения клиентов (ACS)" }, "spNameIdFormat": { - "message": "Формат ID названия" + "message": "Формат имени ID" }, "spOutboundSigningAlgorithm": { "message": "Алгоритм исходящей подписи" @@ -4777,7 +4777,7 @@ "message": "Поведение при подписании" }, "spMinIncomingSigningAlgorithm": { - "message": "Алгоритм минимальной входящей подписи" + "message": "Минимальный входящий алгоритм подписи" }, "spWantAssertionsSigned": { "message": "Ожидание подписанных подтверждений" @@ -4813,7 +4813,7 @@ "message": "Подписать запросы аутентификации" }, "ssoSettingsSaved": { - "message": "Конфигурация единого входа сохранена." + "message": "Конфигурация единого входа сохранена" }, "sponsoredFamilies": { "message": "Бесплатный план Bitwarden Families" @@ -4882,7 +4882,7 @@ "message": "Активирован" }, "redeemedAccount": { - "message": "Активированный аккаунт" + "message": "Аккаунт активирован" }, "revokeAccount": { "message": "Отозвать аккаунт $NAME$", @@ -4927,7 +4927,7 @@ "message": "После удаления этого аккаунта спонсирование плана Families истечет в конце расчетного периода. Вы не сможете воспользоваться новым спонсорским предложением, пока не истечет срок действия существующего. Вы уверены, что хотите продолжить?" }, "removeSponsorshipSuccess": { - "message": "Спонсорство удалено" + "message": "Спонсирование удалено" }, "ssoKeyConnectorError": { "message": "Ошибка соединителя ключей: убедитесь, что он доступен и работает корректно." @@ -4972,7 +4972,7 @@ "message": "Удалить мастер-пароль" }, "removedMasterPassword": { - "message": "Мастер-пароль удален." + "message": "Мастер-пароль удален" }, "allowSso": { "message": "Разрешить аутентификацию SSO" @@ -4996,7 +4996,7 @@ "message": "Для настройки расшифровки соединителя ключей требуются политики аутентификации SSO и единой организации." }, "memberDecryptionOption": { - "message": "Параметры расшифровки членов" + "message": "Параметры расшифровки участников" }, "memberDecryptionPassDesc": { "message": "После аутентификации участники будут расшифровывать данные хранилища, используя свои мастер-пароли." @@ -5005,10 +5005,10 @@ "message": "Соединитель ключей" }, "memberDecryptionKeyConnectorDesc": { - "message": "Подключите авторизацию посредством SSO к вашему собственному серверу расшифровки ключей. При использовании этой опции членам не нужно будет использовать свои мастер-пароли для расшифровки данных хранилища." + "message": "Подключите авторизацию SSO к вашему собственному серверу расшифровки ключей. При использовании этой опции участникам не придется использовать свои мастер-пароли для расшифровки данных хранилища. Обратитесь в службу поддержки Bitwarden за помощью в настройке." }, "keyConnectorPolicyRestriction": { - "message": "\"Авторизация посредством SSO и расшифровки соединителя ключей\" включена. Эта политика будет применяться только к владельцам и администраторам." + "message": "\"Авторизация SSO и расшифровка соединителя ключей\" включена. Эта политика будет применяться только к владельцам и администраторам." }, "enabledSso": { "message": "SSO включен" @@ -5023,7 +5023,7 @@ "message": "Соединитель ключей отключен" }, "keyConnectorWarning": { - "message": "После настройки соединителя ключей параметры расшифровки членов не могут быть изменены." + "message": "После того как участники начнут использовать соединитель ключей, ваша организация не сможет вернуться к расшифровке посредством мастер-пароля. Продолжайте только в том случае, если вам удобно развертывать и управлять сервером ключей." }, "migratedKeyConnector": { "message": "Выполнена миграция на соединитель ключей" @@ -5077,7 +5077,7 @@ "message": "Настроить синхронизацию биллинга" }, "generateToken": { - "message": "Сгенерировать токен" + "message": "Создать токен" }, "rotateToken": { "message": "Сменить токен" @@ -5086,7 +5086,7 @@ "message": "При продолжении, вам нужно будет заново настроить синхронизацию биллинга на вашем сервере." }, "rotateBillingSyncTokenTitle": { - "message": "Смена токена синхронизации биллинга аннулирует предыдущий токен." + "message": "Смена токена синхронизации биллинга сделает предыдущий токен недействительным." }, "selfHostingTitle": { "message": "Собственное размещение" @@ -5095,7 +5095,7 @@ "message": "Чтобы создать свою организацию на собственном сервере, потребуется загрузить файл лицензии. Для поддержки тарифных планов Free Families и расширенных возможностей выставления счетов для вашей организации, размещенной на собственном сервере необходимо настроить синхронизацию биллинга." }, "billingSyncApiKeyRotated": { - "message": "Токен сменен." + "message": "Токен сменен" }, "billingSync": { "message": "Синхронизация биллинга" @@ -5260,7 +5260,7 @@ "message": "Служба" }, "unknownCipher": { - "message": "Неизвестный элемент, вам может потребоваться запросить разрешение на доступ к этому элементу." + "message": "Неизвестный элемент. Вам может потребоваться запросить разрешение на доступ к этому элементу." }, "cannotSponsorSelf": { "message": "Вы не можете активировать существующий аккаунт. Введите другой адрес электронной почты." @@ -5313,7 +5313,7 @@ "description": "This text is displayed if an organization's billing is managed by a Provider. It tells the user to contact the Provider for assistance." }, "forwardedEmail": { - "message": "Псевдоним электронной почты для пересылки" + "message": "Псевдоним email для пересылки" }, "forwardedEmailDesc": { "message": "Создать псевдоним электронной почты для внешней службы пересылки." @@ -5332,7 +5332,7 @@ "message": "Включить проверку устройства" }, "deviceVerificationDesc": { - "message": "Если включено, то при входе с нераспознанного устройства на ваш адрес электронной почты отправляются проверочные коды" + "message": "Коды подтверждения отправляются на ваш адрес email при авторизации с нераспознанного устройства" }, "updatedDeviceVerification": { "message": "Проверка устройства обновлена" @@ -5419,7 +5419,7 @@ } }, "inputMaxLength": { - "message": "Длина ввода не должна превышать $COUNT$ символов.", + "message": "Длина вводимых данных не должна превышать $COUNT$ символов.", "placeholders": { "count": { "content": "$1", @@ -5476,10 +5476,10 @@ "message": "От" }, "to": { - "message": "Для" + "message": "Кому" }, "member": { - "message": "Участник" + "message": "Пользователь" }, "update": { "message": "Обновить" @@ -5497,7 +5497,7 @@ "message": "Может редактировать" }, "canEditExceptPass": { - "message": "Может просматривать, кроме паролей" + "message": "Может редактировать, кроме паролей" }, "group": { "message": "Группа" diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 690b57e0fa9..029b078423b 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -570,19 +570,19 @@ "message": "Logga in eller skapa ett nytt konto för att komma åt ditt valv." }, "loginWithDevice": { - "message": "Log in with device" + "message": "Logga in med enhet" }, "loginWithDeviceEnabledInfo": { - "message": "Log in with device must be set up in the settings of the Bitwarden mobile app. Need another option?" + "message": "\"Logga in med enhet\" måste ställas in i inställningarna i Bitwardens mobilapp. Behöver du ett annat alternativ?" }, "loginWithMasterPassword": { - "message": "Log in with master password" + "message": "Logga in med huvudlösenord" }, "createAccount": { "message": "Skapa konto" }, "newAroundHere": { - "message": "New around here?" + "message": "Är du ny här?" }, "startTrial": { "message": "Starta provversion" @@ -591,7 +591,7 @@ "message": "Logga in" }, "logInInitiated": { - "message": "Log in initiated" + "message": "Inloggning påbörjad" }, "submit": { "message": "Skicka" @@ -612,7 +612,7 @@ "message": "Huvudlösenordet är det lösenord som du använder för att komma åt ditt valv. Det är väldigt viktigt att du inte glömmer bort ditt huvudlösenord, eftersom det inte går att återställa lösenordet ifall du skulle glömma bort det." }, "masterPassImportant": { - "message": "Master passwords cannot be recovered if you forget it!" + "message": "Huvudlösenordet kan inte återställas om du glömmer det!" }, "masterPassHintDesc": { "message": "En huvudlösenordsledtråd kan hjälpa dig att komma ihåg ditt lösenord om du glömmer bort det." @@ -645,13 +645,13 @@ "message": "Ogiltig e-postadress." }, "masterPasswordRequired": { - "message": "Master password is required." + "message": "Huvudlösenord krävs." }, "confirmMasterPasswordRequired": { - "message": "Master password retype is required." + "message": "Huvudlösenord måste anges igen." }, "masterPasswordMinlength": { - "message": "Master password must be at least 8 characters long." + "message": "Huvudlösenordet måste vara minst 8 tecken långt." }, "masterPassDoesntMatch": { "message": "Huvudlösenorden stämmer inte överens." @@ -660,7 +660,7 @@ "message": "Ditt nya konto har skapats! Du kan nu logga in." }, "trialAccountCreated": { - "message": "Account created successfully." + "message": "Kontot har skapats." }, "masterPassSent": { "message": "Vi har skickat ett e-postmeddelande till dig med din huvudlösenordsledtråd." @@ -694,7 +694,7 @@ "message": "Ogiltigt huvudlösenord" }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "Ogiltigt fillösenord, använd lösenordet du angav när du skapade exportfilen." }, "lockNow": { "message": "Lås nu" @@ -721,7 +721,7 @@ "message": "Du tillhör inte några organisationer. Organisationer möjliggör säker delning av objekt med andra användare." }, "notificationSentDevice": { - "message": "A notification has been sent to your device." + "message": "En avisering har skickats till din enhet." }, "versionNumber": { "message": "Version $VERSION_NUMBER$", @@ -912,10 +912,10 @@ "message": "Filformat" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "Den exporterade filen kommer vara lösenordsskyddad. Du kommer behöva ange fillösenordet för att dekryptera den." }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "Detta lösenord kommer att användas för att exportera och importera denna fil" }, "confirmMasterPassword": { "message": "Bekräfta huvudlösenord" @@ -924,16 +924,16 @@ "message": "Bekräfta format" }, "filePassword": { - "message": "File password" + "message": "Fillösenord" }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "Bekräfta fillösenord" }, "accountBackupOptionDescription": { - "message": "Use your account encryption key to encrypt the export and restrict import to only the current Bitwarden account." + "message": "Kryptera exportfilen med ditt kontos krypteringsnyckel så att filen endast kan importeras till ditt Bitwarden-konto." }, "passwordProtectedOptionDescription": { - "message": "Set a password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "Välj ett fillösenord för att kryptera exportfilen. Filen kan sedan importeras till valfritt Bitwarden-konto med hjälp av fillösenordet." }, "fileTypeHeading": { "message": "Filtyp" @@ -942,16 +942,16 @@ "message": "Kontobackup" }, "passwordProtected": { - "message": "Password protected" + "message": "Lösenordsskyddad" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "“Fillösenord” och “Bekräfta fillösenord” matchar inte." }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "Bekräfta importering av valv" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "Den här filen är lösenordsskyddad. Ange lösenordet för att importera data." }, "exportSuccess": { "message": "Ditt valv har exporterats" @@ -1233,10 +1233,10 @@ "message": "Ändra språket som används i webbvalvet." }, "enableFavicon": { - "message": "Show website icons" + "message": "Visa webbplatsikoner" }, "faviconDesc": { - "message": "Show a recognizable image next to each login." + "message": "Visa en igenkännbar bild bredvid varje inloggning." }, "enableFullWidth": { "message": "Aktivera layout med full bredd", @@ -1291,23 +1291,23 @@ "message": "Tvåfaktorsautentisering" }, "twoStepLoginEnforcement": { - "message": "Two-step Login Enforcement" + "message": "Kräv tvåstegsverifiering" }, "twoStepLoginDesc": { "message": "Säkra ditt konto genom att kräva ett ytterligare steg vid inloggning." }, "twoStepLoginOrganizationDescStart": { - "message": "Enforce Bitwarden Two-step Login options for members by using the ", + "message": "Kräv att medlemmar använder tvåstegsverifiering genom att använda ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" }, "twoStepLoginPolicy": { - "message": "Two-step Login Policy" + "message": "policyn för tvåstegsinloggning" }, "twoStepLoginOrganizationDuoDesc": { - "message": "To enforce Two-step Login through Duo, use the options below." + "message": "För att kräva tvåstegsverifiering med Duo, använd alternativen nedan." }, "twoStepLoginOrganizationSsoDesc": { - "message": "If you have setup SSO or plan to, Two-step Login may already be enforced through your Identity Provider." + "message": "Om du har konfigurerat SSO eller planerar att göra det, kan tvåstegsverifiering redan krävas av din ID-leverantör." }, "twoStepLoginRecoveryWarning": { "message": "Att aktivera tvåstegsverifiering kan låsa ute dig från ditt Bitwarden-konto permanent. En återställningskod låter dig komma åt ditt konto om du inte längre kan använda din vanliga metod för tvåstegsverifiering (t.ex. om du förlorar din enhet). Bitwardens kundservice kommer inte att kunna hjälpa dig om du förlorar åtkomst till ditt konto. Vi rekommenderar att du skriver ner eller skriver ut återställningskoden och förvarar den på ett säkert ställe." @@ -1326,7 +1326,7 @@ "message": "Aktiverad" }, "restoreAccess": { - "message": "Restore access" + "message": "Återställ åtkomst" }, "premium": { "message": "Premium", @@ -1354,7 +1354,7 @@ "message": "Inaktivera" }, "revokeAccess": { - "message": "Revoke access" + "message": "Återkalla åtkomst" }, "twoStepLoginProviderEnabled": { "message": "Denna metod för tvåstegsverifiering är aktiverad på ditt konto." @@ -1557,7 +1557,7 @@ "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "orgsReportsDesc": { - "message": "Identify and close security gaps in your organization's accounts by clicking the reports below.", + "message": "Identifiera och åtgärda säkerhetsluckor i din organisations konton genom att klicka på rapporterna nedan.", "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization Vault." }, "unsecuredWebsitesReport": { @@ -1755,7 +1755,7 @@ "message": "Faktureringsplan" }, "paymentType": { - "message": "Payment type" + "message": "Betalningssätt" }, "accountCredit": { "message": "Kontokredit", @@ -1905,7 +1905,7 @@ "message": "Faktureringsuppgifter" }, "billingTrialSubLabel": { - "message": "Your payment method will not be charged during the 7 day free trial." + "message": "Du kommer inte att debiteras under din 7-dagars gratis provperiod." }, "creditCard": { "message": "Kreditkort" diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index fd895926467..8624a02f8fd 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -1291,23 +1291,23 @@ "message": "İki aşamalı giriş" }, "twoStepLoginEnforcement": { - "message": "İki-Aşamalı Giriş Zorunluluğu" + "message": "İki aşamalı giriş zorunluluğu" }, "twoStepLoginDesc": { "message": "Oturum açarken ek bir adım talep ederek hesabınızı güvenceye alabilirsiniz." }, "twoStepLoginOrganizationDescStart": { - "message": "Bitwarden İki-Aşamalı Giriş seçeneklerini zorunlu tut ", + "message": "Üyeler için Bitwarden iki aşamalı giriş seçeneklerini ", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" }, "twoStepLoginPolicy": { - "message": "İki-Aşamalı Oturum Açma Seçenekleri" + "message": "iki aşamalı giriş ilkesi ile zorunlu tut" }, "twoStepLoginOrganizationDuoDesc": { - "message": "Duo ile İki-Aşamalı Giriş için aşağıdaki seçenekleri kullanın." + "message": "Duo ile iki aşamalı girişi zorunlu tutmak için aşağıdaki seçenekleri kullanın." }, "twoStepLoginOrganizationSsoDesc": { - "message": "Eğer SSO kurduysanız ya da kurmayı planlıyorsanız, Kimlik Sağlayıcınız tarafından İki-Aşamalı Giriş halihazırda sağlanıyor olabilir." + "message": "SSO kurduysanız veya kurmayı planlıyorsanız kimlik sağlayıcınız iki aşamalı girişi zaten zorunlu tutmuş olabilir." }, "twoStepLoginRecoveryWarning": { "message": "İki aşamalı girişi etkinleştirmek, Bitwarden hesabınızı kalıcı olarak kilitleyebilir. Kurtarma kodunuz, iki aşamalı giriş sağlayıcınızı kullanamamanız durumunda hesabınıza erişmenize olanak sağlar (ör. cihazınızı kaybedersiniz). Hesabınıza erişiminizi kaybederseniz Bitwarden destek ekibi size yardımcı olamaz. Kurtarma kodunu not almanızı veya yazdırmanızı ve güvenli bir yerde saklamanızı öneririz." @@ -1663,7 +1663,7 @@ "message": "Yeniden kullanılmış parolalar" }, "reusedPasswordsReportDesc": { - "message": "Kullandığınız bir servis ele geçirilirse, aynı parolayı başka yerlerde kullanmanız hacker'ların diğer hesaplarınıza da kolayca erişmesine olanak tanıyabilir. bu yüzden her hesap ve hizmet için farklı bir parola kullanmalısınız." + "message": "Aynı parolayı başka yerlerde kullanmanız saldırganların diğer hesaplarınıza da erişmesini kolaylaştırır. Bu parolaları değiştirin ve aynı parolayı farklı yerlerde kullanmayın." }, "reusedPasswordsFound": { "message": "Yeniden kullanılmış parolalar bulundu" @@ -1693,7 +1693,7 @@ "message": "Veri ihlali" }, "breachDesc": { - "message": "Hacker'ların bir sitenin verilerine yasadışı bir şekilde erişip bunları herkese açık bir şekilde yayımlamalarına \"ihlal\" denir. Ele geçirilen veri türlerini (e-posta adresleri, parolalar, kredi kartları vb.) inceleyin ve gerekli önlemleri alın (örn. parolaları değiştirin)." + "message": "Ele geçirilen hesaplar kişisel bilgilerinizi açığa çıkarabilir. İki aşamalı girişi etkinleştirerek veya daha güçlü parolalar kullanarak ele geçirilen hesapları güvence altına alın." }, "breachCheckUsernameEmail": { "message": "Kullandığınız kullanıcı adlarını ve e-posta adreslerini kontrol edin." @@ -2993,13 +2993,13 @@ "message": "E-postamı hatırla" }, "recoverAccountTwoStepDesc": { - "message": "Eğer hesabınıza iki aşamalı doğrulama ile erişimde bir sorun yaşıyorsanız, kurtarma kodunuz ile iki aşamalı doğrulama özelliğini kapatabilirsiniz." + "message": "Hesabınıza normalde kullandığınız iki aşamalı giriş yöntemleriyle erişemiyorsanız iki aşamalı giriş kurtarma kodunuzu kullanarak heasbınızdaki tüm iki aşamalı giriş sağlayıcılarını kapatabilirsiniz." }, "recoverAccountTwoStep": { - "message": "İki-Adımlı Hesap Girişini Kurtar" + "message": "Hesabı iki aşamalı girişten kurtar" }, "twoStepRecoverDisabled": { - "message": "İki aşamalı doğrulama hesabınızda devre dışı bırakıldı." + "message": "Hesabınızda iki aşamalı giriş devre dışı bırakıldı." }, "learnMore": { "message": "Daha fazla bilgi al" @@ -3011,7 +3011,7 @@ "message": "Hesabınız varsa gerekli talimatları içeren bir mesajı e-posta adresinize gönderdik." }, "deleteRecoverConfirmDesc": { - "message": "Bitwarden hesabınızı silme talebinde bulundunuz. Onaylamak için aşağıdaki düğmeye tıklayın." + "message": "Bitwarden hesabınızı silme talebinde bulundunuz. Onaylamak için aşağıdaki düğmeyi kullanın." }, "myOrganization": { "message": "Kuruluşum" @@ -3096,7 +3096,7 @@ "message": "Banka hesabıyla ödeme yalnızca Amerika Birleşik Devletleri'ndeki müşteriler tarafından kullanılabilir. Banka hesabınızı doğrulamanız istenecektir. Önümüzdeki 1-2 iş günü içinde iki mikro para yatırma işlemi yapacağız. Banka hesabını doğrulamak için bu tutarları kuruluşun faturalandırma sayfasına girin." }, "verifyBankAccountFailureWarning": { - "message": "Banka hesabınız doğrulanmazsa ödeme yapılamaz ve aboneliğiniz devre dışı bırakılır." + "message": "Banka hesabınız doğrulanmazsa ödeme yapılamaz ve aboneliğiniz duraklatılır." }, "verifiedBankAccount": { "message": "Banka hesabı doğrulandı" @@ -3492,10 +3492,10 @@ "message": "Ana parola gereksinimleri" }, "masterPassPolicyDesc": { - "message": "Ana parola gücü için minimum gereksinimleri ayarlayın." + "message": "Ana parola gücü gereksinimlerini ayarlayın." }, "twoStepLoginPolicyTitle": { - "message": "İki adımlı giriş gereklidir" + "message": "İki adımlı girişi zorunlu tut" }, "twoStepLoginPolicyDesc": { "message": "Kullanıcıların hesaplarında iki aşamalı giriş kullanmalarını zorunlu tutun." @@ -3611,7 +3611,7 @@ } }, "permanentlyDeletedItemId": { - "message": "$ID$ kaydı kalıcı olarak silindi.", + "message": "$ID$ kaydı kalıcı olarak silindi", "placeholders": { "id": { "content": "$1", @@ -3650,7 +3650,7 @@ } }, "restoredItemId": { - "message": "$ID$ kaydı geri yüklendi.", + "message": "$ID$ kaydı geri yüklendi", "placeholders": { "id": { "content": "$1", @@ -3662,7 +3662,7 @@ "message": "Çıkış yaptığınızda kasanıza erişiminiz tamamen sonlanacak ve zaman aşımının ardından çevrimiçi kimlik doğrulaması yapmanız gerekecek. Bu ayarı kullanmak istediğinizden emin misiniz?" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "Zaman Aşımı Eylem Onayı" + "message": "Zaman aşımı eylem onayı" }, "hidePasswords": { "message": "Parolaları gizle" @@ -3692,7 +3692,7 @@ "message": "Kuruluş tanımlayıcı" }, "ssoLogInWithOrgIdentifier": { - "message": "Kuruluşunuzun tek oturum açma portalını kullanarak giriş yapabilirsiniz. Başlamak için lütfen kuruluşunuzun tanımlayıcısını girin." + "message": "Kuruluşunuzun tek oturum açma portalını kullanarak giriş yapabilirsiniz. Başlamak için lütfen kuruluşunuzun SSO tanımlayıcısını girin." }, "enterpriseSingleSignOn": { "message": "Kurumsal tek oturum açma" @@ -3713,10 +3713,10 @@ "message": "SSO doğrulaması başarısız oldu" }, "ssoIdentifierRequired": { - "message": "Kuruluş tanımlayıcısı gereklidir." + "message": "Kuruluş SSO tanımlayıcısı gereklidir." }, "ssoIdentifier": { - "message": "SSO Tanımlayıcı" + "message": "SSO tanımlayıcı" }, "ssoIdentifierHint": { "message": "SSO ile giriş yapan üyelere bu kimliği sağlayın." @@ -3734,7 +3734,7 @@ "message": "Tek kuruluş" }, "singleOrgDesc": { - "message": "Kullanıcıların diğer kuruluşlara katılmasını kısıtlayın." + "message": "Üyelerin diğer kuruluşlara katılmasını kısıtlayın." }, "singleOrgBlockCreateMessage": { "message": "Mevcut kuruluşunuzun birden fazla kuruluşa katılmanıza izin vermeyen bir ilkesi var. Lütfen kuruluş yöneticilerinizle iletişime geçin veya farklı bir Bitwarden hesabı açın." @@ -3767,7 +3767,7 @@ "message": "Metin" }, "createSend": { - "message": "Yeni Send oluştur", + "message": "Yeni Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -3775,11 +3775,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Send oluşturuldu", + "message": "Send kaydedildi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Send düzenlendi", + "message": "Send kaydedildi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { @@ -3834,7 +3834,7 @@ "message": "Devre dışı" }, "revoked": { - "message": "İptal Edildi" + "message": "İptal edildi" }, "sendLink": { "message": "Send bağlantısı", @@ -4032,7 +4032,7 @@ } }, "emergencyApproved": { - "message": "Acil durum erişimi onaylandı." + "message": "Acil durum erişimi onaylandı" }, "emergencyRejected": { "message": "Acil durum erişimi reddedildi" @@ -4059,7 +4059,7 @@ "message": "Bir kuruluş ilkesi nedeniyle kişisel kasanıza hesap kaydetmeniz kısıtlanmış. Sahip seçeneğini bir kuruluş olarak değiştirin ve mevcut koleksiyonlar arasından seçim yapın." }, "disableSend": { - "message": "Send'i devre dışı bırak" + "message": "Send'i sil" }, "disableSendPolicyDesc": { "message": "Kullanıcıların Bitwarden Send oluşturmasına veya düzenlemesine izin verme. Mevcut Send'leri silmeye yine de izin verilir.", @@ -4069,7 +4069,7 @@ "message": "Kuruluş ilkelerini yönetebilen kullanıcılar bu ilkenin uygulanmasından muaf tutulur." }, "sendDisabled": { - "message": "Send devre dışı", + "message": "Send silindi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -4077,7 +4077,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptions": { - "message": "Send Seçenekleri", + "message": "Send seçenekleri", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendOptionsPolicyDesc": { @@ -4123,7 +4123,7 @@ "message": "İzinler" }, "permission": { - "message": "Yetki" + "message": "İzin" }, "managerPermissions": { "message": "Yetkili İzinleri" @@ -4333,7 +4333,7 @@ } }, "eventWithdrawPasswordReset": { - "message": "$ID$ kullanıcı parola sıfırlama desteğinden geri çekildi.", + "message": "$ID$ adlı kullanıcı parola sıfırlamadan geri çekildi.", "placeholders": { "id": { "content": "$1", @@ -4351,7 +4351,7 @@ } }, "eventResetSsoLink": { - "message": "$ID$ kullanıcısı için Toa bağlantısını sıfırla", + "message": "$ID$ kullanıcısı için SSO bağlantısını sıfırla", "placeholders": { "id": { "content": "$1", @@ -4441,7 +4441,7 @@ "message": "Davetleri yeniden gönder" }, "resendNotification": { - "message": "Bildirimi Tekrar Yolla" + "message": "Bildirimi yeniden gönder" }, "noSelectedUsersApplicable": { "message": "Bu eylem seçilen kullanıcılardan hiçbirine uygulanamıyor." @@ -4477,10 +4477,10 @@ "message": "Toplu işlem durumu" }, "bulkConfirmMessage": { - "message": "Başarıyla onaylandı." + "message": "Başarıyla onaylandı" }, "bulkReinviteMessage": { - "message": "Yeniden davet edildi." + "message": "Yeniden davet edildi" }, "bulkRemovedMessage": { "message": "Başarıyla kaldırıldı" @@ -4489,7 +4489,7 @@ "message": "Kuruluş erişimi başarıyla iptal edildi" }, "bulkRestoredMessage": { - "message": "Kuruluş erişimi başarıyla geri yüklendi" + "message": "Kuruluş erişimi başarıyla geri getirildi" }, "bulkFilteredMessage": { "message": "İstisna. Bu eylem için geçerli değildir." @@ -4635,7 +4635,7 @@ "message": "Ekle" }, "updatedMasterPassword": { - "message": "Ana parola güncellendi" + "message": "Ana parola kaydedildi" }, "updateMasterPassword": { "message": "Ana parolayı güncelle" @@ -4687,7 +4687,7 @@ "message": "Minimum özel zaman aşımı 1 dakikadır." }, "vaultTimeoutRangeError": { - "message": "Kasa Zaman Aşımı izin verilen aralıkta değil." + "message": "Kasa zaman aşımı izin verilen aralıkta değil." }, "disablePersonalVaultExport": { "message": "Kişisel kasayı dışa aktarmayı devre dışı bırak" @@ -4918,7 +4918,7 @@ "message": "Bir sponsorluğu kaldırdıktan sonra, bu abonelikten ve ilgili faturalardan siz sorumlu olacaksınız. Devam etmek istediğine emin misin?" }, "sponsorshipCreated": { - "message": "Sponsorluk oluştur" + "message": "Sponsorluk oluşturuldu" }, "emailSent": { "message": "E-posta gönderildi" @@ -4972,7 +4972,7 @@ "message": "Ana parolayı kaldır" }, "removedMasterPassword": { - "message": "Ana parola kaldırıldı." + "message": "Ana parola kaldırıldı" }, "allowSso": { "message": "SSO doğrulamasına izin ver" @@ -5011,7 +5011,7 @@ "message": "\"TOA ve Anahtar Bağlayıcı Şifre Çözme ile Oturum Açma\" etkinleştirildi. Bu politika yalnızca Sahipler ve Yöneticiler için geçerli olacaktır." }, "enabledSso": { - "message": "SSO etkinleştirildi" + "message": "SSO açıldı" }, "disabledSso": { "message": "SSO kapatıldı" @@ -5329,7 +5329,7 @@ "message": "Cihaz doğrulama" }, "enableDeviceVerification": { - "message": "Cihaz doğrulamasını etkinleştir" + "message": "Cihaz doğrulamasını aç" }, "deviceVerificationDesc": { "message": "Etkinleştirildiğinde, tanınmayan bir cihazdan oturum açarken e-posta adresinize doğrulama kodları gönderilir" @@ -5373,7 +5373,7 @@ "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateScimKey": { - "message": "SCIM API Anahtarını yenileyin", + "message": "SCIM API anahtarını yenile", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateScimKeyWarning": { @@ -5381,10 +5381,10 @@ "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "rotateKey": { - "message": "Anahtarı Yenile" + "message": "Anahtarı yenile" }, "scimApiKey": { - "message": "SCIM API Anahtarı", + "message": "SCIM API anahtarı", "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "copyScimUrl": { @@ -5400,7 +5400,7 @@ "description": "the text, 'SCIM' and 'API', are acronymns and should not be translated." }, "scimSettingsSaved": { - "message": "SCIM ayarları başarıyla kaydedildi", + "message": "SCIM ayarları kaydedildi", "description": "the text, 'SCIM', is an acronymn and should not be translated." }, "inputRequired": { diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 1dc1bc6d132..7145d3c606c 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -2501,19 +2501,19 @@ "message": "网页密码库" }, "loggedIn": { - "message": "已登录。" + "message": "已登录" }, "changedPassword": { - "message": "已更改帐户密码" + "message": "更改了帐户密码" }, "enabledUpdated2fa": { - "message": "两步登录已保存" + "message": "保存了两步登录" }, "disabled2fa": { - "message": "两步登录已停用" + "message": "停用了两步登录" }, "recovered2fa": { - "message": "已从两步登录中恢复帐户。" + "message": "从两步登录中恢复了帐户。" }, "failedLogin": { "message": "登录失败,密码不正确。" @@ -2522,16 +2522,16 @@ "message": "登录失败,两步登录不正确。" }, "exportedVault": { - "message": "密码库已导出" + "message": "导出了密码库。" }, "exportedOrganizationVault": { - "message": "已导出组织密码库。" + "message": "导出了组织密码库。" }, "editedOrgSettings": { - "message": "已编辑组织设置。" + "message": "编辑了组织设置。" }, "createdItemId": { - "message": "已创建项目 $ID$。", + "message": "创建了项目 $ID$。", "placeholders": { "id": { "content": "$1", @@ -2540,7 +2540,7 @@ } }, "editedItemId": { - "message": "已编辑项目 $ID$。", + "message": "编辑了项目 $ID$。", "placeholders": { "id": { "content": "$1", @@ -2549,7 +2549,7 @@ } }, "deletedItemId": { - "message": "发送项目 $ID$ 到回收站。", + "message": "发送了项目 $ID$ 到回收站。", "placeholders": { "id": { "content": "$1", @@ -2558,7 +2558,7 @@ } }, "movedItemIdToOrg": { - "message": "已将项目 $ID$ 移动到组织。", + "message": "移动了项目 $ID$ 到组织。", "placeholders": { "id": { "content": "$1", @@ -2597,7 +2597,7 @@ } }, "viewedCardNumberItemId": { - "message": "已查看项目 $ID$ 的卡号。", + "message": "查看了项目 $ID$ 的卡号。", "placeholders": { "id": { "content": "$1", @@ -2633,7 +2633,7 @@ } }, "copiedSecurityCodeItemId": { - "message": "复制项目 $ID$ 的安全代码。", + "message": "复制了项目 $ID$ 的安全代码。", "placeholders": { "id": { "content": "$1", @@ -2642,7 +2642,7 @@ } }, "autofilledItemId": { - "message": "项目 $ID$ 已自动填充。", + "message": "自动填充了项目 $ID$。", "placeholders": { "id": { "content": "$1", @@ -4123,7 +4123,7 @@ "message": "权限" }, "permission": { - "message": "Permission" + "message": "权限" }, "managerPermissions": { "message": "经理权限" @@ -4405,7 +4405,7 @@ "message": "自动注册" }, "resetPasswordPolicyAutoEnrollDescription": { - "message": "邀请被接受后的所有成员,将会被自动注册密码重置,并且不允许撤销。" + "message": "所有接受邀请的用戶,将会被自动注册密码重置,并且不允许撤销。" }, "resetPasswordPolicyAutoEnrollWarning": { "message": "已经在组织中的成员将不会被注册密码重置。需要他们自行注册后,管理员才能重置他们的主密码。" @@ -4492,7 +4492,7 @@ "message": "成功恢复组织的访问权限" }, "bulkFilteredMessage": { - "message": "排除,不适用于此操作" + "message": "已排除,不适用于此操作" }, "fingerprint": { "message": "指纹" @@ -5011,7 +5011,7 @@ "message": "「SSO 登录和 Key Connector 解密」已启用。此策略仅适用于所有者和管理员。" }, "enabledSso": { - "message": "SSO 已启用" + "message": "启用了 SSO" }, "disabledSso": { "message": "SSO 已停用" @@ -5020,7 +5020,7 @@ "message": "Key Connector 已启用" }, "disabledKeyConnector": { - "message": "Key Connector 已停用" + "message": "停用了 Key Connector" }, "keyConnectorWarning": { "message": "一旦成员开始使用 Key Connector,您的组织就无法恢复为使用主密码解密。仅当您可以轻松地部署和管理密钥服务器时才继续操作。" @@ -5446,7 +5446,7 @@ "message": "成员" }, "reporting": { - "message": "Reporting" + "message": "报告" }, "cardBrandMir": { "message": "Mir" @@ -5485,22 +5485,22 @@ "message": "更新" }, "role": { - "message": "Role" + "message": "角色" }, "canView": { - "message": "Can view" + "message": "可以查看" }, "canViewExceptPass": { - "message": "Can view, except passwords" + "message": "除密码外,可以查看" }, "canEdit": { - "message": "Can edit" + "message": "可以编辑" }, "canEditExceptPass": { - "message": "Can edit, except passwords" + "message": "除密码外,可以编辑" }, "group": { - "message": "Group" + "message": "群组" }, "groupAccessAll": { "message": "此群组可以访问和修改所有项目。" diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js index cecfbea3998..2000fe877ab 100644 --- a/apps/web/webpack.config.js +++ b/apps/web/webpack.config.js @@ -273,6 +273,7 @@ const devServer = https://quack.duckduckgo.com/api/email/addresses https://app.anonaddy.com/api/v1/aliases https://api.fastmail.com + http://localhost:5000 ;object-src 'self' blob: diff --git a/libs/angular/src/components/two-factor.component.ts b/libs/angular/src/components/two-factor.component.ts index f61e54710b2..28799df6600 100644 --- a/libs/angular/src/components/two-factor.component.ts +++ b/libs/angular/src/components/two-factor.component.ts @@ -281,12 +281,12 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI return ( this.authService.authingWithPassword() || this.authService.authingWithSso() || - this.authService.authingWithApiKey() || + this.authService.authingWithUserApiKey() || this.authService.authingWithPasswordless() ); } get needsLock(): boolean { - return this.authService.authingWithSso() || this.authService.authingWithApiKey(); + return this.authService.authingWithSso() || this.authService.authingWithUserApiKey(); } } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 5f49041bd76..5399c90e635 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -35,7 +35,10 @@ import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/abstr import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service"; import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationService as OrganizationServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; +import { + InternalOrganizationService, + OrganizationService as OrganizationServiceAbstraction, +} from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/abstractions/passwordReprompt.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service"; @@ -356,7 +359,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; StateServiceAbstraction, ProviderServiceAbstraction, FolderApiServiceAbstraction, - SyncNotifierServiceAbstraction, + OrganizationServiceAbstraction, LOGOUT_CALLBACK, ], }, @@ -506,6 +509,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; LogService, OrganizationServiceAbstraction, CryptoFunctionServiceAbstraction, + SyncNotifierServiceAbstraction, + MessagingServiceAbstraction, LOGOUT_CALLBACK, ], }, @@ -522,7 +527,11 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; { provide: OrganizationServiceAbstraction, useClass: OrganizationService, - deps: [StateServiceAbstraction, SyncNotifierServiceAbstraction], + deps: [StateServiceAbstraction], + }, + { + provide: InternalOrganizationService, + useExisting: OrganizationServiceAbstraction, }, { provide: ProviderServiceAbstraction, diff --git a/libs/common/spec/misc/logInStrategies/apiLogIn.strategy.spec.ts b/libs/common/spec/misc/logInStrategies/user-api-login.strategy.spec.ts similarity index 90% rename from libs/common/spec/misc/logInStrategies/apiLogIn.strategy.spec.ts rename to libs/common/spec/misc/logInStrategies/user-api-login.strategy.spec.ts index 6e80ffa2916..bc61d2c7e22 100644 --- a/libs/common/spec/misc/logInStrategies/apiLogIn.strategy.spec.ts +++ b/libs/common/spec/misc/logInStrategies/user-api-login.strategy.spec.ts @@ -12,13 +12,13 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti import { StateService } from "@bitwarden/common/abstractions/state.service"; import { TokenService } from "@bitwarden/common/abstractions/token.service"; import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service"; -import { ApiLogInStrategy } from "@bitwarden/common/misc/logInStrategies/apiLogin.strategy"; +import { UserApiLogInStrategy } from "@bitwarden/common/misc/logInStrategies/user-api-login.strategy"; import { Utils } from "@bitwarden/common/misc/utils"; -import { ApiLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials"; +import { UserApiLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials"; import { identityTokenResponseFactory } from "./logIn.strategy.spec"; -describe("ApiLogInStrategy", () => { +describe("UserApiLogInStrategy", () => { let cryptoService: SubstituteOf; let apiService: SubstituteOf; let tokenService: SubstituteOf; @@ -31,8 +31,8 @@ describe("ApiLogInStrategy", () => { let stateService: SubstituteOf; let twoFactorService: SubstituteOf; - let apiLogInStrategy: ApiLogInStrategy; - let credentials: ApiLogInCredentials; + let apiLogInStrategy: UserApiLogInStrategy; + let credentials: UserApiLogInCredentials; const deviceId = Utils.newGuid(); const keyConnectorUrl = "KEY_CONNECTOR_URL"; @@ -55,7 +55,7 @@ describe("ApiLogInStrategy", () => { appIdService.getAppId().resolves(deviceId); tokenService.getTwoFactorToken().resolves(null); - apiLogInStrategy = new ApiLogInStrategy( + apiLogInStrategy = new UserApiLogInStrategy( cryptoService, apiService, tokenService, @@ -69,7 +69,7 @@ describe("ApiLogInStrategy", () => { keyConnectorService ); - credentials = new ApiLogInCredentials(apiClientId, apiClientSecret); + credentials = new UserApiLogInCredentials(apiClientId, apiClientSecret); }); it("sends api key credentials to the server", async () => { diff --git a/libs/common/spec/services/organization/organization.service.spec.ts b/libs/common/spec/services/organization/organization.service.spec.ts index 87226735712..42a327c18fd 100644 --- a/libs/common/spec/services/organization/organization.service.spec.ts +++ b/libs/common/spec/services/organization/organization.service.spec.ts @@ -1,12 +1,9 @@ -import { MockProxy, mock, any, mockClear, matches } from "jest-mock-extended"; -import { BehaviorSubject, firstValueFrom, Subject } from "rxjs"; +import { MockProxy, mock, any, mockClear } from "jest-mock-extended"; +import { BehaviorSubject, firstValueFrom } from "rxjs"; import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { SyncNotifierService } from "@bitwarden/common/abstractions/sync/syncNotifier.service.abstraction"; import { OrganizationData } from "@bitwarden/common/models/data/organization.data"; -import { SyncResponse } from "@bitwarden/common/models/response/sync.response"; import { OrganizationService } from "@bitwarden/common/services/organization/organization.service"; -import { SyncEventArgs } from "@bitwarden/common/types/syncEventArgs"; describe("Organization Service", () => { let organizationService: OrganizationService; @@ -14,8 +11,6 @@ describe("Organization Service", () => { let stateService: MockProxy; let activeAccount: BehaviorSubject; let activeAccountUnlocked: BehaviorSubject; - let syncNotifierService: MockProxy; - let sync: Subject; const resetStateService = async ( customizeStateService: (stateService: MockProxy) => void @@ -25,7 +20,7 @@ describe("Organization Service", () => { stateService.activeAccount$ = activeAccount; stateService.activeAccountUnlocked$ = activeAccountUnlocked; customizeStateService(stateService); - organizationService = new OrganizationService(stateService, syncNotifierService); + organizationService = new OrganizationService(stateService); await new Promise((r) => setTimeout(r, 50)); }; @@ -41,12 +36,7 @@ describe("Organization Service", () => { "1": organizationData("1", "Test Org"), }); - sync = new Subject(); - - syncNotifierService = mock(); - syncNotifierService.sync$ = sync; - - organizationService = new OrganizationService(stateService, syncNotifierService); + organizationService = new OrganizationService(stateService); }); afterEach(() => { @@ -169,36 +159,6 @@ describe("Organization Service", () => { }); }); - describe("syncEvent works", () => { - it("Complete event updates data", async () => { - sync.next({ - status: "Completed", - successfully: true, - data: new SyncResponse({ - profile: { - organizations: [ - { - id: "1", - name: "Updated Name", - }, - ], - }, - }), - }); - - await new Promise((r) => setTimeout(r, 500)); - - expect(stateService.setOrganizations).toHaveBeenCalledTimes(1); - - expect(stateService.setOrganizations).toHaveBeenLastCalledWith( - matches((organizationData: { [id: string]: OrganizationData }) => { - const organization = organizationData["1"]; - return organization?.name === "Updated Name"; - }) - ); - }); - }); - function organizationData(id: string, name: string) { const data = new OrganizationData({} as any); data.id = id; diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 74f9edebd6f..a8e2f45283c 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -24,9 +24,9 @@ import { EmergencyAccessUpdateRequest } from "../models/request/emergency-access import { EventRequest } from "../models/request/event.request"; import { GroupRequest } from "../models/request/group.request"; import { IapCheckRequest } from "../models/request/iap-check.request"; -import { ApiTokenRequest } from "../models/request/identity-token/api-token.request"; import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request"; import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request"; +import { UserApiTokenRequest } from "../models/request/identity-token/user-api-token.request"; import { ImportCiphersRequest } from "../models/request/import-ciphers.request"; import { ImportOrganizationCiphersRequest } from "../models/request/import-organization-ciphers.request"; import { KdfRequest } from "../models/request/kdf.request"; @@ -175,7 +175,7 @@ export abstract class ApiService { ) => Promise; postIdentityToken: ( - request: PasswordTokenRequest | SsoTokenRequest | ApiTokenRequest + request: PasswordTokenRequest | SsoTokenRequest | UserApiTokenRequest ) => Promise; refreshIdentityToken: () => Promise; diff --git a/libs/common/src/abstractions/auth.service.ts b/libs/common/src/abstractions/auth.service.ts index cadc7bb60b3..ca75e273b5c 100644 --- a/libs/common/src/abstractions/auth.service.ts +++ b/libs/common/src/abstractions/auth.service.ts @@ -3,7 +3,7 @@ import { Observable } from "rxjs"; import { AuthenticationStatus } from "../enums/authenticationStatus"; import { AuthResult } from "../models/domain/auth-result"; import { - ApiLogInCredentials, + UserApiLogInCredentials, PasswordLogInCredentials, SsoLogInCredentials, PasswordlessLogInCredentials, @@ -20,7 +20,7 @@ export abstract class AuthService { logIn: ( credentials: - | ApiLogInCredentials + | UserApiLogInCredentials | PasswordLogInCredentials | SsoLogInCredentials | PasswordlessLogInCredentials @@ -31,7 +31,7 @@ export abstract class AuthService { ) => Promise; logOut: (callback: () => void) => void; makePreloginKey: (masterPassword: string, email: string) => Promise; - authingWithApiKey: () => boolean; + authingWithUserApiKey: () => boolean; authingWithSso: () => boolean; authingWithPassword: () => boolean; authingWithPasswordless: () => boolean; diff --git a/libs/common/src/abstractions/folder/folder.service.abstraction.ts b/libs/common/src/abstractions/folder/folder.service.abstraction.ts index f8df1f7db63..44db4a985b4 100644 --- a/libs/common/src/abstractions/folder/folder.service.abstraction.ts +++ b/libs/common/src/abstractions/folder/folder.service.abstraction.ts @@ -12,6 +12,10 @@ export abstract class FolderService { clearCache: () => Promise; encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise; get: (id: string) => Promise; + /** + * @deprecated Only use in CLI! + */ + getFromState: (id: string) => Promise; /** * @deprecated Only use in CLI! */ diff --git a/libs/common/src/abstractions/organization/organization.service.abstraction.ts b/libs/common/src/abstractions/organization/organization.service.abstraction.ts index 3dfeb90cb03..68c64654d57 100644 --- a/libs/common/src/abstractions/organization/organization.service.abstraction.ts +++ b/libs/common/src/abstractions/organization/organization.service.abstraction.ts @@ -1,6 +1,7 @@ import { map, Observable } from "rxjs"; import { Utils } from "../../misc/utils"; +import { OrganizationData } from "../../models/data/organization.data"; import { Organization } from "../../models/domain/organization"; import { I18nService } from "../i18n.service"; @@ -83,3 +84,7 @@ export abstract class OrganizationService { canManageSponsorships: () => Promise; hasOrganizations: () => boolean; } + +export abstract class InternalOrganizationService extends OrganizationService { + replace: (organizations: { [id: string]: OrganizationData }) => Promise; +} diff --git a/libs/common/src/enums/authenticationType.ts b/libs/common/src/enums/authenticationType.ts index 5133c4f648e..531c571b469 100644 --- a/libs/common/src/enums/authenticationType.ts +++ b/libs/common/src/enums/authenticationType.ts @@ -1,6 +1,6 @@ export enum AuthenticationType { Password = 0, Sso = 1, - Api = 2, + UserApi = 2, Passwordless = 3, } diff --git a/libs/common/src/misc/logInStrategies/logIn.strategy.ts b/libs/common/src/misc/logInStrategies/logIn.strategy.ts index 8ac3ea2b55b..b0cf06e9f9a 100644 --- a/libs/common/src/misc/logInStrategies/logIn.strategy.ts +++ b/libs/common/src/misc/logInStrategies/logIn.strategy.ts @@ -11,23 +11,23 @@ import { TwoFactorProviderType } from "../../enums/twoFactorProviderType"; import { Account, AccountProfile, AccountTokens } from "../../models/domain/account"; import { AuthResult } from "../../models/domain/auth-result"; import { - ApiLogInCredentials, + UserApiLogInCredentials, PasswordLogInCredentials, SsoLogInCredentials, PasswordlessLogInCredentials, } from "../../models/domain/log-in-credentials"; import { DeviceRequest } from "../../models/request/device.request"; -import { ApiTokenRequest } from "../../models/request/identity-token/api-token.request"; import { PasswordTokenRequest } from "../../models/request/identity-token/password-token.request"; import { SsoTokenRequest } from "../../models/request/identity-token/sso-token.request"; import { TokenTwoFactorRequest } from "../../models/request/identity-token/token-two-factor.request"; +import { UserApiTokenRequest } from "../../models/request/identity-token/user-api-token.request"; import { KeysRequest } from "../../models/request/keys.request"; import { IdentityCaptchaResponse } from "../../models/response/identity-captcha.response"; import { IdentityTokenResponse } from "../../models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response"; export abstract class LogInStrategy { - protected abstract tokenRequest: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest; + protected abstract tokenRequest: UserApiTokenRequest | PasswordTokenRequest | SsoTokenRequest; protected captchaBypassToken: string = null; constructor( @@ -44,7 +44,7 @@ export abstract class LogInStrategy { abstract logIn( credentials: - | ApiLogInCredentials + | UserApiLogInCredentials | PasswordLogInCredentials | SsoLogInCredentials | PasswordlessLogInCredentials diff --git a/libs/common/src/misc/logInStrategies/apiLogin.strategy.ts b/libs/common/src/misc/logInStrategies/user-api-login.strategy.ts similarity index 85% rename from libs/common/src/misc/logInStrategies/apiLogin.strategy.ts rename to libs/common/src/misc/logInStrategies/user-api-login.strategy.ts index a60735343ad..7b435c74fd4 100644 --- a/libs/common/src/misc/logInStrategies/apiLogin.strategy.ts +++ b/libs/common/src/misc/logInStrategies/user-api-login.strategy.ts @@ -9,14 +9,14 @@ import { PlatformUtilsService } from "../../abstractions/platformUtils.service"; import { StateService } from "../../abstractions/state.service"; import { TokenService } from "../../abstractions/token.service"; import { TwoFactorService } from "../../abstractions/twoFactor.service"; -import { ApiLogInCredentials } from "../../models/domain/log-in-credentials"; -import { ApiTokenRequest } from "../../models/request/identity-token/api-token.request"; +import { UserApiLogInCredentials } from "../../models/domain/log-in-credentials"; +import { UserApiTokenRequest } from "../../models/request/identity-token/user-api-token.request"; import { IdentityTokenResponse } from "../../models/response/identity-token.response"; import { LogInStrategy } from "./logIn.strategy"; -export class ApiLogInStrategy extends LogInStrategy { - tokenRequest: ApiTokenRequest; +export class UserApiLogInStrategy extends LogInStrategy { + tokenRequest: UserApiTokenRequest; constructor( cryptoService: CryptoService, @@ -51,8 +51,8 @@ export class ApiLogInStrategy extends LogInStrategy { } } - async logIn(credentials: ApiLogInCredentials) { - this.tokenRequest = new ApiTokenRequest( + async logIn(credentials: UserApiLogInCredentials) { + this.tokenRequest = new UserApiTokenRequest( credentials.clientId, credentials.clientSecret, await this.buildTwoFactor(), diff --git a/libs/common/src/models/domain/log-in-credentials.ts b/libs/common/src/models/domain/log-in-credentials.ts index 46f2fe5f82e..81323934bf0 100644 --- a/libs/common/src/models/domain/log-in-credentials.ts +++ b/libs/common/src/models/domain/log-in-credentials.ts @@ -26,8 +26,8 @@ export class SsoLogInCredentials { ) {} } -export class ApiLogInCredentials { - readonly type = AuthenticationType.Api; +export class UserApiLogInCredentials { + readonly type = AuthenticationType.UserApi; constructor(public clientId: string, public clientSecret: string) {} } diff --git a/libs/common/src/models/request/identity-token/token.request.ts b/libs/common/src/models/request/identity-token/token.request.ts index 91007ce3424..32becd222a9 100644 --- a/libs/common/src/models/request/identity-token/token.request.ts +++ b/libs/common/src/models/request/identity-token/token.request.ts @@ -42,10 +42,12 @@ export abstract class TokenRequest { obj.authRequest = this.passwordlessAuthRequest; } - if (this.twoFactor.token && this.twoFactor.provider != null) { - obj.twoFactorToken = this.twoFactor.token; - obj.twoFactorProvider = this.twoFactor.provider; - obj.twoFactorRemember = this.twoFactor.remember ? "1" : "0"; + if (this.twoFactor) { + if (this.twoFactor.token && this.twoFactor.provider != null) { + obj.twoFactorToken = this.twoFactor.token; + obj.twoFactorProvider = this.twoFactor.provider; + obj.twoFactorRemember = this.twoFactor.remember ? "1" : "0"; + } } return obj; diff --git a/libs/common/src/models/request/identity-token/api-token.request.ts b/libs/common/src/models/request/identity-token/user-api-token.request.ts similarity index 91% rename from libs/common/src/models/request/identity-token/api-token.request.ts rename to libs/common/src/models/request/identity-token/user-api-token.request.ts index 60c8b6d3817..15a9bbfe681 100644 --- a/libs/common/src/models/request/identity-token/api-token.request.ts +++ b/libs/common/src/models/request/identity-token/user-api-token.request.ts @@ -3,7 +3,7 @@ import { DeviceRequest } from "../device.request"; import { TokenTwoFactorRequest } from "./token-two-factor.request"; import { TokenRequest } from "./token.request"; -export class ApiTokenRequest extends TokenRequest { +export class UserApiTokenRequest extends TokenRequest { constructor( public clientId: string, public clientSecret: string, diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 6c15d85c0d1..6b660bb367e 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -31,10 +31,10 @@ import { EmergencyAccessUpdateRequest } from "../models/request/emergency-access import { EventRequest } from "../models/request/event.request"; import { GroupRequest } from "../models/request/group.request"; import { IapCheckRequest } from "../models/request/iap-check.request"; -import { ApiTokenRequest } from "../models/request/identity-token/api-token.request"; import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request"; import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request"; import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request"; +import { UserApiTokenRequest } from "../models/request/identity-token/user-api-token.request"; import { ImportCiphersRequest } from "../models/request/import-ciphers.request"; import { ImportOrganizationCiphersRequest } from "../models/request/import-organization-ciphers.request"; import { KdfRequest } from "../models/request/kdf.request"; @@ -206,7 +206,7 @@ export class ApiService implements ApiServiceAbstraction { // Auth APIs async postIdentityToken( - request: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest + request: UserApiTokenRequest | PasswordTokenRequest | SsoTokenRequest ): Promise { const headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", @@ -219,7 +219,7 @@ export class ApiService implements ApiServiceAbstraction { request.alterIdentityTokenHeaders(headers); const identityToken = - request instanceof ApiTokenRequest + request instanceof UserApiTokenRequest ? request.toIdentityToken() : request.toIdentityToken(this.platformUtilsService.getClientType()); @@ -2271,8 +2271,7 @@ export class ApiService implements ApiServiceAbstraction { const appId = await this.appIdService.getAppId(); const deviceRequest = new DeviceRequest(appId, this.platformUtilsService); - - const tokenRequest = new ApiTokenRequest( + const tokenRequest = new UserApiTokenRequest( clientId, clientSecret, new TokenTwoFactorRequest(), diff --git a/libs/common/src/services/auth.service.ts b/libs/common/src/services/auth.service.ts index 4a851fb13df..4e97f701113 100644 --- a/libs/common/src/services/auth.service.ts +++ b/libs/common/src/services/auth.service.ts @@ -17,13 +17,13 @@ import { AuthenticationStatus } from "../enums/authenticationStatus"; import { AuthenticationType } from "../enums/authenticationType"; import { KdfType } from "../enums/kdfType"; import { KeySuffixOptions } from "../enums/keySuffixOptions"; -import { ApiLogInStrategy } from "../misc/logInStrategies/apiLogin.strategy"; import { PasswordLogInStrategy } from "../misc/logInStrategies/passwordLogin.strategy"; import { PasswordlessLogInStrategy } from "../misc/logInStrategies/passwordlessLogin.strategy"; import { SsoLogInStrategy } from "../misc/logInStrategies/ssoLogin.strategy"; +import { UserApiLogInStrategy } from "../misc/logInStrategies/user-api-login.strategy"; import { AuthResult } from "../models/domain/auth-result"; import { - ApiLogInCredentials, + UserApiLogInCredentials, PasswordLogInCredentials, SsoLogInCredentials, PasswordlessLogInCredentials, @@ -67,7 +67,7 @@ export class AuthService implements AuthServiceAbstraction { } private logInStrategy: - | ApiLogInStrategy + | UserApiLogInStrategy | PasswordLogInStrategy | SsoLogInStrategy | PasswordlessLogInStrategy; @@ -92,7 +92,7 @@ export class AuthService implements AuthServiceAbstraction { async logIn( credentials: - | ApiLogInCredentials + | UserApiLogInCredentials | PasswordLogInCredentials | SsoLogInCredentials | PasswordlessLogInCredentials @@ -100,7 +100,7 @@ export class AuthService implements AuthServiceAbstraction { this.clearState(); let strategy: - | ApiLogInStrategy + | UserApiLogInStrategy | PasswordLogInStrategy | SsoLogInStrategy | PasswordlessLogInStrategy; @@ -134,8 +134,8 @@ export class AuthService implements AuthServiceAbstraction { this.keyConnectorService ); break; - case AuthenticationType.Api: - strategy = new ApiLogInStrategy( + case AuthenticationType.UserApi: + strategy = new UserApiLogInStrategy( this.cryptoService, this.apiService, this.tokenService, @@ -203,8 +203,8 @@ export class AuthService implements AuthServiceAbstraction { this.messagingService.send("loggedOut"); } - authingWithApiKey(): boolean { - return this.logInStrategy instanceof ApiLogInStrategy; + authingWithUserApiKey(): boolean { + return this.logInStrategy instanceof UserApiLogInStrategy; } authingWithSso(): boolean { @@ -272,7 +272,7 @@ export class AuthService implements AuthServiceAbstraction { private saveState( strategy: - | ApiLogInStrategy + | UserApiLogInStrategy | PasswordLogInStrategy | SsoLogInStrategy | PasswordlessLogInStrategy diff --git a/libs/common/src/services/folder/folder.service.ts b/libs/common/src/services/folder/folder.service.ts index 4d8981c4eae..096d89cea9f 100644 --- a/libs/common/src/services/folder/folder.service.ts +++ b/libs/common/src/services/folder/folder.service.ts @@ -64,6 +64,20 @@ export class FolderService implements InternalFolderServiceAbstraction { return folders.find((folder) => folder.id === id); } + /** + * @deprecated For the CLI only + * @param id id of the folder + */ + async getFromState(id: string): Promise { + const foldersMap = await this.stateService.getEncryptedFolders(); + const folder = foldersMap[id]; + if (folder == null) { + return null; + } + + return new Folder(folder); + } + /** * @deprecated Only use in CLI! */ diff --git a/libs/common/src/services/organization/organization.service.ts b/libs/common/src/services/organization/organization.service.ts index 090e0143c66..b0d7791ec26 100644 --- a/libs/common/src/services/organization/organization.service.ts +++ b/libs/common/src/services/organization/organization.service.ts @@ -1,21 +1,16 @@ -import { BehaviorSubject, concatMap, filter } from "rxjs"; +import { BehaviorSubject, concatMap } from "rxjs"; -import { OrganizationService as OrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction"; +import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "../../abstractions/organization/organization.service.abstraction"; import { StateService } from "../../abstractions/state.service"; -import { SyncNotifierService } from "../../abstractions/sync/syncNotifier.service.abstraction"; import { OrganizationData } from "../../models/data/organization.data"; import { Organization } from "../../models/domain/organization"; -import { isSuccessfullyCompleted } from "../../types/syncEventArgs"; -export class OrganizationService implements OrganizationServiceAbstraction { +export class OrganizationService implements InternalOrganizationServiceAbstraction { protected _organizations = new BehaviorSubject([]); organizations$ = this._organizations.asObservable(); - constructor( - private stateService: StateService, - private syncNotifierService: SyncNotifierService - ) { + constructor(private stateService: StateService) { this.stateService.activeAccountUnlocked$ .pipe( concatMap(async (unlocked) => { @@ -29,28 +24,6 @@ export class OrganizationService implements OrganizationServiceAbstraction { }) ) .subscribe(); - - this.syncNotifierService.sync$ - .pipe( - filter(isSuccessfullyCompleted), - concatMap(async ({ data }) => { - const { profile } = data; - const organizations: { [id: string]: OrganizationData } = {}; - profile.organizations.forEach((o) => { - organizations[o.id] = new OrganizationData(o); - }); - - profile.providerOrganizations.forEach((o) => { - if (organizations[o.id] == null) { - organizations[o.id] = new OrganizationData(o); - organizations[o.id].isProviderUser = true; - } - }); - - await this.updateStateAndObservables(organizations); - }) - ) - .subscribe(); } async getAll(userId?: string): Promise { @@ -78,7 +51,7 @@ export class OrganizationService implements OrganizationServiceAbstraction { organizations[organization.id] = organization; - await this.updateStateAndObservables(organizations); + await this.replace(organizations); } async delete(id: string): Promise { @@ -92,7 +65,7 @@ export class OrganizationService implements OrganizationServiceAbstraction { } delete organizations[id]; - await this.updateStateAndObservables(organizations); + await this.replace(organizations); } get(id: string): Organization { @@ -121,9 +94,9 @@ export class OrganizationService implements OrganizationServiceAbstraction { return organizations.find((organization) => organization.identifier === identifier); } - private async updateStateAndObservables(organizationsMap: { [id: string]: OrganizationData }) { - await this.stateService.setOrganizations(organizationsMap); - this.updateObservables(organizationsMap); + async replace(organizations: { [id: string]: OrganizationData }) { + await this.stateService.setOrganizations(organizations); + this.updateObservables(organizations); } private updateObservables(organizationsMap: { [id: string]: OrganizationData }) { diff --git a/libs/common/src/services/sync/sync.service.ts b/libs/common/src/services/sync/sync.service.ts index 2513871636d..cd3f8e40ff6 100644 --- a/libs/common/src/services/sync/sync.service.ts +++ b/libs/common/src/services/sync/sync.service.ts @@ -7,17 +7,18 @@ import { InternalFolderService } from "../../abstractions/folder/folder.service. import { KeyConnectorService } from "../../abstractions/keyConnector.service"; import { LogService } from "../../abstractions/log.service"; import { MessagingService } from "../../abstractions/messaging.service"; +import { InternalOrganizationService } from "../../abstractions/organization/organization.service.abstraction"; import { InternalPolicyService } from "../../abstractions/policy/policy.service.abstraction"; import { ProviderService } from "../../abstractions/provider.service"; import { SendService } from "../../abstractions/send.service"; import { SettingsService } from "../../abstractions/settings.service"; import { StateService } from "../../abstractions/state.service"; import { SyncService as SyncServiceAbstraction } from "../../abstractions/sync/sync.service.abstraction"; -import { SyncNotifierService } from "../../abstractions/sync/syncNotifier.service.abstraction"; import { sequentialize } from "../../misc/sequentialize"; import { CipherData } from "../../models/data/cipher.data"; import { CollectionData } from "../../models/data/collection.data"; import { FolderData } from "../../models/data/folder.data"; +import { OrganizationData } from "../../models/data/organization.data"; import { PolicyData } from "../../models/data/policy.data"; import { ProviderData } from "../../models/data/provider.data"; import { SendData } from "../../models/data/send.data"; @@ -52,7 +53,7 @@ export class SyncService implements SyncServiceAbstraction { private stateService: StateService, private providerService: ProviderService, private folderApiService: FolderApiServiceAbstraction, - private syncNotifierService: SyncNotifierService, + private organizationService: InternalOrganizationService, private logoutCallback: (expired: boolean) => Promise ) {} @@ -76,10 +77,8 @@ export class SyncService implements SyncServiceAbstraction { @sequentialize(() => "fullSync") async fullSync(forceSync: boolean, allowThrowOnError = false): Promise { this.syncStarted(); - this.syncNotifierService.next({ status: "Started" }); const isAuthenticated = await this.stateService.getIsAuthenticated(); if (!isAuthenticated) { - this.syncNotifierService.next({ status: "Completed", successfully: false }); return this.syncCompleted(false); } @@ -95,7 +94,6 @@ export class SyncService implements SyncServiceAbstraction { if (!needsSync) { await this.setLastSync(now); - this.syncNotifierService.next({ status: "Completed", successfully: false }); return this.syncCompleted(false); } @@ -112,13 +110,11 @@ export class SyncService implements SyncServiceAbstraction { await this.syncPolicies(response.policies); await this.setLastSync(now); - this.syncNotifierService.next({ status: "Completed", successfully: true, data: response }); return this.syncCompleted(true); } catch (e) { if (allowThrowOnError) { throw e; } else { - this.syncNotifierService.next({ status: "Completed", successfully: false }); return this.syncCompleted(false); } } @@ -315,11 +311,24 @@ export class SyncService implements SyncServiceAbstraction { await this.stateService.setForcePasswordReset(response.forcePasswordReset); await this.keyConnectorService.setUsesKeyConnector(response.usesKeyConnector); + const organizations: { [id: string]: OrganizationData } = {}; + response.organizations.forEach((o) => { + organizations[o.id] = new OrganizationData(o); + }); + const providers: { [id: string]: ProviderData } = {}; response.providers.forEach((p) => { providers[p.id] = new ProviderData(p); }); + response.providerOrganizations.forEach((o) => { + if (organizations[o.id] == null) { + organizations[o.id] = new OrganizationData(o); + organizations[o.id].isProviderUser = true; + } + }); + + await this.organizationService.replace(organizations); await this.providerService.save(providers); if (await this.keyConnectorService.userNeedsMigration()) { diff --git a/libs/components/src/color-password/color-password.component.ts b/libs/components/src/color-password/color-password.component.ts new file mode 100644 index 00000000000..b55384dea1f --- /dev/null +++ b/libs/components/src/color-password/color-password.component.ts @@ -0,0 +1,79 @@ +import { Component, HostBinding, Input } from "@angular/core"; + +import { Utils } from "@bitwarden/common/misc/utils"; + +enum CharacterType { + Letter, + Emoji, + Special, + Number, +} + +@Component({ + selector: "bit-color-password", + template: `
+ {{ character }} + {{ + i + 1 + }} +
`, +}) +export class ColorPasswordComponent { + @Input() private password: string = null; + @Input() showCount = false; + + characterStyles: Record = { + [CharacterType.Emoji]: [], + [CharacterType.Letter]: ["tw-text-main"], + [CharacterType.Special]: ["tw-text-danger"], + [CharacterType.Number]: ["tw-text-primary-500"], + }; + + @HostBinding("class") + get classList() { + return ["tw-min-w-0", "tw-whitespace-pre-wrap", "tw-break-all"]; + } + + get passwordArray() { + // Convert to an array to handle cases that strings have special characters, i.e.: emoji. + return Array.from(this.password); + } + + getCharacterClass(character: string) { + const charType = this.getCharacterType(character); + const charClass = this.characterStyles[charType].concat("tw-inline-flex"); + + if (this.showCount) { + return charClass.concat([ + "tw-inline-flex", + "tw-flex-col", + "tw-items-center", + "tw-w-7", + "tw-py-1", + "odd:tw-bg-secondary-100", + ]); + } + + return charClass; + } + + private getCharacterType(character: string): CharacterType { + if (character.match(Utils.regexpEmojiPresentation)) { + return CharacterType.Emoji; + } + + if (character.match(/\d/)) { + return CharacterType.Number; + } + + const specials = ["&", "<", ">", " "]; + if (specials.includes(character) || character.match(/[^\w ]/)) { + return CharacterType.Special; + } + + return CharacterType.Letter; + } +} diff --git a/libs/components/src/color-password/color-password.module.ts b/libs/components/src/color-password/color-password.module.ts new file mode 100644 index 00000000000..692c206bb4c --- /dev/null +++ b/libs/components/src/color-password/color-password.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; + +import { ColorPasswordComponent } from "./color-password.component"; + +@NgModule({ + imports: [CommonModule], + exports: [ColorPasswordComponent], + declarations: [ColorPasswordComponent], +}) +export class ColorPasswordModule {} diff --git a/libs/components/src/color-password/color-password.stories.ts b/libs/components/src/color-password/color-password.stories.ts new file mode 100644 index 00000000000..87565b81273 --- /dev/null +++ b/libs/components/src/color-password/color-password.stories.ts @@ -0,0 +1,52 @@ +import { Meta, Story } from "@storybook/angular"; + +import { ColorPasswordComponent } from "./color-password.component"; + +const examplePassword = "Wq$Jk😀7jDX#rS5Sdi!z"; + +export default { + title: "Component Library/Color Password", + component: ColorPasswordComponent, + args: { + password: examplePassword, + showCount: false, + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/file/6fvTDa3zfvgWdizLQ7nSTP/Numbered-Password", + }, + }, +} as Meta; + +const Template: Story = (args: ColorPasswordComponent) => ({ + props: args, + template: ` + + `, +}); + +const WrappedTemplate: Story = (args: ColorPasswordComponent) => ({ + props: args, + template: ` +
+ +
+ `, +}); + +export const ColorPassword = Template.bind({}); + +export const WrappedColorPassword = WrappedTemplate.bind({}); + +export const ColorPasswordCount = Template.bind({}); +ColorPasswordCount.args = { + password: examplePassword, + showCount: true, +}; + +export const WrappedColorPasswordCount = WrappedTemplate.bind({}); +WrappedColorPasswordCount.args = { + password: examplePassword, + showCount: true, +}; diff --git a/libs/components/src/color-password/index.ts b/libs/components/src/color-password/index.ts new file mode 100644 index 00000000000..86718f037f7 --- /dev/null +++ b/libs/components/src/color-password/index.ts @@ -0,0 +1 @@ +export * from "./color-password.module"; diff --git a/libs/components/src/dialog/dialog.service.stories.ts b/libs/components/src/dialog/dialog.service.stories.ts index 942891ba061..678494cd9f8 100644 --- a/libs/components/src/dialog/dialog.service.stories.ts +++ b/libs/components/src/dialog/dialog.service.stories.ts @@ -1,10 +1,12 @@ -import { DialogModule, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { DIALOG_DATA, DialogModule, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; import { Meta, moduleMetadata, Story } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { ButtonModule } from "../button"; +import { IconButtonModule } from "../icon-button"; +import { SharedModule } from "../shared"; import { I18nMockService } from "../utils/i18n-mock.service"; import { DialogService } from "./dialog.service"; @@ -35,7 +37,7 @@ class StoryDialogComponent { @Component({ selector: "story-dialog-content", template: ` - + Dialog Title Dialog body text goes here. @@ -68,7 +70,7 @@ export default { DialogTitleContainerDirective, StoryDialogContentComponent, ], - imports: [ButtonModule, DialogModule], + imports: [SharedModule, ButtonModule, DialogModule, IconButtonModule], providers: [ DialogService, { diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index e217c6bf026..8abb6bade3d 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -14,4 +14,5 @@ export * from "./multi-select"; export * from "./tabs"; export * from "./table"; export * from "./toggle-group"; +export * from "./color-password"; export * from "./utils/i18n-mock.service"; diff --git a/libs/components/src/link/link.directive.ts b/libs/components/src/link/link.directive.ts index d4ae14058ec..0d80fb27f18 100644 --- a/libs/components/src/link/link.directive.ts +++ b/libs/components/src/link/link.directive.ts @@ -6,49 +6,85 @@ const linkStyles: Record = { primary: [ "!tw-text-primary-500", "hover:!tw-text-primary-500", - "focus-visible:tw-ring-primary-700", + "focus-visible:before:tw-ring-primary-700", "disabled:!tw-text-primary-500/60", ], secondary: [ "!tw-text-main", "hover:!tw-text-main", - "focus-visible:tw-ring-primary-700", + "focus-visible:before:tw-ring-primary-700", "disabled:!tw-text-muted/60", ], contrast: [ "!tw-text-contrast", "hover:!tw-text-contrast", - "focus-visible:tw-ring-text-contrast", + "focus-visible:before:tw-ring-text-contrast", "disabled:!tw-text-contrast/60", ], }; -@Directive({ - selector: "button[bitLink], a[bitLink]", -}) -export class LinkDirective { - @HostBinding("class") get classList() { - return [ - "tw-font-semibold", - "tw-py-0.5", - "tw-px-0", - "tw-bg-transparent", - "tw-border-0", - "tw-border-none", - "tw-rounded", - "tw-transition", - "hover:tw-underline", - "hover:tw-decoration-1", - "focus-visible:tw-outline-none", - "focus-visible:tw-underline", - "focus-visible:tw-decoration-1", - "focus-visible:tw-ring-2", - "focus-visible:tw-z-10", - "disabled:tw-no-underline", - "disabled:tw-cursor-not-allowed", - ].concat(linkStyles[this.linkType] ?? []); - } +const commonStyles = [ + "tw-leading-none", + "tw-p-0", + "tw-font-semibold", + "tw-bg-transparent", + "tw-border-0", + "tw-border-none", + "tw-rounded", + "tw-transition", + "hover:tw-underline", + "hover:tw-decoration-1", + "disabled:tw-no-underline", + "disabled:tw-cursor-not-allowed", + "focus-visible:tw-outline-none", + "focus-visible:tw-underline", + "focus-visible:tw-decoration-1", + // Workaround for html button tag not being able to be set to `display: inline` + // and at the same time not being able to use `tw-ring-offset` because of box-shadow issue. + // https://github.com/w3c/csswg-drafts/issues/3226 + // Add `tw-inline`, add `tw-py-0.5` and use regular `tw-ring` if issue is fixed. + // + // https://github.com/tailwindlabs/tailwindcss/issues/3595 + // Remove `before:` and use regular `tw-ring` when browser no longer has bug, or better: + // switch to `outline` with `outline-offset` when Safari supports border radius on outline. + // Using `box-shadow` to create outlines is a hack and as such `outline` should be preferred. + "tw-relative", + "before:tw-content-['']", + "before:tw-block", + "before:tw-absolute", + "before:-tw-inset-x-[0.1em]", + "before:tw-rounded-md", + "before:tw-transition", + "before:tw-ring-2", + "focus-visible:before:tw-ring-text-contrast", + "focus-visible:tw-z-10", +]; + +@Directive() +abstract class LinkDirective { @Input() linkType: LinkType = "primary"; } + +@Directive({ + selector: "a[bitLink]", +}) +export class AnchorLinkDirective extends LinkDirective { + @HostBinding("class") get classList() { + return ["before:-tw-inset-y-[0.125rem]"] + .concat(commonStyles) + .concat(linkStyles[this.linkType] ?? []); + } +} + +@Directive({ + selector: "button[bitLink]", +}) +export class ButtonLinkDirective extends LinkDirective { + @HostBinding("class") get classList() { + return ["before:-tw-inset-y-[0.25rem]"] + .concat(commonStyles) + .concat(linkStyles[this.linkType] ?? []); + } +} diff --git a/libs/components/src/link/link.module.ts b/libs/components/src/link/link.module.ts index 73e78edf785..b8b54d57c00 100644 --- a/libs/components/src/link/link.module.ts +++ b/libs/components/src/link/link.module.ts @@ -1,11 +1,11 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { LinkDirective } from "./link.directive"; +import { AnchorLinkDirective, ButtonLinkDirective } from "./link.directive"; @NgModule({ imports: [CommonModule], - exports: [LinkDirective], - declarations: [LinkDirective], + exports: [AnchorLinkDirective, ButtonLinkDirective], + declarations: [AnchorLinkDirective, ButtonLinkDirective], }) export class LinkModule {} diff --git a/libs/components/src/link/link.stories.ts b/libs/components/src/link/link.stories.ts index 5c7b11fe352..68567e2f755 100644 --- a/libs/components/src/link/link.stories.ts +++ b/libs/components/src/link/link.stories.ts @@ -1,10 +1,15 @@ -import { Meta, Story } from "@storybook/angular"; +import { Meta, moduleMetadata, Story } from "@storybook/angular"; -import { LinkDirective } from "./link.directive"; +import { AnchorLinkDirective, ButtonLinkDirective } from "./link.directive"; +import { LinkModule } from "./link.module"; export default { title: "Component Library/Link", - component: LinkDirective, + decorators: [ + moduleMetadata({ + imports: [LinkModule], + }), + ], argTypes: { linkType: { options: ["primary", "secondary", "contrast"], @@ -19,25 +24,33 @@ export default { }, } as Meta; -const ButtonTemplate: Story = (args: LinkDirective) => ({ +const ButtonTemplate: Story = (args: ButtonLinkDirective) => ({ props: args, template: `
- - - - +
+ +
+
+ +
+
+ +
+
+ +
`, }); -const AnchorTemplate: Story = (args: LinkDirective) => ({ +const AnchorTemplate: Story = (args: AnchorLinkDirective) => ({ props: args, template: `
@@ -73,6 +86,20 @@ Anchors.args = { linkType: "primary", }; +const InlineTemplate: Story = (args) => ({ + props: args, + template: ` + + On the internet pargraphs often contain inline links, but few know that can be used for similar purposes. + + `, +}); + +export const Inline = InlineTemplate.bind({}); +Inline.args = { + linkType: "primary", +}; + const DisabledTemplate: Story = (args) => ({ props: args, template: ` diff --git a/libs/components/src/tabs/tab-group/tab-group.component.html b/libs/components/src/tabs/tab-group/tab-group.component.html index dbe71fb4b99..071f5c2259f 100644 --- a/libs/components/src/tabs/tab-group/tab-group.component.html +++ b/libs/components/src/tabs/tab-group/tab-group.component.html @@ -34,7 +34,7 @@ role="tabpanel" *ngFor="let tab of tabs; let i = index" [id]="getTabContentId(i)" - [attr.tabindex]="selectedIndex === i ? 0 : -1" + [attr.tabindex]="tab.contentTabIndex" [attr.labeledby]="getTabLabelId(i)" [active]="tab.isActive" [content]="tab.content" diff --git a/libs/components/src/tabs/tab-group/tab.component.ts b/libs/components/src/tabs/tab-group/tab.component.ts index 05e61ffacc2..9a3a9380031 100644 --- a/libs/components/src/tabs/tab-group/tab.component.ts +++ b/libs/components/src/tabs/tab-group/tab.component.ts @@ -20,9 +20,18 @@ import { TabLabelDirective } from "./tab-label.directive"; }) export class TabComponent implements OnInit { @Input() disabled = false; - @Input("label") textLabel = ""; + /** + * Optional tabIndex for the tabPanel that contains this tab's content. + * + * If the tabpanel does not contain any focusable elements or the first element with content is not focusable, + * this should be set to 0 to include it in the tab sequence of the page. + * + * @remarks See note 4 of https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/ + */ + @Input() contentTabIndex: number | undefined; + @ViewChild(TemplateRef, { static: true }) implicitContent: TemplateRef; @ContentChild(TabLabelDirective) templateLabel: TabLabelDirective; diff --git a/libs/components/src/tabs/tabs.stories.ts b/libs/components/src/tabs/tabs.stories.ts index a520b50e17a..32c7e950e73 100644 --- a/libs/components/src/tabs/tabs.stories.ts +++ b/libs/components/src/tabs/tabs.stories.ts @@ -3,6 +3,9 @@ import { Component } from "@angular/core"; import { RouterModule } from "@angular/router"; import { Meta, moduleMetadata, Story } from "@storybook/angular"; +import { ButtonModule } from "../button"; +import { FormFieldModule } from "../form-field"; + import { TabGroupComponent } from "./tab-group/tab-group.component"; import { TabsModule } from "./tabs.module"; @@ -44,6 +47,8 @@ export default { imports: [ CommonModule, TabsModule, + ButtonModule, + FormFieldModule, RouterModule.forRoot( [ { path: "", redirectTo: "active", pathMatch: "full" }, @@ -125,3 +130,32 @@ const PreserveContentTabGroupTemplate: Story = (args: any) => }); export const PreserveContentTabs = PreserveContentTabGroupTemplate.bind({}); + +const KeyboardNavTabGroupTemplate: Story = (args: any) => ({ + props: args, + template: ` + + +

+ You can navigate through all tab labels, form inputs, and the button that is outside the tab group via + the keyboard. +

+ + First Input + + + + Second Input + + +
+ + +

This tab has no focusable content, but the panel should still be focusable

+
+
+ +`, +}); + +export const KeyboardNavigation = KeyboardNavTabGroupTemplate.bind({}); diff --git a/libs/node/spec/services/nodeCryptoFunction.service.spec.ts b/libs/node/spec/services/node-crypto-function.service.spec.ts similarity index 99% rename from libs/node/spec/services/nodeCryptoFunction.service.spec.ts rename to libs/node/spec/services/node-crypto-function.service.spec.ts index d0097c29304..1dbdcfb93ae 100644 --- a/libs/node/spec/services/nodeCryptoFunction.service.spec.ts +++ b/libs/node/spec/services/node-crypto-function.service.spec.ts @@ -1,6 +1,6 @@ import { Utils } from "@bitwarden/common/misc/utils"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; -import { NodeCryptoFunctionService } from "@bitwarden/node/services/nodeCryptoFunction.service"; +import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; const RsaPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" + diff --git a/libs/node/src/cli/baseProgram.ts b/libs/node/src/cli/baseProgram.ts deleted file mode 100644 index 46e9db3ec98..00000000000 --- a/libs/node/src/cli/baseProgram.ts +++ /dev/null @@ -1,113 +0,0 @@ -import * as chalk from "chalk"; - -import { StateService } from "@bitwarden/common/abstractions/state.service"; - -import { Response } from "./models/response"; -import { ListResponse } from "./models/response/listResponse"; -import { MessageResponse } from "./models/response/messageResponse"; -import { StringResponse } from "./models/response/stringResponse"; - -export abstract class BaseProgram { - constructor( - protected stateService: StateService, - private writeLn: (s: string, finalLine: boolean, error: boolean) => void - ) {} - - protected processResponse( - response: Response, - exitImmediately = false, - dataProcessor: () => string = null - ) { - if (!response.success) { - if (process.env.BW_QUIET !== "true") { - if (process.env.BW_RESPONSE === "true") { - this.writeLn(this.getJson(response), true, false); - } else { - this.writeLn(chalk.redBright(response.message), true, true); - } - } - const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; - if (exitImmediately) { - process.exit(exitCode); - } else { - process.exitCode = exitCode; - } - return; - } - - if (process.env.BW_RESPONSE === "true") { - this.writeLn(this.getJson(response), true, false); - } else if (response.data != null) { - let out: string = dataProcessor != null ? dataProcessor() : null; - if (out == null) { - if (response.data.object === "string") { - const data = (response.data as StringResponse).data; - if (data != null) { - out = data; - } - } else if (response.data.object === "list") { - out = this.getJson((response.data as ListResponse).data); - } else if (response.data.object === "message") { - out = this.getMessage(response); - } else { - out = this.getJson(response.data); - } - } - - if (out != null && process.env.BW_QUIET !== "true") { - this.writeLn(out, true, false); - } - } - if (exitImmediately) { - process.exit(0); - } else { - process.exitCode = 0; - } - } - - protected getJson(obj: any): string { - if (process.env.BW_PRETTY === "true") { - return JSON.stringify(obj, null, " "); - } else { - return JSON.stringify(obj); - } - } - - protected getMessage(response: Response): string { - const message = response.data as MessageResponse; - if (process.env.BW_RAW === "true") { - return message.raw; - } - - let out = ""; - if (message.title != null) { - if (message.noColor) { - out = message.title; - } else { - out = chalk.greenBright(message.title); - } - } - if (message.message != null) { - if (message.title != null) { - out += "\n"; - } - out += message.message; - } - return out.trim() === "" ? null : out; - } - - protected async exitIfAuthed() { - const authed = await this.stateService.getIsAuthenticated(); - if (authed) { - const email = await this.stateService.getEmail(); - this.processResponse(Response.error("You are already logged in as " + email + "."), true); - } - } - - protected async exitIfNotAuthed() { - const authed = await this.stateService.getIsAuthenticated(); - if (!authed) { - this.processResponse(Response.error("You are not logged in."), true); - } - } -} diff --git a/libs/node/src/cli/commands/login.command.ts b/libs/node/src/cli/commands/login.command.ts deleted file mode 100644 index dfff7b9ad20..00000000000 --- a/libs/node/src/cli/commands/login.command.ts +++ /dev/null @@ -1,627 +0,0 @@ -import * as http from "http"; - -import * as program from "commander"; -import * as inquirer from "inquirer"; -import Separator from "inquirer/lib/objects/separator"; -import { firstValueFrom } from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AuthService } from "@bitwarden/common/abstractions/auth.service"; -import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; -import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; -import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; -import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; -import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service"; -import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; -import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; -import { Utils } from "@bitwarden/common/misc/utils"; -import { AuthResult } from "@bitwarden/common/models/domain/auth-result"; -import { - ApiLogInCredentials, - PasswordLogInCredentials, - SsoLogInCredentials, -} from "@bitwarden/common/models/domain/log-in-credentials"; -import { TokenTwoFactorRequest } from "@bitwarden/common/models/request/identity-token/token-two-factor.request"; -import { TwoFactorEmailRequest } from "@bitwarden/common/models/request/two-factor-email.request"; -import { UpdateTempPasswordRequest } from "@bitwarden/common/models/request/update-temp-password.request"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; - -import { Response } from "../models/response"; -import { MessageResponse } from "../models/response/messageResponse"; - -export class LoginCommand { - protected validatedParams: () => Promise; - protected success: () => Promise; - protected logout: () => Promise; - protected canInteract: boolean; - protected clientId: string; - protected clientSecret: string; - protected email: string; - - private ssoRedirectUri: string = null; - - constructor( - protected authService: AuthService, - protected apiService: ApiService, - protected i18nService: I18nService, - protected environmentService: EnvironmentService, - protected passwordGenerationService: PasswordGenerationService, - protected cryptoFunctionService: CryptoFunctionService, - protected platformUtilsService: PlatformUtilsService, - protected stateService: StateService, - protected cryptoService: CryptoService, - protected policyService: PolicyService, - protected twoFactorService: TwoFactorService, - clientId: string - ) { - this.clientId = clientId; - } - - async run(email: string, password: string, options: program.OptionValues) { - this.canInteract = process.env.BW_NOINTERACTION !== "true"; - - let ssoCodeVerifier: string = null; - let ssoCode: string = null; - let orgIdentifier: string = null; - - let clientId: string = null; - let clientSecret: string = null; - - let selectedProvider: any = null; - - if (options.apikey != null) { - const apiIdentifiers = await this.apiIdentifiers(); - clientId = apiIdentifiers.clientId; - clientSecret = apiIdentifiers.clientSecret; - } else if (options.sso != null && this.canInteract) { - const passwordOptions: any = { - type: "password", - length: 64, - uppercase: true, - lowercase: true, - numbers: true, - special: false, - }; - const state = await this.passwordGenerationService.generatePassword(passwordOptions); - ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); - const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); - const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); - try { - const ssoParams = await this.openSsoPrompt(codeChallenge, state); - ssoCode = ssoParams.ssoCode; - orgIdentifier = ssoParams.orgIdentifier; - } catch { - return Response.badRequest("Something went wrong. Try again."); - } - } else { - if ((email == null || email === "") && this.canInteract) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "input", - name: "email", - message: "Email address:", - }); - email = answer.email; - } - if (email == null || email.trim() === "") { - return Response.badRequest("Email address is required."); - } - if (email.indexOf("@") === -1) { - return Response.badRequest("Email address is invalid."); - } - this.email = email; - - if (password == null || password === "") { - if (options.passwordfile) { - password = await NodeUtils.readFirstLine(options.passwordfile); - } else if (options.passwordenv && process.env[options.passwordenv]) { - password = process.env[options.passwordenv]; - } else if (this.canInteract) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "password", - name: "password", - message: "Master password:", - }); - password = answer.password; - } - } - - if (password == null || password === "") { - return Response.badRequest("Master password is required."); - } - } - - let twoFactorToken: string = options.code; - let twoFactorMethod: TwoFactorProviderType = null; - try { - if (options.method != null) { - twoFactorMethod = parseInt(options.method, null); - } - } catch (e) { - return Response.error("Invalid two-step login method."); - } - - const twoFactor = - twoFactorToken == null - ? null - : new TokenTwoFactorRequest(twoFactorMethod, twoFactorToken, false); - - try { - if (this.validatedParams != null) { - await this.validatedParams(); - } - - let response: AuthResult = null; - if (clientId != null && clientSecret != null) { - response = await this.authService.logIn(new ApiLogInCredentials(clientId, clientSecret)); - } else if (ssoCode != null && ssoCodeVerifier != null) { - response = await this.authService.logIn( - new SsoLogInCredentials( - ssoCode, - ssoCodeVerifier, - this.ssoRedirectUri, - orgIdentifier, - twoFactor - ) - ); - } else { - response = await this.authService.logIn( - new PasswordLogInCredentials(email, password, null, twoFactor) - ); - } - if (response.captchaSiteKey) { - const credentials = new PasswordLogInCredentials(email, password); - const handledResponse = await this.handleCaptchaRequired(twoFactor, credentials); - - // Error Response - if (handledResponse instanceof Response) { - return handledResponse; - } else { - response = handledResponse; - } - } - if (response.requiresTwoFactor) { - const twoFactorProviders = this.twoFactorService.getSupportedProviders(null); - if (twoFactorProviders.length === 0) { - return Response.badRequest("No providers available for this client."); - } - - if (twoFactorMethod != null) { - try { - selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0]; - } catch (e) { - return Response.error("Invalid two-step login method."); - } - } - - if (selectedProvider == null) { - if (twoFactorProviders.length === 1) { - selectedProvider = twoFactorProviders[0]; - } else if (this.canInteract) { - const twoFactorOptions: (string | Separator)[] = twoFactorProviders.map((p) => p.name); - twoFactorOptions.push(new inquirer.Separator()); - twoFactorOptions.push("Cancel"); - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "list", - name: "method", - message: "Two-step login method:", - choices: twoFactorOptions, - }); - const i = twoFactorOptions.indexOf(answer.method); - if (i === twoFactorOptions.length - 1) { - return Response.error("Login failed."); - } - selectedProvider = twoFactorProviders[i]; - } - if (selectedProvider == null) { - return Response.error("Login failed. No provider selected."); - } - } - - if ( - twoFactorToken == null && - response.twoFactorProviders.size > 1 && - selectedProvider.type === TwoFactorProviderType.Email - ) { - const emailReq = new TwoFactorEmailRequest(); - emailReq.email = this.authService.email; - emailReq.masterPasswordHash = this.authService.masterPasswordHash; - await this.apiService.postTwoFactorEmail(emailReq); - } - - if (twoFactorToken == null) { - if (this.canInteract) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "input", - name: "token", - message: "Two-step login code:", - }); - twoFactorToken = answer.token; - } - if (twoFactorToken == null || twoFactorToken === "") { - return Response.badRequest("Code is required."); - } - } - - response = await this.authService.logInTwoFactor( - new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken), - null - ); - } - - if (response.captchaSiteKey) { - const twoFactorRequest = new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken); - const handledResponse = await this.handleCaptchaRequired(twoFactorRequest); - - // Error Response - if (handledResponse instanceof Response) { - return handledResponse; - } else { - response = handledResponse; - } - } - - if (response.requiresTwoFactor) { - return Response.error("Login failed."); - } - - if (response.resetMasterPassword) { - return Response.error( - "In order to log in with SSO from the CLI, you must first log in" + - " through the web vault to set your master password." - ); - } - - // Handle Updating Temp Password if NOT using an API Key for authentication - if (response.forcePasswordReset && clientId == null && clientSecret == null) { - return await this.updateTempPassword(); - } - - return await this.handleSuccessResponse(); - } catch (e) { - return Response.error(e); - } - } - - private async handleSuccessResponse(): Promise { - if (this.success != null) { - const res = await this.success(); - return Response.success(res); - } else { - const res = new MessageResponse("You are logged in!", null); - return Response.success(res); - } - } - - private async updateTempPassword(error?: string): Promise { - // If no interaction available, alert user to use web vault - if (!this.canInteract) { - await this.logout(); - this.authService.logOut(() => { - /* Do nothing */ - }); - return Response.error( - new MessageResponse( - "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now via the web vault. You have been logged out.", - null - ) - ); - } - - if (this.email == null || this.email === "undefined") { - this.email = await this.stateService.getEmail(); - } - - // Get New Master Password - const baseMessage = - "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now.\n" + - "Master password: "; - const firstMessage = error != null ? error + baseMessage : baseMessage; - const mp: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ - type: "password", - name: "password", - message: firstMessage, - }); - const masterPassword = mp.password; - - // Master Password Validation - if (masterPassword == null || masterPassword === "") { - return this.updateTempPassword("Master password is required.\n"); - } - - if (masterPassword.length < 8) { - return this.updateTempPassword("Master password must be at least 8 characters long.\n"); - } - - // Strength & Policy Validation - const strengthResult = this.passwordGenerationService.passwordStrength( - masterPassword, - this.getPasswordStrengthUserInput() - ); - - // Get New Master Password Re-type - const reTypeMessage = "Re-type New Master password (Strength: " + strengthResult.score + ")"; - const retype: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ - type: "password", - name: "password", - message: reTypeMessage, - }); - const masterPasswordRetype = retype.password; - - // Re-type Validation - if (masterPassword !== masterPasswordRetype) { - return this.updateTempPassword("Master password confirmation does not match.\n"); - } - - // Get Hint (optional) - const hint: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ - type: "input", - name: "input", - message: "Master Password Hint (optional):", - }); - const masterPasswordHint = hint.input; - - // Retrieve details for key generation - const enforcedPolicyOptions = await firstValueFrom( - this.policyService.masterPasswordPolicyOptions$() - ); - const kdf = await this.stateService.getKdfType(); - const kdfIterations = await this.stateService.getKdfIterations(); - - if ( - enforcedPolicyOptions != null && - !this.policyService.evaluateMasterPassword( - strengthResult.score, - masterPassword, - enforcedPolicyOptions - ) - ) { - return this.updateTempPassword( - "Your new master password does not meet the policy requirements.\n" - ); - } - - try { - // Create new key and hash new password - const newKey = await this.cryptoService.makeKey( - masterPassword, - this.email.trim().toLowerCase(), - kdf, - kdfIterations - ); - const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey); - - // Grab user's current enc key - const userEncKey = await this.cryptoService.getEncKey(); - - // Create new encKey for the User - const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey); - - // Create request - const request = new UpdateTempPasswordRequest(); - request.key = newEncKey[1].encryptedString; - request.newMasterPasswordHash = newPasswordHash; - request.masterPasswordHint = masterPasswordHint; - - // Update user's password - await this.apiService.putUpdateTempPassword(request); - return this.handleSuccessResponse(); - } catch (e) { - await this.logout(); - this.authService.logOut(() => { - /* Do nothing */ - }); - return Response.error(e); - } - } - - private async handleCaptchaRequired( - twoFactorRequest: TokenTwoFactorRequest, - credentials: PasswordLogInCredentials = null - ): Promise { - const badCaptcha = Response.badRequest( - "Your authentication request has been flagged and will require user interaction to proceed.\n" + - "Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n" + - "(https://bitwarden.com/help/cli-auth-challenges)" - ); - - try { - const captchaClientSecret = await this.apiClientSecret(true); - if (Utils.isNullOrWhitespace(captchaClientSecret)) { - return badCaptcha; - } - - let authResultResponse: AuthResult = null; - if (credentials != null) { - credentials.captchaToken = captchaClientSecret; - credentials.twoFactor = twoFactorRequest; - authResultResponse = await this.authService.logIn(credentials); - } else { - authResultResponse = await this.authService.logInTwoFactor( - twoFactorRequest, - captchaClientSecret - ); - } - - return authResultResponse; - } catch (e) { - if ( - e instanceof ErrorResponse || - (e.constructor.name === ErrorResponse.name && - (e as ErrorResponse).message.includes("Captcha is invalid")) - ) { - return badCaptcha; - } else { - return Response.error(e); - } - } - } - - private getPasswordStrengthUserInput() { - let userInput: string[] = []; - const atPosition = this.email.indexOf("@"); - if (atPosition > -1) { - userInput = userInput.concat( - this.email - .substr(0, atPosition) - .trim() - .toLowerCase() - .split(/[^A-Za-z0-9]/) - ); - } - return userInput; - } - - private async apiClientId(): Promise { - let clientId: string = null; - - const storedClientId: string = process.env.BW_CLIENTID; - if (storedClientId == null) { - if (this.canInteract) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "input", - name: "clientId", - message: "client_id:", - }); - clientId = answer.clientId; - } else { - clientId = null; - } - } else { - clientId = storedClientId; - } - - return clientId; - } - - private async apiClientSecret(isAdditionalAuthentication = false): Promise { - const additionalAuthenticationMessage = "Additional authentication required.\nAPI key "; - let clientSecret: string = null; - - const storedClientSecret: string = this.clientSecret || process.env.BW_CLIENTSECRET; - if (this.canInteract && storedClientSecret == null) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "input", - name: "clientSecret", - message: - (isAdditionalAuthentication ? additionalAuthenticationMessage : "") + "client_secret:", - }); - clientSecret = answer.clientSecret; - } else { - clientSecret = storedClientSecret; - } - - return clientSecret; - } - - private async apiIdentifiers(): Promise<{ clientId: string; clientSecret: string }> { - return { - clientId: await this.apiClientId(), - clientSecret: await this.apiClientSecret(), - }; - } - - private async openSsoPrompt( - codeChallenge: string, - state: string - ): Promise<{ ssoCode: string; orgIdentifier: string }> { - return new Promise((resolve, reject) => { - const callbackServer = http.createServer((req, res) => { - const urlString = "http://localhost" + req.url; - const url = new URL(urlString); - const code = url.searchParams.get("code"); - const receivedState = url.searchParams.get("state"); - const orgIdentifier = this.getOrgIdentifierFromState(receivedState); - res.setHeader("Content-Type", "text/html"); - if (code != null && receivedState != null && this.checkState(receivedState, state)) { - res.writeHead(200); - res.end( - "Success | Bitwarden CLI" + - "

Successfully authenticated with the Bitwarden CLI

" + - "

You may now close this tab and return to the terminal.

" + - "" - ); - callbackServer.close(() => - resolve({ - ssoCode: code, - orgIdentifier: orgIdentifier, - }) - ); - } else { - res.writeHead(400); - res.end( - "Failed | Bitwarden CLI" + - "

Something went wrong logging into the Bitwarden CLI

" + - "

You may now close this tab and return to the terminal.

" + - "" - ); - callbackServer.close(() => reject()); - } - }); - let foundPort = false; - const webUrl = this.environmentService.getWebVaultUrl(); - for (let port = 8065; port <= 8070; port++) { - try { - this.ssoRedirectUri = "http://localhost:" + port; - callbackServer.listen(port, () => { - this.platformUtilsService.launchUri( - webUrl + - "/#/sso?clientId=" + - this.clientId + - "&redirectUri=" + - encodeURIComponent(this.ssoRedirectUri) + - "&state=" + - state + - "&codeChallenge=" + - codeChallenge - ); - }); - foundPort = true; - break; - } catch { - // Ignore error since we run the same command up to 5 times. - } - } - if (!foundPort) { - reject(); - } - }); - } - - private getOrgIdentifierFromState(state: string): string { - if (state === null || state === undefined) { - return null; - } - - const stateSplit = state.split("_identifier="); - return stateSplit.length > 1 ? stateSplit[1] : null; - } - - private checkState(state: string, checkState: string): boolean { - if (state === null || state === undefined) { - return false; - } - if (checkState === null || checkState === undefined) { - return false; - } - - const stateSplit = state.split("_identifier="); - const checkStateSplit = checkState.split("_identifier="); - return stateSplit[0] === checkStateSplit[0]; - } -} diff --git a/libs/node/src/services/nodeCryptoFunction.service.ts b/libs/node/src/services/node-crypto-function.service.ts similarity index 100% rename from libs/node/src/services/nodeCryptoFunction.service.ts rename to libs/node/src/services/node-crypto-function.service.ts