Apply Prettier (#2238)

This commit is contained in:
Oscar Hinton 2021-12-21 15:43:35 +01:00 committed by GitHub
parent cebee8aa81
commit 8fe821b9a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
174 changed files with 17599 additions and 14766 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

View File

@ -6,7 +6,7 @@ body:
attributes:
value: |
Thanks for taking the time to fill out this bug report!
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
- type: textarea
id: reproduce

View File

@ -1,4 +1,5 @@
## Type of change
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
@ -6,27 +7,26 @@
- [ ] Other
## Objective
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
## Code changes
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
<!--Also refer to any related changes or PRs in other repositories-->
* **file.ext:** Description of what was changed and why
- **file.ext:** Description of what was changed and why
## Screenshots
<!--Required for any UI changes. Delete if not applicable-->
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
- [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team)

View File

@ -104,8 +104,8 @@ jobs:
npm run dist
npm run test
# - name: Run linter
# run: npm run lint
- name: Run linter
run: npm run lint
- name: Gulp
run: gulp ci

View File

@ -6,17 +6,12 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera
Here is how you can get involved:
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
* **Translate:** See the localization (i10n) section below
- **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
- **Report a bug or submit a bugfix:** Use Github issues and pull requests
- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
- **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
- **Translate:** See the localization (i10n) section below
## Contributor Agreement
@ -24,9 +19,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/brows
## Pull Request Guidelines
* use `npm run lint` and fix any linting suggestions before submitting a pull request
* commit any pull requests against the `master` branch
* include a link to your Community Forums post
- use `npm run lint` and fix any linting suggestions before submitting a pull request
- commit any pull requests against the `master` branch
- include a link to your Community Forums post
# Localization (l10n)

View File

@ -7,7 +7,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
third-party. We may publicly disclose the issue before resolving it, if appropriate.
third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.

View File

@ -1,223 +1,242 @@
const gulp = require('gulp'),
gulpif = require('gulp-if'),
filter = require('gulp-filter'),
replace = require('gulp-replace'),
jeditor = require("gulp-json-editor"),
child = require('child_process'),
zip = require('gulp-zip'),
manifest = require('./src/manifest.json'),
del = require('del'),
fs = require('fs');
const gulp = require("gulp"),
gulpif = require("gulp-if"),
filter = require("gulp-filter"),
replace = require("gulp-replace"),
jeditor = require("gulp-json-editor"),
child = require("child_process"),
zip = require("gulp-zip"),
manifest = require("./src/manifest.json"),
del = require("del"),
fs = require("fs");
const paths = {
build: './build/',
dist: './dist/',
coverage: './coverage/',
node_modules: './node_modules/',
popupDir: './src/popup/',
cssDir: './src/popup/css/',
safari: './src/safari/'
build: "./build/",
dist: "./dist/",
coverage: "./coverage/",
node_modules: "./node_modules/",
popupDir: "./src/popup/",
cssDir: "./src/popup/css/",
safari: "./src/safari/",
};
const filters = {
fonts: [
'!build/popup/fonts/*',
'build/popup/fonts/Open_Sans*.woff',
'build/popup/fonts/fontawesome*.woff2',
'build/popup/fonts/fontawesome*.woff'
],
safari: [
'!build/safari/**/*'
],
fonts: [
"!build/popup/fonts/*",
"build/popup/fonts/Open_Sans*.woff",
"build/popup/fonts/fontawesome*.woff2",
"build/popup/fonts/fontawesome*.woff",
],
safari: ["!build/safari/**/*"],
};
function buildString() {
var build = '';
if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== '') {
build = `-${process.env.BUILD_NUMBER}`;
}
return build;
var build = "";
if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== "") {
build = `-${process.env.BUILD_NUMBER}`;
}
return build;
}
function distFileName(browserName, ext) {
return `dist-${browserName}${buildString()}.${ext}`;
return `dist-${browserName}${buildString()}.${ext}`;
}
function dist(browserName, manifest) {
return gulp.src(paths.build + '**/*')
.pipe(filter(['**'].concat(filters.fonts).concat(filters.safari)))
.pipe(gulpif('popup/index.html', replace('__BROWSER__', 'browser_' + browserName)))
.pipe(gulpif('manifest.json', jeditor(manifest)))
.pipe(zip(distFileName(browserName, 'zip')))
.pipe(gulp.dest(paths.dist));
return gulp
.src(paths.build + "**/*")
.pipe(filter(["**"].concat(filters.fonts).concat(filters.safari)))
.pipe(gulpif("popup/index.html", replace("__BROWSER__", "browser_" + browserName)))
.pipe(gulpif("manifest.json", jeditor(manifest)))
.pipe(zip(distFileName(browserName, "zip")))
.pipe(gulp.dest(paths.dist));
}
function distFirefox() {
return dist('firefox', (manifest) => {
delete manifest.content_security_policy;
removeShortcuts(manifest);
return manifest;
});
return dist("firefox", (manifest) => {
delete manifest.content_security_policy;
removeShortcuts(manifest);
return manifest;
});
}
function distOpera() {
return dist('opera', (manifest) => {
delete manifest.applications;
delete manifest.content_security_policy;
removeShortcuts(manifest);
return manifest;
});
return dist("opera", (manifest) => {
delete manifest.applications;
delete manifest.content_security_policy;
removeShortcuts(manifest);
return manifest;
});
}
function distChrome() {
return dist('chrome', (manifest) => {
delete manifest.applications;
delete manifest.content_security_policy;
delete manifest.sidebar_action;
delete manifest.commands._execute_sidebar_action;
return manifest;
});
return dist("chrome", (manifest) => {
delete manifest.applications;
delete manifest.content_security_policy;
delete manifest.sidebar_action;
delete manifest.commands._execute_sidebar_action;
return manifest;
});
}
function distEdge() {
return dist('edge', (manifest) => {
delete manifest.applications;
delete manifest.content_security_policy;
delete manifest.sidebar_action;
delete manifest.commands._execute_sidebar_action;
return manifest;
});
return dist("edge", (manifest) => {
delete manifest.applications;
delete manifest.content_security_policy;
delete manifest.sidebar_action;
delete manifest.commands._execute_sidebar_action;
return manifest;
});
}
function removeShortcuts(manifest) {
if (manifest.content_scripts && manifest.content_scripts.length > 1) {
const shortcutsScript = manifest.content_scripts[1];
if (shortcutsScript.js.indexOf('content/shortcuts.js') > -1) {
manifest.content_scripts.splice(1, 1);
}
if (manifest.content_scripts && manifest.content_scripts.length > 1) {
const shortcutsScript = manifest.content_scripts[1];
if (shortcutsScript.js.indexOf("content/shortcuts.js") > -1) {
manifest.content_scripts.splice(1, 1);
}
}
}
function distSafariMas(cb) {
return distSafariApp(cb, 'mas');
return distSafariApp(cb, "mas");
}
function distSafariMasDev(cb) {
return distSafariApp(cb, 'masdev');
return distSafariApp(cb, "masdev");
}
function distSafariDmg(cb) {
return distSafariApp(cb, 'dmg');
return distSafariApp(cb, "dmg");
}
function distSafariApp(cb, subBuildPath) {
const buildPath = paths.dist + 'Safari/' + subBuildPath + '/';
const builtAppexPath = buildPath + 'build/Release/safari.appex';
const builtAppexFrameworkPath = buildPath + 'build/Release/safari.appex/Contents/Frameworks/';
const entitlementsPath = paths.safari + 'safari/safari.entitlements';
var args = [
'--verbose',
'--force',
'-o',
'runtime',
'--sign',
'Developer ID Application: 8bit Solutions LLC',
'--entitlements',
entitlementsPath
const buildPath = paths.dist + "Safari/" + subBuildPath + "/";
const builtAppexPath = buildPath + "build/Release/safari.appex";
const builtAppexFrameworkPath = buildPath + "build/Release/safari.appex/Contents/Frameworks/";
const entitlementsPath = paths.safari + "safari/safari.entitlements";
var args = [
"--verbose",
"--force",
"-o",
"runtime",
"--sign",
"Developer ID Application: 8bit Solutions LLC",
"--entitlements",
entitlementsPath,
];
if (subBuildPath !== "dmg") {
args = [
"--verbose",
"--force",
"--sign",
subBuildPath === "mas"
? "3rd Party Mac Developer Application: 8bit Solutions LLC"
: "6B287DD81FF922D86FD836128B0F62F358B38726",
"--entitlements",
entitlementsPath,
];
if (subBuildPath !== 'dmg') {
args = [
'--verbose',
'--force',
'--sign',
subBuildPath === 'mas' ? '3rd Party Mac Developer Application: 8bit Solutions LLC' :
'6B287DD81FF922D86FD836128B0F62F358B38726',
'--entitlements',
entitlementsPath
];
}
}
return del([buildPath + '**/*'])
.then(() => safariCopyAssets(paths.safari + '**/*', buildPath))
.then(() => safariCopyBuild(paths.build + '**/*', buildPath + 'safari/app'))
.then(() => {
const proc = child.spawn('xcodebuild', [
'-project',
buildPath + 'desktop.xcodeproj',
'-alltargets',
'-configuration',
'Release']);
stdOutProc(proc);
return new Promise((resolve) => proc.on('close', resolve));
}).then(() => {
const libs = fs.readdirSync(builtAppexFrameworkPath).filter((p) => p.endsWith('.dylib'))
.map((p) => builtAppexFrameworkPath + p);
const libPromises = [];
libs.forEach((i) => {
const proc = child.spawn('codesign', args.concat([i]));
stdOutProc(proc);
libPromises.push(new Promise((resolve) => proc.on('close', resolve)));
});
return Promise.all(libPromises);
}).then(() => {
const proc = child.spawn('codesign', args.concat([builtAppexPath]));
stdOutProc(proc);
return new Promise((resolve) => proc.on('close', resolve));
}).then(() => {
return cb;
}, () => {
return cb;
});
return del([buildPath + "**/*"])
.then(() => safariCopyAssets(paths.safari + "**/*", buildPath))
.then(() => safariCopyBuild(paths.build + "**/*", buildPath + "safari/app"))
.then(() => {
const proc = child.spawn("xcodebuild", [
"-project",
buildPath + "desktop.xcodeproj",
"-alltargets",
"-configuration",
"Release",
]);
stdOutProc(proc);
return new Promise((resolve) => proc.on("close", resolve));
})
.then(() => {
const libs = fs
.readdirSync(builtAppexFrameworkPath)
.filter((p) => p.endsWith(".dylib"))
.map((p) => builtAppexFrameworkPath + p);
const libPromises = [];
libs.forEach((i) => {
const proc = child.spawn("codesign", args.concat([i]));
stdOutProc(proc);
libPromises.push(new Promise((resolve) => proc.on("close", resolve)));
});
return Promise.all(libPromises);
})
.then(() => {
const proc = child.spawn("codesign", args.concat([builtAppexPath]));
stdOutProc(proc);
return new Promise((resolve) => proc.on("close", resolve));
})
.then(
() => {
return cb;
},
() => {
return cb;
}
);
}
function safariCopyAssets(source, dest) {
return new Promise((resolve, reject) => {
gulp.src(source)
.on('error', reject)
.pipe(gulpif('safari/Info.plist', replace('0.0.1', manifest.version)))
.pipe(gulpif('safari/Info.plist', replace('0.0.2', process.env.BUILD_NUMBER || manifest.version)))
.pipe(gulpif('desktop.xcodeproj/project.pbxproj', replace('../../../build', '../safari/app')))
.pipe(gulp.dest(dest))
.on('end', resolve);
});
return new Promise((resolve, reject) => {
gulp
.src(source)
.on("error", reject)
.pipe(gulpif("safari/Info.plist", replace("0.0.1", manifest.version)))
.pipe(
gulpif("safari/Info.plist", replace("0.0.2", process.env.BUILD_NUMBER || manifest.version))
)
.pipe(gulpif("desktop.xcodeproj/project.pbxproj", replace("../../../build", "../safari/app")))
.pipe(gulp.dest(dest))
.on("end", resolve);
});
}
function safariCopyBuild(source, dest) {
return new Promise((resolve, reject) => {
gulp.src(source)
.on('error', reject)
.pipe(filter(['**'].concat(filters.fonts)))
.pipe(gulpif('popup/index.html', replace('__BROWSER__', 'browser_safari')))
.pipe(gulpif('manifest.json', jeditor((manifest) => {
delete manifest.optional_permissions;
manifest.permissions.push("nativeMessaging");
return manifest;
})))
.pipe(gulp.dest(dest))
.on('end', resolve);
});
return new Promise((resolve, reject) => {
gulp
.src(source)
.on("error", reject)
.pipe(filter(["**"].concat(filters.fonts)))
.pipe(gulpif("popup/index.html", replace("__BROWSER__", "browser_safari")))
.pipe(
gulpif(
"manifest.json",
jeditor((manifest) => {
delete manifest.optional_permissions;
manifest.permissions.push("nativeMessaging");
return manifest;
})
)
)
.pipe(gulp.dest(dest))
.on("end", resolve);
});
}
function stdOutProc(proc) {
proc.stdout.on('data', (data) => console.log(data.toString()));
proc.stderr.on('data', (data) => console.error(data.toString()));
proc.stdout.on("data", (data) => console.log(data.toString()));
proc.stderr.on("data", (data) => console.error(data.toString()));
}
function ciCoverage(cb) {
return gulp.src(paths.coverage + '**/*')
.pipe(filter(['**', '!coverage/coverage*.zip']))
.pipe(zip(`coverage${buildString()}.zip`))
.pipe(gulp.dest(paths.coverage));
return gulp
.src(paths.coverage + "**/*")
.pipe(filter(["**", "!coverage/coverage*.zip"]))
.pipe(zip(`coverage${buildString()}.zip`))
.pipe(gulp.dest(paths.coverage));
}
exports['dist:firefox'] = distFirefox;
exports['dist:chrome'] = distChrome;
exports['dist:opera'] = distOpera;
exports['dist:edge'] = distEdge;
exports['dist:safari'] = gulp.parallel(distSafariMas, distSafariMasDev, distSafariDmg);
exports['dist:safari:mas'] = distSafariMas;
exports['dist:safari:masdev'] = distSafariMasDev;
exports['dist:safari:dmg'] = distSafariDmg;
exports["dist:firefox"] = distFirefox;
exports["dist:chrome"] = distChrome;
exports["dist:opera"] = distOpera;
exports["dist:edge"] = distEdge;
exports["dist:safari"] = gulp.parallel(distSafariMas, distSafariMasDev, distSafariDmg);
exports["dist:safari:mas"] = distSafariMas;
exports["dist:safari:masdev"] = distSafariMasDev;
exports["dist:safari:dmg"] = distSafariDmg;
exports.dist = gulp.parallel(distFirefox, distChrome, distOpera, distEdge);
exports['ci:coverage'] = ciCoverage;
exports["ci:coverage"] = ciCoverage;
exports.ci = ciCoverage;

View File

@ -1,76 +1,71 @@
const path = require('path');
const path = require("path");
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: "",
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine', 'webpack'],
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ["jasmine", "webpack"],
// list of files / patterns to load in the browser
files: [
{ pattern: 'src/**/*.spec.ts', watch: false },
],
// list of files / patterns to load in the browser
files: [{ pattern: "src/**/*.spec.ts", watch: false }],
exclude: [
],
exclude: [],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'src/**/*.ts': 'webpack'
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
"src/**/*.ts": "webpack",
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ["progress", "kjhtml"],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ["Chrome"],
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity,
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
webpack: {
mode: "production",
resolve: {
extensions: [".js", ".ts", ".tsx"],
alias: {
"jslib-common": path.join(__dirname, "jslib/common/src"),
"jslib-angular": path.join(__dirname, "jslib/angular/src"),
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'kjhtml'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
},
module: {
rules: [{ test: /\.tsx?$/, loader: "ts-loader" }],
},
stats: {
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity,
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
webpack: {
mode: 'production',
resolve: {
extensions: ['.js', '.ts', '.tsx'],
alias: {
"jslib-common": path.join(__dirname, 'jslib/common/src'),
"jslib-angular": path.join(__dirname, 'jslib/angular/src'),
},
},
module: {
rules: [
{test: /\.tsx?$/, loader: 'ts-loader'}
]
},
stats: {
colors: true,
modules: true,
reasons: true,
errorDetails: true
},
devtool: 'inline-source-map',
},
})
}
modules: true,
reasons: true,
errorDetails: true,
},
devtool: "inline-source-map",
},
});
};

View File

@ -1,7 +1,6 @@
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
</body>
<head>
<meta charset="UTF-8" />
</head>
<body></body>
</html>

View File

@ -1,6 +1,6 @@
import MainBackground from './background/main.background';
import MainBackground from "./background/main.background";
const bitwardenMain = (window as any).bitwardenMain = new MainBackground();
const bitwardenMain = ((window as any).bitwardenMain = new MainBackground());
bitwardenMain.bootstrap().then(() => {
// Finished bootstrapping
// Finished bootstrapping
});

View File

@ -1,98 +1,112 @@
import { BrowserApi } from '../browser/browserApi';
import { BrowserApi } from "../browser/browserApi";
import MainBackground from './main.background';
import MainBackground from "./main.background";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
export default class CommandsBackground {
private isSafari: boolean;
private isVivaldi: boolean;
private isSafari: boolean;
private isVivaldi: boolean;
constructor(private main: MainBackground, private passwordGenerationService: PasswordGenerationService,
private platformUtilsService: PlatformUtilsService, private vaultTimeoutService: VaultTimeoutService) {
this.isSafari = this.platformUtilsService.isSafari();
this.isVivaldi = this.platformUtilsService.isVivaldi();
}
constructor(
private main: MainBackground,
private passwordGenerationService: PasswordGenerationService,
private platformUtilsService: PlatformUtilsService,
private vaultTimeoutService: VaultTimeoutService
) {
this.isSafari = this.platformUtilsService.isSafari();
this.isVivaldi = this.platformUtilsService.isVivaldi();
}
async init() {
BrowserApi.messageListener('commands.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
if (msg.command === 'unlockCompleted' && msg.data.target === 'commands.background') {
await this.processCommand(msg.data.commandToRetry.msg.command, msg.data.commandToRetry.sender);
}
if (this.isVivaldi && msg.command === 'keyboardShortcutTriggered' && msg.shortcut) {
await this.processCommand(msg.shortcut, sender);
}
});
if (!this.isVivaldi && chrome && chrome.commands) {
chrome.commands.onCommand.addListener(async (command: string) => {
await this.processCommand(command);
});
}
}
private async processCommand(command: string, sender?: chrome.runtime.MessageSender) {
switch (command) {
case 'generate_password':
await this.generatePasswordToClipboard();
break;
case 'autofill_login':
await this.autoFillLogin(sender ? sender.tab : null);
break;
case 'open_popup':
await this.openPopup();
break;
case 'lock_vault':
await this.vaultTimeoutService.lock(true);
break;
default:
break;
}
}
private async generatePasswordToClipboard() {
const options = (await this.passwordGenerationService.getOptions())[0];
const password = await this.passwordGenerationService.generatePassword(options);
this.platformUtilsService.copyToClipboard(password, { window: window });
this.passwordGenerationService.addHistory(password);
}
private async autoFillLogin(tab?: chrome.tabs.Tab) {
if (!tab) {
tab = await BrowserApi.getTabFromCurrentWindowId();
async init() {
BrowserApi.messageListener(
"commands.background",
async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
if (msg.command === "unlockCompleted" && msg.data.target === "commands.background") {
await this.processCommand(
msg.data.commandToRetry.msg.command,
msg.data.commandToRetry.sender
);
}
if (tab == null) {
return;
if (this.isVivaldi && msg.command === "keyboardShortcutTriggered" && msg.shortcut) {
await this.processCommand(msg.shortcut, sender);
}
}
);
if (await this.vaultTimeoutService.isLocked()) {
const retryMessage: LockedVaultPendingNotificationsItem = {
commandToRetry: {
msg: { command: 'autofill_login' },
sender: { tab: tab },
},
target: 'commands.background',
};
await BrowserApi.tabSendMessageData(tab, 'addToLockedVaultPendingNotifications', retryMessage);
if (!this.isVivaldi && chrome && chrome.commands) {
chrome.commands.onCommand.addListener(async (command: string) => {
await this.processCommand(command);
});
}
}
BrowserApi.tabSendMessageData(tab, 'promptForLogin');
return;
}
private async processCommand(command: string, sender?: chrome.runtime.MessageSender) {
switch (command) {
case "generate_password":
await this.generatePasswordToClipboard();
break;
case "autofill_login":
await this.autoFillLogin(sender ? sender.tab : null);
break;
case "open_popup":
await this.openPopup();
break;
case "lock_vault":
await this.vaultTimeoutService.lock(true);
break;
default:
break;
}
}
await this.main.collectPageDetailsForContentScript(tab, 'autofill_cmd');
private async generatePasswordToClipboard() {
const options = (await this.passwordGenerationService.getOptions())[0];
const password = await this.passwordGenerationService.generatePassword(options);
this.platformUtilsService.copyToClipboard(password, { window: window });
this.passwordGenerationService.addHistory(password);
}
private async autoFillLogin(tab?: chrome.tabs.Tab) {
if (!tab) {
tab = await BrowserApi.getTabFromCurrentWindowId();
}
private async openPopup() {
// Chrome APIs cannot open popup
if (!this.isSafari) {
return;
}
this.main.openPopup();
if (tab == null) {
return;
}
if (await this.vaultTimeoutService.isLocked()) {
const retryMessage: LockedVaultPendingNotificationsItem = {
commandToRetry: {
msg: { command: "autofill_login" },
sender: { tab: tab },
},
target: "commands.background",
};
await BrowserApi.tabSendMessageData(
tab,
"addToLockedVaultPendingNotifications",
retryMessage
);
BrowserApi.tabSendMessageData(tab, "promptForLogin");
return;
}
await this.main.collectPageDetailsForContentScript(tab, "autofill_cmd");
}
private async openPopup() {
// Chrome APIs cannot open popup
if (!this.isSafari) {
return;
}
this.main.openPopup();
}
}

View File

@ -1,123 +1,142 @@
import { BrowserApi } from '../browser/browserApi';
import { BrowserApi } from "../browser/browserApi";
import MainBackground from './main.background';
import MainBackground from "./main.background";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { EventService } from 'jslib-common/abstractions/event.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { TotpService } from 'jslib-common/abstractions/totp.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { EventService } from "jslib-common/abstractions/event.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TotpService } from "jslib-common/abstractions/totp.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
import { EventType } from 'jslib-common/enums/eventType';
import { CipherView } from 'jslib-common/models/view/cipherView';
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
import { EventType } from "jslib-common/enums/eventType";
import { CipherView } from "jslib-common/models/view/cipherView";
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
export default class ContextMenusBackground {
private readonly noopCommandSuffix = 'noop';
private contextMenus: any;
private readonly noopCommandSuffix = "noop";
private contextMenus: any;
constructor(private main: MainBackground, private cipherService: CipherService,
private passwordGenerationService: PasswordGenerationService,
private platformUtilsService: PlatformUtilsService, private vaultTimeoutService: VaultTimeoutService,
private eventService: EventService, private totpService: TotpService) {
this.contextMenus = chrome.contextMenus;
constructor(
private main: MainBackground,
private cipherService: CipherService,
private passwordGenerationService: PasswordGenerationService,
private platformUtilsService: PlatformUtilsService,
private vaultTimeoutService: VaultTimeoutService,
private eventService: EventService,
private totpService: TotpService
) {
this.contextMenus = chrome.contextMenus;
}
async init() {
if (!this.contextMenus) {
return;
}
async init() {
if (!this.contextMenus) {
return;
this.contextMenus.onClicked.addListener(
async (info: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab) => {
if (info.menuItemId === "generate-password") {
await this.generatePasswordToClipboard();
} else if (info.menuItemId === "copy-identifier") {
await this.getClickedElement(tab, info.frameId);
} else if (
info.parentMenuItemId === "autofill" ||
info.parentMenuItemId === "copy-username" ||
info.parentMenuItemId === "copy-password" ||
info.parentMenuItemId === "copy-totp"
) {
await this.cipherAction(tab, info);
}
}
);
this.contextMenus.onClicked.addListener(async (info: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab) => {
if (info.menuItemId === 'generate-password') {
await this.generatePasswordToClipboard();
} else if (info.menuItemId === 'copy-identifier') {
await this.getClickedElement(tab, info.frameId);
} else if (info.parentMenuItemId === 'autofill' ||
info.parentMenuItemId === 'copy-username' ||
info.parentMenuItemId === 'copy-password' ||
info.parentMenuItemId === 'copy-totp') {
await this.cipherAction(tab, info);
}
});
BrowserApi.messageListener(
"contextmenus.background",
async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
if (msg.command === "unlockCompleted" && msg.data.target === "contextmenus.background") {
await this.cipherAction(
msg.data.commandToRetry.sender.tab,
msg.data.commandToRetry.msg.data
);
}
}
);
}
BrowserApi.messageListener('contextmenus.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
if (msg.command === 'unlockCompleted' && msg.data.target === 'contextmenus.background') {
await this.cipherAction(msg.data.commandToRetry.sender.tab, msg.data.commandToRetry.msg.data);
}
});
private async generatePasswordToClipboard() {
const options = (await this.passwordGenerationService.getOptions())[0];
const password = await this.passwordGenerationService.generatePassword(options);
this.platformUtilsService.copyToClipboard(password, { window: window });
this.passwordGenerationService.addHistory(password);
}
private async getClickedElement(tab: chrome.tabs.Tab, frameId: number) {
if (tab == null) {
return;
}
private async generatePasswordToClipboard() {
const options = (await this.passwordGenerationService.getOptions())[0];
const password = await this.passwordGenerationService.generatePassword(options);
this.platformUtilsService.copyToClipboard(password, { window: window });
this.passwordGenerationService.addHistory(password);
BrowserApi.tabSendMessage(tab, { command: "getClickedElement" }, { frameId: frameId });
}
private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) {
const id = info.menuItemId.split("_")[1];
if (await this.vaultTimeoutService.isLocked()) {
const retryMessage: LockedVaultPendingNotificationsItem = {
commandToRetry: {
msg: { command: this.noopCommandSuffix, data: info },
sender: { tab: tab },
},
target: "contextmenus.background",
};
await BrowserApi.tabSendMessageData(
tab,
"addToLockedVaultPendingNotifications",
retryMessage
);
BrowserApi.tabSendMessageData(tab, "promptForLogin");
return;
}
private async getClickedElement(tab: chrome.tabs.Tab, frameId: number) {
if (tab == null) {
return;
}
BrowserApi.tabSendMessage(tab, { command: 'getClickedElement' }, { frameId: frameId });
let cipher: CipherView;
if (id === this.noopCommandSuffix) {
const ciphers = await this.cipherService.getAllDecryptedForUrl(tab.url);
cipher = ciphers.find((c) => c.reprompt === CipherRepromptType.None);
} else {
const ciphers = await this.cipherService.getAllDecrypted();
cipher = ciphers.find((c) => c.id === id);
}
private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) {
const id = info.menuItemId.split('_')[1];
if (await this.vaultTimeoutService.isLocked()) {
const retryMessage: LockedVaultPendingNotificationsItem = {
commandToRetry: {
msg: { command: this.noopCommandSuffix, data: info },
sender: { tab: tab },
},
target: 'contextmenus.background',
};
await BrowserApi.tabSendMessageData(tab, 'addToLockedVaultPendingNotifications', retryMessage);
BrowserApi.tabSendMessageData(tab, 'promptForLogin');
return;
}
let cipher: CipherView;
if (id === this.noopCommandSuffix) {
const ciphers = await this.cipherService.getAllDecryptedForUrl(tab.url);
cipher = ciphers.find(c => c.reprompt === CipherRepromptType.None);
} else {
const ciphers = await this.cipherService.getAllDecrypted();
cipher = ciphers.find(c => c.id === id);
}
if (cipher == null) {
return;
}
if (info.parentMenuItemId === 'autofill') {
await this.startAutofillPage(tab, cipher);
} else if (info.parentMenuItemId === 'copy-username') {
this.platformUtilsService.copyToClipboard(cipher.login.username, { window: window });
} else if (info.parentMenuItemId === 'copy-password') {
this.platformUtilsService.copyToClipboard(cipher.login.password, { window: window });
this.eventService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
} else if (info.parentMenuItemId === 'copy-totp') {
const totpValue = await this.totpService.getCode(cipher.login.totp);
this.platformUtilsService.copyToClipboard(totpValue, { window: window });
}
if (cipher == null) {
return;
}
private async startAutofillPage(tab: chrome.tabs.Tab, cipher: CipherView) {
this.main.loginToAutoFill = cipher;
if (tab == null) {
return;
}
BrowserApi.tabSendMessage(tab, {
command: 'collectPageDetails',
tab: tab,
sender: 'contextMenu',
});
if (info.parentMenuItemId === "autofill") {
await this.startAutofillPage(tab, cipher);
} else if (info.parentMenuItemId === "copy-username") {
this.platformUtilsService.copyToClipboard(cipher.login.username, { window: window });
} else if (info.parentMenuItemId === "copy-password") {
this.platformUtilsService.copyToClipboard(cipher.login.password, { window: window });
this.eventService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
} else if (info.parentMenuItemId === "copy-totp") {
const totpValue = await this.totpService.getCode(cipher.login.totp);
this.platformUtilsService.copyToClipboard(totpValue, { window: window });
}
}
private async startAutofillPage(tab: chrome.tabs.Tab, cipher: CipherView) {
this.main.loginToAutoFill = cipher;
if (tab == null) {
return;
}
BrowserApi.tabSendMessage(tab, {
command: "collectPageDetails",
tab: tab,
sender: "contextMenu",
});
}
}

View File

@ -1,68 +1,75 @@
import { NotificationsService } from 'jslib-common/abstractions/notifications.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { NotificationsService } from "jslib-common/abstractions/notifications.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ConstantsService } from "jslib-common/services/constants.service";
const IdleInterval = 60 * 5; // 5 minutes
export default class IdleBackground {
private idle: any;
private idleTimer: number = null;
private idleState = 'active';
private idle: any;
private idleTimer: number = null;
private idleState = "active";
constructor(private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService,
private notificationsService: NotificationsService) {
this.idle = chrome.idle || (browser != null ? browser.idle : null);
constructor(
private vaultTimeoutService: VaultTimeoutService,
private storageService: StorageService,
private notificationsService: NotificationsService
) {
this.idle = chrome.idle || (browser != null ? browser.idle : null);
}
async init() {
if (!this.idle) {
return;
}
async init() {
if (!this.idle) {
return;
}
const idleHandler = (newState: string) => {
if (newState === "active") {
this.notificationsService.reconnectFromActivity();
} else {
this.notificationsService.disconnectFromInactivity();
}
};
if (this.idle.onStateChanged && this.idle.setDetectionInterval) {
this.idle.setDetectionInterval(IdleInterval);
this.idle.onStateChanged.addListener(idleHandler);
} else {
this.pollIdle(idleHandler);
}
const idleHandler = (newState: string) => {
if (newState === 'active') {
this.notificationsService.reconnectFromActivity();
if (this.idle.onStateChanged) {
this.idle.onStateChanged.addListener(async (newState: string) => {
if (newState === "locked") {
// If the screen is locked or the screensaver activates
const timeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
if (timeout === -2) {
// On System Lock vault timeout option
const action = await this.storageService.get<string>(
ConstantsService.vaultTimeoutActionKey
);
if (action === "logOut") {
await this.vaultTimeoutService.logOut();
} else {
this.notificationsService.disconnectFromInactivity();
await this.vaultTimeoutService.lock(true);
}
};
if (this.idle.onStateChanged && this.idle.setDetectionInterval) {
this.idle.setDetectionInterval(IdleInterval);
this.idle.onStateChanged.addListener(idleHandler);
} else {
this.pollIdle(idleHandler);
}
if (this.idle.onStateChanged) {
this.idle.onStateChanged.addListener(async (newState: string) => {
if (newState === 'locked') { // If the screen is locked or the screensaver activates
const timeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
if (timeout === -2) { // On System Lock vault timeout option
const action = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
if (action === 'logOut') {
await this.vaultTimeoutService.logOut();
} else {
await this.vaultTimeoutService.lock(true);
}
}
}
});
}
}
});
}
}
private pollIdle(handler: (newState: string) => void) {
if (this.idleTimer != null) {
window.clearTimeout(this.idleTimer);
this.idleTimer = null;
}
this.idle.queryState(IdleInterval, (state: string) => {
if (state !== this.idleState) {
this.idleState = state;
handler(state);
}
this.idleTimer = window.setTimeout(() => this.pollIdle(handler), 5000);
});
private pollIdle(handler: (newState: string) => void) {
if (this.idleTimer != null) {
window.clearTimeout(this.idleTimer);
this.idleTimer = null;
}
this.idle.queryState(IdleInterval, (state: string) => {
if (state !== this.idleState) {
this.idleState = state;
handler(state);
}
this.idleTimer = window.setTimeout(() => this.pollIdle(handler), 5000);
});
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
import NotificationQueueMessage from './notificationQueueMessage';
import NotificationQueueMessage from "./notificationQueueMessage";
export default class AddChangePasswordQueueMessage extends NotificationQueueMessage {
cipherId: string;
newPassword: string;
cipherId: string;
newPassword: string;
}

View File

@ -1,7 +1,7 @@
import NotificationQueueMessage from './notificationQueueMessage';
import NotificationQueueMessage from "./notificationQueueMessage";
export default class AddLoginQueueMessage extends NotificationQueueMessage {
username: string;
password: string;
uri: string;
username: string;
password: string;
uri: string;
}

View File

@ -1,5 +1,5 @@
export default class AddLoginRuntimeMessage {
username: string;
password: string;
url: string;
username: string;
password: string;
url: string;
}

View File

@ -1,5 +1,5 @@
export default class ChangePasswordRuntimeMessage {
currentPassword: string;
newPassword: string;
url: string;
currentPassword: string;
newPassword: string;
url: string;
}

View File

@ -1,7 +1,7 @@
export default class LockedVaultPendingNotificationsItem {
commandToRetry: {
msg: any;
sender: chrome.runtime.MessageSender;
};
target: string;
commandToRetry: {
msg: any;
sender: chrome.runtime.MessageSender;
};
target: string;
}

View File

@ -1,9 +1,9 @@
import { NotificationQueueMessageType } from './notificationQueueMessageType';
import { NotificationQueueMessageType } from "./notificationQueueMessageType";
export default class NotificationQueueMessage {
type: NotificationQueueMessageType;
domain: string;
tabId: number;
expires: Date;
wasVaultLocked: boolean;
type: NotificationQueueMessageType;
domain: string;
tabId: number;
expires: Date;
wasVaultLocked: boolean;
}

View File

@ -1,4 +1,4 @@
export enum NotificationQueueMessageType {
addLogin = 'addLogin',
changePassword = 'changePassword',
addLogin = "addLogin",
changePassword = "changePassword",
}

View File

@ -1,333 +1,350 @@
import { AppIdService } from 'jslib-common/abstractions/appId.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { AppIdService } from "jslib-common/abstractions/appId.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { UserService } from "jslib-common/abstractions/user.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { ConstantsService } from "jslib-common/services/constants.service";
import { Utils } from 'jslib-common/misc/utils';
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { Utils } from "jslib-common/misc/utils";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { BrowserApi } from '../browser/browserApi';
import RuntimeBackground from './runtime.background';
import { BrowserApi } from "../browser/browserApi";
import RuntimeBackground from "./runtime.background";
const MessageValidTimeout = 10 * 1000;
const EncryptionAlgorithm = 'sha1';
const EncryptionAlgorithm = "sha1";
export class NativeMessagingBackground {
private connected = false;
private connecting: boolean;
private port: browser.runtime.Port | chrome.runtime.Port;
private connected = false;
private connecting: boolean;
private port: browser.runtime.Port | chrome.runtime.Port;
private resolver: any = null;
private privateKey: ArrayBuffer = null;
private publicKey: ArrayBuffer = null;
private secureSetupResolve: any = null;
private sharedSecret: SymmetricCryptoKey;
private appId: string;
private validatingFingerprint: boolean;
private resolver: any = null;
private privateKey: ArrayBuffer = null;
private publicKey: ArrayBuffer = null;
private secureSetupResolve: any = null;
private sharedSecret: SymmetricCryptoKey;
private appId: string;
private validatingFingerprint: boolean;
constructor(private storageService: StorageService, private cryptoService: CryptoService,
private cryptoFunctionService: CryptoFunctionService, private vaultTimeoutService: VaultTimeoutService,
private runtimeBackground: RuntimeBackground, private i18nService: I18nService, private userService: UserService,
private messagingService: MessagingService, private appIdService: AppIdService,
private platformUtilsService: PlatformUtilsService) {
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
constructor(
private storageService: StorageService,
private cryptoService: CryptoService,
private cryptoFunctionService: CryptoFunctionService,
private vaultTimeoutService: VaultTimeoutService,
private runtimeBackground: RuntimeBackground,
private i18nService: I18nService,
private userService: UserService,
private messagingService: MessagingService,
private appIdService: AppIdService,
private platformUtilsService: PlatformUtilsService
) {
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
if (chrome?.permissions?.onAdded) {
// Reload extension to activate nativeMessaging
chrome.permissions.onAdded.addListener(permissions => {
BrowserApi.reloadExtension(null);
});
if (chrome?.permissions?.onAdded) {
// Reload extension to activate nativeMessaging
chrome.permissions.onAdded.addListener((permissions) => {
BrowserApi.reloadExtension(null);
});
}
}
async connect() {
this.appId = await this.appIdService.getAppId();
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
return new Promise<void>((resolve, reject) => {
this.port = BrowserApi.connectNative("com.8bit.bitwarden");
this.connecting = true;
const connectedCallback = () => {
this.connected = true;
this.connecting = false;
resolve();
};
// Safari has a bundled native component which is always available, no need to
// check if the desktop app is running.
if (this.platformUtilsService.isSafari()) {
connectedCallback();
}
this.port.onMessage.addListener(async (message: any) => {
switch (message.command) {
case "connected":
connectedCallback();
break;
case "disconnected":
if (this.connecting) {
this.messagingService.send("showDialog", {
text: this.i18nService.t("startDesktopDesc"),
title: this.i18nService.t("startDesktopTitle"),
confirmText: this.i18nService.t("ok"),
type: "error",
});
reject();
}
}
async connect() {
this.appId = await this.appIdService.getAppId();
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
return new Promise<void>((resolve, reject) => {
this.port = BrowserApi.connectNative('com.8bit.bitwarden');
this.connecting = true;
const connectedCallback = () => {
this.connected = true;
this.connecting = false;
resolve();
};
// Safari has a bundled native component which is always available, no need to
// check if the desktop app is running.
if (this.platformUtilsService.isSafari()) {
connectedCallback();
this.connected = false;
this.port.disconnect();
break;
case "setupEncryption":
// Ignore since it belongs to another device
if (message.appId !== this.appId) {
return;
}
this.port.onMessage.addListener(async (message: any) => {
switch (message.command) {
case 'connected':
connectedCallback();
break;
case 'disconnected':
if (this.connecting) {
this.messagingService.send('showDialog', {
text: this.i18nService.t('startDesktopDesc'),
title: this.i18nService.t('startDesktopTitle'),
confirmText: this.i18nService.t('ok'),
type: 'error',
});
reject();
}
this.connected = false;
this.port.disconnect();
break;
case 'setupEncryption':
// Ignore since it belongs to another device
if (message.appId !== this.appId) {
return;
}
const encrypted = Utils.fromB64ToArray(message.sharedSecret);
const decrypted = await this.cryptoFunctionService.rsaDecrypt(
encrypted.buffer,
this.privateKey,
EncryptionAlgorithm
);
const encrypted = Utils.fromB64ToArray(message.sharedSecret);
const decrypted = await this.cryptoFunctionService.rsaDecrypt(encrypted.buffer, this.privateKey, EncryptionAlgorithm);
if (this.validatingFingerprint) {
this.validatingFingerprint = false;
this.storageService.save(ConstantsService.biometricFingerprintValidated, true);
}
this.sharedSecret = new SymmetricCryptoKey(decrypted);
this.secureSetupResolve();
break;
case 'invalidateEncryption':
// Ignore since it belongs to another device
if (message.appId !== this.appId) {
return;
}
this.sharedSecret = null;
this.privateKey = null;
this.connected = false;
this.messagingService.send('showDialog', {
text: this.i18nService.t('nativeMessagingInvalidEncryptionDesc'),
title: this.i18nService.t('nativeMessagingInvalidEncryptionTitle'),
confirmText: this.i18nService.t('ok'),
type: 'error',
});
break;
case 'verifyFingerprint': {
if (this.sharedSecret == null) {
this.validatingFingerprint = true;
this.showFingerprintDialog();
}
break;
}
case 'wrongUserId':
this.showWrongUserDialog();
default:
// Ignore since it belongs to another device
if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) {
return;
}
this.onMessage(message.message);
}
});
this.port.onDisconnect.addListener((p: any) => {
let error;
if (BrowserApi.isWebExtensionsApi) {
error = p.error.message;
} else {
error = chrome.runtime.lastError.message;
}
if (error != null) {
this.messagingService.send('showDialog', {
text: this.i18nService.t('desktopIntegrationDisabledDesc'),
title: this.i18nService.t('desktopIntegrationDisabledTitle'),
confirmText: this.i18nService.t('ok'),
type: 'error',
});
}
this.sharedSecret = null;
this.privateKey = null;
this.connected = false;
reject();
});
});
}
showWrongUserDialog() {
this.messagingService.send('showDialog', {
text: this.i18nService.t('nativeMessagingWrongUserDesc'),
title: this.i18nService.t('nativeMessagingWrongUserTitle'),
confirmText: this.i18nService.t('ok'),
type: 'error',
});
}
async send(message: any) {
if (!this.connected) {
await this.connect();
}
if (this.platformUtilsService.isSafari()) {
this.postMessage(message);
} else {
this.postMessage({appId: this.appId, message: await this.encryptMessage(message)});
}
}
async encryptMessage(message: any) {
if (this.sharedSecret == null) {
await this.secureCommunication();
}
message.timestamp = Date.now();
return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret);
}
getResponse(): Promise<any> {
return new Promise((resolve, reject) => {
this.resolver = resolve;
});
}
private postMessage(message: any) {
// Wrap in try-catch to when the port disconnected without triggering `onDisconnect`.
try {
this.port.postMessage(message);
} catch (e) {
// tslint:disable-next-line
console.error("NativeMessaging port disconnected, disconnecting.");
if (this.validatingFingerprint) {
this.validatingFingerprint = false;
this.storageService.save(ConstantsService.biometricFingerprintValidated, true);
}
this.sharedSecret = new SymmetricCryptoKey(decrypted);
this.secureSetupResolve();
break;
case "invalidateEncryption":
// Ignore since it belongs to another device
if (message.appId !== this.appId) {
return;
}
this.sharedSecret = null;
this.privateKey = null;
this.connected = false;
this.messagingService.send('showDialog', {
text: this.i18nService.t('nativeMessagingInvalidEncryptionDesc'),
title: this.i18nService.t('nativeMessagingInvalidEncryptionTitle'),
confirmText: this.i18nService.t('ok'),
type: 'error',
this.messagingService.send("showDialog", {
text: this.i18nService.t("nativeMessagingInvalidEncryptionDesc"),
title: this.i18nService.t("nativeMessagingInvalidEncryptionTitle"),
confirmText: this.i18nService.t("ok"),
type: "error",
});
break;
case "verifyFingerprint": {
if (this.sharedSecret == null) {
this.validatingFingerprint = true;
this.showFingerprintDialog();
}
break;
}
case "wrongUserId":
this.showWrongUserDialog();
default:
// Ignore since it belongs to another device
if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) {
return;
}
this.onMessage(message.message);
}
});
this.port.onDisconnect.addListener((p: any) => {
let error;
if (BrowserApi.isWebExtensionsApi) {
error = p.error.message;
} else {
error = chrome.runtime.lastError.message;
}
if (error != null) {
this.messagingService.send("showDialog", {
text: this.i18nService.t("desktopIntegrationDisabledDesc"),
title: this.i18nService.t("desktopIntegrationDisabledTitle"),
confirmText: this.i18nService.t("ok"),
type: "error",
});
}
this.sharedSecret = null;
this.privateKey = null;
this.connected = false;
reject();
});
});
}
showWrongUserDialog() {
this.messagingService.send("showDialog", {
text: this.i18nService.t("nativeMessagingWrongUserDesc"),
title: this.i18nService.t("nativeMessagingWrongUserTitle"),
confirmText: this.i18nService.t("ok"),
type: "error",
});
}
async send(message: any) {
if (!this.connected) {
await this.connect();
}
private async onMessage(rawMessage: any) {
let message = rawMessage;
if (!this.platformUtilsService.isSafari()) {
message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret));
if (this.platformUtilsService.isSafari()) {
this.postMessage(message);
} else {
this.postMessage({ appId: this.appId, message: await this.encryptMessage(message) });
}
}
async encryptMessage(message: any) {
if (this.sharedSecret == null) {
await this.secureCommunication();
}
message.timestamp = Date.now();
return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret);
}
getResponse(): Promise<any> {
return new Promise((resolve, reject) => {
this.resolver = resolve;
});
}
private postMessage(message: any) {
// Wrap in try-catch to when the port disconnected without triggering `onDisconnect`.
try {
this.port.postMessage(message);
} catch (e) {
// tslint:disable-next-line
console.error("NativeMessaging port disconnected, disconnecting.");
this.sharedSecret = null;
this.privateKey = null;
this.connected = false;
this.messagingService.send("showDialog", {
text: this.i18nService.t("nativeMessagingInvalidEncryptionDesc"),
title: this.i18nService.t("nativeMessagingInvalidEncryptionTitle"),
confirmText: this.i18nService.t("ok"),
type: "error",
});
}
}
private async onMessage(rawMessage: any) {
let message = rawMessage;
if (!this.platformUtilsService.isSafari()) {
message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret));
}
if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) {
// tslint:disable-next-line
console.error("NativeMessage is to old, ignoring.");
return;
}
switch (message.command) {
case "biometricUnlock":
await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance);
if (message.response === "not enabled") {
this.messagingService.send("showDialog", {
text: this.i18nService.t("biometricsNotEnabledDesc"),
title: this.i18nService.t("biometricsNotEnabledTitle"),
confirmText: this.i18nService.t("ok"),
type: "error",
});
break;
} else if (message.response === "not supported") {
this.messagingService.send("showDialog", {
text: this.i18nService.t("biometricsNotSupportedDesc"),
title: this.i18nService.t("biometricsNotSupportedTitle"),
confirmText: this.i18nService.t("ok"),
type: "error",
});
break;
}
if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) {
const enabled = await this.storageService.get(ConstantsService.biometricUnlockKey);
if (enabled === null || enabled === false) {
if (message.response === "unlocked") {
await this.storageService.save(ConstantsService.biometricUnlockKey, true);
}
break;
}
// Ignore unlock if already unlockeded
if (!this.vaultTimeoutService.biometricLocked) {
break;
}
if (message.response === "unlocked") {
await this.cryptoService.setKey(
new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer)
);
// Verify key is correct by attempting to decrypt a secret
try {
await this.cryptoService.getFingerprint(await this.userService.getUserId());
} catch (e) {
// tslint:disable-next-line
console.error('NativeMessage is to old, ignoring.');
return;
}
switch (message.command) {
case 'biometricUnlock':
await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance);
if (message.response === 'not enabled') {
this.messagingService.send('showDialog', {
text: this.i18nService.t('biometricsNotEnabledDesc'),
title: this.i18nService.t('biometricsNotEnabledTitle'),
confirmText: this.i18nService.t('ok'),
type: 'error',
});
break;
} else if (message.response === 'not supported') {
this.messagingService.send('showDialog', {
text: this.i18nService.t('biometricsNotSupportedDesc'),
title: this.i18nService.t('biometricsNotSupportedTitle'),
confirmText: this.i18nService.t('ok'),
type: 'error',
});
break;
}
const enabled = await this.storageService.get(ConstantsService.biometricUnlockKey);
if (enabled === null || enabled === false) {
if (message.response === 'unlocked') {
await this.storageService.save(ConstantsService.biometricUnlockKey, true);
}
break;
}
// Ignore unlock if already unlockeded
if (!this.vaultTimeoutService.biometricLocked) {
break;
}
if (message.response === 'unlocked') {
await this.cryptoService.setKey(new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer));
// Verify key is correct by attempting to decrypt a secret
try {
await this.cryptoService.getFingerprint(await this.userService.getUserId());
} catch (e) {
// tslint:disable-next-line
console.error('Unable to verify key:', e);
await this.cryptoService.clearKey();
this.showWrongUserDialog();
message = false;
break;
}
this.vaultTimeoutService.biometricLocked = false;
this.runtimeBackground.processMessage({command: 'unlocked'}, null, null);
}
break;
default:
// tslint:disable-next-line
console.error('NativeMessage, got unknown command: ', message.command);
}
if (this.resolver) {
this.resolver(message);
console.error("Unable to verify key:", e);
await this.cryptoService.clearKey();
this.showWrongUserDialog();
message = false;
break;
}
this.vaultTimeoutService.biometricLocked = false;
this.runtimeBackground.processMessage({ command: "unlocked" }, null, null);
}
break;
default:
// tslint:disable-next-line
console.error("NativeMessage, got unknown command: ", message.command);
}
private async secureCommunication() {
const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048);
this.publicKey = publicKey;
this.privateKey = privateKey;
if (this.resolver) {
this.resolver(message);
}
}
this.sendUnencrypted({
command: 'setupEncryption',
publicKey: Utils.fromBufferToB64(publicKey),
userId: await this.userService.getUserId(),
});
private async secureCommunication() {
const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048);
this.publicKey = publicKey;
this.privateKey = privateKey;
return new Promise((resolve, reject) => this.secureSetupResolve = resolve);
this.sendUnencrypted({
command: "setupEncryption",
publicKey: Utils.fromBufferToB64(publicKey),
userId: await this.userService.getUserId(),
});
return new Promise((resolve, reject) => (this.secureSetupResolve = resolve));
}
private async sendUnencrypted(message: any) {
if (!this.connected) {
await this.connect();
}
private async sendUnencrypted(message: any) {
if (!this.connected) {
await this.connect();
}
message.timestamp = Date.now();
message.timestamp = Date.now();
this.postMessage({ appId: this.appId, message: message });
}
this.postMessage({appId: this.appId, message: message});
}
private async showFingerprintDialog() {
const fingerprint = (
await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey)
).join(" ");
private async showFingerprintDialog() {
const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey)).join(' ');
this.messagingService.send('showDialog', {
html: `${this.i18nService.t('desktopIntegrationVerificationText')}<br><br><strong>${fingerprint}</strong>`,
title: this.i18nService.t('desktopSyncVerificationTitle'),
confirmText: this.i18nService.t('ok'),
type: 'warning',
});
}
this.messagingService.send("showDialog", {
html: `${this.i18nService.t(
"desktopIntegrationVerificationText"
)}<br><br><strong>${fingerprint}</strong>`,
title: this.i18nService.t("desktopSyncVerificationTitle"),
confirmText: this.i18nService.t("ok"),
type: "warning",
});
}
}

View File

@ -1,417 +1,460 @@
import { CipherType } from 'jslib-common/enums/cipherType';
import { CipherType } from "jslib-common/enums/cipherType";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { LoginUriView } from 'jslib-common/models/view/loginUriView';
import { LoginView } from 'jslib-common/models/view/loginView';
import { CipherView } from "jslib-common/models/view/cipherView";
import { LoginUriView } from "jslib-common/models/view/loginUriView";
import { LoginView } from "jslib-common/models/view/loginView";
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { UserService } from "jslib-common/abstractions/user.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ConstantsService } from "jslib-common/services/constants.service";
import { AutofillService } from '../services/abstractions/autofill.service';
import { AutofillService } from "../services/abstractions/autofill.service";
import { BrowserApi } from '../browser/browserApi';
import { BrowserApi } from "../browser/browserApi";
import MainBackground from './main.background';
import MainBackground from "./main.background";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
import { PolicyType } from 'jslib-common/enums/policyType';
import { PolicyType } from "jslib-common/enums/policyType";
import AddChangePasswordQueueMessage from './models/addChangePasswordQueueMessage';
import AddLoginQueueMessage from './models/addLoginQueueMessage';
import AddLoginRuntimeMessage from './models/addLoginRuntimeMessage';
import ChangePasswordRuntimeMessage from './models/changePasswordRuntimeMessage';
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
import { NotificationQueueMessageType } from './models/notificationQueueMessageType';
import AddChangePasswordQueueMessage from "./models/addChangePasswordQueueMessage";
import AddLoginQueueMessage from "./models/addLoginQueueMessage";
import AddLoginRuntimeMessage from "./models/addLoginRuntimeMessage";
import ChangePasswordRuntimeMessage from "./models/changePasswordRuntimeMessage";
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
import { NotificationQueueMessageType } from "./models/notificationQueueMessageType";
export default class NotificationBackground {
private notificationQueue: (AddLoginQueueMessage | AddChangePasswordQueueMessage)[] = [];
private notificationQueue: (AddLoginQueueMessage | AddChangePasswordQueueMessage)[] = [];
constructor(
private main: MainBackground,
private autofillService: AutofillService,
private cipherService: CipherService,
private storageService: StorageService,
private vaultTimeoutService: VaultTimeoutService,
private policyService: PolicyService,
private folderService: FolderService,
private userService: UserService
) {}
constructor(private main: MainBackground, private autofillService: AutofillService,
private cipherService: CipherService, private storageService: StorageService,
private vaultTimeoutService: VaultTimeoutService, private policyService: PolicyService,
private folderService: FolderService, private userService: UserService) {
async init() {
if (chrome.runtime == null) {
return;
}
async init() {
if (chrome.runtime == null) {
return;
BrowserApi.messageListener(
"notification.background",
async (msg: any, sender: chrome.runtime.MessageSender) => {
await this.processMessage(msg, sender);
}
);
this.cleanupNotificationQueue();
}
async processMessage(msg: any, sender: chrome.runtime.MessageSender) {
switch (msg.command) {
case "unlockCompleted":
if (msg.data.target !== "notification.background") {
return;
}
BrowserApi.messageListener('notification.background', async (msg: any, sender: chrome.runtime.MessageSender) => {
await this.processMessage(msg, sender);
});
this.cleanupNotificationQueue();
}
async processMessage(msg: any, sender: chrome.runtime.MessageSender) {
switch (msg.command) {
case 'unlockCompleted':
if (msg.data.target !== 'notification.background') {
return;
}
await this.processMessage(msg.data.commandToRetry.msg, msg.data.commandToRetry.sender);
break;
case 'bgGetDataForTab':
await this.getDataForTab(sender.tab, msg.responseCommand);
break;
case 'bgCloseNotificationBar':
await BrowserApi.tabSendMessageData(sender.tab, 'closeNotificationBar');
break;
case 'bgAdjustNotificationBar':
await BrowserApi.tabSendMessageData(sender.tab, 'adjustNotificationBar', msg.data);
break;
case 'bgAddLogin':
await this.addLogin(msg.login, sender.tab);
break;
case 'bgChangedPassword':
await this.changedPassword(msg.data, sender.tab);
break;
case 'bgAddClose':
case 'bgChangeClose':
this.removeTabFromNotificationQueue(sender.tab);
break;
case 'bgAddSave':
case 'bgChangeSave':
if (await this.vaultTimeoutService.isLocked()) {
const retryMessage: LockedVaultPendingNotificationsItem = {
commandToRetry: {
msg: msg,
sender: sender,
},
target: 'notification.background',
};
await BrowserApi.tabSendMessageData(sender.tab, 'addToLockedVaultPendingNotifications', retryMessage);
await BrowserApi.tabSendMessageData(sender.tab, 'promptForLogin');
return;
}
await this.saveOrUpdateCredentials(sender.tab, msg.folder);
break;
case 'bgNeverSave':
await this.saveNever(sender.tab);
break;
case 'collectPageDetailsResponse':
switch (msg.sender) {
case 'notificationBar':
const forms = this.autofillService.getFormsWithPasswordFields(msg.details);
await BrowserApi.tabSendMessageData(msg.tab, 'notificationBarPageDetails', {
details: msg.details,
forms: forms,
});
break;
default:
break;
}
break;
default:
break;
await this.processMessage(msg.data.commandToRetry.msg, msg.data.commandToRetry.sender);
break;
case "bgGetDataForTab":
await this.getDataForTab(sender.tab, msg.responseCommand);
break;
case "bgCloseNotificationBar":
await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar");
break;
case "bgAdjustNotificationBar":
await BrowserApi.tabSendMessageData(sender.tab, "adjustNotificationBar", msg.data);
break;
case "bgAddLogin":
await this.addLogin(msg.login, sender.tab);
break;
case "bgChangedPassword":
await this.changedPassword(msg.data, sender.tab);
break;
case "bgAddClose":
case "bgChangeClose":
this.removeTabFromNotificationQueue(sender.tab);
break;
case "bgAddSave":
case "bgChangeSave":
if (await this.vaultTimeoutService.isLocked()) {
const retryMessage: LockedVaultPendingNotificationsItem = {
commandToRetry: {
msg: msg,
sender: sender,
},
target: "notification.background",
};
await BrowserApi.tabSendMessageData(
sender.tab,
"addToLockedVaultPendingNotifications",
retryMessage
);
await BrowserApi.tabSendMessageData(sender.tab, "promptForLogin");
return;
}
}
async checkNotificationQueue(tab: chrome.tabs.Tab = null): Promise<void> {
if (this.notificationQueue.length === 0) {
return;
}
if (tab != null) {
this.doNotificationQueueCheck(tab);
return;
}
const currentTab = await BrowserApi.getTabFromCurrentWindow();
if (currentTab != null) {
this.doNotificationQueueCheck(currentTab);
}
}
private cleanupNotificationQueue() {
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
if (this.notificationQueue[i].expires < new Date()) {
this.notificationQueue.splice(i, 1);
}
}
setTimeout(() => this.cleanupNotificationQueue(), 2 * 60 * 1000); // check every 2 minutes
}
private doNotificationQueueCheck(tab: chrome.tabs.Tab): void {
if (tab == null) {
return;
}
const tabDomain = Utils.getDomain(tab.url);
if (tabDomain == null) {
return;
}
for (let i = 0; i < this.notificationQueue.length; i++) {
if (this.notificationQueue[i].tabId !== tab.id || this.notificationQueue[i].domain !== tabDomain) {
continue;
}
if (this.notificationQueue[i].type === NotificationQueueMessageType.addLogin) {
BrowserApi.tabSendMessageData(tab, 'openNotificationBar', {
type: 'add',
typeData: {
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
},
});
} else if (this.notificationQueue[i].type === NotificationQueueMessageType.changePassword) {
BrowserApi.tabSendMessageData(tab, 'openNotificationBar', {
type: 'change',
typeData: {
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
},
});
}
await this.saveOrUpdateCredentials(sender.tab, msg.folder);
break;
case "bgNeverSave":
await this.saveNever(sender.tab);
break;
case "collectPageDetailsResponse":
switch (msg.sender) {
case "notificationBar":
const forms = this.autofillService.getFormsWithPasswordFields(msg.details);
await BrowserApi.tabSendMessageData(msg.tab, "notificationBarPageDetails", {
details: msg.details,
forms: forms,
});
break;
default:
break;
}
break;
default:
break;
}
}
async checkNotificationQueue(tab: chrome.tabs.Tab = null): Promise<void> {
if (this.notificationQueue.length === 0) {
return;
}
private removeTabFromNotificationQueue(tab: chrome.tabs.Tab) {
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
if (this.notificationQueue[i].tabId === tab.id) {
this.notificationQueue.splice(i, 1);
}
}
if (tab != null) {
this.doNotificationQueueCheck(tab);
return;
}
private async addLogin(loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab) {
if (!await this.userService.isAuthenticated()) {
return;
}
const currentTab = await BrowserApi.getTabFromCurrentWindow();
if (currentTab != null) {
this.doNotificationQueueCheck(currentTab);
}
}
const loginDomain = Utils.getDomain(loginInfo.url);
if (loginDomain == null) {
return;
}
private cleanupNotificationQueue() {
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
if (this.notificationQueue[i].expires < new Date()) {
this.notificationQueue.splice(i, 1);
}
}
setTimeout(() => this.cleanupNotificationQueue(), 2 * 60 * 1000); // check every 2 minutes
}
let normalizedUsername = loginInfo.username;
if (normalizedUsername != null) {
normalizedUsername = normalizedUsername.toLowerCase();
}
const disabledAddLogin = await this.storageService.get<boolean>(ConstantsService.disableAddLoginNotificationKey);
if (await this.vaultTimeoutService.isLocked()) {
if (disabledAddLogin) {
return;
}
if (!await this.allowPersonalOwnership()) {
return;
}
this.pushAddLoginToQueue(loginDomain, loginInfo, tab, true);
return;
}
const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url);
const usernameMatches = ciphers.filter(c =>
c.login.username != null && c.login.username.toLowerCase() === normalizedUsername);
if (usernameMatches.length === 0) {
if (disabledAddLogin) {
return;
}
if (!await this.allowPersonalOwnership()) {
return;
}
this.pushAddLoginToQueue(loginDomain, loginInfo, tab);
} else if (usernameMatches.length === 1 && usernameMatches[0].login.password !== loginInfo.password) {
const disabledChangePassword = await this.storageService.get<boolean>(
ConstantsService.disableChangedPasswordNotificationKey);
if (disabledChangePassword) {
return;
}
this.pushChangePasswordToQueue(usernameMatches[0].id, loginDomain, loginInfo.password, tab);
}
private doNotificationQueueCheck(tab: chrome.tabs.Tab): void {
if (tab == null) {
return;
}
private async pushAddLoginToQueue(loginDomain: string, loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab, isVaultLocked: boolean = false) {
// remove any old messages for this tab
this.removeTabFromNotificationQueue(tab);
const message: AddLoginQueueMessage = {
type: NotificationQueueMessageType.addLogin,
username: loginInfo.username,
password: loginInfo.password,
domain: loginDomain,
uri: loginInfo.url,
tabId: tab.id,
expires: new Date((new Date()).getTime() + 5 * 60000), // 5 minutes
wasVaultLocked: isVaultLocked,
};
this.notificationQueue.push(message);
await this.checkNotificationQueue(tab);
const tabDomain = Utils.getDomain(tab.url);
if (tabDomain == null) {
return;
}
private async changedPassword(changeData: ChangePasswordRuntimeMessage, tab: chrome.tabs.Tab) {
const loginDomain = Utils.getDomain(changeData.url);
if (loginDomain == null) {
return;
}
for (let i = 0; i < this.notificationQueue.length; i++) {
if (
this.notificationQueue[i].tabId !== tab.id ||
this.notificationQueue[i].domain !== tabDomain
) {
continue;
}
if (await this.vaultTimeoutService.isLocked()) {
this.pushChangePasswordToQueue(null, loginDomain, changeData.newPassword, tab, true);
return;
}
if (this.notificationQueue[i].type === NotificationQueueMessageType.addLogin) {
BrowserApi.tabSendMessageData(tab, "openNotificationBar", {
type: "add",
typeData: {
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
},
});
} else if (this.notificationQueue[i].type === NotificationQueueMessageType.changePassword) {
BrowserApi.tabSendMessageData(tab, "openNotificationBar", {
type: "change",
typeData: {
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
},
});
}
break;
}
}
let id: string = null;
const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url);
if (changeData.currentPassword != null) {
const passwordMatches = ciphers.filter(c => c.login.password === changeData.currentPassword);
if (passwordMatches.length === 1) {
id = passwordMatches[0].id;
}
} else if (ciphers.length === 1) {
id = ciphers[0].id;
}
if (id != null) {
this.pushChangePasswordToQueue(id, loginDomain, changeData.newPassword, tab);
}
private removeTabFromNotificationQueue(tab: chrome.tabs.Tab) {
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
if (this.notificationQueue[i].tabId === tab.id) {
this.notificationQueue.splice(i, 1);
}
}
}
private async addLogin(loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab) {
if (!(await this.userService.isAuthenticated())) {
return;
}
private async pushChangePasswordToQueue(cipherId: string, loginDomain: string, newPassword: string, tab: chrome.tabs.Tab, isVaultLocked: boolean = false) {
// remove any old messages for this tab
this.removeTabFromNotificationQueue(tab);
const message: AddChangePasswordQueueMessage = {
type: NotificationQueueMessageType.changePassword,
cipherId: cipherId,
newPassword: newPassword,
domain: loginDomain,
tabId: tab.id,
expires: new Date((new Date()).getTime() + 5 * 60000), // 5 minutes
wasVaultLocked: isVaultLocked,
};
this.notificationQueue.push(message);
await this.checkNotificationQueue(tab);
const loginDomain = Utils.getDomain(loginInfo.url);
if (loginDomain == null) {
return;
}
private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, folderId?: string) {
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
const queueMessage = this.notificationQueue[i];
if (queueMessage.tabId !== tab.id ||
(queueMessage.type !== NotificationQueueMessageType.addLogin && queueMessage.type !== NotificationQueueMessageType.changePassword)) {
continue;
}
let normalizedUsername = loginInfo.username;
if (normalizedUsername != null) {
normalizedUsername = normalizedUsername.toLowerCase();
}
const tabDomain = Utils.getDomain(tab.url);
if (tabDomain != null && tabDomain !== queueMessage.domain) {
continue;
}
const disabledAddLogin = await this.storageService.get<boolean>(
ConstantsService.disableAddLoginNotificationKey
);
if (await this.vaultTimeoutService.isLocked()) {
if (disabledAddLogin) {
return;
}
this.notificationQueue.splice(i, 1);
BrowserApi.tabSendMessageData(tab, 'closeNotificationBar');
if (!(await this.allowPersonalOwnership())) {
return;
}
if (queueMessage.type === NotificationQueueMessageType.changePassword) {
const message = (queueMessage as AddChangePasswordQueueMessage);
const cipher = await this.getDecryptedCipherById(message.cipherId);
if (cipher == null) {
return;
}
await this.updateCipher(cipher, message.newPassword);
return;
}
this.pushAddLoginToQueue(loginDomain, loginInfo, tab, true);
return;
}
if (!queueMessage.wasVaultLocked) {
await this.createNewCipher(queueMessage as AddLoginQueueMessage, folderId);
}
const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url);
const usernameMatches = ciphers.filter(
(c) => c.login.username != null && c.login.username.toLowerCase() === normalizedUsername
);
if (usernameMatches.length === 0) {
if (disabledAddLogin) {
return;
}
// If the vault was locked, check if a cipher needs updating instead of creating a new one
if (queueMessage.type === NotificationQueueMessageType.addLogin && queueMessage.wasVaultLocked === true) {
const message = (queueMessage as AddLoginQueueMessage);
const ciphers = await this.cipherService.getAllDecryptedForUrl(message.uri);
const usernameMatches = ciphers.filter(c => c.login.username != null &&
c.login.username.toLowerCase() === message.username);
if (!(await this.allowPersonalOwnership())) {
return;
}
if (usernameMatches.length >= 1) {
await this.updateCipher(usernameMatches[0], message.password);
return;
}
this.pushAddLoginToQueue(loginDomain, loginInfo, tab);
} else if (
usernameMatches.length === 1 &&
usernameMatches[0].login.password !== loginInfo.password
) {
const disabledChangePassword = await this.storageService.get<boolean>(
ConstantsService.disableChangedPasswordNotificationKey
);
if (disabledChangePassword) {
return;
}
this.pushChangePasswordToQueue(usernameMatches[0].id, loginDomain, loginInfo.password, tab);
}
}
await this.createNewCipher(message, folderId);
}
private async pushAddLoginToQueue(
loginDomain: string,
loginInfo: AddLoginRuntimeMessage,
tab: chrome.tabs.Tab,
isVaultLocked: boolean = false
) {
// remove any old messages for this tab
this.removeTabFromNotificationQueue(tab);
const message: AddLoginQueueMessage = {
type: NotificationQueueMessageType.addLogin,
username: loginInfo.username,
password: loginInfo.password,
domain: loginDomain,
uri: loginInfo.url,
tabId: tab.id,
expires: new Date(new Date().getTime() + 5 * 60000), // 5 minutes
wasVaultLocked: isVaultLocked,
};
this.notificationQueue.push(message);
await this.checkNotificationQueue(tab);
}
private async changedPassword(changeData: ChangePasswordRuntimeMessage, tab: chrome.tabs.Tab) {
const loginDomain = Utils.getDomain(changeData.url);
if (loginDomain == null) {
return;
}
if (await this.vaultTimeoutService.isLocked()) {
this.pushChangePasswordToQueue(null, loginDomain, changeData.newPassword, tab, true);
return;
}
let id: string = null;
const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url);
if (changeData.currentPassword != null) {
const passwordMatches = ciphers.filter(
(c) => c.login.password === changeData.currentPassword
);
if (passwordMatches.length === 1) {
id = passwordMatches[0].id;
}
} else if (ciphers.length === 1) {
id = ciphers[0].id;
}
if (id != null) {
this.pushChangePasswordToQueue(id, loginDomain, changeData.newPassword, tab);
}
}
private async pushChangePasswordToQueue(
cipherId: string,
loginDomain: string,
newPassword: string,
tab: chrome.tabs.Tab,
isVaultLocked: boolean = false
) {
// remove any old messages for this tab
this.removeTabFromNotificationQueue(tab);
const message: AddChangePasswordQueueMessage = {
type: NotificationQueueMessageType.changePassword,
cipherId: cipherId,
newPassword: newPassword,
domain: loginDomain,
tabId: tab.id,
expires: new Date(new Date().getTime() + 5 * 60000), // 5 minutes
wasVaultLocked: isVaultLocked,
};
this.notificationQueue.push(message);
await this.checkNotificationQueue(tab);
}
private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, folderId?: string) {
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
const queueMessage = this.notificationQueue[i];
if (
queueMessage.tabId !== tab.id ||
(queueMessage.type !== NotificationQueueMessageType.addLogin &&
queueMessage.type !== NotificationQueueMessageType.changePassword)
) {
continue;
}
const tabDomain = Utils.getDomain(tab.url);
if (tabDomain != null && tabDomain !== queueMessage.domain) {
continue;
}
this.notificationQueue.splice(i, 1);
BrowserApi.tabSendMessageData(tab, "closeNotificationBar");
if (queueMessage.type === NotificationQueueMessageType.changePassword) {
const message = queueMessage as AddChangePasswordQueueMessage;
const cipher = await this.getDecryptedCipherById(message.cipherId);
if (cipher == null) {
return;
}
}
await this.updateCipher(cipher, message.newPassword);
return;
}
private async createNewCipher(queueMessage: AddLoginQueueMessage, folderId: string) {
const loginModel = new LoginView();
const loginUri = new LoginUriView();
loginUri.uri = queueMessage.uri;
loginModel.uris = [loginUri];
loginModel.username = queueMessage.username;
loginModel.password = queueMessage.password;
const model = new CipherView();
model.name = Utils.getHostname(queueMessage.uri) || queueMessage.domain;
model.name = model.name.replace(/^www\./, '');
model.type = CipherType.Login;
model.login = loginModel;
if (!queueMessage.wasVaultLocked) {
await this.createNewCipher(queueMessage as AddLoginQueueMessage, folderId);
}
if (!Utils.isNullOrWhitespace(folderId)) {
const folders = await this.folderService.getAllDecrypted();
if (folders.some(x => x.id === folderId)) {
model.folderId = folderId;
}
// If the vault was locked, check if a cipher needs updating instead of creating a new one
if (
queueMessage.type === NotificationQueueMessageType.addLogin &&
queueMessage.wasVaultLocked === true
) {
const message = queueMessage as AddLoginQueueMessage;
const ciphers = await this.cipherService.getAllDecryptedForUrl(message.uri);
const usernameMatches = ciphers.filter(
(c) => c.login.username != null && c.login.username.toLowerCase() === message.username
);
if (usernameMatches.length >= 1) {
await this.updateCipher(usernameMatches[0], message.password);
return;
}
const cipher = await this.cipherService.encrypt(model);
await this.cipherService.saveWithServer(cipher);
await this.createNewCipher(message, folderId);
}
}
}
private async createNewCipher(queueMessage: AddLoginQueueMessage, folderId: string) {
const loginModel = new LoginView();
const loginUri = new LoginUriView();
loginUri.uri = queueMessage.uri;
loginModel.uris = [loginUri];
loginModel.username = queueMessage.username;
loginModel.password = queueMessage.password;
const model = new CipherView();
model.name = Utils.getHostname(queueMessage.uri) || queueMessage.domain;
model.name = model.name.replace(/^www\./, "");
model.type = CipherType.Login;
model.login = loginModel;
if (!Utils.isNullOrWhitespace(folderId)) {
const folders = await this.folderService.getAllDecrypted();
if (folders.some((x) => x.id === folderId)) {
model.folderId = folderId;
}
}
private async getDecryptedCipherById(cipherId: string) {
const cipher = await this.cipherService.get(cipherId);
if (cipher != null && cipher.type === CipherType.Login) {
return await cipher.decrypt();
}
return null;
const cipher = await this.cipherService.encrypt(model);
await this.cipherService.saveWithServer(cipher);
}
private async getDecryptedCipherById(cipherId: string) {
const cipher = await this.cipherService.get(cipherId);
if (cipher != null && cipher.type === CipherType.Login) {
return await cipher.decrypt();
}
return null;
}
private async updateCipher(cipher: CipherView, newPassword: string) {
if (cipher != null && cipher.type === CipherType.Login) {
cipher.login.password = newPassword;
const newCipher = await this.cipherService.encrypt(cipher);
await this.cipherService.saveWithServer(newCipher);
}
}
private async saveNever(tab: chrome.tabs.Tab) {
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
const queueMessage = this.notificationQueue[i];
if (
queueMessage.tabId !== tab.id ||
queueMessage.type !== NotificationQueueMessageType.addLogin
) {
continue;
}
const tabDomain = Utils.getDomain(tab.url);
if (tabDomain != null && tabDomain !== queueMessage.domain) {
continue;
}
this.notificationQueue.splice(i, 1);
BrowserApi.tabSendMessageData(tab, "closeNotificationBar");
const hostname = Utils.getHostname(tab.url);
await this.cipherService.saveNeverDomain(hostname);
}
}
private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) {
const responseData: any = {};
if (responseCommand === "notificationBarGetFoldersList") {
responseData.folders = await this.folderService.getAllDecrypted();
}
private async updateCipher(cipher: CipherView, newPassword: string) {
if (cipher != null && cipher.type === CipherType.Login) {
cipher.login.password = newPassword;
const newCipher = await this.cipherService.encrypt(cipher);
await this.cipherService.saveWithServer(newCipher);
}
}
await BrowserApi.tabSendMessageData(tab, responseCommand, responseData);
}
private async saveNever(tab: chrome.tabs.Tab) {
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
const queueMessage = this.notificationQueue[i];
if (queueMessage.tabId !== tab.id || queueMessage.type !== NotificationQueueMessageType.addLogin) {
continue;
}
const tabDomain = Utils.getDomain(tab.url);
if (tabDomain != null && tabDomain !== queueMessage.domain) {
continue;
}
this.notificationQueue.splice(i, 1);
BrowserApi.tabSendMessageData(tab, 'closeNotificationBar');
const hostname = Utils.getHostname(tab.url);
await this.cipherService.saveNeverDomain(hostname);
}
}
private async getDataForTab(tab: chrome.tabs.Tab, responseCommand: string) {
const responseData: any = {};
if (responseCommand === 'notificationBarGetFoldersList') {
responseData.folders = await this.folderService.getAllDecrypted();
}
await BrowserApi.tabSendMessageData(tab, responseCommand, responseData);
}
private async allowPersonalOwnership(): Promise<boolean> {
return !await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership);
}
private async allowPersonalOwnership(): Promise<boolean> {
return !(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership));
}
}

View File

@ -1,219 +1,248 @@
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { NotificationsService } from 'jslib-common/abstractions/notifications.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SystemService } from 'jslib-common/abstractions/system.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { NotificationsService } from "jslib-common/abstractions/notifications.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { SystemService } from "jslib-common/abstractions/system.service";
import { ConstantsService } from "jslib-common/services/constants.service";
import { AutofillService } from '../services/abstractions/autofill.service';
import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service';
import { AutofillService } from "../services/abstractions/autofill.service";
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
import { BrowserApi } from '../browser/browserApi';
import { BrowserApi } from "../browser/browserApi";
import MainBackground from './main.background';
import MainBackground from "./main.background";
import { Utils } from 'jslib-common/misc/utils';
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
import { Utils } from "jslib-common/misc/utils";
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
export default class RuntimeBackground {
private autofillTimeout: any;
private pageDetailsToAutoFill: any[] = [];
private onInstalledReason: string = null;
private lockedVaultPendingNotifications: LockedVaultPendingNotificationsItem[] = [];
private autofillTimeout: any;
private pageDetailsToAutoFill: any[] = [];
private onInstalledReason: string = null;
private lockedVaultPendingNotifications: LockedVaultPendingNotificationsItem[] = [];
constructor(private main: MainBackground, private autofillService: AutofillService,
private platformUtilsService: BrowserPlatformUtilsService,
private storageService: StorageService, private i18nService: I18nService,
private notificationsService: NotificationsService, private systemService: SystemService,
private environmentService: EnvironmentService, private messagingService: MessagingService,
private logService: LogService) {
constructor(
private main: MainBackground,
private autofillService: AutofillService,
private platformUtilsService: BrowserPlatformUtilsService,
private storageService: StorageService,
private i18nService: I18nService,
private notificationsService: NotificationsService,
private systemService: SystemService,
private environmentService: EnvironmentService,
private messagingService: MessagingService,
private logService: LogService
) {
// onInstalled listener must be wired up before anything else, so we do it in the ctor
chrome.runtime.onInstalled.addListener((details: any) => {
this.onInstalledReason = details.reason;
});
}
// onInstalled listener must be wired up before anything else, so we do it in the ctor
chrome.runtime.onInstalled.addListener((details: any) => {
this.onInstalledReason = details.reason;
});
async init() {
if (!chrome.runtime) {
return;
}
async init() {
if (!chrome.runtime) {
return;
await this.checkOnInstalled();
BrowserApi.messageListener(
"runtime.background",
async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
await this.processMessage(msg, sender, sendResponse);
}
);
}
async processMessage(msg: any, sender: any, sendResponse: any) {
switch (msg.command) {
case "loggedIn":
case "unlocked":
let item: LockedVaultPendingNotificationsItem;
if (this.lockedVaultPendingNotifications.length > 0) {
await BrowserApi.closeLoginTab();
item = this.lockedVaultPendingNotifications.pop();
if (item.commandToRetry.sender?.tab?.id) {
await BrowserApi.focusSpecifiedTab(item.commandToRetry.sender.tab.id);
}
}
await this.checkOnInstalled();
BrowserApi.messageListener('runtime.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
await this.processMessage(msg, sender, sendResponse);
});
}
await this.main.setIcon();
await this.main.refreshBadgeAndMenu(false);
this.notificationsService.updateConnection(msg.command === "unlocked");
this.systemService.cancelProcessReload();
async processMessage(msg: any, sender: any, sendResponse: any) {
switch (msg.command) {
case 'loggedIn':
case 'unlocked':
let item: LockedVaultPendingNotificationsItem;
if (this.lockedVaultPendingNotifications.length > 0) {
await BrowserApi.closeLoginTab();
item = this.lockedVaultPendingNotifications.pop();
if (item.commandToRetry.sender?.tab?.id) {
await BrowserApi.focusSpecifiedTab(item.commandToRetry.sender.tab.id);
}
}
await this.main.setIcon();
await this.main.refreshBadgeAndMenu(false);
this.notificationsService.updateConnection(msg.command === 'unlocked');
this.systemService.cancelProcessReload();
if (item) {
await BrowserApi.tabSendMessageData(item.commandToRetry.sender.tab, 'unlockCompleted', item);
}
break;
case 'addToLockedVaultPendingNotifications':
this.lockedVaultPendingNotifications.push(msg.data);
break;
case 'logout':
await this.main.logout(msg.expired);
break;
case 'syncCompleted':
if (msg.successfully) {
setTimeout(async () => await this.main.refreshBadgeAndMenu(), 2000);
}
break;
case 'openPopup':
await this.main.openPopup();
break;
case 'promptForLogin':
await BrowserApi.createNewTab('popup/index.html?uilocation=popout', true, true);
break;
case 'showDialogResolve':
this.platformUtilsService.resolveDialogPromise(msg.dialogId, msg.confirmed);
break;
case 'bgCollectPageDetails':
await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId);
break;
case 'bgUpdateContextMenu':
case 'editedCipher':
case 'addedCipher':
case 'deletedCipher':
await this.main.refreshBadgeAndMenu();
break;
case 'bgReseedStorage':
await this.main.reseedStorage();
break;
case 'collectPageDetailsResponse':
switch (msg.sender) {
case 'autofiller':
case 'autofill_cmd':
const totpCode = await this.autofillService.doAutoFillActiveTab([{
frameId: sender.frameId,
tab: msg.tab,
details: msg.details,
}], msg.sender === 'autofill_cmd');
if (totpCode != null) {
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
}
break;
case 'contextMenu':
clearTimeout(this.autofillTimeout);
this.pageDetailsToAutoFill.push({
frameId: sender.frameId,
tab: msg.tab,
details: msg.details,
});
this.autofillTimeout = setTimeout(async () => await this.autofillPage(), 300);
break;
default:
break;
}
break;
case 'authResult':
const vaultUrl = this.environmentService.getWebVaultUrl();
if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) {
return;
}
try {
BrowserApi.createNewTab('popup/index.html?uilocation=popout#/sso?code=' +
encodeURIComponent(msg.code) + '&state=' + encodeURIComponent(msg.state));
}
catch {
this.logService.error('Unable to open sso popout tab');
}
break;
case 'webAuthnResult':
const vaultUrl2 = this.environmentService.getWebVaultUrl();
if (msg.referrer == null || Utils.getHostname(vaultUrl2) !== msg.referrer) {
return;
}
const params = `webAuthnResponse=${encodeURIComponent(msg.data)};` +
`remember=${encodeURIComponent(msg.remember)}`;
BrowserApi.createNewTab(`popup/index.html?uilocation=popout#/2fa;${params}`, undefined, false);
break;
case 'reloadPopup':
this.messagingService.send('reloadPopup');
break;
case 'emailVerificationRequired':
this.messagingService.send('showDialog', {
dialogId: 'emailVerificationRequired',
title: this.i18nService.t('emailVerificationRequired'),
text: this.i18nService.t('emailVerificationRequiredDesc'),
confirmText: this.i18nService.t('ok'),
type: 'info',
});
break;
case 'getClickedElementResponse':
this.platformUtilsService.copyToClipboard(msg.identifier, { window: window });
default:
break;
if (item) {
await BrowserApi.tabSendMessageData(
item.commandToRetry.sender.tab,
"unlockCompleted",
item
);
}
}
private async autofillPage() {
const totpCode = await this.autofillService.doAutoFill({
cipher: this.main.loginToAutoFill,
pageDetails: this.pageDetailsToAutoFill,
fillNewPassword: true,
});
if (totpCode != null) {
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
break;
case "addToLockedVaultPendingNotifications":
this.lockedVaultPendingNotifications.push(msg.data);
break;
case "logout":
await this.main.logout(msg.expired);
break;
case "syncCompleted":
if (msg.successfully) {
setTimeout(async () => await this.main.refreshBadgeAndMenu(), 2000);
}
// reset
this.main.loginToAutoFill = null;
this.pageDetailsToAutoFill = [];
}
private async checkOnInstalled() {
setTimeout(async () => {
if (this.onInstalledReason != null) {
if (this.onInstalledReason === 'install') {
BrowserApi.createNewTab('https://bitwarden.com/browser-start/');
await this.setDefaultSettings();
}
this.onInstalledReason = null;
break;
case "openPopup":
await this.main.openPopup();
break;
case "promptForLogin":
await BrowserApi.createNewTab("popup/index.html?uilocation=popout", true, true);
break;
case "showDialogResolve":
this.platformUtilsService.resolveDialogPromise(msg.dialogId, msg.confirmed);
break;
case "bgCollectPageDetails":
await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId);
break;
case "bgUpdateContextMenu":
case "editedCipher":
case "addedCipher":
case "deletedCipher":
await this.main.refreshBadgeAndMenu();
break;
case "bgReseedStorage":
await this.main.reseedStorage();
break;
case "collectPageDetailsResponse":
switch (msg.sender) {
case "autofiller":
case "autofill_cmd":
const totpCode = await this.autofillService.doAutoFillActiveTab(
[
{
frameId: sender.frameId,
tab: msg.tab,
details: msg.details,
},
],
msg.sender === "autofill_cmd"
);
if (totpCode != null) {
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
}
}, 100);
}
break;
case "contextMenu":
clearTimeout(this.autofillTimeout);
this.pageDetailsToAutoFill.push({
frameId: sender.frameId,
tab: msg.tab,
details: msg.details,
});
this.autofillTimeout = setTimeout(async () => await this.autofillPage(), 300);
break;
default:
break;
}
break;
case "authResult":
const vaultUrl = this.environmentService.getWebVaultUrl();
private async setDefaultSettings() {
// Default timeout option to "on restart".
const currentVaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
if (currentVaultTimeout == null) {
await this.storageService.save(ConstantsService.vaultTimeoutKey, -1);
if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) {
return;
}
// Default action to "lock".
const currentVaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
if (currentVaultTimeoutAction == null) {
await this.storageService.save(ConstantsService.vaultTimeoutActionKey, 'lock');
try {
BrowserApi.createNewTab(
"popup/index.html?uilocation=popout#/sso?code=" +
encodeURIComponent(msg.code) +
"&state=" +
encodeURIComponent(msg.state)
);
} catch {
this.logService.error("Unable to open sso popout tab");
}
break;
case "webAuthnResult":
const vaultUrl2 = this.environmentService.getWebVaultUrl();
if (msg.referrer == null || Utils.getHostname(vaultUrl2) !== msg.referrer) {
return;
}
const params =
`webAuthnResponse=${encodeURIComponent(msg.data)};` +
`remember=${encodeURIComponent(msg.remember)}`;
BrowserApi.createNewTab(
`popup/index.html?uilocation=popout#/2fa;${params}`,
undefined,
false
);
break;
case "reloadPopup":
this.messagingService.send("reloadPopup");
break;
case "emailVerificationRequired":
this.messagingService.send("showDialog", {
dialogId: "emailVerificationRequired",
title: this.i18nService.t("emailVerificationRequired"),
text: this.i18nService.t("emailVerificationRequiredDesc"),
confirmText: this.i18nService.t("ok"),
type: "info",
});
break;
case "getClickedElementResponse":
this.platformUtilsService.copyToClipboard(msg.identifier, { window: window });
default:
break;
}
}
private async autofillPage() {
const totpCode = await this.autofillService.doAutoFill({
cipher: this.main.loginToAutoFill,
pageDetails: this.pageDetailsToAutoFill,
fillNewPassword: true,
});
if (totpCode != null) {
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
}
// reset
this.main.loginToAutoFill = null;
this.pageDetailsToAutoFill = [];
}
private async checkOnInstalled() {
setTimeout(async () => {
if (this.onInstalledReason != null) {
if (this.onInstalledReason === "install") {
BrowserApi.createNewTab("https://bitwarden.com/browser-start/");
await this.setDefaultSettings();
}
this.onInstalledReason = null;
}
}, 100);
}
private async setDefaultSettings() {
// Default timeout option to "on restart".
const currentVaultTimeout = await this.storageService.get<number>(
ConstantsService.vaultTimeoutKey
);
if (currentVaultTimeout == null) {
await this.storageService.save(ConstantsService.vaultTimeoutKey, -1);
}
// Default action to "lock".
const currentVaultTimeoutAction = await this.storageService.get<string>(
ConstantsService.vaultTimeoutActionKey
);
if (currentVaultTimeoutAction == null) {
await this.storageService.save(ConstantsService.vaultTimeoutActionKey, "lock");
}
}
}

View File

@ -1,41 +1,45 @@
import MainBackground from './main.background';
import NotificationBackground from './notification.background';
import MainBackground from "./main.background";
import NotificationBackground from "./notification.background";
export default class TabsBackground {
constructor(private main: MainBackground, private notificationBackground: NotificationBackground) {
constructor(
private main: MainBackground,
private notificationBackground: NotificationBackground
) {}
async init() {
if (!chrome.tabs) {
return;
}
async init() {
if (!chrome.tabs) {
return;
chrome.tabs.onActivated.addListener(async (activeInfo: chrome.tabs.TabActiveInfo) => {
await this.main.refreshBadgeAndMenu();
this.main.messagingService.send("tabActivated");
this.main.messagingService.send("tabChanged");
});
chrome.tabs.onReplaced.addListener(async (addedTabId: number, removedTabId: number) => {
if (this.main.onReplacedRan) {
return;
}
this.main.onReplacedRan = true;
await this.notificationBackground.checkNotificationQueue();
await this.main.refreshBadgeAndMenu();
this.main.messagingService.send("tabReplaced");
this.main.messagingService.send("tabChanged");
});
chrome.tabs.onUpdated.addListener(
async (tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => {
if (this.main.onUpdatedRan) {
return;
}
chrome.tabs.onActivated.addListener(async (activeInfo: chrome.tabs.TabActiveInfo) => {
await this.main.refreshBadgeAndMenu();
this.main.messagingService.send('tabActivated');
this.main.messagingService.send('tabChanged');
});
chrome.tabs.onReplaced.addListener(async (addedTabId: number, removedTabId: number) => {
if (this.main.onReplacedRan) {
return;
}
this.main.onReplacedRan = true;
await this.notificationBackground.checkNotificationQueue();
await this.main.refreshBadgeAndMenu();
this.main.messagingService.send('tabReplaced');
this.main.messagingService.send('tabChanged');
});
chrome.tabs.onUpdated.addListener(async (tabId: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => {
if (this.main.onUpdatedRan) {
return;
}
this.main.onUpdatedRan = true;
await this.notificationBackground.checkNotificationQueue(tab);
await this.main.refreshBadgeAndMenu();
this.main.messagingService.send('tabUpdated');
this.main.messagingService.send('tabChanged');
});
}
this.main.onUpdatedRan = true;
await this.notificationBackground.checkNotificationQueue(tab);
await this.main.refreshBadgeAndMenu();
this.main.messagingService.send("tabUpdated");
this.main.messagingService.send("tabChanged");
}
);
}
}

View File

@ -1,78 +1,94 @@
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { UriMatchType } from 'jslib-common/enums/uriMatchType';
import { UriMatchType } from "jslib-common/enums/uriMatchType";
export default class WebRequestBackground {
private pendingAuthRequests: any[] = [];
private webRequest: any;
private isFirefox: boolean;
private pendingAuthRequests: any[] = [];
private webRequest: any;
private isFirefox: boolean;
constructor(platformUtilsService: PlatformUtilsService, private cipherService: CipherService,
private vaultTimeoutService: VaultTimeoutService) {
this.webRequest = (window as any).chrome.webRequest;
this.isFirefox = platformUtilsService.isFirefox();
constructor(
platformUtilsService: PlatformUtilsService,
private cipherService: CipherService,
private vaultTimeoutService: VaultTimeoutService
) {
this.webRequest = (window as any).chrome.webRequest;
this.isFirefox = platformUtilsService.isFirefox();
}
async init() {
if (!this.webRequest || !this.webRequest.onAuthRequired) {
return;
}
async init() {
if (!this.webRequest || !this.webRequest.onAuthRequired) {
return;
this.webRequest.onAuthRequired.addListener(
async (details: any, callback: any) => {
if (!details.url || this.pendingAuthRequests.indexOf(details.requestId) !== -1) {
if (callback) {
callback();
}
return;
}
this.webRequest.onAuthRequired.addListener(async (details: any, callback: any) => {
if (!details.url || this.pendingAuthRequests.indexOf(details.requestId) !== -1) {
if (callback) {
callback();
}
return;
}
this.pendingAuthRequests.push(details.requestId);
this.pendingAuthRequests.push(details.requestId);
if (this.isFirefox) {
return new Promise(async (resolve, reject) => {
await this.resolveAuthCredentials(details.url, resolve, reject);
});
} else {
await this.resolveAuthCredentials(details.url, callback, callback);
}
},
{ urls: ["http://*/*", "https://*/*"] },
[this.isFirefox ? "blocking" : "asyncBlocking"]
);
if (this.isFirefox) {
return new Promise(async (resolve, reject) => {
await this.resolveAuthCredentials(details.url, resolve, reject);
});
} else {
await this.resolveAuthCredentials(details.url, callback, callback);
}
}, { urls: ['http://*/*', 'https://*/*'] }, [this.isFirefox ? 'blocking' : 'asyncBlocking']);
this.webRequest.onCompleted.addListener((details: any) => this.completeAuthRequest(details), {
urls: ["http://*/*"],
});
this.webRequest.onErrorOccurred.addListener(
(details: any) => this.completeAuthRequest(details),
{
urls: ["http://*/*"],
}
);
}
this.webRequest.onCompleted.addListener(
(details: any) => this.completeAuthRequest(details), { urls: ['http://*/*'] });
this.webRequest.onErrorOccurred.addListener(
(details: any) => this.completeAuthRequest(details), { urls: ['http://*/*'] });
private async resolveAuthCredentials(domain: string, success: Function, error: Function) {
if (await this.vaultTimeoutService.isLocked()) {
error();
return;
}
private async resolveAuthCredentials(domain: string, success: Function, error: Function) {
if (await this.vaultTimeoutService.isLocked()) {
error();
return;
}
try {
const ciphers = await this.cipherService.getAllDecryptedForUrl(
domain,
null,
UriMatchType.Host
);
if (ciphers == null || ciphers.length !== 1) {
error();
return;
}
try {
const ciphers = await this.cipherService.getAllDecryptedForUrl(domain, null, UriMatchType.Host);
if (ciphers == null || ciphers.length !== 1) {
error();
return;
}
success({
authCredentials: {
username: ciphers[0].login.username,
password: ciphers[0].login.password,
},
});
} catch {
error();
}
success({
authCredentials: {
username: ciphers[0].login.username,
password: ciphers[0].login.password,
},
});
} catch {
error();
}
}
private completeAuthRequest(details: any) {
const i = this.pendingAuthRequests.indexOf(details.requestId);
if (i > -1) {
this.pendingAuthRequests.splice(i, 1);
}
private completeAuthRequest(details: any) {
const i = this.pendingAuthRequests.indexOf(details.requestId);
if (i > -1) {
this.pendingAuthRequests.splice(i, 1);
}
}
}

View File

@ -1,25 +1,25 @@
import MainBackground from './main.background';
import MainBackground from "./main.background";
export default class WindowsBackground {
private windows: any;
private windows: any;
constructor(private main: MainBackground) {
this.windows = chrome.windows;
constructor(private main: MainBackground) {
this.windows = chrome.windows;
}
async init() {
if (!this.windows) {
return;
}
async init() {
if (!this.windows) {
return;
}
this.windows.onFocusChanged.addListener(async (windowId: any) => {
if (windowId === null || windowId < 0) {
return;
}
this.windows.onFocusChanged.addListener(async (windowId: any) => {
if (windowId === null || windowId < 0) {
return;
}
await this.main.refreshBadgeAndMenu();
this.main.messagingService.send('windowFocused');
this.main.messagingService.send('windowChanged');
});
}
await this.main.refreshBadgeAndMenu();
this.main.messagingService.send("windowFocused");
this.main.messagingService.send("windowChanged");
});
}
}

View File

@ -1,208 +1,228 @@
import { SafariApp } from './safariApp';
import { SafariApp } from "./safariApp";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
export class BrowserApi {
static isWebExtensionsApi: boolean = (typeof browser !== 'undefined');
static isSafariApi: boolean = navigator.userAgent.indexOf(' Safari/') !== -1 &&
navigator.userAgent.indexOf(' Chrome/') === -1 &&
navigator.userAgent.indexOf(' Chromium/') === -1;
static isChromeApi: boolean = !BrowserApi.isSafariApi && (typeof chrome !== 'undefined');
static isFirefoxOnAndroid: boolean = navigator.userAgent.indexOf('Firefox/') !== -1 &&
navigator.userAgent.indexOf('Android') !== -1;
static isWebExtensionsApi: boolean = typeof browser !== "undefined";
static isSafariApi: boolean =
navigator.userAgent.indexOf(" Safari/") !== -1 &&
navigator.userAgent.indexOf(" Chrome/") === -1 &&
navigator.userAgent.indexOf(" Chromium/") === -1;
static isChromeApi: boolean = !BrowserApi.isSafariApi && typeof chrome !== "undefined";
static isFirefoxOnAndroid: boolean =
navigator.userAgent.indexOf("Firefox/") !== -1 && navigator.userAgent.indexOf("Android") !== -1;
static async getTabFromCurrentWindowId(): Promise<chrome.tabs.Tab> | null {
return await BrowserApi.tabsQueryFirst({
active: true,
windowId: chrome.windows.WINDOW_ID_CURRENT,
});
static async getTabFromCurrentWindowId(): Promise<chrome.tabs.Tab> | null {
return await BrowserApi.tabsQueryFirst({
active: true,
windowId: chrome.windows.WINDOW_ID_CURRENT,
});
}
static async getTabFromCurrentWindow(): Promise<chrome.tabs.Tab> | null {
return await BrowserApi.tabsQueryFirst({
active: true,
currentWindow: true,
});
}
static async getActiveTabs(): Promise<chrome.tabs.Tab[]> {
return await BrowserApi.tabsQuery({
active: true,
});
}
static async tabsQuery(options: chrome.tabs.QueryInfo): Promise<chrome.tabs.Tab[]> {
return new Promise((resolve) => {
chrome.tabs.query(options, (tabs: any[]) => {
resolve(tabs);
});
});
}
static async tabsQueryFirst(options: chrome.tabs.QueryInfo): Promise<chrome.tabs.Tab> | null {
const tabs = await BrowserApi.tabsQuery(options);
if (tabs.length > 0) {
return tabs[0];
}
static async getTabFromCurrentWindow(): Promise<chrome.tabs.Tab> | null {
return await BrowserApi.tabsQueryFirst({
active: true,
currentWindow: true,
});
return null;
}
static tabSendMessageData(
tab: chrome.tabs.Tab,
command: string,
data: any = null
): Promise<any[]> {
const obj: any = {
command: command,
};
if (data != null) {
obj.data = data;
}
static async getActiveTabs(): Promise<chrome.tabs.Tab[]> {
return await BrowserApi.tabsQuery({
active: true,
});
return BrowserApi.tabSendMessage(tab, obj);
}
static async tabSendMessage(
tab: chrome.tabs.Tab,
obj: any,
options: chrome.tabs.MessageSendOptions = null
): Promise<any> {
if (!tab || !tab.id) {
return;
}
static async tabsQuery(options: chrome.tabs.QueryInfo): Promise<chrome.tabs.Tab[]> {
return new Promise(resolve => {
chrome.tabs.query(options, (tabs: any[]) => {
resolve(tabs);
});
});
}
static async tabsQueryFirst(options: chrome.tabs.QueryInfo): Promise<chrome.tabs.Tab> | null {
const tabs = await BrowserApi.tabsQuery(options);
if (tabs.length > 0) {
return tabs[0];
return new Promise<void>((resolve) => {
chrome.tabs.sendMessage(tab.id, obj, options, () => {
if (chrome.runtime.lastError) {
// Some error happened
}
resolve();
});
});
}
return null;
static getBackgroundPage(): any {
return chrome.extension.getBackgroundPage();
}
static getApplicationVersion(): string {
return chrome.runtime.getManifest().version;
}
static async isPopupOpen(): Promise<boolean> {
return Promise.resolve(chrome.extension.getViews({ type: "popup" }).length > 0);
}
static createNewTab(url: string, extensionPage: boolean = false, active: boolean = true) {
chrome.tabs.create({ url: url, active: active });
}
static messageListener(
name: string,
callback: (message: any, sender: chrome.runtime.MessageSender, response: any) => void
) {
chrome.runtime.onMessage.addListener(
(msg: any, sender: chrome.runtime.MessageSender, response: any) => {
callback(msg, sender, response);
}
);
}
static async closeLoginTab() {
const tabs = await BrowserApi.tabsQuery({
active: true,
title: "Bitwarden",
windowType: "normal",
currentWindow: true,
});
if (tabs.length === 0) {
return;
}
static tabSendMessageData(tab: chrome.tabs.Tab, command: string, data: any = null): Promise<any[]> {
const obj: any = {
command: command,
};
const tabToClose = tabs[tabs.length - 1].id;
chrome.tabs.remove(tabToClose);
}
if (data != null) {
obj.data = data;
}
static async focusSpecifiedTab(tabId: number) {
chrome.tabs.update(tabId, { active: true, highlighted: true });
}
return BrowserApi.tabSendMessage(tab, obj);
static closePopup(win: Window) {
if (BrowserApi.isWebExtensionsApi && BrowserApi.isFirefoxOnAndroid) {
// Reactivating the active tab dismisses the popup tab. The promise final
// condition is only called if the popup wasn't already dismissed (future proofing).
// ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1433604
browser.tabs.update({ active: true }).finally(win.close);
} else {
win.close();
}
}
static async tabSendMessage(tab: chrome.tabs.Tab, obj: any, options: chrome.tabs.MessageSendOptions = null): Promise<any> {
if (!tab || !tab.id) {
return;
}
return new Promise<void>(resolve => {
chrome.tabs.sendMessage(tab.id, obj, options, () => {
if (chrome.runtime.lastError) {
// Some error happened
}
resolve();
});
});
static downloadFile(win: Window, blobData: any, blobOptions: any, fileName: string) {
if (BrowserApi.isSafariApi) {
const type = blobOptions != null ? blobOptions.type : null;
let data: string = null;
if (type === "text/plain" && typeof blobData === "string") {
data = blobData;
} else {
data = Utils.fromBufferToB64(blobData);
}
SafariApp.sendMessageToApp(
"downloadFile",
JSON.stringify({
blobData: data,
blobOptions: blobOptions,
fileName: fileName,
}),
true
);
} else {
const blob = new Blob([blobData], blobOptions);
if (navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, fileName);
} else {
const a = win.document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = fileName;
win.document.body.appendChild(a);
a.click();
win.document.body.removeChild(a);
}
}
}
static getBackgroundPage(): any {
return chrome.extension.getBackgroundPage();
static gaFilter() {
return process.env.ENV !== "production";
}
static getUILanguage(win: Window) {
return chrome.i18n.getUILanguage();
}
static reloadExtension(win: Window) {
if (win != null) {
return win.location.reload(true);
} else {
return chrome.runtime.reload();
}
}
static getApplicationVersion(): string {
return chrome.runtime.getManifest().version;
static reloadOpenWindows() {
const views = chrome.extension.getViews() as Window[];
views
.filter((w) => w.location.href != null)
.forEach((w) => {
w.location.reload();
});
}
static connectNative(application: string): browser.runtime.Port | chrome.runtime.Port {
if (BrowserApi.isWebExtensionsApi) {
return browser.runtime.connectNative(application);
} else if (BrowserApi.isChromeApi) {
return chrome.runtime.connectNative(application);
}
}
static async isPopupOpen(): Promise<boolean> {
return Promise.resolve(chrome.extension.getViews({ type: 'popup' }).length > 0);
static requestPermission(permission: any) {
if (BrowserApi.isWebExtensionsApi) {
return browser.permissions.request(permission);
}
return new Promise((resolve, reject) => {
chrome.permissions.request(permission, resolve);
});
}
static createNewTab(url: string, extensionPage: boolean = false, active: boolean = true) {
chrome.tabs.create({ url: url, active: active });
}
static messageListener(name: string, callback: (message: any, sender: chrome.runtime.MessageSender, response: any) => void) {
chrome.runtime.onMessage.addListener((msg: any, sender: chrome.runtime.MessageSender, response: any) => {
callback(msg, sender, response);
});
}
static async closeLoginTab() {
const tabs = await BrowserApi.tabsQuery({
active: true,
title: 'Bitwarden',
windowType: 'normal',
currentWindow: true,
});
if (tabs.length === 0) {
return;
}
const tabToClose = tabs[tabs.length - 1].id;
chrome.tabs.remove(tabToClose);
}
static async focusSpecifiedTab(tabId: number) {
chrome.tabs.update(tabId, { active: true, highlighted: true });
}
static closePopup(win: Window) {
if (BrowserApi.isWebExtensionsApi && BrowserApi.isFirefoxOnAndroid) {
// Reactivating the active tab dismisses the popup tab. The promise final
// condition is only called if the popup wasn't already dismissed (future proofing).
// ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1433604
browser.tabs.update({ active: true }).finally(win.close);
} else {
win.close();
}
}
static downloadFile(win: Window, blobData: any, blobOptions: any, fileName: string) {
if (BrowserApi.isSafariApi) {
const type = blobOptions != null ? blobOptions.type : null;
let data: string = null;
if (type === 'text/plain' && typeof (blobData) === 'string') {
data = blobData;
} else {
data = Utils.fromBufferToB64(blobData);
}
SafariApp.sendMessageToApp('downloadFile', JSON.stringify({
blobData: data,
blobOptions: blobOptions,
fileName: fileName,
}), true);
} else {
const blob = new Blob([blobData], blobOptions);
if (navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, fileName);
} else {
const a = win.document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = fileName;
win.document.body.appendChild(a);
a.click();
win.document.body.removeChild(a);
}
}
}
static gaFilter() {
return process.env.ENV !== 'production';
}
static getUILanguage(win: Window) {
return chrome.i18n.getUILanguage();
}
static reloadExtension(win: Window) {
if (win != null) {
return win.location.reload(true);
} else {
return chrome.runtime.reload();
}
}
static reloadOpenWindows() {
const views = chrome.extension.getViews() as Window[];
views.filter(w => w.location.href != null).forEach(w => {
w.location.reload();
});
}
static connectNative(application: string): browser.runtime.Port | chrome.runtime.Port {
if (BrowserApi.isWebExtensionsApi) {
return browser.runtime.connectNative(application);
} else if (BrowserApi.isChromeApi) {
return chrome.runtime.connectNative(application);
}
}
static requestPermission(permission: any) {
if (BrowserApi.isWebExtensionsApi) {
return browser.permissions.request(permission);
}
return new Promise((resolve, reject) => {
chrome.permissions.request(permission, resolve);
});
}
static getPlatformInfo(): Promise<browser.runtime.PlatformInfo | chrome.runtime.PlatformInfo> {
if (BrowserApi.isWebExtensionsApi) {
return browser.runtime.getPlatformInfo();
}
return new Promise(resolve => {
chrome.runtime.getPlatformInfo(resolve);
});
static getPlatformInfo(): Promise<browser.runtime.PlatformInfo | chrome.runtime.PlatformInfo> {
if (BrowserApi.isWebExtensionsApi) {
return browser.runtime.getPlatformInfo();
}
return new Promise((resolve) => {
chrome.runtime.getPlatformInfo(resolve);
});
}
}

View File

@ -1,21 +1,26 @@
import { BrowserApi } from './browserApi';
import { BrowserApi } from "./browserApi";
export class SafariApp {
static sendMessageToApp(command: string, data: any = null, resolveNow = false): Promise<any> {
if (!BrowserApi.isSafariApi) {
return Promise.resolve(null);
}
return new Promise(resolve => {
const now = new Date();
const messageId = now.getTime().toString() + '_' + Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
(browser as any).runtime.sendNativeMessage('com.bitwarden.desktop', {
id: messageId,
command: command,
data: data,
responseData: null,
}, (response: any) => {
resolve(response);
});
});
static sendMessageToApp(command: string, data: any = null, resolveNow = false): Promise<any> {
if (!BrowserApi.isSafariApi) {
return Promise.resolve(null);
}
return new Promise((resolve) => {
const now = new Date();
const messageId =
now.getTime().toString() + "_" + Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
(browser as any).runtime.sendNativeMessage(
"com.bitwarden.desktop",
{
id: messageId,
command: command,
data: data,
responseData: null,
},
(response: any) => {
resolve(response);
}
);
});
}
}

View File

@ -1,36 +1,36 @@
@-webkit-keyframes bitwardenfill {
0% {
-webkit-transform: scale(1.0, 1.0);
}
0% {
-webkit-transform: scale(1, 1);
}
50% {
-webkit-transform: scale(1.2, 1.2);
}
50% {
-webkit-transform: scale(1.2, 1.2);
}
100% {
-webkit-transform: scale(1.0, 1.0);
}
100% {
-webkit-transform: scale(1, 1);
}
}
@-moz-keyframes bitwardenfill {
0% {
transform: scale(1.0, 1.0);
}
0% {
transform: scale(1, 1);
}
50% {
transform: scale(1.2, 1.2);
}
50% {
transform: scale(1.2, 1.2);
}
100% {
transform: scale(1.0, 1.0);
}
100% {
transform: scale(1, 1);
}
}
span[data-bwautofill].com-bitwarden-browser-animated-fill {
display: inline-block;
display: inline-block;
}
.com-bitwarden-browser-animated-fill {
animation: bitwardenfill 200ms ease-in-out 0ms 1;
-webkit-animation: bitwardenfill 200ms ease-in-out 0ms 1;
animation: bitwardenfill 200ms ease-in-out 0ms 1;
-webkit-animation: bitwardenfill 200ms ease-in-out 0ms 1;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,43 +1,43 @@
document.addEventListener('DOMContentLoaded', event => {
let pageHref: string = null;
let filledThisHref = false;
let delayFillTimeout: number;
document.addEventListener("DOMContentLoaded", (event) => {
let pageHref: string = null;
let filledThisHref = false;
let delayFillTimeout: number;
const enabledKey = 'enableAutoFillOnPageLoad';
chrome.storage.local.get(enabledKey, (obj: any) => {
if (obj != null && obj[enabledKey] === true) {
setInterval(() => doFillIfNeeded(), 500);
}
});
chrome.runtime.onMessage.addListener((msg: any, sender: any, sendResponse: Function) => {
if (msg.command === 'fillForm' && pageHref === msg.url) {
filledThisHref = true;
}
});
function doFillIfNeeded(force: boolean = false) {
if (force || pageHref !== window.location.href) {
if (!force) {
// Some websites are slow and rendering all page content. Try to fill again later
// if we haven't already.
filledThisHref = false;
if (delayFillTimeout != null) {
window.clearTimeout(delayFillTimeout);
}
delayFillTimeout = window.setTimeout(() => {
if (!filledThisHref) {
doFillIfNeeded(true);
}
}, 1500);
}
pageHref = window.location.href;
const msg: any = {
command: 'bgCollectPageDetails',
sender: 'autofiller',
};
chrome.runtime.sendMessage(msg);
}
const enabledKey = "enableAutoFillOnPageLoad";
chrome.storage.local.get(enabledKey, (obj: any) => {
if (obj != null && obj[enabledKey] === true) {
setInterval(() => doFillIfNeeded(), 500);
}
});
chrome.runtime.onMessage.addListener((msg: any, sender: any, sendResponse: Function) => {
if (msg.command === "fillForm" && pageHref === msg.url) {
filledThisHref = true;
}
});
function doFillIfNeeded(force: boolean = false) {
if (force || pageHref !== window.location.href) {
if (!force) {
// Some websites are slow and rendering all page content. Try to fill again later
// if we haven't already.
filledThisHref = false;
if (delayFillTimeout != null) {
window.clearTimeout(delayFillTimeout);
}
delayFillTimeout = window.setTimeout(() => {
if (!filledThisHref) {
doFillIfNeeded(true);
}
}, 1500);
}
pageHref = window.location.href;
const msg: any = {
command: "bgCollectPageDetails",
sender: "autofiller",
};
chrome.runtime.sendMessage(msg);
}
}
});

View File

@ -1,66 +1,66 @@
const inputTags = ['input', 'textarea', 'select'];
const labelTags = ['label', 'span'];
const attributes = ['id', 'name', 'label-aria', 'placeholder'];
const invalidElement = chrome.i18n.getMessage('copyCustomFieldNameInvalidElement');
const noUniqueIdentifier = chrome.i18n.getMessage('copyCustomFieldNameNotUnique');
const inputTags = ["input", "textarea", "select"];
const labelTags = ["label", "span"];
const attributes = ["id", "name", "label-aria", "placeholder"];
const invalidElement = chrome.i18n.getMessage("copyCustomFieldNameInvalidElement");
const noUniqueIdentifier = chrome.i18n.getMessage("copyCustomFieldNameNotUnique");
let clickedEl: HTMLElement = null;
// Find the best attribute to be used as the Name for an element in a custom field.
function getClickedElementIdentifier() {
if (clickedEl == null) {
return invalidElement;
}
if (clickedEl == null) {
return invalidElement;
}
const clickedTag = clickedEl.nodeName.toLowerCase();
let inputEl = null;
const clickedTag = clickedEl.nodeName.toLowerCase();
let inputEl = null;
// Try to identify the input element (which may not be the clicked element)
if (labelTags.includes(clickedTag)) {
let inputId = null;
if (clickedTag === 'label') {
inputId = clickedEl.getAttribute('for');
} else {
inputId = clickedEl.closest('label')?.getAttribute('for');
}
inputEl = document.getElementById(inputId);
// Try to identify the input element (which may not be the clicked element)
if (labelTags.includes(clickedTag)) {
let inputId = null;
if (clickedTag === "label") {
inputId = clickedEl.getAttribute("for");
} else {
inputEl = clickedEl;
inputId = clickedEl.closest("label")?.getAttribute("for");
}
if (inputEl == null || !inputTags.includes(inputEl.nodeName.toLowerCase())) {
return invalidElement;
}
inputEl = document.getElementById(inputId);
} else {
inputEl = clickedEl;
}
for (const attr of attributes) {
const attributeValue = inputEl.getAttribute(attr);
const selector = '[' + attr + '="' + attributeValue + '"]';
if (!isNullOrEmpty(attributeValue) && document.querySelectorAll(selector)?.length === 1) {
return attributeValue;
}
if (inputEl == null || !inputTags.includes(inputEl.nodeName.toLowerCase())) {
return invalidElement;
}
for (const attr of attributes) {
const attributeValue = inputEl.getAttribute(attr);
const selector = "[" + attr + '="' + attributeValue + '"]';
if (!isNullOrEmpty(attributeValue) && document.querySelectorAll(selector)?.length === 1) {
return attributeValue;
}
return noUniqueIdentifier;
}
return noUniqueIdentifier;
}
function isNullOrEmpty(s: string) {
return s == null || s === '';
return s == null || s === "";
}
// We only have access to the element that's been clicked when the context menu is first opened.
// Remember it for use later.
document.addEventListener('contextmenu', event => {
clickedEl = event.target as HTMLElement;
document.addEventListener("contextmenu", (event) => {
clickedEl = event.target as HTMLElement;
});
// Runs when the 'Copy Custom Field Name' context menu item is actually clicked.
chrome.runtime.onMessage.addListener(event => {
if (event.command === 'getClickedElement') {
const identifier = getClickedElementIdentifier();
chrome.runtime.sendMessage({
command: 'getClickedElementResponse',
sender: 'contextMenuHandler',
identifier: identifier,
});
}
chrome.runtime.onMessage.addListener((event) => {
if (event.command === "getClickedElement") {
const identifier = getClickedElementIdentifier();
chrome.runtime.sendMessage({
command: "getClickedElementResponse",
sender: "contextMenuHandler",
identifier: identifier,
});
}
});

View File

@ -1,30 +1,37 @@
window.addEventListener('message', event => {
if (event.source !== window)
return;
window.addEventListener(
"message",
(event) => {
if (event.source !== window) return;
if (event.data.command && (event.data.command === 'authResult')) {
chrome.runtime.sendMessage({
command: event.data.command,
code: event.data.code,
state: event.data.state,
referrer: event.source.location.hostname,
});
if (event.data.command && event.data.command === "authResult") {
chrome.runtime.sendMessage({
command: event.data.command,
code: event.data.code,
state: event.data.state,
referrer: event.source.location.hostname,
});
}
if (event.data.command && (event.data.command === 'webAuthnResult')) {
chrome.runtime.sendMessage({
command: event.data.command,
data: event.data.data,
remember: event.data.remember,
referrer: event.source.location.hostname,
});
if (event.data.command && event.data.command === "webAuthnResult") {
chrome.runtime.sendMessage({
command: event.data.command,
data: event.data.data,
remember: event.data.remember,
referrer: event.source.location.hostname,
});
}
}, false);
},
false
);
const forwardCommands = ['promptForLogin', 'addToLockedVaultPendingNotifications', 'unlockCompleted'];
const forwardCommands = [
"promptForLogin",
"addToLockedVaultPendingNotifications",
"unlockCompleted",
];
chrome.runtime.onMessage.addListener(event => {
if (forwardCommands.includes(event.command)) {
chrome.runtime.sendMessage(event);
}
chrome.runtime.onMessage.addListener((event) => {
if (forwardCommands.includes(event.command)) {
chrome.runtime.sendMessage(event);
}
});

File diff suppressed because it is too large Load Diff

View File

@ -1,50 +1,52 @@
import * as Mousetrap from 'mousetrap';
import * as Mousetrap from "mousetrap";
document.addEventListener('DOMContentLoaded', event => {
const isSafari = (typeof safari !== 'undefined') && navigator.userAgent.indexOf(' Safari/') !== -1 &&
navigator.userAgent.indexOf('Chrome') === -1;
const isVivaldi = !isSafari && navigator.userAgent.indexOf(' Vivaldi/') !== -1;
document.addEventListener("DOMContentLoaded", (event) => {
const isSafari =
typeof safari !== "undefined" &&
navigator.userAgent.indexOf(" Safari/") !== -1 &&
navigator.userAgent.indexOf("Chrome") === -1;
const isVivaldi = !isSafari && navigator.userAgent.indexOf(" Vivaldi/") !== -1;
if (!isSafari && !isVivaldi) {
return;
}
if (!isSafari && !isVivaldi) {
return;
}
if (isSafari && (window as any).__bitwardenFrameId == null) {
(window as any).__bitwardenFrameId = Math.floor(Math.random() * Math.floor(99999999));
}
if (isSafari && (window as any).__bitwardenFrameId == null) {
(window as any).__bitwardenFrameId = Math.floor(Math.random() * Math.floor(99999999));
}
Mousetrap.prototype.stopCallback = () => {
return false;
};
Mousetrap.prototype.stopCallback = () => {
return false;
};
let autofillCommand = ['mod+shift+l'];
if (isSafari) {
autofillCommand = ['mod+\\', 'mod+8', 'mod+shift+p'];
}
Mousetrap.bind(autofillCommand, () => {
sendMessage('autofill_login');
let autofillCommand = ["mod+shift+l"];
if (isSafari) {
autofillCommand = ["mod+\\", "mod+8", "mod+shift+p"];
}
Mousetrap.bind(autofillCommand, () => {
sendMessage("autofill_login");
});
if (isSafari) {
Mousetrap.bind("mod+shift+y", () => {
sendMessage("open_popup");
});
if (isSafari) {
Mousetrap.bind('mod+shift+y', () => {
sendMessage('open_popup');
});
Mousetrap.bind("mod+shift+s", () => {
sendMessage("lock_vault");
});
} else {
Mousetrap.bind("mod+shift+9", () => {
sendMessage("generate_password");
});
}
Mousetrap.bind('mod+shift+s', () => {
sendMessage('lock_vault');
});
} else {
Mousetrap.bind('mod+shift+9', () => {
sendMessage('generate_password');
});
}
function sendMessage(shortcut: string) {
const msg: any = {
command: "keyboardShortcutTriggered",
shortcut: shortcut,
};
function sendMessage(shortcut: string) {
const msg: any = {
command: 'keyboardShortcutTriggered',
shortcut: shortcut,
};
chrome.runtime.sendMessage(msg);
}
chrome.runtime.sendMessage(msg);
}
});

View File

@ -23,47 +23,25 @@
"content/notificationBar.js",
"content/contextMenuHandler.js"
],
"matches": [
"http://*/*",
"https://*/*",
"file:///*"
],
"matches": ["http://*/*", "https://*/*", "file:///*"],
"run_at": "document_start"
},
{
"all_frames": false,
"js": [
"content/shortcuts.js"
],
"matches": [
"http://*/*",
"https://*/*",
"file:///*"
],
"js": ["content/shortcuts.js"],
"matches": ["http://*/*", "https://*/*", "file:///*"],
"run_at": "document_start"
},
{
"all_frames": false,
"js": [
"content/message_handler.js"
],
"matches": [
"http://*/*",
"https://*/*",
"file:///*"
],
"js": ["content/message_handler.js"],
"matches": ["http://*/*", "https://*/*", "file:///*"],
"run_at": "document_start"
},
{
"all_frames": true,
"css": [
"content/autofill.css"
],
"matches": [
"http://*/*",
"https://*/*",
"file:///*"
],
"css": ["content/autofill.css"],
"matches": ["http://*/*", "https://*/*", "file:///*"],
"run_at": "document_end"
}
],
@ -92,9 +70,7 @@
"webRequest",
"webRequestBlocking"
],
"optional_permissions": [
"nativeMessaging"
],
"optional_permissions": ["nativeMessaging"],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"commands": {
"_execute_browser_action": {
@ -144,4 +120,4 @@
"default_panel": "popup/index.html?uilocation=sidebar",
"default_icon": "images/icon19.png"
}
}
}

View File

@ -1,25 +1,25 @@
export default class AutofillField {
opid: string;
elementNumber: number;
visible: boolean;
viewable: boolean;
htmlID: string;
htmlName: string;
htmlClass: string;
'label-left': string;
'label-right': string;
'label-top': string;
'label-tag': string;
'label-aria': string;
placeholder: string;
type: string;
value: string;
disabled: boolean;
readonly: boolean;
onePasswordFieldType: string;
form: string;
autoCompleteType: string;
selectInfo: any;
maxLength: number;
tagName: string;
opid: string;
elementNumber: number;
visible: boolean;
viewable: boolean;
htmlID: string;
htmlName: string;
htmlClass: string;
"label-left": string;
"label-right": string;
"label-top": string;
"label-tag": string;
"label-aria": string;
placeholder: string;
type: string;
value: string;
disabled: boolean;
readonly: boolean;
onePasswordFieldType: string;
form: string;
autoCompleteType: string;
selectInfo: any;
maxLength: number;
tagName: string;
}

View File

@ -1,7 +1,7 @@
export default class AutofillForm {
opid: string;
htmlName: string;
htmlID: string;
htmlAction: string;
htmlMethod: string;
opid: string;
htmlName: string;
htmlID: string;
htmlAction: string;
htmlMethod: string;
}

View File

@ -1,13 +1,13 @@
import AutofillField from './autofillField';
import AutofillForm from './autofillForm';
import AutofillField from "./autofillField";
import AutofillForm from "./autofillForm";
export default class AutofillPageDetails {
documentUUID: string;
title: string;
url: string;
documentUrl: string;
tabUrl: string;
forms: { [id: string]: AutofillForm; };
fields: AutofillField[];
collectedTimestamp: number;
documentUUID: string;
title: string;
url: string;
documentUrl: string;
tabUrl: string;
forms: { [id: string]: AutofillForm };
fields: AutofillField[];
collectedTimestamp: number;
}

View File

@ -1,12 +1,12 @@
export default class AutofillScript {
script: string[][] = [];
documentUUID: any = {};
properties: any = {};
options: any = {};
metadata: any = {};
autosubmit: any = null;
script: string[][] = [];
documentUUID: any = {};
properties: any = {};
options: any = {};
metadata: any = {};
autosubmit: any = null;
constructor(documentUUID: string) {
this.documentUUID = documentUUID;
}
constructor(documentUUID: string) {
this.documentUUID = documentUUID;
}
}

View File

@ -1,41 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<head>
<title>Bitwarden</title>
<meta charset="utf-8" />
</head>
</head>
<body>
<body>
<div class="outer-wrapper">
<div class="logo">
<a href="https://vault.bitwarden.com" target="_blank" id="logo-link">
<img id="logo" alt="Bitwarden" />
</a>
</div>
<div id="content"></div>
<div>
<button type="button" class="neutral" id="close-button">
<img id="close" alt="Close" />
</button>
</div>
<div class="logo">
<a href="https://vault.bitwarden.com" target="_blank" id="logo-link">
<img id="logo" alt="Bitwarden" />
</a>
</div>
<div id="content"></div>
<div>
<button type="button" class="neutral" id="close-button">
<img id="close" alt="Close" />
</button>
</div>
</div>
<div id="templates" style="display: none;">
<div class="inner-wrapper" id="template-add">
<div class="add-text"></div>
<div class="add-buttons">
<button type="button" class="never-save link"></button>
<select class="select-folder" isVaultLocked="false"></select>
<button type="button" class="add-save"></button>
</div>
<div id="templates" style="display: none">
<div class="inner-wrapper" id="template-add">
<div class="add-text"></div>
<div class="add-buttons">
<button type="button" class="never-save link"></button>
<select class="select-folder" isVaultLocked="false"></select>
<button type="button" class="add-save"></button>
</div>
<div class="inner-wrapper" id="template-change">
<div class="change-text"></div>
<div class="change-buttons">
<button type="button" class="change-save"></button>
</div>
</div>
<div class="inner-wrapper" id="template-change">
<div class="change-text"></div>
<div class="change-buttons">
<button type="button" class="change-save"></button>
</div>
</div>
</div>
</body>
</body>
</html>

View File

@ -1,159 +1,160 @@
require('./bar.scss');
require("./bar.scss");
document.addEventListener('DOMContentLoaded', () => {
var i18n = {};
var lang = window.navigator.language;
document.addEventListener("DOMContentLoaded", () => {
var i18n = {};
var lang = window.navigator.language;
i18n.appName = chrome.i18n.getMessage('appName');
i18n.close = chrome.i18n.getMessage('close');
i18n.never = chrome.i18n.getMessage('never');
i18n.folder = chrome.i18n.getMessage('folder');
i18n.notificationAddSave = chrome.i18n.getMessage('notificationAddSave');
i18n.notificationAddDesc = chrome.i18n.getMessage('notificationAddDesc');
i18n.notificationChangeSave = chrome.i18n.getMessage('notificationChangeSave');
i18n.notificationChangeDesc = chrome.i18n.getMessage('notificationChangeDesc');
lang = chrome.i18n.getUILanguage();
i18n.appName = chrome.i18n.getMessage("appName");
i18n.close = chrome.i18n.getMessage("close");
i18n.never = chrome.i18n.getMessage("never");
i18n.folder = chrome.i18n.getMessage("folder");
i18n.notificationAddSave = chrome.i18n.getMessage("notificationAddSave");
i18n.notificationAddDesc = chrome.i18n.getMessage("notificationAddDesc");
i18n.notificationChangeSave = chrome.i18n.getMessage("notificationChangeSave");
i18n.notificationChangeDesc = chrome.i18n.getMessage("notificationChangeDesc");
lang = chrome.i18n.getUILanguage();
// delay 50ms so that we get proper body dimensions
setTimeout(load, 50);
// delay 50ms so that we get proper body dimensions
setTimeout(load, 50);
function load() {
const isVaultLocked = getQueryVariable('isVaultLocked') == 'true';
document.getElementById('logo').src = isVaultLocked
? chrome.runtime.getURL('images/icon38_locked.png')
: chrome.runtime.getURL('images/icon38.png');
function load() {
const isVaultLocked = getQueryVariable("isVaultLocked") == "true";
document.getElementById("logo").src = isVaultLocked
? chrome.runtime.getURL("images/icon38_locked.png")
: chrome.runtime.getURL("images/icon38.png");
document.getElementById('logo-link').title = i18n.appName;
document.getElementById("logo-link").title = i18n.appName;
var neverButton = document.querySelector('#template-add .never-save');
neverButton.textContent = i18n.never;
var neverButton = document.querySelector("#template-add .never-save");
neverButton.textContent = i18n.never;
var selectFolder = document.querySelector('#template-add .select-folder');
selectFolder.setAttribute('aria-label', i18n.folder);
selectFolder.setAttribute('isVaultLocked', isVaultLocked.toString());
var selectFolder = document.querySelector("#template-add .select-folder");
selectFolder.setAttribute("aria-label", i18n.folder);
selectFolder.setAttribute("isVaultLocked", isVaultLocked.toString());
var addButton = document.querySelector('#template-add .add-save');
addButton.textContent = i18n.notificationAddSave;
var addButton = document.querySelector("#template-add .add-save");
addButton.textContent = i18n.notificationAddSave;
var changeButton = document.querySelector('#template-change .change-save');
changeButton.textContent = i18n.notificationChangeSave;
var changeButton = document.querySelector("#template-change .change-save");
changeButton.textContent = i18n.notificationChangeSave;
var closeIcon = document.getElementById('close');
closeIcon.src = chrome.runtime.getURL('images/close.png');
closeIcon.alt = i18n.close;
var closeIcon = document.getElementById("close");
closeIcon.src = chrome.runtime.getURL("images/close.png");
closeIcon.alt = i18n.close;
var closeButton = document.getElementById('close-button')
closeButton.title = i18n.close;
closeButton.setAttribute('aria-label', i18n.close);
var closeButton = document.getElementById("close-button");
closeButton.title = i18n.close;
closeButton.setAttribute("aria-label", i18n.close);
document.querySelector('#template-add .add-text').textContent = i18n.notificationAddDesc;
document.querySelector('#template-change .change-text').textContent = i18n.notificationChangeDesc;
document.querySelector("#template-add .add-text").textContent = i18n.notificationAddDesc;
document.querySelector("#template-change .change-text").textContent =
i18n.notificationChangeDesc;
if (getQueryVariable('add')) {
setContent(document.getElementById('template-add'));
if (getQueryVariable("add")) {
setContent(document.getElementById("template-add"));
var addButton = document.querySelector('#template-add-clone .add-save'),
neverButton = document.querySelector('#template-add-clone .never-save');
var addButton = document.querySelector("#template-add-clone .add-save"),
neverButton = document.querySelector("#template-add-clone .never-save");
addButton.addEventListener('click', (e) => {
e.preventDefault();
addButton.addEventListener("click", (e) => {
e.preventDefault();
const folderId = document.querySelector('#template-add-clone .select-folder').value;
const folderId = document.querySelector("#template-add-clone .select-folder").value;
const bgAddSaveMessage = {
command: 'bgAddSave',
folder: folderId,
};
sendPlatformMessage(bgAddSaveMessage);
});
const bgAddSaveMessage = {
command: "bgAddSave",
folder: folderId,
};
sendPlatformMessage(bgAddSaveMessage);
});
neverButton.addEventListener('click', (e) => {
e.preventDefault();
sendPlatformMessage({
command: 'bgNeverSave'
});
});
if (!isVaultLocked) {
const responseFoldersCommand = 'notificationBarGetFoldersList';
chrome.runtime.onMessage.addListener((msg) => {
if (msg.command === responseFoldersCommand && msg.data) {
fillSelectorWithFolders(msg.data.folders);
}
});
sendPlatformMessage({
command: 'bgGetDataForTab',
responseCommand: responseFoldersCommand
});
}
} else if (getQueryVariable('change')) {
setContent(document.getElementById('template-change'));
var changeButton = document.querySelector('#template-change-clone .change-save');
changeButton.addEventListener('click', (e) => {
e.preventDefault();
const bgChangeSaveMessage = {
command: 'bgChangeSave'
};
sendPlatformMessage(bgChangeSaveMessage);
});
}
closeButton.addEventListener('click', (e) => {
e.preventDefault();
sendPlatformMessage({
command: 'bgCloseNotificationBar'
});
});
window.addEventListener("resize", adjustHeight);
adjustHeight();
}
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (pair[0] === variable) {
return pair[1];
}
}
return null;
}
function setContent(element) {
const content = document.getElementById('content');
while (content.firstChild) {
content.removeChild(content.firstChild);
}
var newElement = element.cloneNode(true);
newElement.id = newElement.id + '-clone';
content.appendChild(newElement);
}
function sendPlatformMessage(msg) {
chrome.runtime.sendMessage(msg);
}
function fillSelectorWithFolders(folders) {
const select = document.querySelector('#template-add-clone .select-folder');
select.appendChild(new Option(chrome.i18n.getMessage('selectFolder'), null, true));
folders.forEach((folder) => {
//Select "No Folder" (id=null) folder by default
select.appendChild(new Option(folder.name, folder.id || '', false));
});
}
function adjustHeight() {
neverButton.addEventListener("click", (e) => {
e.preventDefault();
sendPlatformMessage({
command: 'bgAdjustNotificationBar',
data: {
height: document.querySelector('body').scrollHeight
}
command: "bgNeverSave",
});
});
if (!isVaultLocked) {
const responseFoldersCommand = "notificationBarGetFoldersList";
chrome.runtime.onMessage.addListener((msg) => {
if (msg.command === responseFoldersCommand && msg.data) {
fillSelectorWithFolders(msg.data.folders);
}
});
sendPlatformMessage({
command: "bgGetDataForTab",
responseCommand: responseFoldersCommand,
});
}
} else if (getQueryVariable("change")) {
setContent(document.getElementById("template-change"));
var changeButton = document.querySelector("#template-change-clone .change-save");
changeButton.addEventListener("click", (e) => {
e.preventDefault();
const bgChangeSaveMessage = {
command: "bgChangeSave",
};
sendPlatformMessage(bgChangeSaveMessage);
});
}
closeButton.addEventListener("click", (e) => {
e.preventDefault();
sendPlatformMessage({
command: "bgCloseNotificationBar",
});
});
window.addEventListener("resize", adjustHeight);
adjustHeight();
}
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if (pair[0] === variable) {
return pair[1];
}
}
return null;
}
function setContent(element) {
const content = document.getElementById("content");
while (content.firstChild) {
content.removeChild(content.firstChild);
}
var newElement = element.cloneNode(true);
newElement.id = newElement.id + "-clone";
content.appendChild(newElement);
}
function sendPlatformMessage(msg) {
chrome.runtime.sendMessage(msg);
}
function fillSelectorWithFolders(folders) {
const select = document.querySelector("#template-add-clone .select-folder");
select.appendChild(new Option(chrome.i18n.getMessage("selectFolder"), null, true));
folders.forEach((folder) => {
//Select "No Folder" (id=null) folder by default
select.appendChild(new Option(folder.name, folder.id || "", false));
});
}
function adjustHeight() {
sendPlatformMessage({
command: "bgAdjustNotificationBar",
data: {
height: document.querySelector("body").scrollHeight,
},
});
}
});

View File

@ -1,94 +1,94 @@
body {
background-color: #ffffff;
padding: 0;
margin: 0;
height: 100%;
font-size: 14px;
line-height: 16px;
color: #333333;
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
background-color: #ffffff;
padding: 0;
margin: 0;
height: 100%;
font-size: 14px;
line-height: 16px;
color: #333333;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.outer-wrapper {
padding: 0 10px;
border-bottom: 2px solid #175ddc;
display: grid;
grid-template-columns: 24px auto 55px;
grid-column-gap: 10px;
box-sizing: border-box;
min-height: 42px;
padding: 0 10px;
border-bottom: 2px solid #175ddc;
display: grid;
grid-template-columns: 24px auto 55px;
grid-column-gap: 10px;
box-sizing: border-box;
min-height: 42px;
}
.inner-wrapper {
display: grid;
grid-template-columns: auto max-content;
display: grid;
grid-template-columns: auto max-content;
}
.outer-wrapper > *, .inner-wrapper > * {
align-self: center;
.outer-wrapper > *,
.inner-wrapper > * {
align-self: center;
}
img {
border: 0;
margin: 0;
padding: 0;
border: 0;
margin: 0;
padding: 0;
}
#logo {
width: 24px;
height: 24px;
display: block;
width: 24px;
height: 24px;
display: block;
}
#close {
width: 15px;
height: 15px;
display: block;
padding: 5px 0;
margin-right: 10px;
width: 15px;
height: 15px;
display: block;
padding: 5px 0;
margin-right: 10px;
}
button:not(.link),
button:not(.neutral) {
background-color: #175DDC;
padding: 5px 15px;
border-radius: 3px;
color: #ffffff;
border: 0;
background-color: #175ddc;
padding: 5px 15px;
border-radius: 3px;
color: #ffffff;
border: 0;
&:hover {
cursor: pointer;
background-color: #1751bd;
}
&:hover {
cursor: pointer;
background-color: #1751bd;
}
}
button.link,
button.neutral {
background: none;
padding: 5px 15px;
color: #175DDC;
border: 0;
background: none;
padding: 5px 15px;
color: #175ddc;
border: 0;
&:hover {
cursor: pointer;
background: none;
text-decoration: underline;
}
&:hover {
cursor: pointer;
background: none;
text-decoration: underline;
}
}
.select-folder[isVaultLocked="true"] {
display: none
display: none;
}
@media screen and (max-width: 768px) {
.select-folder {
display: none
}
.select-folder {
display: none;
}
}
@media (print) {
body {
display: none;
}
body {
display: none;
}
}

View File

@ -1,67 +1,104 @@
<form #form (ngSubmit)="submit()">
<header>
<div class="left">
<a routerLink="/home">{{'close' | i18n}}</a>
<header>
<div class="left">
<a routerLink="/home">{{ "close" | i18n }}</a>
</div>
<h1 class="center">
<span class="title">{{ "appName" | i18n }}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{ "save" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<content>
<div class="box">
<h2 class="box-header">
{{ "selfHostedEnvironment" | i18n }}
</h2>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="baseUrl">{{ "baseUrl" | i18n }}</label>
<input
id="baseUrl"
type="text"
name="BaseUrl"
[(ngModel)]="baseUrl"
placeholder="ex. https://bitwarden.company.com"
appInputVerbatim
/>
</div>
<h1 class="center">
<span class="title">{{'appName' | i18n}}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'save' | i18n}}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
<div class="box-footer">
{{ "selfHostedEnvironmentFooter" | i18n }}
</div>
</div>
<div class="box">
<h2 class="box-header">
{{ "customEnvironment" | i18n }}
</h2>
<div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{ "webVaultUrl" | i18n }}</label>
<input
id="webVaultUrl"
type="text"
name="WebVaultUrl"
[(ngModel)]="webVaultUrl"
inputmode="url"
appInputVerbatim
/>
</div>
</header>
<content>
<div class="box">
<h2 class="box-header">
{{'selfHostedEnvironment' | i18n}}
</h2>
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="baseUrl">{{'baseUrl' | i18n}}</label>
<input id="baseUrl" type="text" name="BaseUrl" [(ngModel)]="baseUrl"
placeholder="ex. https://bitwarden.company.com" appInputVerbatim>
</div>
</div>
<div class="box-footer">
{{'selfHostedEnvironmentFooter' | i18n}}
</div>
<div class="box-content-row" appBoxRow>
<label for="apiUrl">{{ "apiUrl" | i18n }}</label>
<input
id="apiUrl"
type="text"
name="ApiUrl"
[(ngModel)]="apiUrl"
inputmode="url"
appInputVerbatim
/>
</div>
<div class="box">
<h2 class="box-header">
{{'customEnvironment' | i18n}}
</h2>
<div class="box-content" [hidden]="!showCustom">
<div class="box-content-row" appBoxRow>
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label>
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl" inputmode="url"
appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="apiUrl">{{'apiUrl' | i18n}}</label>
<input id="apiUrl" type="text" name="ApiUrl" [(ngModel)]="apiUrl" inputmode="url" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="identityUrl">{{'identityUrl' | i18n}}</label>
<input id="identityUrl" type="text" name="IdentityUrl" [(ngModel)]="identityUrl" inputmode="url"
appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="notificationsUrl">{{'notificationsUrl' | i18n}}</label>
<input id="notificationsUrl" type="text" name="NotificationsUrl" inputmode="url"
[(ngModel)]="notificationsUrl" appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{'iconsUrl' | i18n}}</label>
<input id="iconsUrl" type="text" name="IconsUrl" [(ngModel)]="iconsUrl" inputmode="url"
appInputVerbatim>
</div>
</div>
<div class="box-footer" [hidden]="!showCustom">
{{'customEnvironmentFooter' | i18n}}
</div>
<div class="box-content-row" appBoxRow>
<label for="identityUrl">{{ "identityUrl" | i18n }}</label>
<input
id="identityUrl"
type="text"
name="IdentityUrl"
[(ngModel)]="identityUrl"
inputmode="url"
appInputVerbatim
/>
</div>
</content>
<div class="box-content-row" appBoxRow>
<label for="notificationsUrl">{{ "notificationsUrl" | i18n }}</label>
<input
id="notificationsUrl"
type="text"
name="NotificationsUrl"
inputmode="url"
[(ngModel)]="notificationsUrl"
appInputVerbatim
/>
</div>
<div class="box-content-row" appBoxRow>
<label for="iconsUrl">{{ "iconsUrl" | i18n }}</label>
<input
id="iconsUrl"
type="text"
name="IconsUrl"
[(ngModel)]="iconsUrl"
inputmode="url"
appInputVerbatim
/>
</div>
</div>
<div class="box-footer" [hidden]="!showCustom">
{{ "customEnvironmentFooter" | i18n }}
</div>
</div>
</content>
</form>

View File

@ -1,25 +1,29 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { EnvironmentComponent as BaseEnvironmentComponent } from 'jslib-angular/components/environment.component';
import { EnvironmentComponent as BaseEnvironmentComponent } from "jslib-angular/components/environment.component";
@Component({
selector: 'app-environment',
templateUrl: 'environment.component.html',
selector: "app-environment",
templateUrl: "environment.component.html",
})
export class EnvironmentComponent extends BaseEnvironmentComponent {
constructor(platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
i18nService: I18nService, private router: Router) {
super(platformUtilsService, environmentService, i18nService);
this.showCustom = true;
}
constructor(
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
i18nService: I18nService,
private router: Router
) {
super(platformUtilsService, environmentService, i18nService);
this.showCustom = true;
}
saved() {
super.saved();
this.router.navigate(['']);
}
saved() {
super.saved();
this.router.navigate([""]);
}
}

View File

@ -1,30 +1,38 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<a routerLink="/login">{{'cancel' | i18n}}</a>
<header>
<div class="left">
<a routerLink="/login">{{ "cancel" | i18n }}</a>
</div>
<h1 class="center">
<span class="title">{{ "passwordHint" | i18n }}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<content>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
appAutofocus
inputmode="email"
appInputVerbatim="false"
/>
</div>
<h1 class="center">
<span class="title">{{'passwordHint' | i18n}}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<content>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required appAutofocus
inputmode="email" appInputVerbatim="false">
</div>
</div>
<div class="box-footer">
{{'enterEmailToGetHint' | i18n}}
</div>
</div>
</content>
</div>
<div class="box-footer">
{{ "enterEmailToGetHint" | i18n }}
</div>
</div>
</content>
</form>

View File

@ -1,20 +1,25 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { HintComponent as BaseHintComponent } from 'jslib-angular/components/hint.component';
import { HintComponent as BaseHintComponent } from "jslib-angular/components/hint.component";
@Component({
selector: 'app-hint',
templateUrl: 'hint.component.html',
selector: "app-hint",
templateUrl: "hint.component.html",
})
export class HintComponent extends BaseHintComponent {
constructor(router: Router, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, apiService: ApiService, logService: LogService) {
super(router, i18nService, apiService, platformUtilsService, logService);
}
constructor(
router: Router,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
apiService: ApiService,
logService: LogService
) {
super(router, i18nService, apiService, platformUtilsService, logService);
}
}

View File

@ -1,14 +1,16 @@
<div class="center-content">
<div class="content">
<div class="logo-image"></div>
<p class="lead text-center">{{'loginOrCreateNewAccount' | i18n}}</p>
<a class="btn primary block" routerLink="/login"><b>{{'login' | i18n}}</b></a>
<button type="button" (click)="launchSsoBrowser()" class="btn block">
<i class="fa fa-bank" aria-hidden="true"></i> {{'enterpriseSingleSignOn' | i18n}}
</button>
<a class="btn block" routerLink="/register">{{'createAccount' | i18n}}</a>
</div>
<div class="content">
<div class="logo-image"></div>
<p class="lead text-center">{{ "loginOrCreateNewAccount" | i18n }}</p>
<a class="btn primary block" routerLink="/login"
><b>{{ "login" | i18n }}</b></a
>
<button type="button" (click)="launchSsoBrowser()" class="btn block">
<i class="fa fa-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</button>
<a class="btn block" routerLink="/register">{{ "createAccount" | i18n }}</a>
</div>
</div>
<a routerLink="/environment" class="settings-icon">
<i class="fa fa-cog fa-lg" aria-hidden="true"></i><span>&nbsp;{{'settings' | i18n}}</span>
<i class="fa fa-cog fa-lg" aria-hidden="true"></i><span>&nbsp;{{ "settings" | i18n }}</span>
</a>

View File

@ -1,53 +1,66 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ConstantsService } from "jslib-common/services/constants.service";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
@Component({
selector: 'app-home',
templateUrl: 'home.component.html',
selector: "app-home",
templateUrl: "home.component.html",
})
export class HomeComponent {
constructor(protected platformUtilsService: PlatformUtilsService,
private passwordGenerationService: PasswordGenerationService, private storageService: StorageService,
private cryptoFunctionService: CryptoFunctionService, private environmentService: EnvironmentService) { }
constructor(
protected platformUtilsService: PlatformUtilsService,
private passwordGenerationService: PasswordGenerationService,
private storageService: StorageService,
private cryptoFunctionService: CryptoFunctionService,
private environmentService: EnvironmentService
) {}
async launchSsoBrowser() {
// Generate necessary sso params
const passwordOptions: any = {
type: 'password',
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
async launchSsoBrowser() {
// Generate necessary sso params
const passwordOptions: any = {
type: "password",
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
const state = (await this.passwordGenerationService.generatePassword(passwordOptions)) + ':clientId=browser';
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256');
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
const state =
(await this.passwordGenerationService.generatePassword(passwordOptions)) +
":clientId=browser";
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256");
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier);
await this.storageService.save(ConstantsService.ssoStateKey, state);
await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier);
await this.storageService.save(ConstantsService.ssoStateKey, state);
let url = this.environmentService.getWebVaultUrl();
if (url == null) {
url = 'https://vault.bitwarden.com';
}
const redirectUri = url + '/sso-connector.html';
// Launch browser
this.platformUtilsService.launchUri(url + '/#/sso?clientId=browser' +
'&redirectUri=' + encodeURIComponent(redirectUri) +
'&state=' + state + '&codeChallenge=' + codeChallenge);
let url = this.environmentService.getWebVaultUrl();
if (url == null) {
url = "https://vault.bitwarden.com";
}
const redirectUri = url + "/sso-connector.html";
// Launch browser
this.platformUtilsService.launchUri(
url +
"/#/sso?clientId=browser" +
"&redirectUri=" +
encodeURIComponent(redirectUri) +
"&state=" +
state +
"&codeChallenge=" +
codeChallenge
);
}
}

View File

@ -1,49 +1,74 @@
<form (ngSubmit)="submit()">
<header>
<div class="left"></div>
<h1 class="center">
<span class="title">{{'verifyIdentity' | i18n}}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick *ngIf="!hideInput">{{'unlock' | i18n}}</button>
<header>
<div class="left"></div>
<h1 class="center">
<span class="title">{{ "verifyIdentity" | i18n }}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick *ngIf="!hideInput">{{ "unlock" | i18n }}</button>
</div>
</header>
<content>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
<div class="row-main" *ngIf="pinLock">
<label for="pin">{{ "pin" | i18n }}</label>
<input
id="pin"
type="{{ showPassword ? 'text' : 'password' }}"
name="PIN"
class="monospaced"
[(ngModel)]="pin"
required
appInputVerbatim
/>
</div>
<div class="row-main" *ngIf="!pinLock">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
aria-hidden="true"
></i>
</button>
</div>
</div>
</header>
<content>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
<div class="row-main" *ngIf="pinLock">
<label for="pin">{{'pin' | i18n}}</label>
<input id="pin" type="{{showPassword ? 'text' : 'password'}}" name="PIN" class="monospaced"
[(ngModel)]="pin" required appInputVerbatim>
</div>
<div class="row-main" *ngIf="!pinLock">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required appInputVerbatim>
</div>
<div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()" [attr.aria-pressed]="showPassword">
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"
aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="box-footer">
<p>{{'yourVaultIsLocked' | i18n}}</p>
{{'loggedInAsOn' | i18n : email : webVaultHostname}}
</div>
</div>
<div class="box" *ngIf="biometricLock">
<div class="box-footer">
<button type="button" class="btn primary block" (click)="unlockBiometric()"
appStopClick>{{'unlockWithBiometrics' | i18n}}</button>
</div>
</div>
<p class="text-center">
<button type="button" appStopClick (click)="logOut()">{{'logOut' | i18n}}</button>
</p>
</content>
</div>
<div class="box-footer">
<p>{{ "yourVaultIsLocked" | i18n }}</p>
{{ "loggedInAsOn" | i18n: email:webVaultHostname }}
</div>
</div>
<div class="box" *ngIf="biometricLock">
<div class="box-footer">
<button type="button" class="btn primary block" (click)="unlockBiometric()" appStopClick>
{{ "unlockWithBiometrics" | i18n }}
</button>
</div>
</div>
<p class="text-center">
<button type="button" appStopClick (click)="logOut()">{{ "logOut" | i18n }}</button>
</p>
</content>
</form>

View File

@ -1,87 +1,107 @@
import {
Component,
NgZone,
} from '@angular/core';
import { Router } from '@angular/router';
import Swal from 'sweetalert2';
import { Component, NgZone } from "@angular/core";
import { Router } from "@angular/router";
import Swal from "sweetalert2";
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ConstantsService } from "jslib-common/services/constants.service";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { UserService } from "jslib-common/abstractions/user.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { LockComponent as BaseLockComponent } from 'jslib-angular/components/lock.component';
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
@Component({
selector: 'app-lock',
templateUrl: 'lock.component.html',
selector: "app-lock",
templateUrl: "lock.component.html",
})
export class LockComponent extends BaseLockComponent {
private isInitialLockScreen: boolean;
private isInitialLockScreen: boolean;
constructor(router: Router, i18nService: I18nService,
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
userService: UserService, cryptoService: CryptoService,
storageService: StorageService, vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService, stateService: StateService,
apiService: ApiService, logService: LogService, keyConnectorService: KeyConnectorService,
ngZone: NgZone) {
super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService,
storageService, vaultTimeoutService, environmentService, stateService, apiService, logService,
keyConnectorService, ngZone);
this.successRoute = '/tabs/current';
this.isInitialLockScreen = (window as any).previousPopupUrl == null;
}
constructor(
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
messagingService: MessagingService,
userService: UserService,
cryptoService: CryptoService,
storageService: StorageService,
vaultTimeoutService: VaultTimeoutService,
environmentService: EnvironmentService,
stateService: StateService,
apiService: ApiService,
logService: LogService,
keyConnectorService: KeyConnectorService,
ngZone: NgZone
) {
super(
router,
i18nService,
platformUtilsService,
messagingService,
userService,
cryptoService,
storageService,
vaultTimeoutService,
environmentService,
stateService,
apiService,
logService,
keyConnectorService,
ngZone
);
this.successRoute = "/tabs/current";
this.isInitialLockScreen = (window as any).previousPopupUrl == null;
}
async ngOnInit() {
await super.ngOnInit();
const disableAutoBiometricsPrompt = await this.storageService.get<boolean>(
ConstantsService.disableAutoBiometricsPromptKey) ?? true;
async ngOnInit() {
await super.ngOnInit();
const disableAutoBiometricsPrompt =
(await this.storageService.get<boolean>(ConstantsService.disableAutoBiometricsPromptKey)) ??
true;
window.setTimeout(async () => {
document.getElementById(this.pinLock ? 'pin' : 'masterPassword').focus();
if (this.biometricLock && !disableAutoBiometricsPrompt && this.isInitialLockScreen) {
if (await this.vaultTimeoutService.isLocked()) {
await this.unlockBiometric();
}
}
}, 100);
}
async unlockBiometric(): Promise<boolean> {
if (!this.biometricLock) {
return;
window.setTimeout(async () => {
document.getElementById(this.pinLock ? "pin" : "masterPassword").focus();
if (this.biometricLock && !disableAutoBiometricsPrompt && this.isInitialLockScreen) {
if (await this.vaultTimeoutService.isLocked()) {
await this.unlockBiometric();
}
}
}, 100);
}
const div = document.createElement('div');
div.innerHTML = `<div class="swal2-text">${this.i18nService.t('awaitDesktop')}</div>`;
Swal.fire({
heightAuto: false,
buttonsStyling: false,
html: div,
showCancelButton: true,
cancelButtonText: this.i18nService.t('cancel'),
showConfirmButton: false,
});
const success = await super.unlockBiometric();
// Avoid closing the error dialogs
if (success) {
Swal.close();
}
return success;
async unlockBiometric(): Promise<boolean> {
if (!this.biometricLock) {
return;
}
const div = document.createElement("div");
div.innerHTML = `<div class="swal2-text">${this.i18nService.t("awaitDesktop")}</div>`;
Swal.fire({
heightAuto: false,
buttonsStyling: false,
html: div,
showCancelButton: true,
cancelButtonText: this.i18nService.t("cancel"),
showConfirmButton: false,
});
const success = await super.unlockBiometric();
// Avoid closing the error dialogs
if (success) {
Swal.close();
}
return success;
}
}

View File

@ -1,47 +1,71 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<a routerLink="/home">{{'cancel' | i18n}}</a>
<header>
<div class="left">
<a routerLink="/home">{{ "cancel" | i18n }}</a>
</div>
<h1 class="center">
<span class="title">{{ "appName" | i18n }}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{ "login" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<content>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
inputmode="email"
appInputVerbatim="false"
/>
</div>
<h1 class="center">
<span class="title">{{'appName' | i18n}}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'login' | i18n}}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
aria-hidden="true"
></i>
</button>
</div>
</div>
</header>
<content>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required inputmode="email"
appInputVerbatim="false">
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required appInputVerbatim>
</div>
<div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()" [attr.aria-pressed]="showPassword">
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"
aria-hidden="true"></i>
</button>
</div>
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80"></iframe>
</div>
<p class="text-center">
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
</p>
</content>
</div>
</div>
<p class="text-center">
<a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
</p>
</content>
</form>

View File

@ -1,42 +1,58 @@
import {
Component,
NgZone,
} from '@angular/core';
import { Router } from '@angular/router';
import { Component, NgZone } from "@angular/core";
import { Router } from "@angular/router";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { LoginComponent as BaseLoginComponent } from 'jslib-angular/components/login.component';
import { LoginComponent as BaseLoginComponent } from "jslib-angular/components/login.component";
@Component({
selector: 'app-login',
templateUrl: 'login.component.html',
selector: "app-login",
templateUrl: "login.component.html",
})
export class LoginComponent extends BaseLoginComponent {
constructor(authService: AuthService, router: Router,
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
protected stateService: StateService, protected environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService, storageService: StorageService,
syncService: SyncService, logService: LogService, ngZone: NgZone) {
super(authService, router, platformUtilsService, i18nService, stateService, environmentService,
passwordGenerationService, cryptoFunctionService, storageService, logService, ngZone);
super.onSuccessfulLogin = async () => {
await syncService.fullSync(true);
};
super.successRoute = '/tabs/vault';
}
constructor(
authService: AuthService,
router: Router,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
protected stateService: StateService,
protected environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService,
storageService: StorageService,
syncService: SyncService,
logService: LogService,
ngZone: NgZone
) {
super(
authService,
router,
platformUtilsService,
i18nService,
stateService,
environmentService,
passwordGenerationService,
cryptoFunctionService,
storageService,
logService,
ngZone
);
super.onSuccessfulLogin = async () => {
await syncService.fullSync(true);
};
super.successRoute = "/tabs/vault";
}
settings() {
this.router.navigate(['environment']);
}
settings() {
this.router.navigate(["environment"]);
}
}

View File

@ -1,100 +1,160 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<a routerLink="/home">{{'cancel' | i18n}}</a>
<header>
<div class="left">
<a routerLink="/home">{{ "cancel" | i18n }}</a>
</div>
<h1 class="center">
<span class="title">{{ "createAccount" | i18n }}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<content>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="text"
name="Email"
[(ngModel)]="email"
required
[appAutofocus]="email === ''"
inputmode="email"
appInputVerbatim="false"
/>
</div>
<h1 class="center">
<span class="title">{{'createAccount' | i18n}}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreColor }}"
*ngIf="masterPasswordScoreText"
>
{{ masterPasswordScoreText }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
[appAutofocus]="email !== ''"
appInputVerbatim
(input)="updatePasswordStrength()"
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreColor }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
></div>
</div>
</div>
</div>
<div class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="confirmMasterPassword"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</header>
<content>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{'emailAddress' | i18n}}</label>
<input id="email" type="text" name="Email" [(ngModel)]="email" required
[appAutofocus]="email === ''" inputmode="email" appInputVerbatim="false">
</div>
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreColor}}"
*ngIf="masterPasswordScoreText">
{{masterPasswordScoreText}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
[appAutofocus]="email !== ''" appInputVerbatim (input)="updatePasswordStrength()">
</div>
<div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)" [attr.aria-pressed]="showPassword">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreColor}}" role="progressbar" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100" [ngStyle]="{width: (masterPasswordScoreWidth + '%')}"
attr.aria-valuenow="{{masterPasswordScoreWidth}}"></div>
</div>
</div>
</div>
<div class="box-footer">
{{'masterPassDesc' | i18n}}
</div>
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="confirmMasterPassword" required
appInputVerbatim>
</div>
<div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)" [attr.aria-pressed]="showPassword">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
<div [hidden]="!showCaptcha()"><iframe id="hcaptcha_iframe" height="80"></iframe></div>
<div class="box last" *ngIf="showTerms">
<div class="box-content">
<div
class="box-content-row box-content-row-checkbox box-content-row-checkbox-left box-content-row-word-break"
appBoxRow
>
<input
type="checkbox"
id="acceptPolicies"
[(ngModel)]="acceptPolicies"
name="AcceptPolicies"
/>
<label for="acceptPolicies">
{{ "acceptPolicies" | i18n }}<br />
<a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
"termsOfService" | i18n
}}</a
>,
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
"privacyPolicy" | i18n
}}</a>
</label>
</div>
<div [hidden]="!showCaptcha()"><iframe id="hcaptcha_iframe" height="80"></iframe></div>
<div class="box last" *ngIf="showTerms">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox box-content-row-checkbox-left box-content-row-word-break"
appBoxRow>
<input type="checkbox" id="acceptPolicies" [(ngModel)]="acceptPolicies" name="AcceptPolicies">
<label for="acceptPolicies">
{{'acceptPolicies' | i18n}}<br>
<a href="https://bitwarden.com/terms/" target="_blank"
rel="noopener">{{'termsOfService' | i18n}}</a>,
<a href="https://bitwarden.com/privacy/" target="_blank"
rel="noopener">{{'privacyPolicy' | i18n}}</a>
</label>
</div>
</div>
</div>
</content>
</div>
</div>
</content>
</form>

View File

@ -1,29 +1,46 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { RegisterComponent as BaseRegisterComponent } from 'jslib-angular/components/register.component';
import { LogService } from 'jslib-common/abstractions/log.service';
import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
import { LogService } from "jslib-common/abstractions/log.service";
@Component({
selector: 'app-register',
templateUrl: 'register.component.html',
selector: "app-register",
templateUrl: "register.component.html",
})
export class RegisterComponent extends BaseRegisterComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, cryptoService: CryptoService,
apiService: ApiService, stateService: StateService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, environmentService: EnvironmentService,
logService: LogService) {
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService,
passwordGenerationService, environmentService, logService);
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
cryptoService: CryptoService,
apiService: ApiService,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
environmentService: EnvironmentService,
logService: LogService
) {
super(
authService,
router,
i18nService,
cryptoService,
apiService,
stateService,
platformUtilsService,
passwordGenerationService,
environmentService,
logService
);
}
}

View File

@ -1,29 +1,49 @@
<header>
<div class="left"></div>
<div class="center">
<span class="title">{{'removeMasterPassword' | i18n}}</span>
</div>
<div class="right"></div>
<div class="left"></div>
<div class="center">
<span class="title">{{ "removeMasterPassword" | i18n }}</span>
</div>
<div class="right"></div>
</header>
<content>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<p>{{'convertOrganizationEncryptionDesc' | i18n : organization.name}}</p>
</div>
<div class="box-content-row">
<button type="button" class="btn block primary" (click)="convert()" [disabled]="actionPromise">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="continuing"></i>
{{'removeMasterPassword' | i18n}}
</button>
</div>
<div class="box-content-row">
<button type="button" class="btn btn-outline-secondary block" (click)="leave()" [disabled]="actionPromise">
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="leaving"></i>
{{'leaveOrganization' | i18n}}
</button>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
</div>
<div class="box-content-row">
<button
type="button"
class="btn block primary"
(click)="convert()"
[disabled]="actionPromise"
>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="continuing"
></i>
{{ "removeMasterPassword" | i18n }}
</button>
</div>
<div class="box-content-row">
<button
type="button"
class="btn btn-outline-secondary block"
(click)="leave()"
[disabled]="actionPromise"
>
<i
class="fa fa-spinner fa-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
*ngIf="leaving"
></i>
{{ "leaveOrganization" | i18n }}
</button>
</div>
</div>
</div>
</content>

View File

@ -1,10 +1,9 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { RemovePasswordComponent as BaseRemovePasswordComponent } from 'jslib-angular/components/remove-password.component';
import { RemovePasswordComponent as BaseRemovePasswordComponent } from "jslib-angular/components/remove-password.component";
@Component({
selector: 'app-remove-password',
templateUrl: 'remove-password.component.html',
selector: "app-remove-password",
templateUrl: "remove-password.component.html",
})
export class RemovePasswordComponent extends BaseRemovePasswordComponent {
}
export class RemovePasswordComponent extends BaseRemovePasswordComponent {}

View File

@ -1,100 +1,150 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<a routerLink="/home">{{'cancel' | i18n}}</a>
</div>
<h1 class="center">
<span class="title">{{'setMasterPassword' | i18n}}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<content>
<div class="full-loading-spinner" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin fa-3x" aria-hidden="true"></i>
</div>
<div *ngIf="!syncLoading">
<div class="box">
<app-callout type="tip">{{'ssoCompleteRegistration' | i18n}}</app-callout>
<app-callout type="warning" title="{{'resetPasswordPolicyAutoEnroll' | i18n}}"
*ngIf="resetPasswordAutoEnroll">
{{'resetPasswordAutoEnrollInviteWarning' | i18n}}
</app-callout>
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</app-callout>
<header>
<div class="left">
<a routerLink="/home">{{ "cancel" | i18n }}</a>
</div>
<h1 class="center">
<span class="title">{{ "setMasterPassword" | i18n }}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<content>
<div class="full-loading-spinner" *ngIf="syncLoading">
<i class="fa fa-spinner fa-spin fa-3x" aria-hidden="true"></i>
</div>
<div *ngIf="!syncLoading">
<div class="box">
<app-callout type="tip">{{ "ssoCompleteRegistration" | i18n }}</app-callout>
<app-callout
type="warning"
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
*ngIf="resetPasswordAutoEnroll"
>
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword"
>{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreColor }}"
*ngIf="masterPasswordScoreText"
>
{{ masterPasswordScoreText }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
(input)="updatePasswordStrength()"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreColor}}"
*ngIf="masterPasswordScoreText">
{{masterPasswordScoreText}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
(input)="updatePasswordStrength()" appInputVerbatim>
</div>
<div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)" [attr.aria-pressed]="showPassword">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreColor}}" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
[ngStyle]="{width: (masterPasswordScoreWidth + '%')}"
attr.aria-valuenow="{{masterPasswordScoreWidth}}">
</div>
</div>
</div>
</div>
<div class="box-footer">
{{'masterPassDesc' | i18n}}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="password" name="MasterPasswordRetype"
class="monospaced" [(ngModel)]="masterPasswordRetype" required appInputVerbatim
autocomplete="new-password">
</div>
<div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)" [attr.aria-pressed]="showPassword">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreColor }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
></div>
</div>
</div>
</div>
</content>
<div class="box-footer">
{{ "masterPassDesc" | i18n }}
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="password"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
autocomplete="new-password"
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
</div>
</content>
</form>

View File

@ -1,65 +1,79 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { UserService } from "jslib-common/abstractions/user.service";
import {
SetPasswordComponent as BaseSetPasswordComponent,
} from 'jslib-angular/components/set-password.component';
import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
@Component({
selector: 'app-set-password',
templateUrl: 'set-password.component.html',
selector: "app-set-password",
templateUrl: "set-password.component.html",
})
export class SetPasswordComponent extends BaseSetPasswordComponent {
constructor(apiService: ApiService, i18nService: I18nService,
cryptoService: CryptoService, messagingService: MessagingService,
userService: UserService, passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService, router: Router,
syncService: SyncService, route: ActivatedRoute) {
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
platformUtilsService, policyService, router, apiService, syncService, route);
}
constructor(
apiService: ApiService,
i18nService: I18nService,
cryptoService: CryptoService,
messagingService: MessagingService,
userService: UserService,
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
router: Router,
syncService: SyncService,
route: ActivatedRoute
) {
super(
i18nService,
cryptoService,
messagingService,
userService,
passwordGenerationService,
platformUtilsService,
policyService,
router,
apiService,
syncService,
route
);
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreWidth() {
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
}
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return 'success';
case 3:
return 'primary';
case 2:
return 'warning';
default:
return 'danger';
}
get masterPasswordScoreColor() {
switch (this.masterPasswordScore) {
case 4:
return "success";
case 3:
return "primary";
case 2:
return "warning";
default:
return "danger";
}
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t('strong');
case 3:
return this.i18nService.t('good');
case 2:
return this.i18nService.t('weak');
default:
return this.masterPasswordScore != null ? this.i18nService.t('weak') : null;
}
get masterPasswordScoreText() {
switch (this.masterPasswordScore) {
case 4:
return this.i18nService.t("strong");
case 3:
return this.i18nService.t("good");
case 2:
return this.i18nService.t("weak");
default:
return this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
}
}
}

View File

@ -1,55 +1,73 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { ActivatedRoute, Router } from "@angular/router";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component';
import { BrowserApi } from '../../browser/browserApi';
import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
import { BrowserApi } from "../../browser/browserApi";
@Component({
selector: 'app-sso',
templateUrl: 'sso.component.html',
selector: "app-sso",
templateUrl: "sso.component.html",
})
export class SsoComponent extends BaseSsoComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, route: ActivatedRoute,
storageService: StorageService, stateService: StateService,
platformUtilsService: PlatformUtilsService, apiService: ApiService,
cryptoFunctionService: CryptoFunctionService, passwordGenerationService: PasswordGenerationService,
syncService: SyncService, environmentService: EnvironmentService, logService: LogService,
private vaultTimeoutService: VaultTimeoutService) {
super(authService, router, i18nService, route, storageService, stateService, platformUtilsService,
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService);
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
route: ActivatedRoute,
storageService: StorageService,
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
cryptoFunctionService: CryptoFunctionService,
passwordGenerationService: PasswordGenerationService,
syncService: SyncService,
environmentService: EnvironmentService,
logService: LogService,
private vaultTimeoutService: VaultTimeoutService
) {
super(
authService,
router,
i18nService,
route,
storageService,
stateService,
platformUtilsService,
apiService,
cryptoFunctionService,
environmentService,
passwordGenerationService,
logService
);
const url = this.environmentService.getWebVaultUrl();
const url = this.environmentService.getWebVaultUrl();
this.redirectUri = url + '/sso-connector.html';
this.clientId = 'browser';
this.redirectUri = url + "/sso-connector.html";
this.clientId = "browser";
super.onSuccessfulLogin = async () => {
await syncService.fullSync(true);
if (await this.vaultTimeoutService.isLocked()) {
// If the vault is unlocked then this will clear keys from memory, which we don't want to do
BrowserApi.reloadOpenWindows();
}
super.onSuccessfulLogin = async () => {
await syncService.fullSync(true);
if (await this.vaultTimeoutService.isLocked()) {
// If the vault is unlocked then this will clear keys from memory, which we don't want to do
BrowserApi.reloadOpenWindows();
}
const thisWindow = window.open('', '_self');
thisWindow.close();
};
}
const thisWindow = window.open("", "_self");
thisWindow.close();
};
}
}

View File

@ -1,23 +1,29 @@
<header>
<div class="left">
<a routerLink="/2fa">{{'close' | i18n}}</a>
</div>
<h1 class="center">
<span class="title">{{'twoStepOptions' | i18n}}</span>
</h1>
<div class="right"></div>
<div class="left">
<a routerLink="/2fa">{{ "close" | i18n }}</a>
</div>
<h1 class="center">
<span class="title">{{ "twoStepOptions" | i18n }}</span>
</h1>
<div class="right"></div>
</header>
<content>
<div class="box">
<div class="box-content">
<button type="button" appStopClick *ngFor="let p of providers" class="box-content-row" (click)="choose(p)">
<span class="text">{{p.name}}</span>
<span class="detail">{{p.description}}</span>
</button>
<button type="button" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{'recoveryCodeTitle' | i18n}}</span>
<span class="detail">{{'recoveryCodeDesc' | i18n}}</span>
</button>
</div>
<div class="box">
<div class="box-content">
<button
type="button"
appStopClick
*ngFor="let p of providers"
class="box-content-row"
(click)="choose(p)"
>
<span class="text">{{ p.name }}</span>
<span class="detail">{{ p.description }}</span>
</button>
<button type="button" appStopClick class="box-content-row" (click)="recover()">
<span class="text">{{ "recoveryCodeTitle" | i18n }}</span>
<span class="detail">{{ "recoveryCodeDesc" | i18n }}</span>
</button>
</div>
</div>
</content>

View File

@ -1,27 +1,29 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
} from 'jslib-angular/components/two-factor-options.component';
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component";
@Component({
selector: 'app-two-factor-options',
templateUrl: 'two-factor-options.component.html',
selector: "app-two-factor-options",
templateUrl: "two-factor-options.component.html",
})
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
constructor(authService: AuthService, router: Router,
i18nService: I18nService, platformUtilsService: PlatformUtilsService) {
super(authService, router, i18nService, platformUtilsService, window);
}
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService
) {
super(authService, router, i18nService, platformUtilsService, window);
}
choose(p: any) {
super.choose(p);
this.authService.selectedTwoFactorProviderType = p.type;
this.router.navigate(['2fa']);
}
choose(p: any) {
super.choose(p);
this.authService.selectedTwoFactorProviderType = p.type;
this.router.navigate(["2fa"]);
}
}

View File

@ -1,106 +1,141 @@
<form id="two-factor-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<a routerLink="/login">{{'back' | i18n}}</a>
<header>
<div class="left">
<a routerLink="/login">{{ "back" | i18n }}</a>
</div>
<h1 class="center">
<span class="title">{{ title }}</span>
</h1>
<div class="right">
<button
type="submit"
appBlurClick
[disabled]="form.loading"
*ngIf="
selectedProviderType != null &&
selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo &&
(selectedProviderType !== providerType.WebAuthn || form.loading)
"
>
<span [hidden]="form.loading">{{ "continue" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<content>
<ng-container
*ngIf="
selectedProviderType === providerType.Authenticator ||
selectedProviderType === providerType.Email
"
>
<div class="content text-center">
<span *ngIf="selectedProviderType === providerType.Authenticator">
{{ "enterVerificationCodeApp" | i18n }}
</span>
<span *ngIf="selectedProviderType === providerType.Email">
{{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
</span>
</div>
<div class="box first">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code">{{ "verificationCode" | i18n }}</label>
<input
id="code"
type="text"
name="Code"
[(ngModel)]="token"
required
appAutofocus
inputmode="tel"
appInputVerbatim
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
<h1 class="center">
<span class="title">{{title}}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading" *ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
selectedProviderType !== providerType.OrganizationDuo &&
(selectedProviderType !== providerType.WebAuthn || form.loading)">
<span [hidden]="form.loading">{{'continue' | i18n}}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<div class="content text-center">
<p class="text-center">{{ "insertYubiKey" | i18n }}</p>
<img src="../images/yubikey.jpg" class="img-rounded img-responsive" alt="" />
</div>
<div class="box first">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label>
<input
id="code"
type="password"
name="Code"
[(ngModel)]="token"
required
appAutofocus
appInputVerbatim
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</header>
<content>
<ng-container *ngIf="selectedProviderType === providerType.Authenticator ||
selectedProviderType === providerType.Email">
<div class="content text-center">
<span *ngIf="selectedProviderType === providerType.Authenticator">
{{'enterVerificationCodeApp' | i18n}}
</span>
<span *ngIf="selectedProviderType === providerType.Email">
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
</span>
</div>
<div class="box first">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code">{{'verificationCode' | i18n}}</label>
<input id="code" type="text" name="Code" [(ngModel)]="token" required appAutofocus
inputmode="tel" appInputVerbatim>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<div class="content text-center">
<p class="text-center">{{'insertYubiKey' | i18n}}</p>
<img src="../images/yubikey.jpg" class="img-rounded img-responsive" alt="">
</div>
<div class="box first">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="code" class="sr-only">{{'verificationCode' | i18n}}</label>
<input id="code" type="password" name="Code" [(ngModel)]="token" required appAutofocus
appInputVerbatim>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn && !webAuthnNewTab">
<div id="web-authn-frame"><iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe></div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn && webAuthnNewTab">
<div class="content text-center" *ngIf="webAuthnNewTab">
<p class="text-center">{{'webAuthnNewTab' | i18n}}</p>
<button type="button" class="btn primary block" (click)="authWebAuthn()" appStopClick>{{'webAuthnNewTabOpen' | i18n}}</button>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo">
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{'rememberMe' | i18n}}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember">
</div>
</div>
</div>
</ng-container>
<div class="content" *ngIf="selectedProviderType == null">
<p class="text-center">{{'noTwoStepProviders' | i18n}}</p>
<p class="text-center">{{'noTwoStepProviders2' | i18n}}</p>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn && !webAuthnNewTab">
<div id="web-authn-frame"><iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe></div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
<div class="content no-vpad" *ngIf="selectedProviderType != null">
<p class="text-center">
<button type="button" appStopClick (click)="anotherMethod()">{{'useAnotherTwoStepMethod' | i18n}}</button>
</p>
<p *ngIf="selectedProviderType === providerType.Email" class="text-center">
<button type="button" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise">
{{'sendVerificationCodeEmailAgain' | i18n}}
</button>
</p>
</div>
</ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn && webAuthnNewTab">
<div class="content text-center" *ngIf="webAuthnNewTab">
<p class="text-center">{{ "webAuthnNewTab" | i18n }}</p>
<button type="button" class="btn primary block" (click)="authWebAuthn()" appStopClick>
{{ "webAuthnNewTabOpen" | i18n }}
</button>
</div>
</ng-container>
<ng-container
*ngIf="
selectedProviderType === providerType.Duo ||
selectedProviderType === providerType.OrganizationDuo
"
>
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="remember">{{ "rememberMe" | i18n }}</label>
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
</div>
</div>
</content>
</div>
</ng-container>
<div class="content" *ngIf="selectedProviderType == null">
<p class="text-center">{{ "noTwoStepProviders" | i18n }}</p>
<p class="text-center">{{ "noTwoStepProviders2" | i18n }}</p>
</div>
<div class="content no-vpad" *ngIf="selectedProviderType != null">
<p class="text-center">
<button type="button" appStopClick (click)="anotherMethod()">
{{ "useAnotherTwoStepMethod" | i18n }}
</button>
</p>
<p *ngIf="selectedProviderType === providerType.Email" class="text-center">
<button type="button" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise">
{{ "sendVerificationCodeEmailAgain" | i18n }}
</button>
</p>
</div>
</content>
</form>

View File

@ -1,116 +1,140 @@
import { Component } from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { first } from 'rxjs/operators';
import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType';
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib-angular/components/two-factor.component';
import { TwoFactorComponent as BaseTwoFactorComponent } from "jslib-angular/components/two-factor.component";
import { PopupUtilsService } from '../services/popup-utils.service';
import { PopupUtilsService } from "../services/popup-utils.service";
import { BrowserApi } from '../../browser/browserApi';
import { BrowserApi } from "../../browser/browserApi";
const BroadcasterSubscriptionId = 'TwoFactorComponent';
const BroadcasterSubscriptionId = "TwoFactorComponent";
@Component({
selector: 'app-two-factor',
templateUrl: 'two-factor.component.html',
selector: "app-two-factor",
templateUrl: "two-factor.component.html",
})
export class TwoFactorComponent extends BaseTwoFactorComponent {
showNewWindowMessage = false;
showNewWindowMessage = false;
constructor(authService: AuthService, router: Router,
i18nService: I18nService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, private syncService: SyncService,
environmentService: EnvironmentService, private broadcasterService: BroadcasterService,
private popupUtilsService: PopupUtilsService, stateService: StateService,
storageService: StorageService, route: ActivatedRoute, private messagingService: MessagingService,
logService: LogService) {
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
stateService, storageService, route, logService);
constructor(
authService: AuthService,
router: Router,
i18nService: I18nService,
apiService: ApiService,
platformUtilsService: PlatformUtilsService,
private syncService: SyncService,
environmentService: EnvironmentService,
private broadcasterService: BroadcasterService,
private popupUtilsService: PopupUtilsService,
stateService: StateService,
storageService: StorageService,
route: ActivatedRoute,
private messagingService: MessagingService,
logService: LogService
) {
super(
authService,
router,
i18nService,
apiService,
platformUtilsService,
window,
environmentService,
stateService,
storageService,
route,
logService
);
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
super.successRoute = "/tabs/vault";
this.webAuthnNewTab =
this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari();
}
async ngOnInit() {
if (this.route.snapshot.paramMap.has("webAuthnResponse")) {
// WebAuthn fallback response
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
this.token = this.route.snapshot.paramMap.get("webAuthnResponse");
super.onSuccessfulLogin = async () => {
this.syncService.fullSync(true);
this.messagingService.send("reloadPopup");
window.close();
};
this.remember = this.route.snapshot.paramMap.get("remember") === "true";
await this.doSubmit();
return;
}
await super.ngOnInit();
if (this.selectedProviderType == null) {
return;
}
// WebAuthn prompt appears inside the popup on linux, and requires a larger popup width
// than usual to avoid cutting off the dialog.
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
document.body.classList.add("linux-webauthn");
}
if (
this.selectedProviderType === TwoFactorProviderType.Email &&
this.popupUtilsService.inPopup(window)
) {
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t("popup2faCloseMessage"),
null,
this.i18nService.t("yes"),
this.i18nService.t("no")
);
if (confirmed) {
this.popupUtilsService.popOut(window);
}
}
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.sso === "true") {
super.onSuccessfulLogin = () => {
return syncService.fullSync(true);
BrowserApi.reloadOpenWindows();
const thisWindow = window.open("", "_self");
thisWindow.close();
return this.syncService.fullSync(true);
};
super.successRoute = '/tabs/vault';
this.webAuthnNewTab = this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari();
}
});
}
async ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
document.body.classList.remove("linux-webauthn");
}
super.ngOnDestroy();
}
async ngOnInit() {
if (this.route.snapshot.paramMap.has('webAuthnResponse')) {
// WebAuthn fallback response
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
this.token = this.route.snapshot.paramMap.get('webAuthnResponse');
super.onSuccessfulLogin = async () => {
this.syncService.fullSync(true);
this.messagingService.send('reloadPopup');
window.close();
};
this.remember = this.route.snapshot.paramMap.get('remember') === 'true';
await this.doSubmit();
return;
}
anotherMethod() {
this.router.navigate(["2fa-options"]);
}
await super.ngOnInit();
if (this.selectedProviderType == null) {
return;
}
// WebAuthn prompt appears inside the popup on linux, and requires a larger popup width
// than usual to avoid cutting off the dialog.
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && await this.isLinux()) {
document.body.classList.add('linux-webauthn');
}
if (this.selectedProviderType === TwoFactorProviderType.Email &&
this.popupUtilsService.inPopup(window)) {
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('popup2faCloseMessage'),
null, this.i18nService.t('yes'), this.i18nService.t('no'));
if (confirmed) {
this.popupUtilsService.popOut(window);
}
}
this.route.queryParams.pipe(first()).subscribe(async qParams => {
if (qParams.sso === 'true') {
super.onSuccessfulLogin = () => {
BrowserApi.reloadOpenWindows();
const thisWindow = window.open('', '_self');
thisWindow.close();
return this.syncService.fullSync(true);
};
}
});
}
async ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && await this.isLinux()) {
document.body.classList.remove('linux-webauthn');
}
super.ngOnDestroy();
}
anotherMethod() {
this.router.navigate(['2fa-options']);
}
async isLinux() {
return (await BrowserApi.getPlatformInfo()).os === 'linux';
}
async isLinux() {
return (await BrowserApi.getPlatformInfo()).os === "linux";
}
}

View File

@ -1,86 +1,130 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
<header>
<div class="left">
<a (click)="logOut()">{{'logOut' | i18n}}</a>
<header>
<div class="left">
<a (click)="logOut()">{{ "logOut" | i18n }}</a>
</div>
<h1 class="center">
<span class="title">{{ "updateMasterPassword" | i18n }}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
</header>
<content>
<app-callout type="warning" title="{{ 'updateMasterPassword' | i18n }}">
{{ "updateMasterPasswordWarning" | i18n }}
</app-callout>
<app-callout
type="info"
[enforcedPolicyOptions]="enforcedPolicyOptions"
*ngIf="enforcedPolicyOptions"
>
</app-callout>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{ "masterPass" | i18n }}
<strong
class="sub-label text-{{ masterPasswordScoreStyle.Color }}"
*ngIf="masterPasswordScoreStyle.Text"
>
{{ masterPasswordScoreStyle.Text }}
</strong>
</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appInputVerbatim
(input)="updatePasswordStrength()"
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(false)"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
<div class="progress">
<div
class="progress-bar bg-{{ masterPasswordScoreStyle.Color }}"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
[ngStyle]="{ width: masterPasswordScoreStyle.Width + '%' }"
attr.aria-valuenow="{{ masterPasswordScoreStyle.Width }}"
></div>
</div>
</div>
<h1 class="center">
<span class="title">{{'updateMasterPassword' | i18n}}</span>
</h1>
<div class="right">
<button type="submit" appBlurClick [disabled]="form.loading">
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{ "reTypeMasterPass" | i18n }}</label>
<input
id="masterPasswordRetype"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPasswordRetype"
class="monospaced"
[(ngModel)]="masterPasswordRetype"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword(true)"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</header>
<content>
<app-callout type="warning" title="{{'updateMasterPassword' | i18n}}">
{{'updateMasterPasswordWarning' | i18n}}
</app-callout>
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
</app-callout>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<div class="box-content-row-flex">
<div class="row-main">
<label for="masterPassword">
{{'masterPass' | i18n}}
<strong class="sub-label text-{{masterPasswordScoreStyle.Color}}"
*ngIf="masterPasswordScoreStyle.Text">
{{masterPasswordScoreStyle.Text}}
</strong>
</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPassword" class="monospaced" [(ngModel)]="masterPassword" required
appInputVerbatim (input)="updatePasswordStrength()">
</div>
<div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(false)" [attr.aria-pressed]="showPassword">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
<div class="progress">
<div class="progress-bar bg-{{masterPasswordScoreStyle.Color}}" role="progressbar"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
[ngStyle]="{width: (masterPasswordScoreStyle.Width + '%')}"
attr.aria-valuenow="{{masterPasswordScoreStyle.Width}}"></div>
</div>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{ "masterPassHint" | i18n }}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="masterPasswordRetype" required
appInputVerbatim>
</div>
<div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)" [attr.aria-pressed]="showPassword">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="hint">{{'masterPassHint' | i18n}}</label>
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
</div>
</div>
<div class="box-footer">
{{'masterPassHintDesc' | i18n}}
</div>
</div>
</content>
</div>
<div class="box-footer">
{{ "masterPassHintDesc" | i18n }}
</div>
</div>
</content>
</form>

View File

@ -1,65 +1,82 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { LogService } from 'jslib-common/abstractions/log.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { UserService } from "jslib-common/abstractions/user.service";
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from 'jslib-angular/components/update-temp-password.component';
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "jslib-angular/components/update-temp-password.component";
interface MasterPasswordScore {
Color: string;
Text: string;
Width: number;
Color: string;
Text: string;
Width: number;
}
@Component({
selector: 'app-update-temp-password',
templateUrl: 'update-temp-password.component.html',
selector: "app-update-temp-password",
templateUrl: "update-temp-password.component.html",
})
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
get masterPasswordScoreStyle(): MasterPasswordScore {
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) {
case 4:
return {
Color: 'bg-success',
Text: 'strong',
Width: scoreWidth,
};
case 3:
return {
Color: 'bg-primary',
Text: 'good',
Width: scoreWidth,
};
case 2:
return {
Color: 'bg-warning',
Text: 'weak',
Width: scoreWidth,
};
default:
return {
Color: 'bg-danger',
Text: 'weak',
Width: scoreWidth,
};
}
get masterPasswordScoreStyle(): MasterPasswordScore {
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
switch (this.masterPasswordScore) {
case 4:
return {
Color: "bg-success",
Text: "strong",
Width: scoreWidth,
};
case 3:
return {
Color: "bg-primary",
Text: "good",
Width: scoreWidth,
};
case 2:
return {
Color: "bg-warning",
Text: "weak",
Width: scoreWidth,
};
default:
return {
Color: "bg-danger",
Text: "weak",
Width: scoreWidth,
};
}
}
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
cryptoService: CryptoService, userService: UserService,
messagingService: MessagingService, apiService: ApiService,
syncService: SyncService, logService: LogService) {
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
userService, messagingService, apiService, syncService, logService);
}
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
passwordGenerationService: PasswordGenerationService,
policyService: PolicyService,
cryptoService: CryptoService,
userService: UserService,
messagingService: MessagingService,
apiService: ApiService,
syncService: SyncService,
logService: LogService
) {
super(
i18nService,
platformUtilsService,
passwordGenerationService,
policyService,
cryptoService,
userService,
messagingService,
apiService,
syncService,
logService
);
}
}

View File

@ -1,199 +1,213 @@
import {
animate,
group,
query,
style,
transition,
trigger,
} from '@angular/animations';
import { animate, group, query, style, transition, trigger } from "@angular/animations";
import { BrowserApi } from '../browser/browserApi';
import { BrowserApi } from "../browser/browserApi";
const queryShown = query(':enter, :leave', [
style({ position: 'fixed', width: '100%', height: '100%' }),
], { optional: true });
const queryShown = query(
":enter, :leave",
[style({ position: "fixed", width: "100%", height: "100%" })],
{
optional: true,
}
);
// ref: https://github.com/angular/angular/issues/15477
const queryChildRoute = query('router-outlet ~ *', [
style({}),
animate(1, style({})),
], { optional: true });
const queryChildRoute = query("router-outlet ~ *", [style({}), animate(1, style({}))], {
optional: true,
});
const speed = '0.4s';
const speed = "0.4s";
export function queryTranslate(direction: string, axis: string, from: number, to: number, zIndex: number = 1000) {
return query(':' + direction, [
style({ transform: 'translate' + axis + '(' + from + '%)', zIndex: zIndex, boxShadow: '0 3px 2px -2px gray' }),
animate(speed + ' ease-in-out', style({ transform: 'translate' + axis + '(' + to + '%)' })),
], { optional: true });
export function queryTranslate(
direction: string,
axis: string,
from: number,
to: number,
zIndex: number = 1000
) {
return query(
":" + direction,
[
style({
transform: "translate" + axis + "(" + from + "%)",
zIndex: zIndex,
boxShadow: "0 3px 2px -2px gray",
}),
animate(speed + " ease-in-out", style({ transform: "translate" + axis + "(" + to + "%)" })),
],
{ optional: true }
);
}
export function queryTranslateX(direction: string, from: number, to: number, zIndex: number = 1000) {
return queryTranslate(direction, 'X', from, to, zIndex);
export function queryTranslateX(
direction: string,
from: number,
to: number,
zIndex: number = 1000
) {
return queryTranslate(direction, "X", from, to, zIndex);
}
export function queryTranslateY(direction: string, from: number, to: number, zIndex: number = 1000) {
return queryTranslate(direction, 'Y', from, to, zIndex);
export function queryTranslateY(
direction: string,
from: number,
to: number,
zIndex: number = 1000
) {
return queryTranslate(direction, "Y", from, to, zIndex);
}
const inSlideLeft = [
queryShown,
group([
queryTranslateX('enter', 100, 0),
queryTranslateX('leave', 0, -100),
queryChildRoute,
]),
queryShown,
group([queryTranslateX("enter", 100, 0), queryTranslateX("leave", 0, -100), queryChildRoute]),
];
const outSlideRight = [
queryShown,
group([
queryTranslateX('enter', -100, 0),
queryTranslateX('leave', 0, 100),
]),
queryShown,
group([queryTranslateX("enter", -100, 0), queryTranslateX("leave", 0, 100)]),
];
const inSlideUp = [
queryShown,
group([
queryTranslateY('enter', 100, 0, 1010),
queryTranslateY('leave', 0, 0),
queryChildRoute,
]),
queryShown,
group([queryTranslateY("enter", 100, 0, 1010), queryTranslateY("leave", 0, 0), queryChildRoute]),
];
const outSlideDown = [
queryShown,
group([
queryTranslateY('enter', 0, 0),
queryTranslateY('leave', 0, 100, 1010),
]),
queryShown,
group([queryTranslateY("enter", 0, 0), queryTranslateY("leave", 0, 100, 1010)]),
];
const inSlideDown = [
queryShown,
group([
queryTranslateY('enter', -100, 0, 1010),
queryTranslateY('leave', 0, 0),
queryChildRoute,
]),
queryShown,
group([queryTranslateY("enter", -100, 0, 1010), queryTranslateY("leave", 0, 0), queryChildRoute]),
];
const outSlideUp = [
queryShown,
group([
queryTranslateY('enter', 0, 0),
queryTranslateY('leave', 0, -100, 1010),
]),
queryShown,
group([queryTranslateY("enter", 0, 0), queryTranslateY("leave", 0, -100, 1010)]),
];
export function tabsToCiphers(fromState: string, toState: string) {
if (fromState == null || toState === null || toState.indexOf('ciphers_') === -1) {
return false;
}
return (fromState.indexOf('ciphers_') === 0 && fromState.indexOf('ciphers_direction=b') === -1) ||
fromState === 'tabs';
if (fromState == null || toState === null || toState.indexOf("ciphers_") === -1) {
return false;
}
return (
(fromState.indexOf("ciphers_") === 0 && fromState.indexOf("ciphers_direction=b") === -1) ||
fromState === "tabs"
);
}
export function ciphersToTabs(fromState: string, toState: string) {
if (fromState == null || toState === null || fromState.indexOf('ciphers_') === -1) {
return false;
}
return toState.indexOf('ciphers_direction=b') === 0 || toState === 'tabs';
if (fromState == null || toState === null || fromState.indexOf("ciphers_") === -1) {
return false;
}
return toState.indexOf("ciphers_direction=b") === 0 || toState === "tabs";
}
export function ciphersToView(fromState: string, toState: string) {
if (fromState == null || toState === null) {
return false;
}
return fromState.indexOf('ciphers_') === 0 &&
(toState === 'view-cipher' || toState === 'add-cipher' || toState === 'clone-cipher');
if (fromState == null || toState === null) {
return false;
}
return (
fromState.indexOf("ciphers_") === 0 &&
(toState === "view-cipher" || toState === "add-cipher" || toState === "clone-cipher")
);
}
export function viewToCiphers(fromState: string, toState: string) {
if (fromState == null || toState === null) {
return false;
}
return (fromState === 'view-cipher' || fromState === 'add-cipher' || fromState === 'clone-cipher') &&
toState.indexOf('ciphers_') === 0;
if (fromState == null || toState === null) {
return false;
}
return (
(fromState === "view-cipher" || fromState === "add-cipher" || fromState === "clone-cipher") &&
toState.indexOf("ciphers_") === 0
);
}
export const routerTransition = trigger('routerTransition', [
transition('void => home', inSlideLeft),
transition('void => tabs', inSlideLeft),
export const routerTransition = trigger("routerTransition", [
transition("void => home", inSlideLeft),
transition("void => tabs", inSlideLeft),
transition('home => environment, home => login, home => register', inSlideUp),
transition("home => environment, home => login, home => register", inSlideUp),
transition('login => home', outSlideDown),
transition('login => hint', inSlideUp),
transition('login => tabs, login => 2fa', inSlideLeft),
transition("login => home", outSlideDown),
transition("login => hint", inSlideUp),
transition("login => tabs, login => 2fa", inSlideLeft),
transition('hint => login, register => home, environment => home', outSlideDown),
transition("hint => login, register => home, environment => home", outSlideDown),
transition('2fa => login', outSlideRight),
transition('2fa => 2fa-options', inSlideUp),
transition('2fa-options => 2fa', outSlideDown),
transition('2fa => tabs', inSlideLeft),
transition("2fa => login", outSlideRight),
transition("2fa => 2fa-options", inSlideUp),
transition("2fa-options => 2fa", outSlideDown),
transition("2fa => tabs", inSlideLeft),
transition(tabsToCiphers, inSlideLeft),
transition(ciphersToTabs, outSlideRight),
transition(tabsToCiphers, inSlideLeft),
transition(ciphersToTabs, outSlideRight),
transition(ciphersToView, inSlideUp),
transition(viewToCiphers, outSlideDown),
transition(ciphersToView, inSlideUp),
transition(viewToCiphers, outSlideDown),
transition('tabs => view-cipher', inSlideUp),
transition('view-cipher => tabs', outSlideDown),
transition("tabs => view-cipher", inSlideUp),
transition("view-cipher => tabs", outSlideDown),
transition('view-cipher => edit-cipher, view-cipher => cipher-password-history', inSlideUp),
transition('edit-cipher => view-cipher, cipher-password-history => view-cipher, edit-cipher => tabs', outSlideDown),
transition("view-cipher => edit-cipher, view-cipher => cipher-password-history", inSlideUp),
transition(
"edit-cipher => view-cipher, cipher-password-history => view-cipher, edit-cipher => tabs",
outSlideDown
),
transition('view-cipher => clone-cipher', inSlideUp),
transition('clone-cipher => view-cipher, clone-cipher => tabs', outSlideDown),
transition("view-cipher => clone-cipher", inSlideUp),
transition("clone-cipher => view-cipher, clone-cipher => tabs", outSlideDown),
transition('view-cipher => share-cipher', inSlideUp),
transition('share-cipher => view-cipher', outSlideDown),
transition("view-cipher => share-cipher", inSlideUp),
transition("share-cipher => view-cipher", outSlideDown),
transition('tabs => add-cipher', inSlideUp),
transition('add-cipher => tabs', outSlideDown),
transition("tabs => add-cipher", inSlideUp),
transition("add-cipher => tabs", outSlideDown),
transition('generator => generator-history, tabs => generator-history', inSlideLeft),
transition('generator-history => generator, generator-history => tabs', outSlideRight),
transition("generator => generator-history, tabs => generator-history", inSlideLeft),
transition("generator-history => generator, generator-history => tabs", outSlideRight),
transition('add-cipher => generator, edit-cipher => generator, clone-cipher => generator', inSlideUp),
transition('generator => add-cipher, generator => edit-cipher, generator => clone-cipher', outSlideDown),
transition(
"add-cipher => generator, edit-cipher => generator, clone-cipher => generator",
inSlideUp
),
transition(
"generator => add-cipher, generator => edit-cipher, generator => clone-cipher",
outSlideDown
),
transition('edit-cipher => attachments, edit-cipher => collections', inSlideLeft),
transition('attachments => edit-cipher, collections => edit-cipher', outSlideRight),
transition("edit-cipher => attachments, edit-cipher => collections", inSlideLeft),
transition("attachments => edit-cipher, collections => edit-cipher", outSlideRight),
transition('clone-cipher => attachments, clone-cipher => collections', inSlideLeft),
transition('attachments => clone-cipher, collections => clone-cipher', outSlideRight),
transition("clone-cipher => attachments, clone-cipher => collections", inSlideLeft),
transition("attachments => clone-cipher, collections => clone-cipher", outSlideRight),
transition('tabs => export', inSlideLeft),
transition('export => tabs', outSlideRight),
transition("tabs => export", inSlideLeft),
transition("export => tabs", outSlideRight),
transition('tabs => folders', inSlideLeft),
transition('folders => tabs', outSlideRight),
transition("tabs => folders", inSlideLeft),
transition("folders => tabs", outSlideRight),
transition('folders => edit-folder, folders => add-folder', inSlideUp),
transition('edit-folder => folders, add-folder => folders', outSlideDown),
transition("folders => edit-folder, folders => add-folder", inSlideUp),
transition("edit-folder => folders, add-folder => folders", outSlideDown),
transition('tabs => sync', inSlideLeft),
transition('sync => tabs', outSlideRight),
transition("tabs => sync", inSlideLeft),
transition("sync => tabs", outSlideRight),
transition('tabs => options', inSlideLeft),
transition('options => tabs', outSlideRight),
transition("tabs => options", inSlideLeft),
transition("options => tabs", outSlideRight),
transition('tabs => premium', inSlideLeft),
transition('premium => tabs', outSlideRight),
transition("tabs => premium", inSlideLeft),
transition("premium => tabs", outSlideRight),
transition('tabs => lock', inSlideDown),
transition("tabs => lock", inSlideDown),
transition('tabs => send-type', inSlideLeft),
transition('send-type => tabs', outSlideRight),
transition("tabs => send-type", inSlideLeft),
transition("send-type => tabs", outSlideRight),
transition('tabs => add-send, send-type => add-send', inSlideUp),
transition('add-send => tabs, add-send => send-type', outSlideDown),
transition("tabs => add-send, send-type => add-send", inSlideUp),
transition("add-send => tabs, add-send => send-type", outSlideDown),
transition('tabs => edit-send, send-type => edit-send', inSlideUp),
transition('edit-send => tabs, edit-send => send-type', outSlideDown),
transition("tabs => edit-send, send-type => edit-send", inSlideUp),
transition("edit-send => tabs, edit-send => send-type", outSlideDown),
]);

View File

@ -1,355 +1,352 @@
import { Injectable, NgModule } from '@angular/core';
import {
ActivatedRouteSnapshot,
RouteReuseStrategy,
RouterModule,
Routes,
} from '@angular/router';
import { Injectable, NgModule } from "@angular/core";
import { ActivatedRouteSnapshot, RouteReuseStrategy, RouterModule, Routes } from "@angular/router";
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service';
import { LockGuardService } from 'jslib-angular/services/lock-guard.service';
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
import { LockGuardService } from "jslib-angular/services/lock-guard.service";
import { DebounceNavigationService } from './services/debounceNavigationService';
import { LaunchGuardService } from './services/launch-guard.service';
import { DebounceNavigationService } from "./services/debounceNavigationService";
import { LaunchGuardService } from "./services/launch-guard.service";
import { EnvironmentComponent } from './accounts/environment.component';
import { HintComponent } from './accounts/hint.component';
import { HomeComponent } from './accounts/home.component';
import { LockComponent } from './accounts/lock.component';
import { LoginComponent } from './accounts/login.component';
import { RegisterComponent } from './accounts/register.component';
import { RemovePasswordComponent } from './accounts/remove-password.component';
import { SetPasswordComponent } from './accounts/set-password.component';
import { SsoComponent } from './accounts/sso.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
import { EnvironmentComponent } from "./accounts/environment.component";
import { HintComponent } from "./accounts/hint.component";
import { HomeComponent } from "./accounts/home.component";
import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from "./accounts/login.component";
import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from "./accounts/set-password.component";
import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component';
import { PasswordGeneratorComponent } from './generator/password-generator.component';
import { PasswordGeneratorHistoryComponent } from "./generator/password-generator-history.component";
import { PasswordGeneratorComponent } from "./generator/password-generator.component";
import { PrivateModeComponent } from './private-mode.component';
import { TabsComponent } from './tabs.component';
import { PrivateModeComponent } from "./private-mode.component";
import { TabsComponent } from "./tabs.component";
import { ExcludedDomainsComponent } from './settings/excluded-domains.component';
import { ExportComponent } from './settings/export.component';
import { FolderAddEditComponent } from './settings/folder-add-edit.component';
import { FoldersComponent } from './settings/folders.component';
import { OptionsComponent } from './settings/options.component';
import { PremiumComponent } from './settings/premium.component';
import { SettingsComponent } from './settings/settings.component';
import { SyncComponent } from './settings/sync.component';
import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
import { ExportComponent } from "./settings/export.component";
import { FolderAddEditComponent } from "./settings/folder-add-edit.component";
import { FoldersComponent } from "./settings/folders.component";
import { OptionsComponent } from "./settings/options.component";
import { PremiumComponent } from "./settings/premium.component";
import { SettingsComponent } from "./settings/settings.component";
import { SyncComponent } from "./settings/sync.component";
import { AddEditComponent } from './vault/add-edit.component';
import { AttachmentsComponent } from './vault/attachments.component';
import { CiphersComponent } from './vault/ciphers.component';
import { CollectionsComponent } from './vault/collections.component';
import { CurrentTabComponent } from './vault/current-tab.component';
import { GroupingsComponent } from './vault/groupings.component';
import { PasswordHistoryComponent } from './vault/password-history.component';
import { ShareComponent } from './vault/share.component';
import { ViewComponent } from './vault/view.component';
import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component";
import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from "./vault/collections.component";
import { CurrentTabComponent } from "./vault/current-tab.component";
import { GroupingsComponent } from "./vault/groupings.component";
import { PasswordHistoryComponent } from "./vault/password-history.component";
import { ShareComponent } from "./vault/share.component";
import { ViewComponent } from "./vault/view.component";
import { SendAddEditComponent } from './send/send-add-edit.component';
import { SendGroupingsComponent } from './send/send-groupings.component';
import { SendTypeComponent } from './send/send-type.component';
import { SendAddEditComponent } from "./send/send-add-edit.component";
import { SendGroupingsComponent } from "./send/send-groupings.component";
import { SendTypeComponent } from "./send/send-type.component";
const routes: Routes = [
{
path: '',
redirectTo: 'home',
pathMatch: 'full',
},
{
path: 'vault',
redirectTo: '/tabs/vault',
pathMatch: 'full',
},
{
path: 'home',
component: HomeComponent,
canActivate: [LaunchGuardService],
data: { state: 'home' },
},
{
path: 'login',
component: LoginComponent,
canActivate: [LaunchGuardService],
data: { state: 'login' },
},
{
path: 'lock',
component: LockComponent,
canActivate: [LockGuardService],
data: { state: 'lock' },
},
{
path: '2fa',
component: TwoFactorComponent,
canActivate: [LaunchGuardService],
data: { state: '2fa' },
},
{
path: '2fa-options',
component: TwoFactorOptionsComponent,
canActivate: [LaunchGuardService],
data: { state: '2fa-options' },
},
{
path: 'sso',
component: SsoComponent,
canActivate: [LaunchGuardService],
data: { state: 'sso' },
},
{
path: 'set-password',
component: SetPasswordComponent,
data: { state: 'set-password' },
},
{
path: 'remove-password',
component: RemovePasswordComponent,
{
path: "",
redirectTo: "home",
pathMatch: "full",
},
{
path: "vault",
redirectTo: "/tabs/vault",
pathMatch: "full",
},
{
path: "home",
component: HomeComponent,
canActivate: [LaunchGuardService],
data: { state: "home" },
},
{
path: "login",
component: LoginComponent,
canActivate: [LaunchGuardService],
data: { state: "login" },
},
{
path: "lock",
component: LockComponent,
canActivate: [LockGuardService],
data: { state: "lock" },
},
{
path: "2fa",
component: TwoFactorComponent,
canActivate: [LaunchGuardService],
data: { state: "2fa" },
},
{
path: "2fa-options",
component: TwoFactorOptionsComponent,
canActivate: [LaunchGuardService],
data: { state: "2fa-options" },
},
{
path: "sso",
component: SsoComponent,
canActivate: [LaunchGuardService],
data: { state: "sso" },
},
{
path: "set-password",
component: SetPasswordComponent,
data: { state: "set-password" },
},
{
path: "remove-password",
component: RemovePasswordComponent,
canActivate: [AuthGuardService],
data: { state: "remove-password" },
},
{
path: "register",
component: RegisterComponent,
canActivate: [LaunchGuardService],
data: { state: "register" },
},
{
path: "hint",
component: HintComponent,
canActivate: [LaunchGuardService],
data: { state: "hint" },
},
{
path: "environment",
component: EnvironmentComponent,
canActivate: [LaunchGuardService],
data: { state: "environment" },
},
{
path: "ciphers",
component: CiphersComponent,
canActivate: [AuthGuardService],
data: { state: "ciphers" },
},
{
path: "view-cipher",
component: ViewComponent,
canActivate: [AuthGuardService],
data: { state: "view-cipher" },
},
{
path: "cipher-password-history",
component: PasswordHistoryComponent,
canActivate: [AuthGuardService],
data: { state: "cipher-password-history" },
},
{
path: "add-cipher",
component: AddEditComponent,
canActivate: [AuthGuardService, DebounceNavigationService],
data: { state: "add-cipher" },
runGuardsAndResolvers: "always",
},
{
path: "edit-cipher",
component: AddEditComponent,
canActivate: [AuthGuardService, DebounceNavigationService],
data: { state: "edit-cipher" },
runGuardsAndResolvers: "always",
},
{
path: "share-cipher",
component: ShareComponent,
canActivate: [AuthGuardService],
data: { state: "share-cipher" },
},
{
path: "collections",
component: CollectionsComponent,
canActivate: [AuthGuardService],
data: { state: "collections" },
},
{
path: "attachments",
component: AttachmentsComponent,
canActivate: [AuthGuardService],
data: { state: "attachments" },
},
{
path: "generator",
component: PasswordGeneratorComponent,
canActivate: [AuthGuardService],
data: { state: "generator" },
},
{
path: "generator-history",
component: PasswordGeneratorHistoryComponent,
canActivate: [AuthGuardService],
data: { state: "generator-history" },
},
{
path: "export",
component: ExportComponent,
canActivate: [AuthGuardService],
data: { state: "export" },
},
{
path: "folders",
component: FoldersComponent,
canActivate: [AuthGuardService],
data: { state: "folders" },
},
{
path: "add-folder",
component: FolderAddEditComponent,
canActivate: [AuthGuardService],
data: { state: "add-folder" },
},
{
path: "edit-folder",
component: FolderAddEditComponent,
canActivate: [AuthGuardService],
data: { state: "edit-folder" },
},
{
path: "sync",
component: SyncComponent,
canActivate: [AuthGuardService],
data: { state: "sync" },
},
{
path: "excluded-domains",
component: ExcludedDomainsComponent,
canActivate: [AuthGuardService],
data: { state: "excluded-domains" },
},
{
path: "premium",
component: PremiumComponent,
canActivate: [AuthGuardService],
data: { state: "premium" },
},
{
path: "options",
component: OptionsComponent,
canActivate: [AuthGuardService],
data: { state: "options" },
},
{
path: "private-mode",
component: PrivateModeComponent,
data: { state: "private-mode" },
},
{
path: "clone-cipher",
component: AddEditComponent,
canActivate: [AuthGuardService],
data: { state: "clone-cipher" },
},
{
path: "send-type",
component: SendTypeComponent,
canActivate: [AuthGuardService],
data: { state: "send-type" },
},
{
path: "add-send",
component: SendAddEditComponent,
canActivate: [AuthGuardService],
data: { state: "add-send" },
},
{
path: "edit-send",
component: SendAddEditComponent,
canActivate: [AuthGuardService],
data: { state: "edit-send" },
},
{
path: "update-temp-password",
component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService],
data: { state: "update-temp-password" },
},
{
path: "tabs",
component: TabsComponent,
data: { state: "tabs" },
children: [
{
path: "",
redirectTo: "/tabs/vault",
pathMatch: "full",
},
{
path: "current",
component: CurrentTabComponent,
canActivate: [AuthGuardService],
data: { state: 'remove-password' },
},
{
path: 'register',
component: RegisterComponent,
canActivate: [LaunchGuardService],
data: { state: 'register' },
},
{
path: 'hint',
component: HintComponent,
canActivate: [LaunchGuardService],
data: { state: 'hint' },
},
{
path: 'environment',
component: EnvironmentComponent,
canActivate: [LaunchGuardService],
data: { state: 'environment' },
},
{
path: 'ciphers',
component: CiphersComponent,
data: { state: "tabs_current" },
runGuardsAndResolvers: "always",
},
{
path: "vault",
component: GroupingsComponent,
canActivate: [AuthGuardService],
data: { state: 'ciphers' },
},
{
path: 'view-cipher',
component: ViewComponent,
canActivate: [AuthGuardService],
data: { state: 'view-cipher' },
},
{
path: 'cipher-password-history',
component: PasswordHistoryComponent,
canActivate: [AuthGuardService],
data: { state: 'cipher-password-history' },
},
{
path: 'add-cipher',
component: AddEditComponent,
canActivate: [AuthGuardService, DebounceNavigationService],
data: { state: 'add-cipher' },
runGuardsAndResolvers: 'always',
},
{
path: 'edit-cipher',
component: AddEditComponent,
canActivate: [AuthGuardService, DebounceNavigationService],
data: { state: 'edit-cipher' },
runGuardsAndResolvers: 'always',
},
{
path: 'share-cipher',
component: ShareComponent,
canActivate: [AuthGuardService],
data: { state: 'share-cipher' },
},
{
path: 'collections',
component: CollectionsComponent,
canActivate: [AuthGuardService],
data: { state: 'collections' },
},
{
path: 'attachments',
component: AttachmentsComponent,
canActivate: [AuthGuardService],
data: { state: 'attachments' },
},
{
path: 'generator',
data: { state: "tabs_vault" },
},
{
path: "generator",
component: PasswordGeneratorComponent,
canActivate: [AuthGuardService],
data: { state: 'generator' },
},
{
path: 'generator-history',
component: PasswordGeneratorHistoryComponent,
data: { state: "tabs_generator" },
},
{
path: "settings",
component: SettingsComponent,
canActivate: [AuthGuardService],
data: { state: 'generator-history' },
},
{
path: 'export',
component: ExportComponent,
data: { state: "tabs_settings" },
},
{
path: "send",
component: SendGroupingsComponent,
canActivate: [AuthGuardService],
data: { state: 'export' },
},
{
path: 'folders',
component: FoldersComponent,
canActivate: [AuthGuardService],
data: { state: 'folders' },
},
{
path: 'add-folder',
component: FolderAddEditComponent,
canActivate: [AuthGuardService],
data: { state: 'add-folder' },
},
{
path: 'edit-folder',
component: FolderAddEditComponent,
canActivate: [AuthGuardService],
data: { state: 'edit-folder' },
},
{
path: 'sync',
component: SyncComponent,
canActivate: [AuthGuardService],
data: { state: 'sync' },
},
{
path: 'excluded-domains',
component: ExcludedDomainsComponent,
canActivate: [AuthGuardService],
data: { state: 'excluded-domains' },
},
{
path: 'premium',
component: PremiumComponent,
canActivate: [AuthGuardService],
data: { state: 'premium' },
},
{
path: 'options',
component: OptionsComponent,
canActivate: [AuthGuardService],
data: { state: 'options' },
},
{
path: 'private-mode',
component: PrivateModeComponent,
data: { state: 'private-mode' },
},
{
path: 'clone-cipher',
component: AddEditComponent,
canActivate: [AuthGuardService],
data: { state: 'clone-cipher' },
},
{
path: 'send-type',
component: SendTypeComponent,
canActivate: [AuthGuardService],
data: { state: 'send-type' },
},
{
path: 'add-send',
component: SendAddEditComponent,
canActivate: [AuthGuardService],
data: { state: 'add-send' },
},
{
path: 'edit-send',
component: SendAddEditComponent,
canActivate: [AuthGuardService],
data: { state: 'edit-send' },
},
{
path: 'update-temp-password',
component: UpdateTempPasswordComponent,
canActivate: [AuthGuardService],
data: { state: 'update-temp-password' },
},
{
path: 'tabs',
component: TabsComponent,
data: { state: 'tabs' },
children: [
{
path: '',
redirectTo: '/tabs/vault',
pathMatch: 'full',
},
{
path: 'current',
component: CurrentTabComponent,
canActivate: [AuthGuardService],
data: { state: 'tabs_current' },
runGuardsAndResolvers: 'always',
},
{
path: 'vault',
component: GroupingsComponent,
canActivate: [AuthGuardService],
data: { state: 'tabs_vault' },
},
{
path: 'generator',
component: PasswordGeneratorComponent,
canActivate: [AuthGuardService],
data: { state: 'tabs_generator' },
},
{
path: 'settings',
component: SettingsComponent,
canActivate: [AuthGuardService],
data: { state: 'tabs_settings' },
},
{
path: 'send',
component: SendGroupingsComponent,
canActivate: [AuthGuardService],
data: { state: 'tabs_send' },
},
],
},
data: { state: "tabs_send" },
},
],
},
];
@Injectable()
export class NoRouteReuseStrategy implements RouteReuseStrategy {
shouldDetach(route: ActivatedRouteSnapshot) {
return false;
}
shouldDetach(route: ActivatedRouteSnapshot) {
return false;
}
store(route: ActivatedRouteSnapshot, handle: {}) { /* Nothing */ }
store(route: ActivatedRouteSnapshot, handle: {}) {
/* Nothing */
}
shouldAttach(route: ActivatedRouteSnapshot) {
return false;
}
shouldAttach(route: ActivatedRouteSnapshot) {
return false;
}
retrieve(route: ActivatedRouteSnapshot): any {
return null;
}
retrieve(route: ActivatedRouteSnapshot): any {
return null;
}
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) {
return false;
}
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) {
return false;
}
}
@NgModule({
imports: [RouterModule.forRoot(routes, {
useHash: true,
onSameUrlNavigation: 'reload',
/*enableTracing: true,*/
})],
exports: [RouterModule],
providers: [
{ provide: RouteReuseStrategy, useClass: NoRouteReuseStrategy },
],
imports: [
RouterModule.forRoot(routes, {
useHash: true,
onSameUrlNavigation: "reload",
/*enableTracing: true,*/
}),
],
exports: [RouterModule],
providers: [{ provide: RouteReuseStrategy, useClass: NoRouteReuseStrategy }],
})
export class AppRoutingModule { }
export class AppRoutingModule {}

View File

@ -1,243 +1,254 @@
import {
ChangeDetectorRef,
Component,
NgZone,
OnInit,
SecurityContext,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import {
NavigationEnd,
Router,
RouterOutlet,
} from '@angular/router';
import {
IndividualConfig,
ToastrService,
} from 'ngx-toastr';
import Swal, { SweetAlertIcon } from 'sweetalert2/src/sweetalert2.js';
import { BrowserApi } from '../browser/browserApi';
import { ChangeDetectorRef, Component, NgZone, OnInit, SecurityContext } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
import { IndividualConfig, ToastrService } from "ngx-toastr";
import Swal, { SweetAlertIcon } from "sweetalert2/src/sweetalert2.js";
import { BrowserApi } from "../browser/browserApi";
import { AuthService } from 'jslib-common/abstractions/auth.service';
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { StorageService } from 'jslib-common/abstractions/storage.service';
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ConstantsService } from "jslib-common/services/constants.service";
import { routerTransition } from './app-routing.animations';
import { routerTransition } from "./app-routing.animations";
@Component({
selector: 'app-root',
styles: [],
animations: [routerTransition],
template: `
<main [@routerTransition]="getState(o)">
<router-outlet #o="outlet"></router-outlet>
</main>`,
selector: "app-root",
styles: [],
animations: [routerTransition],
template: ` <main [@routerTransition]="getState(o)">
<router-outlet #o="outlet"></router-outlet>
</main>`,
})
export class AppComponent implements OnInit {
private lastActivity: number = null;
private lastActivity: number = null;
constructor(
private toastrService: ToastrService,
private storageService: StorageService,
private broadcasterService: BroadcasterService,
private authService: AuthService,
private i18nService: I18nService,
private router: Router,
private stateService: StateService,
private messagingService: MessagingService,
private changeDetectorRef: ChangeDetectorRef,
private ngZone: NgZone,
private sanitizer: DomSanitizer,
private platformUtilsService: PlatformUtilsService,
private keyConnectoService: KeyConnectorService
) {}
constructor(private toastrService: ToastrService, private storageService: StorageService,
private broadcasterService: BroadcasterService, private authService: AuthService,
private i18nService: I18nService, private router: Router,
private stateService: StateService, private messagingService: MessagingService,
private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
private sanitizer: DomSanitizer, private platformUtilsService: PlatformUtilsService,
private keyConnectoService: KeyConnectorService) { }
ngOnInit() {
if (BrowserApi.getBackgroundPage() == null) {
return;
}
ngOnInit() {
if (BrowserApi.getBackgroundPage() == null) {
return;
}
this.ngZone.runOutsideAngular(() => {
window.onmousemove = () => this.recordActivity();
window.onmousedown = () => this.recordActivity();
window.ontouchstart = () => this.recordActivity();
window.onclick = () => this.recordActivity();
window.onscroll = () => this.recordActivity();
window.onkeypress = () => this.recordActivity();
});
this.ngZone.runOutsideAngular(() => {
window.onmousemove = () => this.recordActivity();
window.onmousedown = () => this.recordActivity();
window.ontouchstart = () => this.recordActivity();
window.onclick = () => this.recordActivity();
window.onscroll = () => this.recordActivity();
window.onkeypress = () => this.recordActivity();
(window as any).bitwardenPopupMainMessageListener = async (
msg: any,
sender: any,
sendResponse: any
) => {
if (msg.command === "doneLoggingOut") {
this.ngZone.run(async () => {
this.authService.logOut(() => {
if (msg.expired) {
this.showToast({
type: "warning",
title: this.i18nService.t("loggedOut"),
text: this.i18nService.t("loginExpired"),
});
}
this.router.navigate(["home"]);
this.stateService.purge();
});
this.changeDetectorRef.detectChanges();
});
(window as any).bitwardenPopupMainMessageListener = async (msg: any, sender: any, sendResponse: any) => {
if (msg.command === 'doneLoggingOut') {
this.ngZone.run(async () => {
this.authService.logOut(() => {
if (msg.expired) {
this.showToast({
type: 'warning',
title: this.i18nService.t('loggedOut'),
text: this.i18nService.t('loginExpired'),
});
}
this.router.navigate(['home']);
this.stateService.purge();
});
this.changeDetectorRef.detectChanges();
});
} else if (msg.command === 'authBlocked') {
this.ngZone.run(() => {
this.router.navigate(['home']);
});
} else if (msg.command === 'locked') {
this.stateService.purge();
this.ngZone.run(() => {
this.router.navigate(['lock']);
});
} else if (msg.command === 'showDialog') {
await this.showDialog(msg);
} else if (msg.command === 'showToast') {
this.ngZone.run(() => {
this.showToast(msg);
});
} else if (msg.command === 'reloadProcess') {
const windowReload = this.platformUtilsService.isSafari() ||
this.platformUtilsService.isFirefox() || this.platformUtilsService.isOpera();
if (windowReload) {
// Wait to make sure background has reloaded first.
window.setTimeout(() => BrowserApi.reloadExtension(window), 2000);
}
} else if (msg.command === 'reloadPopup') {
this.ngZone.run(() => {
this.router.navigate(['/']);
});
} else if (msg.command === 'convertAccountToKeyConnector') {
this.ngZone.run(async () => {
await this.keyConnectoService.setConvertAccountRequired(true);
this.router.navigate(['/remove-password']);
});
} else {
msg.webExtSender = sender;
this.broadcasterService.send(msg);
}
};
BrowserApi.messageListener('app.component', (window as any).bitwardenPopupMainMessageListener);
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
const url = event.urlAfterRedirects || event.url || '';
if (url.startsWith('/tabs/') && (window as any).previousPopupUrl != null &&
(window as any).previousPopupUrl.startsWith('/tabs/')) {
this.stateService.remove('GroupingsComponent');
this.stateService.remove('GroupingsComponentScope');
this.stateService.remove('CiphersComponent');
this.stateService.remove('SendGroupingsComponent');
this.stateService.remove('SendGroupingsComponentScope');
this.stateService.remove('SendTypeComponent');
}
if (url.startsWith('/tabs/')) {
this.stateService.remove('addEditCipherInfo');
}
(window as any).previousPopupUrl = url;
// Clear route direction after animation (400ms)
if ((window as any).routeDirection != null) {
window.setTimeout(() => {
(window as any).routeDirection = null;
}, 400);
}
}
} else if (msg.command === "authBlocked") {
this.ngZone.run(() => {
this.router.navigate(["home"]);
});
}
getState(outlet: RouterOutlet) {
if (outlet.activatedRouteData.state === 'ciphers') {
const routeDirection = (window as any).routeDirection != null ? (window as any).routeDirection : '';
return 'ciphers_direction=' + routeDirection + '_' +
(outlet.activatedRoute.queryParams as any).value.folderId + '_' +
(outlet.activatedRoute.queryParams as any).value.collectionId;
} else {
return outlet.activatedRouteData.state;
}
}
private async recordActivity() {
const now = (new Date()).getTime();
if (this.lastActivity != null && now - this.lastActivity < 250) {
return;
}
this.lastActivity = now;
this.storageService.save(ConstantsService.lastActiveKey, now);
}
private showToast(msg: any) {
let message = '';
const options: Partial<IndividualConfig> = {};
if (typeof (msg.text) === 'string') {
message = msg.text;
} else if (msg.text.length === 1) {
message = msg.text[0];
} else {
msg.text.forEach((t: string) =>
message += ('<p>' + this.sanitizer.sanitize(SecurityContext.HTML, t) + '</p>'));
options.enableHtml = true;
}
if (msg.options != null) {
if (msg.options.trustedHtml === true) {
options.enableHtml = true;
}
if (msg.options.timeout != null && msg.options.timeout > 0) {
options.timeOut = msg.options.timeout;
}
}
this.toastrService.show(message, msg.title, options, 'toast-' + msg.type);
}
private async showDialog(msg: any) {
let iconClasses: string = null;
const type = msg.type;
if (type != null) {
// If you add custom types to this part, the type to SweetAlertIcon cast below needs to be changed.
switch (type) {
case 'success':
iconClasses = 'fa-check text-success';
break;
case 'warning':
iconClasses = 'fa-warning text-warning';
break;
case 'error':
iconClasses = 'fa-bolt text-danger';
break;
case 'info':
iconClasses = 'fa-info-circle text-info';
break;
default:
break;
}
}
const cancelText = msg.cancelText;
const confirmText = msg.confirmText;
const confirmed = await Swal.fire({
heightAuto: false,
buttonsStyling: false,
icon: type as SweetAlertIcon, // required to be any of the SweetAlertIcons to output the iconHtml.
iconHtml: iconClasses != null ? `<i class="swal-custom-icon fa ${iconClasses}"></i>` : undefined,
text: msg.text,
html: msg.html,
titleText: msg.title,
showCancelButton: (cancelText != null),
cancelButtonText: cancelText,
showConfirmButton: true,
confirmButtonText: confirmText == null ? this.i18nService.t('ok') : confirmText,
timer: 300000,
} else if (msg.command === "locked") {
this.stateService.purge();
this.ngZone.run(() => {
this.router.navigate(["lock"]);
});
this.messagingService.send('showDialogResolve', {
dialogId: msg.dialogId,
confirmed: confirmed.value,
} else if (msg.command === "showDialog") {
await this.showDialog(msg);
} else if (msg.command === "showToast") {
this.ngZone.run(() => {
this.showToast(msg);
});
} else if (msg.command === "reloadProcess") {
const windowReload =
this.platformUtilsService.isSafari() ||
this.platformUtilsService.isFirefox() ||
this.platformUtilsService.isOpera();
if (windowReload) {
// Wait to make sure background has reloaded first.
window.setTimeout(() => BrowserApi.reloadExtension(window), 2000);
}
} else if (msg.command === "reloadPopup") {
this.ngZone.run(() => {
this.router.navigate(["/"]);
});
} else if (msg.command === "convertAccountToKeyConnector") {
this.ngZone.run(async () => {
await this.keyConnectoService.setConvertAccountRequired(true);
this.router.navigate(["/remove-password"]);
});
} else {
msg.webExtSender = sender;
this.broadcasterService.send(msg);
}
};
BrowserApi.messageListener("app.component", (window as any).bitwardenPopupMainMessageListener);
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
const url = event.urlAfterRedirects || event.url || "";
if (
url.startsWith("/tabs/") &&
(window as any).previousPopupUrl != null &&
(window as any).previousPopupUrl.startsWith("/tabs/")
) {
this.stateService.remove("GroupingsComponent");
this.stateService.remove("GroupingsComponentScope");
this.stateService.remove("CiphersComponent");
this.stateService.remove("SendGroupingsComponent");
this.stateService.remove("SendGroupingsComponentScope");
this.stateService.remove("SendTypeComponent");
}
if (url.startsWith("/tabs/")) {
this.stateService.remove("addEditCipherInfo");
}
(window as any).previousPopupUrl = url;
// Clear route direction after animation (400ms)
if ((window as any).routeDirection != null) {
window.setTimeout(() => {
(window as any).routeDirection = null;
}, 400);
}
}
});
}
getState(outlet: RouterOutlet) {
if (outlet.activatedRouteData.state === "ciphers") {
const routeDirection =
(window as any).routeDirection != null ? (window as any).routeDirection : "";
return (
"ciphers_direction=" +
routeDirection +
"_" +
(outlet.activatedRoute.queryParams as any).value.folderId +
"_" +
(outlet.activatedRoute.queryParams as any).value.collectionId
);
} else {
return outlet.activatedRouteData.state;
}
}
private async recordActivity() {
const now = new Date().getTime();
if (this.lastActivity != null && now - this.lastActivity < 250) {
return;
}
this.lastActivity = now;
this.storageService.save(ConstantsService.lastActiveKey, now);
}
private showToast(msg: any) {
let message = "";
const options: Partial<IndividualConfig> = {};
if (typeof msg.text === "string") {
message = msg.text;
} else if (msg.text.length === 1) {
message = msg.text[0];
} else {
msg.text.forEach(
(t: string) =>
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>")
);
options.enableHtml = true;
}
if (msg.options != null) {
if (msg.options.trustedHtml === true) {
options.enableHtml = true;
}
if (msg.options.timeout != null && msg.options.timeout > 0) {
options.timeOut = msg.options.timeout;
}
}
this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
}
private async showDialog(msg: any) {
let iconClasses: string = null;
const type = msg.type;
if (type != null) {
// If you add custom types to this part, the type to SweetAlertIcon cast below needs to be changed.
switch (type) {
case "success":
iconClasses = "fa-check text-success";
break;
case "warning":
iconClasses = "fa-warning text-warning";
break;
case "error":
iconClasses = "fa-bolt text-danger";
break;
case "info":
iconClasses = "fa-info-circle text-info";
break;
default:
break;
}
}
const cancelText = msg.cancelText;
const confirmText = msg.confirmText;
const confirmed = await Swal.fire({
heightAuto: false,
buttonsStyling: false,
icon: type as SweetAlertIcon, // required to be any of the SweetAlertIcons to output the iconHtml.
iconHtml:
iconClasses != null ? `<i class="swal-custom-icon fa ${iconClasses}"></i>` : undefined,
text: msg.text,
html: msg.html,
titleText: msg.title,
showCancelButton: cancelText != null,
cancelButtonText: cancelText,
showConfirmButton: true,
confirmButtonText: confirmText == null ? this.i18nService.t("ok") : confirmText,
timer: 300000,
});
this.messagingService.send("showDialogResolve", {
dialogId: msg.dialogId,
confirmed: confirmed.value,
});
}
}

View File

@ -1,272 +1,265 @@
import { A11yModule } from '@angular/cdk/a11y';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { A11yModule } from "@angular/cdk/a11y";
import { DragDropModule } from "@angular/cdk/drag-drop";
import { ScrollingModule } from "@angular/cdk/scrolling";
import { AppRoutingModule } from './app-routing.module';
import { ServicesModule } from './services/services.module';
import { AppRoutingModule } from "./app-routing.module";
import { ServicesModule } from "./services/services.module";
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { EnvironmentComponent } from './accounts/environment.component';
import { HintComponent } from './accounts/hint.component';
import { HomeComponent } from './accounts/home.component';
import { LockComponent } from './accounts/lock.component';
import { LoginComponent } from './accounts/login.component';
import { RegisterComponent } from './accounts/register.component';
import { RemovePasswordComponent } from './accounts/remove-password.component';
import { SetPasswordComponent } from './accounts/set-password.component';
import { SsoComponent } from './accounts/sso.component';
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
import { TwoFactorComponent } from './accounts/two-factor.component';
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
import { EnvironmentComponent } from "./accounts/environment.component";
import { HintComponent } from "./accounts/hint.component";
import { HomeComponent } from "./accounts/home.component";
import { LockComponent } from "./accounts/lock.component";
import { LoginComponent } from "./accounts/login.component";
import { RegisterComponent } from "./accounts/register.component";
import { RemovePasswordComponent } from "./accounts/remove-password.component";
import { SetPasswordComponent } from "./accounts/set-password.component";
import { SsoComponent } from "./accounts/sso.component";
import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
import { TwoFactorComponent } from "./accounts/two-factor.component";
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component';
import { PasswordGeneratorComponent } from './generator/password-generator.component';
import { PasswordGeneratorHistoryComponent } from "./generator/password-generator-history.component";
import { PasswordGeneratorComponent } from "./generator/password-generator.component";
import { AppComponent } from './app.component';
import { PrivateModeComponent } from './private-mode.component';
import { TabsComponent } from './tabs.component';
import { AppComponent } from "./app.component";
import { PrivateModeComponent } from "./private-mode.component";
import { TabsComponent } from "./tabs.component";
import { ExcludedDomainsComponent } from './settings/excluded-domains.component';
import { ExportComponent } from './settings/export.component';
import { FolderAddEditComponent } from './settings/folder-add-edit.component';
import { FoldersComponent } from './settings/folders.component';
import { OptionsComponent } from './settings/options.component';
import { PremiumComponent } from './settings/premium.component';
import { SettingsComponent } from './settings/settings.component';
import { SyncComponent } from './settings/sync.component';
import { VaultTimeoutInputComponent } from './settings/vault-timeout-input.component';
import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
import { ExportComponent } from "./settings/export.component";
import { FolderAddEditComponent } from "./settings/folder-add-edit.component";
import { FoldersComponent } from "./settings/folders.component";
import { OptionsComponent } from "./settings/options.component";
import { PremiumComponent } from "./settings/premium.component";
import { SettingsComponent } from "./settings/settings.component";
import { SyncComponent } from "./settings/sync.component";
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
import { AddEditCustomFieldsComponent } from './vault/add-edit-custom-fields.component';
import { AddEditComponent } from './vault/add-edit.component';
import { AttachmentsComponent } from './vault/attachments.component';
import { CiphersComponent } from './vault/ciphers.component';
import { CollectionsComponent } from './vault/collections.component';
import { CurrentTabComponent } from './vault/current-tab.component';
import { GroupingsComponent } from './vault/groupings.component';
import { PasswordHistoryComponent } from './vault/password-history.component';
import { ShareComponent } from './vault/share.component';
import { ViewCustomFieldsComponent } from './vault/view-custom-fields.component';
import { ViewComponent } from './vault/view.component';
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
import { AddEditComponent } from "./vault/add-edit.component";
import { AttachmentsComponent } from "./vault/attachments.component";
import { CiphersComponent } from "./vault/ciphers.component";
import { CollectionsComponent } from "./vault/collections.component";
import { CurrentTabComponent } from "./vault/current-tab.component";
import { GroupingsComponent } from "./vault/groupings.component";
import { PasswordHistoryComponent } from "./vault/password-history.component";
import { ShareComponent } from "./vault/share.component";
import { ViewCustomFieldsComponent } from "./vault/view-custom-fields.component";
import { ViewComponent } from "./vault/view.component";
import { EffluxDatesComponent as SendEffluxDatesComponent } from './send/efflux-dates.component';
import { SendAddEditComponent } from './send/send-add-edit.component';
import { SendGroupingsComponent } from './send/send-groupings.component';
import { SendTypeComponent } from './send/send-type.component';
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
import { SendAddEditComponent } from "./send/send-add-edit.component";
import { SendGroupingsComponent } from "./send/send-groupings.component";
import { SendTypeComponent } from "./send/send-type.component";
import { A11yTitleDirective } from 'jslib-angular/directives/a11y-title.directive';
import { ApiActionDirective } from 'jslib-angular/directives/api-action.directive';
import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive';
import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive';
import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive';
import { CipherListVirtualScroll } from 'jslib-angular/directives/cipherListVirtualScroll.directive';
import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive';
import { InputVerbatimDirective } from 'jslib-angular/directives/input-verbatim.directive';
import { SelectCopyDirective } from 'jslib-angular/directives/select-copy.directive';
import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive';
import { StopPropDirective } from 'jslib-angular/directives/stop-prop.directive';
import { TrueFalseValueDirective } from 'jslib-angular/directives/true-false-value.directive';
import { A11yTitleDirective } from "jslib-angular/directives/a11y-title.directive";
import { ApiActionDirective } from "jslib-angular/directives/api-action.directive";
import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive";
import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive";
import { BoxRowDirective } from "jslib-angular/directives/box-row.directive";
import { CipherListVirtualScroll } from "jslib-angular/directives/cipherListVirtualScroll.directive";
import { FallbackSrcDirective } from "jslib-angular/directives/fallback-src.directive";
import { InputVerbatimDirective } from "jslib-angular/directives/input-verbatim.directive";
import { SelectCopyDirective } from "jslib-angular/directives/select-copy.directive";
import { StopClickDirective } from "jslib-angular/directives/stop-click.directive";
import { StopPropDirective } from "jslib-angular/directives/stop-prop.directive";
import { TrueFalseValueDirective } from "jslib-angular/directives/true-false-value.directive";
import { ColorPasswordPipe } from 'jslib-angular/pipes/color-password.pipe';
import { I18nPipe } from 'jslib-angular/pipes/i18n.pipe';
import { SearchCiphersPipe } from 'jslib-angular/pipes/search-ciphers.pipe';
import { ColorPasswordPipe } from "jslib-angular/pipes/color-password.pipe";
import { I18nPipe } from "jslib-angular/pipes/i18n.pipe";
import { SearchCiphersPipe } from "jslib-angular/pipes/search-ciphers.pipe";
import { ActionButtonsComponent } from './components/action-buttons.component';
import { CipherRowComponent } from './components/cipher-row.component';
import { PasswordRepromptComponent } from './components/password-reprompt.component';
import { PopOutComponent } from './components/pop-out.component';
import { SendListComponent } from './components/send-list.component';
import { SetPinComponent } from './components/set-pin.component';
import { VerifyMasterPasswordComponent } from './components/verify-master-password.component';
import { ActionButtonsComponent } from "./components/action-buttons.component";
import { CipherRowComponent } from "./components/cipher-row.component";
import { PasswordRepromptComponent } from "./components/password-reprompt.component";
import { PopOutComponent } from "./components/pop-out.component";
import { SendListComponent } from "./components/send-list.component";
import { SetPinComponent } from "./components/set-pin.component";
import { VerifyMasterPasswordComponent } from "./components/verify-master-password.component";
import { CalloutComponent } from 'jslib-angular/components/callout.component';
import { IconComponent } from 'jslib-angular/components/icon.component';
import { BitwardenToastModule } from 'jslib-angular/components/toastr.component';
import { CalloutComponent } from "jslib-angular/components/callout.component";
import { IconComponent } from "jslib-angular/components/icon.component";
import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
import {
CurrencyPipe,
DatePipe,
registerLocaleData,
} from '@angular/common';
import localeAz from '@angular/common/locales/az';
import localeBe from '@angular/common/locales/be';
import localeBg from '@angular/common/locales/bg';
import localeBn from '@angular/common/locales/bn';
import localeCa from '@angular/common/locales/ca';
import localeCs from '@angular/common/locales/cs';
import localeDa from '@angular/common/locales/da';
import localeDe from '@angular/common/locales/de';
import localeEl from '@angular/common/locales/el';
import localeEnGb from '@angular/common/locales/en-GB';
import localeEnIn from '@angular/common/locales/en-IN';
import localeEs from '@angular/common/locales/es';
import localeEt from '@angular/common/locales/et';
import localeFa from '@angular/common/locales/fa';
import localeFi from '@angular/common/locales/fi';
import localeFr from '@angular/common/locales/fr';
import localeHe from '@angular/common/locales/he';
import localeHr from '@angular/common/locales/hr';
import localeHu from '@angular/common/locales/hu';
import localeId from '@angular/common/locales/id';
import localeIt from '@angular/common/locales/it';
import localeJa from '@angular/common/locales/ja';
import localeKn from '@angular/common/locales/kn';
import localeKo from '@angular/common/locales/ko';
import localeLv from '@angular/common/locales/lv';
import localeMl from '@angular/common/locales/ml';
import localeNb from '@angular/common/locales/nb';
import localeNl from '@angular/common/locales/nl';
import localePl from '@angular/common/locales/pl';
import localePtBr from '@angular/common/locales/pt';
import localePtPt from '@angular/common/locales/pt-PT';
import localeRo from '@angular/common/locales/ro';
import localeRu from '@angular/common/locales/ru';
import localeSk from '@angular/common/locales/sk';
import localeSr from '@angular/common/locales/sr';
import localeSv from '@angular/common/locales/sv';
import localeTh from '@angular/common/locales/th';
import localeTr from '@angular/common/locales/tr';
import localeUk from '@angular/common/locales/uk';
import localeVi from '@angular/common/locales/vi';
import localeZhCn from '@angular/common/locales/zh-Hans';
import localeZhTw from '@angular/common/locales/zh-Hant';
import { CurrencyPipe, DatePipe, registerLocaleData } from "@angular/common";
import localeAz from "@angular/common/locales/az";
import localeBe from "@angular/common/locales/be";
import localeBg from "@angular/common/locales/bg";
import localeBn from "@angular/common/locales/bn";
import localeCa from "@angular/common/locales/ca";
import localeCs from "@angular/common/locales/cs";
import localeDa from "@angular/common/locales/da";
import localeDe from "@angular/common/locales/de";
import localeEl from "@angular/common/locales/el";
import localeEnGb from "@angular/common/locales/en-GB";
import localeEnIn from "@angular/common/locales/en-IN";
import localeEs from "@angular/common/locales/es";
import localeEt from "@angular/common/locales/et";
import localeFa from "@angular/common/locales/fa";
import localeFi from "@angular/common/locales/fi";
import localeFr from "@angular/common/locales/fr";
import localeHe from "@angular/common/locales/he";
import localeHr from "@angular/common/locales/hr";
import localeHu from "@angular/common/locales/hu";
import localeId from "@angular/common/locales/id";
import localeIt from "@angular/common/locales/it";
import localeJa from "@angular/common/locales/ja";
import localeKn from "@angular/common/locales/kn";
import localeKo from "@angular/common/locales/ko";
import localeLv from "@angular/common/locales/lv";
import localeMl from "@angular/common/locales/ml";
import localeNb from "@angular/common/locales/nb";
import localeNl from "@angular/common/locales/nl";
import localePl from "@angular/common/locales/pl";
import localePtBr from "@angular/common/locales/pt";
import localePtPt from "@angular/common/locales/pt-PT";
import localeRo from "@angular/common/locales/ro";
import localeRu from "@angular/common/locales/ru";
import localeSk from "@angular/common/locales/sk";
import localeSr from "@angular/common/locales/sr";
import localeSv from "@angular/common/locales/sv";
import localeTh from "@angular/common/locales/th";
import localeTr from "@angular/common/locales/tr";
import localeUk from "@angular/common/locales/uk";
import localeVi from "@angular/common/locales/vi";
import localeZhCn from "@angular/common/locales/zh-Hans";
import localeZhTw from "@angular/common/locales/zh-Hant";
registerLocaleData(localeAz, 'az');
registerLocaleData(localeBe, 'be');
registerLocaleData(localeBg, 'bg');
registerLocaleData(localeBn, 'bn');
registerLocaleData(localeCa, 'ca');
registerLocaleData(localeCs, 'cs');
registerLocaleData(localeDa, 'da');
registerLocaleData(localeDe, 'de');
registerLocaleData(localeEl, 'el');
registerLocaleData(localeEnGb, 'en-GB');
registerLocaleData(localeEnIn, 'en-IN');
registerLocaleData(localeEs, 'es');
registerLocaleData(localeEt, 'et');
registerLocaleData(localeFa, 'fa');
registerLocaleData(localeFi, 'fi');
registerLocaleData(localeFr, 'fr');
registerLocaleData(localeHe, 'he');
registerLocaleData(localeHr, 'hr');
registerLocaleData(localeHu, 'hu');
registerLocaleData(localeId, 'id');
registerLocaleData(localeIt, 'it');
registerLocaleData(localeJa, 'ja');
registerLocaleData(localeKo, 'ko');
registerLocaleData(localeKn, 'kn');
registerLocaleData(localeLv, 'lv');
registerLocaleData(localeMl, 'ml');
registerLocaleData(localeNb, 'nb');
registerLocaleData(localeNl, 'nl');
registerLocaleData(localePl, 'pl');
registerLocaleData(localePtBr, 'pt-BR');
registerLocaleData(localePtPt, 'pt-PT');
registerLocaleData(localeRo, 'ro');
registerLocaleData(localeRu, 'ru');
registerLocaleData(localeSk, 'sk');
registerLocaleData(localeSr, 'sr');
registerLocaleData(localeSv, 'sv');
registerLocaleData(localeTh, 'th');
registerLocaleData(localeTr, 'tr');
registerLocaleData(localeUk, 'uk');
registerLocaleData(localeVi, 'vi');
registerLocaleData(localeZhCn, 'zh-CN');
registerLocaleData(localeZhTw, 'zh-TW');
registerLocaleData(localeAz, "az");
registerLocaleData(localeBe, "be");
registerLocaleData(localeBg, "bg");
registerLocaleData(localeBn, "bn");
registerLocaleData(localeCa, "ca");
registerLocaleData(localeCs, "cs");
registerLocaleData(localeDa, "da");
registerLocaleData(localeDe, "de");
registerLocaleData(localeEl, "el");
registerLocaleData(localeEnGb, "en-GB");
registerLocaleData(localeEnIn, "en-IN");
registerLocaleData(localeEs, "es");
registerLocaleData(localeEt, "et");
registerLocaleData(localeFa, "fa");
registerLocaleData(localeFi, "fi");
registerLocaleData(localeFr, "fr");
registerLocaleData(localeHe, "he");
registerLocaleData(localeHr, "hr");
registerLocaleData(localeHu, "hu");
registerLocaleData(localeId, "id");
registerLocaleData(localeIt, "it");
registerLocaleData(localeJa, "ja");
registerLocaleData(localeKo, "ko");
registerLocaleData(localeKn, "kn");
registerLocaleData(localeLv, "lv");
registerLocaleData(localeMl, "ml");
registerLocaleData(localeNb, "nb");
registerLocaleData(localeNl, "nl");
registerLocaleData(localePl, "pl");
registerLocaleData(localePtBr, "pt-BR");
registerLocaleData(localePtPt, "pt-PT");
registerLocaleData(localeRo, "ro");
registerLocaleData(localeRu, "ru");
registerLocaleData(localeSk, "sk");
registerLocaleData(localeSr, "sr");
registerLocaleData(localeSv, "sv");
registerLocaleData(localeTh, "th");
registerLocaleData(localeTr, "tr");
registerLocaleData(localeUk, "uk");
registerLocaleData(localeVi, "vi");
registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, "zh-TW");
@NgModule({
imports: [
A11yModule,
AppRoutingModule,
BrowserAnimationsModule,
BrowserModule,
DragDropModule,
FormsModule,
ReactiveFormsModule,
ScrollingModule,
ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 2,
autoDismiss: true,
closeButton: true,
positionClass: 'toast-bottom-full-width',
}),
],
declarations: [
A11yTitleDirective,
ActionButtonsComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
ApiActionDirective,
AppComponent,
AttachmentsComponent,
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
CalloutComponent,
CipherListVirtualScroll,
CipherRowComponent,
CiphersComponent,
CollectionsComponent,
ColorPasswordPipe,
CurrentTabComponent,
EnvironmentComponent,
ExcludedDomainsComponent,
ExportComponent,
FallbackSrcDirective,
FolderAddEditComponent,
FoldersComponent,
GroupingsComponent,
HintComponent,
HomeComponent,
I18nPipe,
IconComponent,
InputVerbatimDirective,
LockComponent,
LoginComponent,
OptionsComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordHistoryComponent,
PasswordRepromptComponent,
PopOutComponent,
PremiumComponent,
PrivateModeComponent,
RegisterComponent,
SearchCiphersPipe,
SelectCopyDirective,
SendAddEditComponent,
SendEffluxDatesComponent,
SendGroupingsComponent,
SendListComponent,
SendTypeComponent,
SetPasswordComponent,
SetPinComponent,
SettingsComponent,
ShareComponent,
SsoComponent,
StopClickDirective,
StopPropDirective,
SyncComponent,
TabsComponent,
TrueFalseValueDirective,
TwoFactorComponent,
TwoFactorOptionsComponent,
UpdateTempPasswordComponent,
VaultTimeoutInputComponent,
VerifyMasterPasswordComponent,
ViewComponent,
ViewCustomFieldsComponent,
RemovePasswordComponent,
],
entryComponents: [],
providers: [
CurrencyPipe,
DatePipe,
],
bootstrap: [AppComponent],
imports: [
A11yModule,
AppRoutingModule,
BrowserAnimationsModule,
BrowserModule,
DragDropModule,
FormsModule,
ReactiveFormsModule,
ScrollingModule,
ServicesModule,
BitwardenToastModule.forRoot({
maxOpened: 2,
autoDismiss: true,
closeButton: true,
positionClass: "toast-bottom-full-width",
}),
],
declarations: [
A11yTitleDirective,
ActionButtonsComponent,
AddEditComponent,
AddEditCustomFieldsComponent,
ApiActionDirective,
AppComponent,
AttachmentsComponent,
AutofocusDirective,
BlurClickDirective,
BoxRowDirective,
CalloutComponent,
CipherListVirtualScroll,
CipherRowComponent,
CiphersComponent,
CollectionsComponent,
ColorPasswordPipe,
CurrentTabComponent,
EnvironmentComponent,
ExcludedDomainsComponent,
ExportComponent,
FallbackSrcDirective,
FolderAddEditComponent,
FoldersComponent,
GroupingsComponent,
HintComponent,
HomeComponent,
I18nPipe,
IconComponent,
InputVerbatimDirective,
LockComponent,
LoginComponent,
OptionsComponent,
PasswordGeneratorComponent,
PasswordGeneratorHistoryComponent,
PasswordHistoryComponent,
PasswordRepromptComponent,
PopOutComponent,
PremiumComponent,
PrivateModeComponent,
RegisterComponent,
SearchCiphersPipe,
SelectCopyDirective,
SendAddEditComponent,
SendEffluxDatesComponent,
SendGroupingsComponent,
SendListComponent,
SendTypeComponent,
SetPasswordComponent,
SetPinComponent,
SettingsComponent,
ShareComponent,
SsoComponent,
StopClickDirective,
StopPropDirective,
SyncComponent,
TabsComponent,
TrueFalseValueDirective,
TwoFactorComponent,
TwoFactorOptionsComponent,
UpdateTempPasswordComponent,
VaultTimeoutInputComponent,
VerifyMasterPasswordComponent,
ViewComponent,
ViewCustomFieldsComponent,
RemovePasswordComponent,
],
entryComponents: [],
providers: [CurrencyPipe, DatePipe],
bootstrap: [AppComponent],
})
export class AppModule { }
export class AppModule {}

View File

@ -1,41 +1,87 @@
<span class="row-btn" (click)="view()" appStopClick appStopProp appA11yTitle="{{'view' | i18n}}" *ngIf="showView">
<i class="fa fa-lg fa-list-alt" aria-hidden="true"></i>
<span
class="row-btn"
(click)="view()"
appStopClick
appStopProp
appA11yTitle="{{ 'view' | i18n }}"
*ngIf="showView"
>
<i class="fa fa-lg fa-list-alt" aria-hidden="true"></i>
</span>
<ng-container *ngIf="cipher.type === cipherType.Login">
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'launch' | i18n}}" (click)="launchCipher()"
*ngIf="!showView" [ngClass]="{disabled: !cipher.login.canLaunch}">
<i class="fa fa-lg fa-share-square-o" aria-hidden="true"></i>
</span>
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyUsername' | i18n}}"
(click)="copy(cipher, cipher.login.username, 'username', 'Username')"
[ngClass]="{disabled: !cipher.login.username}">
<i class="fa fa-lg fa-user" aria-hidden="true"></i>
</span>
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyPassword' | i18n}}"
(click)="copy(cipher, cipher.login.password, 'password', 'Password')"
[ngClass]="{disabled: (!cipher.login.password || !cipher.viewPassword)}">
<i class="fa fa-lg fa-key" aria-hidden="true"></i>
</span>
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyVerificationCode' | i18n}}"
(click)="copy(cipher, cipher.login.totp, 'verificationCodeTotp', 'TOTP')"
[ngClass]="{disabled: (!displayTotpCopyButton(cipher))}">
<i class="fa fa-lg fa-clock-o" aria-hidden="true"></i>
</span>
<span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'launch' | i18n }}"
(click)="launchCipher()"
*ngIf="!showView"
[ngClass]="{ disabled: !cipher.login.canLaunch }"
>
<i class="fa fa-lg fa-share-square-o" aria-hidden="true"></i>
</span>
<span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copyUsername' | i18n }}"
(click)="copy(cipher, cipher.login.username, 'username', 'Username')"
[ngClass]="{ disabled: !cipher.login.username }"
>
<i class="fa fa-lg fa-user" aria-hidden="true"></i>
</span>
<span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(cipher, cipher.login.password, 'password', 'Password')"
[ngClass]="{ disabled: !cipher.login.password || !cipher.viewPassword }"
>
<i class="fa fa-lg fa-key" aria-hidden="true"></i>
</span>
<span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copyVerificationCode' | i18n }}"
(click)="copy(cipher, cipher.login.totp, 'verificationCodeTotp', 'TOTP')"
[ngClass]="{ disabled: !displayTotpCopyButton(cipher) }"
>
<i class="fa fa-lg fa-clock-o" aria-hidden="true"></i>
</span>
</ng-container>
<ng-container *ngIf="cipher.type === cipherType.Card">
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyNumber' | i18n}}"
(click)="copy(cipher, cipher.card.number, 'number', 'Card Number')" [ngClass]="{disabled: !cipher.card.number}">
<i class="fa fa-lg fa-hashtag" aria-hidden="true"></i>
</span>
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copySecurityCode' | i18n}}"
(click)="copy(cipher, cipher.card.code, 'securityCode', 'Security Code')"
[ngClass]="{disabled: !cipher.card.code}">
<i class="fa fa-lg fa-key" aria-hidden="true"></i>
</span>
<span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copyNumber' | i18n }}"
(click)="copy(cipher, cipher.card.number, 'number', 'Card Number')"
[ngClass]="{ disabled: !cipher.card.number }"
>
<i class="fa fa-lg fa-hashtag" aria-hidden="true"></i>
</span>
<span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copySecurityCode' | i18n }}"
(click)="copy(cipher, cipher.card.code, 'securityCode', 'Security Code')"
[ngClass]="{ disabled: !cipher.card.code }"
>
<i class="fa fa-lg fa-key" aria-hidden="true"></i>
</span>
</ng-container>
<ng-container *ngIf="cipher.type === cipherType.SecureNote">
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyNote' | i18n}}"
(click)="copy(cipher, cipher.notes, 'note', 'Note')" [ngClass]="{disabled: !cipher.notes}">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</span>
<span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copyNote' | i18n }}"
(click)="copy(cipher, cipher.notes, 'note', 'Note')"
[ngClass]="{ disabled: !cipher.notes }"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</span>
</ng-container>

View File

@ -1,82 +1,88 @@
import {
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
import { CipherType } from 'jslib-common/enums/cipherType';
import { EventType } from 'jslib-common/enums/eventType';
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
import { CipherType } from "jslib-common/enums/cipherType";
import { EventType } from "jslib-common/enums/eventType";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherView } from "jslib-common/models/view/cipherView";
import { EventService } from 'jslib-common/abstractions/event.service';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { TotpService } from 'jslib-common/abstractions/totp.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { EventService } from "jslib-common/abstractions/event.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TotpService } from "jslib-common/abstractions/totp.service";
import { UserService } from "jslib-common/abstractions/user.service";
@Component({
selector: 'app-action-buttons',
templateUrl: 'action-buttons.component.html',
selector: "app-action-buttons",
templateUrl: "action-buttons.component.html",
})
export class ActionButtonsComponent {
@Output() onView = new EventEmitter<CipherView>();
@Output() launchEvent = new EventEmitter<CipherView>();
@Input() cipher: CipherView;
@Input() showView = false;
@Output() onView = new EventEmitter<CipherView>();
@Output() launchEvent = new EventEmitter<CipherView>();
@Input() cipher: CipherView;
@Input() showView = false;
cipherType = CipherType;
userHasPremiumAccess = false;
cipherType = CipherType;
userHasPremiumAccess = false;
constructor(private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private eventService: EventService,
private totpService: TotpService, private userService: UserService,
private passwordRepromptService: PasswordRepromptService) { }
constructor(
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private eventService: EventService,
private totpService: TotpService,
private userService: UserService,
private passwordRepromptService: PasswordRepromptService
) {}
async ngOnInit() {
this.userHasPremiumAccess = await this.userService.canAccessPremium();
async ngOnInit() {
this.userHasPremiumAccess = await this.userService.canAccessPremium();
}
launchCipher() {
this.launchEvent.emit(this.cipher);
}
async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
if (
this.cipher.reprompt !== CipherRepromptType.None &&
this.passwordRepromptService.protectedFields().includes(aType) &&
!(await this.passwordRepromptService.showPasswordPrompt())
) {
return;
}
launchCipher() {
this.launchEvent.emit(this.cipher);
if (value == null || (aType === "TOTP" && !this.displayTotpCopyButton(cipher))) {
return;
} else if (value === cipher.login.totp) {
value = await this.totpService.getCode(value);
}
async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
if (this.cipher.reprompt !== CipherRepromptType.None && this.passwordRepromptService.protectedFields().includes(aType) &&
!await this.passwordRepromptService.showPasswordPrompt()) {
return;
}
if (value == null || aType === 'TOTP' && !this.displayTotpCopyButton(cipher)) {
return;
} else if (value === cipher.login.totp) {
value = await this.totpService.getCode(value);
}
if (!cipher.viewPassword) {
return;
}
this.platformUtilsService.copyToClipboard(value, { window: window });
this.platformUtilsService.showToast('info', null,
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
if (typeI18nKey === 'password' || typeI18nKey === 'verificationCodeTotp') {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id);
} else if (typeI18nKey === 'securityCode') {
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
}
if (!cipher.viewPassword) {
return;
}
displayTotpCopyButton(cipher: CipherView) {
return (cipher?.login?.hasTotp ?? false) &&
(cipher.organizationUseTotp || this.userHasPremiumAccess);
}
this.platformUtilsService.copyToClipboard(value, { window: window });
this.platformUtilsService.showToast(
"info",
null,
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey))
);
view() {
this.onView.emit(this.cipher);
if (typeI18nKey === "password" || typeI18nKey === "verificationCodeTotp") {
this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, cipher.id);
} else if (typeI18nKey === "securityCode") {
this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id);
}
}
displayTotpCopyButton(cipher: CipherView) {
return (
(cipher?.login?.hasTotp ?? false) && (cipher.organizationUseTotp || this.userHasPremiumAccess)
);
}
view() {
this.onView.emit(this.cipher);
}
}

View File

@ -1,23 +1,38 @@
<button type="button" (click)="selectCipher(cipher)" (dblclick)="launchCipher(cipher)" appStopClick
title="{{title}} - {{cipher.name}}" class="box-content-row box-content-row-flex virtual-scroll-item">
<div class="row-main">
<app-vault-icon [cipher]="cipher"></app-vault-icon>
<div class="row-main-content">
<span class="text">
{{cipher.name}}
<ng-container *ngIf="cipher.organizationId">
<i class="fa fa-cube text-muted" title="{{'shared' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'shared' | i18n}}</span>
</ng-container>
<ng-container *ngIf="cipher.hasAttachments">
<i class="fa fa-paperclip text-muted" title="{{'attachments' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'attachments' | i18n}}</span>
</ng-container>
</span>
<span class="detail">{{cipher.subTitle}}</span>
</div>
<button
type="button"
(click)="selectCipher(cipher)"
(dblclick)="launchCipher(cipher)"
appStopClick
title="{{ title }} - {{ cipher.name }}"
class="box-content-row box-content-row-flex virtual-scroll-item"
>
<div class="row-main">
<app-vault-icon [cipher]="cipher"></app-vault-icon>
<div class="row-main-content">
<span class="text">
{{ cipher.name }}
<ng-container *ngIf="cipher.organizationId">
<i class="fa fa-cube text-muted" title="{{ 'shared' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "shared" | i18n }}</span>
</ng-container>
<ng-container *ngIf="cipher.hasAttachments">
<i
class="fa fa-paperclip text-muted"
title="{{ 'attachments' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "attachments" | i18n }}</span>
</ng-container>
</span>
<span class="detail">{{ cipher.subTitle }}</span>
</div>
<app-action-buttons [cipher]="cipher" [showView]="showView" (onView)="viewCipher(cipher)" (launchEvent)="launchCipher(cipher)"
class="action-buttons">
</app-action-buttons>
</div>
<app-action-buttons
[cipher]="cipher"
[showView]="showView"
(onView)="viewCipher(cipher)"
(launchEvent)="launchCipher(cipher)"
class="action-buttons"
>
</app-action-buttons>
</button>

View File

@ -1,33 +1,28 @@
import {
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherView } from "jslib-common/models/view/cipherView";
@Component({
selector: 'app-cipher-row',
templateUrl: 'cipher-row.component.html',
selector: "app-cipher-row",
templateUrl: "cipher-row.component.html",
})
export class CipherRowComponent {
@Output() onSelected = new EventEmitter<CipherView>();
@Output() launchEvent = new EventEmitter<CipherView>();
@Output() onView = new EventEmitter<CipherView>();
@Input() cipher: CipherView;
@Input() showView = false;
@Input() title: string;
@Output() onSelected = new EventEmitter<CipherView>();
@Output() launchEvent = new EventEmitter<CipherView>();
@Output() onView = new EventEmitter<CipherView>();
@Input() cipher: CipherView;
@Input() showView = false;
@Input() title: string;
selectCipher(c: CipherView) {
this.onSelected.emit(c);
}
selectCipher(c: CipherView) {
this.onSelected.emit(c);
}
launchCipher(c: CipherView) {
this.launchEvent.emit(c);
}
launchCipher(c: CipherView) {
this.launchEvent.emit(c);
}
viewCipher(c: CipherView) {
this.onView.emit(c);
}
viewCipher(c: CipherView) {
this.onView.emit(c);
}
}

View File

@ -1,38 +1,56 @@
<div class="modal fade" role="dialog" aria-modal="true">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<h1 class="box-header">{{'passwordConfirmation' | i18n}}</h1>
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
class="monospaced" [(ngModel)]="masterPassword" required appAutofocus>
</div>
<div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick role="button"
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()" [attr.aria-pressed]="showPassword">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
</button>
</div>
</div>
</div>
<div class="box-footer">
{{'passwordConfirmationDesc' | i18n}}
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{'ok' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}}
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div class="box">
<h1 class="box-header">{{ "passwordConfirmation" | i18n }}</h1>
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
name="MasterPassword"
class="monospaced"
[(ngModel)]="masterPassword"
required
appAutofocus
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
[attr.aria-pressed]="showPassword"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</form>
</div>
</div>
<div class="box-footer">
{{ "passwordConfirmationDesc" | i18n }}
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@ -1,8 +1,8 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from 'jslib-angular/components/password-reprompt.component';
import { PasswordRepromptComponent as BasePasswordRepromptComponent } from "jslib-angular/components/password-reprompt.component";
@Component({
templateUrl: 'password-reprompt.component.html',
templateUrl: "password-reprompt.component.html",
})
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}

View File

@ -1,5 +1,5 @@
<ng-container>
<button type="button" (click)="expand()" appA11yTitle="{{'popOutNewWindow' | i18n}}">
<i class="fa fa-external-link fa-rotate-270 fa-lg fa-fw" aria-hidden="true"></i>
</button>
<button type="button" (click)="expand()" appA11yTitle="{{ 'popOutNewWindow' | i18n }}">
<i class="fa fa-external-link fa-rotate-270 fa-lg fa-fw" aria-hidden="true"></i>
</button>
</ng-container>

View File

@ -1,32 +1,30 @@
import {
Component,
Input,
OnInit,
} from '@angular/core';
import { Component, Input, OnInit } from "@angular/core";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PopupUtilsService } from '../services/popup-utils.service';
import { PopupUtilsService } from "../services/popup-utils.service";
@Component({
selector: 'app-pop-out',
templateUrl: 'pop-out.component.html',
selector: "app-pop-out",
templateUrl: "pop-out.component.html",
})
export class PopOutComponent implements OnInit {
@Input() show = true;
@Input() show = true;
constructor(private platformUtilsService: PlatformUtilsService,
private popupUtilsService: PopupUtilsService) { }
constructor(
private platformUtilsService: PlatformUtilsService,
private popupUtilsService: PopupUtilsService
) {}
ngOnInit() {
if (this.show) {
if (this.popupUtilsService.inSidebar(window) && this.platformUtilsService.isFirefox()) {
this.show = false;
}
}
ngOnInit() {
if (this.show) {
if (this.popupUtilsService.inSidebar(window) && this.platformUtilsService.isFirefox()) {
this.show = false;
}
}
}
expand() {
this.popupUtilsService.popOut(window);
}
expand() {
this.popupUtilsService.popOut(window);
}
}

View File

@ -1,50 +1,90 @@
<button type="button" *ngFor="let s of sends" (click)="selectSend(s)" appStopClick title="{{title}} - {{s.name}}"
class="box-content-row box-content-row-flex">
<div class="row-main">
<div class="app-vault-icon">
<div class="icon" aria-hidden="true">
<i class="fa fa-fw fa-lg fa-file-text-o" *ngIf="s.type === sendType.Text"></i>
<i class="fa fa-fw fa-lg fa-file-o" *ngIf="s.type === sendType.File"></i>
</div>
</div>
<div class="row-main-content">
<span class="text">
{{s.name}}
<ng-container *ngIf="s.disabled">
<i class="fa fa-warning text-muted" title="{{'disabled' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'disabled' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.password">
<i class="fa fa-key text-muted" title="{{'passwordProtected' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'passwordProtected' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.maxAccessCountReached">
<i class="fa fa-ban text-muted" title="{{'maxAccessCountReached' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'maxAccessCountReached' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.expired">
<i class="fa fa-clock-o text-muted" title="{{'expired' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'expired' | i18n}}</span>
</ng-container>
<ng-container *ngIf="s.pendingDelete">
<i class="fa fa-trash text-muted" title="{{'pendingDeletion' | i18n}}" aria-hidden="true"></i>
<span class="sr-only">{{'pendingDeletion' | i18n}}</span>
</ng-container>
</span>
<span class="detail">{{s.deletionDate | date:'medium'}}</span>
</div>
<button
type="button"
*ngFor="let s of sends"
(click)="selectSend(s)"
appStopClick
title="{{ title }} - {{ s.name }}"
class="box-content-row box-content-row-flex"
>
<div class="row-main">
<div class="app-vault-icon">
<div class="icon" aria-hidden="true">
<i class="fa fa-fw fa-lg fa-file-text-o" *ngIf="s.type === sendType.Text"></i>
<i class="fa fa-fw fa-lg fa-file-o" *ngIf="s.type === sendType.File"></i>
</div>
</div>
<div class="action-buttons">
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copySendLink' | i18n}}"
(click)="copySendLink(s)">
<i class="fa fa-lg fa-copy" aria-hidden="true"></i>
</span>
<span class="row-btn" [ngClass]="{'disabled' : disabledByPolicy}" appStopClick appStopProp
appA11yTitle="{{'removePassword' | i18n}}" (click)="removePassword(s)" *ngIf="s.password">
<i class="fa fa-lg fa-undo" aria-hidden="true"></i>
</span>
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'delete' | i18n}}" (click)="delete(s)">
<i class="fa fa-lg fa-trash-o" aria-hidden="true"></i>
</span>
<div class="row-main-content">
<span class="text">
{{ s.name }}
<ng-container *ngIf="s.disabled">
<i
class="fa fa-warning text-muted"
title="{{ 'disabled' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "disabled" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.password">
<i
class="fa fa-key text-muted"
title="{{ 'passwordProtected' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "passwordProtected" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.maxAccessCountReached">
<i
class="fa fa-ban text-muted"
title="{{ 'maxAccessCountReached' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "maxAccessCountReached" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.expired">
<i class="fa fa-clock-o text-muted" title="{{ 'expired' | i18n }}" aria-hidden="true"></i>
<span class="sr-only">{{ "expired" | i18n }}</span>
</ng-container>
<ng-container *ngIf="s.pendingDelete">
<i
class="fa fa-trash text-muted"
title="{{ 'pendingDeletion' | i18n }}"
aria-hidden="true"
></i>
<span class="sr-only">{{ "pendingDeletion" | i18n }}</span>
</ng-container>
</span>
<span class="detail">{{ s.deletionDate | date: "medium" }}</span>
</div>
</div>
<div class="action-buttons">
<span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'copySendLink' | i18n }}"
(click)="copySendLink(s)"
>
<i class="fa fa-lg fa-copy" aria-hidden="true"></i>
</span>
<span
class="row-btn"
[ngClass]="{ disabled: disabledByPolicy }"
appStopClick
appStopProp
appA11yTitle="{{ 'removePassword' | i18n }}"
(click)="removePassword(s)"
*ngIf="s.password"
>
<i class="fa fa-lg fa-undo" aria-hidden="true"></i>
</span>
<span
class="row-btn"
appStopClick
appStopProp
appA11yTitle="{{ 'delete' | i18n }}"
(click)="delete(s)"
>
<i class="fa fa-lg fa-trash-o" aria-hidden="true"></i>
</span>
</div>
</button>

View File

@ -1,42 +1,37 @@
import {
Component,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { SendView } from 'jslib-common/models/view/sendView';
import { SendView } from "jslib-common/models/view/sendView";
import { SendType } from 'jslib-common/enums/sendType';
import { SendType } from "jslib-common/enums/sendType";
@Component({
selector: 'app-send-list',
templateUrl: 'send-list.component.html',
selector: "app-send-list",
templateUrl: "send-list.component.html",
})
export class SendListComponent {
@Input() sends: SendView[];
@Input() title: string;
@Input() disabledByPolicy = false;
@Output() onSelected = new EventEmitter<SendView>();
@Output() onCopySendLink = new EventEmitter<SendView>();
@Output() onRemovePassword = new EventEmitter<SendView>();
@Output() onDeleteSend = new EventEmitter<SendView>();
@Input() sends: SendView[];
@Input() title: string;
@Input() disabledByPolicy = false;
@Output() onSelected = new EventEmitter<SendView>();
@Output() onCopySendLink = new EventEmitter<SendView>();
@Output() onRemovePassword = new EventEmitter<SendView>();
@Output() onDeleteSend = new EventEmitter<SendView>();
sendType = SendType;
sendType = SendType;
selectSend(s: SendView) {
this.onSelected.emit(s);
}
selectSend(s: SendView) {
this.onSelected.emit(s);
}
copySendLink(s: SendView) {
this.onCopySendLink.emit(s);
}
copySendLink(s: SendView) {
this.onCopySendLink.emit(s);
}
removePassword(s: SendView) {
this.onRemovePassword.emit(s);
}
removePassword(s: SendView) {
this.onRemovePassword.emit(s);
}
delete(s: SendView) {
this.onDeleteSend.emit(s);
}
delete(s: SendView) {
this.onDeleteSend.emit(s);
}
}

View File

@ -1,44 +1,65 @@
<div class="modal fade" role="dialog" aria-modal="true">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div>
{{'setYourPinCode' | i18n}}
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="pin">{{'pin' | i18n}}</label>
<input id="pin" type="{{showPin ? 'text' : 'password'}}" name="Pin"
class="monospaced" [(ngModel)]="pin" required appInputVerbatim>
</div>
<div class="action-buttons">
<button type="button" class="row-btn" appStopClick appBlurClick appA11yTitle="{{'toggleVisibility' | i18n}}"
(click)="toggleVisibility()" [attr.aria-pressed]="showPin">
<i class="fa fa-lg" aria-hidden="true"
[ngClass]="{'fa-eye': !showPin, 'fa-eye-slash': showPin}"></i>
</button>
</div>
</div>
</div>
</div>
<div class="checkbox" *ngIf="showMasterPassOnRestart">
<label for="masterPasswordOnRestart">
<input type="checkbox" id="masterPasswordOnRestart" name="MasterPasswordOnRestart"
[(ngModel)]="masterPassOnRestart">
<span>{{'lockWithMasterPassOnRestart' | i18n}}</span>
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{'ok' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{'cancel' | i18n}}
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form class="modal-content" #form (ngSubmit)="submit()">
<div class="modal-body">
<div>
{{ "setYourPinCode" | i18n }}
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="pin">{{ "pin" | i18n }}</label>
<input
id="pin"
type="{{ showPin ? 'text' : 'password' }}"
name="Pin"
class="monospaced"
[(ngModel)]="pin"
required
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appBlurClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="toggleVisibility()"
[attr.aria-pressed]="showPin"
>
<i
class="fa fa-lg"
aria-hidden="true"
[ngClass]="{ 'fa-eye': !showPin, 'fa-eye-slash': showPin }"
></i>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="checkbox" *ngIf="showMasterPassOnRestart">
<label for="masterPasswordOnRestart">
<input
type="checkbox"
id="masterPasswordOnRestart"
name="MasterPasswordOnRestart"
[(ngModel)]="masterPassOnRestart"
/>
<span>{{ "lockWithMasterPassOnRestart" | i18n }}</span>
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@ -1,8 +1,8 @@
import { Component } from '@angular/core';
import { Component } from "@angular/core";
import { SetPinComponent as BaseSetPinComponent } from 'jslib-angular/components/set-pin.component';
import { SetPinComponent as BaseSetPinComponent } from "jslib-angular/components/set-pin.component";
@Component({
templateUrl: 'set-pin.component.html',
templateUrl: "set-pin.component.html",
})
export class SetPinComponent extends BaseSetPinComponent {}

View File

@ -1,25 +1,46 @@
<ng-container *ngIf="!usesKeyConnector">
<div class="box-content-row" appBoxRow>
<label for="masterPassword">{{'masterPass' | i18n}}</label>
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control"
[formControl]="secret" required appAutofocus appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="password"
name="MasterPasswordHash"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
</div>
</ng-container>
<ng-container *ngIf="usesKeyConnector">
<div class="box-content-row" appBoxRow>
<label class="d-block">{{'sendVerificationCode' | i18n}}</label>
<button type="button" class="btn btn-outline-secondary" (click)="requestOTP()" [disabled]="disableRequestOTP">
{{'sendCode' | i18n}}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
{{'codeSent' | i18n}}
</span>
</div>
<div class="box-content-row" appBoxRow>
<label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
<button
type="button"
class="btn btn-outline-secondary"
(click)="requestOTP()"
[disabled]="disableRequestOTP"
>
{{ "sendCode" | i18n }}
</button>
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
{{ "codeSent" | i18n }}
</span>
</div>
<div class="box-content-row" appBoxRow>
<label for="verificationCode">{{'verificationCode' | i18n}}</label>
<input id="verificationCode" type="input" name="verificationCode" class="form-control"
[formControl]="secret" required appAutofocus appInputVerbatim>
</div>
<div class="box-content-row" appBoxRow>
<label for="verificationCode">{{ "verificationCode" | i18n }}</label>
<input
id="verificationCode"
type="input"
name="verificationCode"
class="form-control"
[formControl]="secret"
required
appAutofocus
appInputVerbatim
/>
</div>
</ng-container>

View File

@ -1,31 +1,23 @@
import {
animate,
style,
transition,
trigger,
} from '@angular/animations';
import { Component } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { animate, style, transition, trigger } from "@angular/animations";
import { Component } from "@angular/core";
import { NG_VALUE_ACCESSOR } from "@angular/forms";
import { VerifyMasterPasswordComponent as BaseComponent } from 'jslib-angular/components/verify-master-password.component';
import { VerifyMasterPasswordComponent as BaseComponent } from "jslib-angular/components/verify-master-password.component";
@Component({
selector: 'app-verify-master-password',
templateUrl: 'verify-master-password.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VerifyMasterPasswordComponent,
},
],
animations: [
trigger('sent', [
transition(':enter', [
style({ opacity: 0 }),
animate('100ms', style({ opacity: 1 })),
]),
]),
],
selector: "app-verify-master-password",
templateUrl: "verify-master-password.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VerifyMasterPasswordComponent,
},
],
animations: [
trigger("sent", [
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
]),
],
})
export class VerifyMasterPasswordComponent extends BaseComponent { }
export class VerifyMasterPasswordComponent extends BaseComponent {}

View File

@ -1,40 +1,48 @@
<header>
<div class="left">
<button type="button" appBlurClick type="button" (click)="close()">
<span class="header-icon" aria-hidden="true"><i class="fa fa-chevron-left"></i></span>
<span>{{'back' | i18n}}</span>
</button>
</div>
<h1 class="center">
<span class="title">{{'passwordHistory' | i18n}}</span>
</h1>
<div class="right">
<button type="button" appBlurClick type="button" (click)="clear()">
{{'clear' | i18n}}
</button>
</div>
<div class="left">
<button type="button" appBlurClick type="button" (click)="close()">
<span class="header-icon" aria-hidden="true"><i class="fa fa-chevron-left"></i></span>
<span>{{ "back" | i18n }}</span>
</button>
</div>
<h1 class="center">
<span class="title">{{ "passwordHistory" | i18n }}</span>
</h1>
<div class="right">
<button type="button" appBlurClick type="button" (click)="clear()">
{{ "clear" | i18n }}
</button>
</div>
</header>
<content>
<div class="box list full-list" *ngIf="history && history.length">
<div class="box-content">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<div class="row-main-content">
<div class="monospaced password-wrapper" appSelectCopy
[innerHTML]="h.password | colorPassword"></div>
<span class="detail">{{h.date | date:'medium'}}</span>
</div>
</div>
<div class="action-buttons">
<button type="button" class="row-btn" appStopClick appA11yTitle="{{'copyPassword' | i18n}}"
(click)="copy(h.password)">
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="box list full-list" *ngIf="history && history.length">
<div class="box-content">
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<div class="row-main-content">
<div
class="monospaced password-wrapper"
appSelectCopy
[innerHTML]="h.password | colorPassword"
></div>
<span class="detail">{{ h.date | date: "medium" }}</span>
</div>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy(h.password)"
>
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="no-items" *ngIf="!history || !history.length">
<p>{{'noPasswordsInList' | i18n}}</p>
</div>
</div>
<div class="no-items" *ngIf="!history || !history.length">
<p>{{ "noPasswordsInList" | i18n }}</p>
</div>
</content>

View File

@ -1,25 +1,27 @@
import { Location } from '@angular/common';
import { Component } from '@angular/core';
import { Location } from "@angular/common";
import { Component } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import {
PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent,
} from 'jslib-angular/components/password-generator-history.component';
import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "jslib-angular/components/password-generator-history.component";
@Component({
selector: 'app-password-generator-history',
templateUrl: 'password-generator-history.component.html',
selector: "app-password-generator-history",
templateUrl: "password-generator-history.component.html",
})
export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, private location: Location) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
constructor(
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
private location: Location
) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
close() {
this.location.back();
}
close() {
this.location.back();
}
}

View File

@ -1,128 +1,229 @@
<header>
<div class="left">
<app-pop-out [show]="!showSelect"></app-pop-out>
<button type="button" appBlurClick (click)="close()" *ngIf="showSelect">{{'cancel' | i18n}}</button>
</div>
<h1 class="center">
<span class="title">{{'passGen' | i18n}}</span>
</h1>
<div class="right">
<button type="button" appBlurClick (click)="select()" *ngIf="showSelect">{{'select' | i18n}}</button>
</div>
<div class="left">
<app-pop-out [show]="!showSelect"></app-pop-out>
<button type="button" appBlurClick (click)="close()" *ngIf="showSelect">
{{ "cancel" | i18n }}
</button>
</div>
<h1 class="center">
<span class="title">{{ "passGen" | i18n }}</span>
</h1>
<div class="right">
<button type="button" appBlurClick (click)="select()" *ngIf="showSelect">
{{ "select" | i18n }}
</button>
</div>
</header>
<content>
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
{{'passwordGeneratorPolicyInEffect' | i18n}}
</app-callout>
<div class="password-block">
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="password-block">
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
</div>
<div class="box list">
<div class="box-content single-line">
<button
type="button"
class="box-content-row text-primary"
appStopClick
appBlurClick
(click)="regenerate()"
>
{{ "regeneratePassword" | i18n }}
</button>
<button
type="button"
class="box-content-row text-primary"
appStopClick
appBlurClick
(click)="copy()"
>
{{ "copyPassword" | i18n }}
</button>
</div>
<div class="box list">
<div class="box-content single-line">
<button type="button" class="box-content-row text-primary" appStopClick appBlurClick
(click)="regenerate()">{{'regeneratePassword' | i18n}}</button>
<button type="button" class="box-content-row text-primary" appStopClick appBlurClick
(click)="copy()">{{'copyPassword' | i18n}}</button>
</div>
</div>
<div class="box list">
<div class="box-content single-line">
<a class="box-content-row box-content-row-flex" routerLink="/generator-history">
<div class="row-main">{{ "passwordHistory" | i18n }}</div>
<i class="fa fa-chevron-right fa-lg row-sub-icon" aria-hidden="true"></i>
</a>
</div>
<div class="box list">
<div class="box-content single-line">
<a class="box-content-row box-content-row-flex" routerLink="/generator-history">
<div class="row-main">{{'passwordHistory' | i18n}}</div>
<i class="fa fa-chevron-right fa-lg row-sub-icon" aria-hidden="true"></i>
</a>
</div>
<div class="box">
<h2 class="box-header">
{{ "options" | i18n }}
</h2>
<div class="box-content">
<div class="box-content-row">
<label class="sr-only radio-header">{{ "type" | i18n }}</label>
<div class="radio-group text-default" appBoxRow *ngFor="let o of passTypeOptions">
<input
type="radio"
[(ngModel)]="options.type"
name="Type_{{ o.value }}"
id="type_{{ o.value }}"
[value]="o.value"
(change)="saveOptions()"
[checked]="options.type === o.value"
/>
<label for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="options.type === 'passphrase'">
<div class="box-content">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="num-words">{{ "numWords" | i18n }}</label>
<input
id="num-words"
type="number"
min="3"
max="20"
(change)="saveOptions()"
[(ngModel)]="options.numWords"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input
id="word-separator"
type="text"
maxlength="1"
(input)="saveOptions()"
[(ngModel)]="options.wordSeparator"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{ "capitalize" | i18n }}</label>
<input
id="capitalize"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.capitalize"
[disabled]="enforcedPolicyOptions?.capitalize"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="include-number">{{ "includeNumber" | i18n }}</label>
<input
id="include-number"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="options.includeNumber"
[disabled]="enforcedPolicyOptions?.includeNumber"
/>
</div>
</div>
</div>
<ng-container *ngIf="options.type === 'password'">
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-slider" appBoxRow>
<label for="length">{{ "length" | i18n }}</label>
<input
id="length"
type="number"
min="5"
max="128"
[(ngModel)]="options.length"
(change)="saveOptions()"
/>
<input
id="lengthRange"
type="range"
min="5"
max="128"
step="1"
[(ngModel)]="options.length"
(change)="sliderChanged()"
(input)="sliderInput()"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="uppercase">A-Z</label>
<input
id="uppercase"
type="checkbox"
(change)="saveOptions()"
attr.aria-label="{{ 'uppercase' | i18n }}"
[disabled]="enforcedPolicyOptions.useUppercase"
[(ngModel)]="options.uppercase"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label>
<input
id="lowercase"
type="checkbox"
(change)="saveOptions()"
attr.aria-label="{{ 'lowercase' | i18n }}"
[disabled]="enforcedPolicyOptions.useLowercase"
[(ngModel)]="options.lowercase"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="numbers">0-9</label>
<input
id="numbers"
type="checkbox"
(change)="saveOptions()"
attr.aria-label="{{ 'numbers' | i18n }}"
[disabled]="enforcedPolicyOptions.useNumbers"
[(ngModel)]="options.number"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="special">!@#$%^&*</label>
<input
id="special"
type="checkbox"
(change)="saveOptions()"
attr.aria-label="{{ 'specialCharacters' | i18n }}"
[disabled]="enforcedPolicyOptions.useSpecial"
[(ngModel)]="options.special"
/>
</div>
</div>
</div>
<div class="box">
<h2 class="box-header">
{{'options' | i18n}}
</h2>
<div class="box-content">
<div class="box-content-row">
<label class="sr-only radio-header">{{'type' | i18n}}</label>
<div class="radio-group text-default" appBoxRow *ngFor="let o of passTypeOptions">
<input type="radio" [(ngModel)]="options.type" name="Type_{{o.value}}" id="type_{{o.value}}"
[value]="o.value" (change)="saveOptions()" [checked]="options.type === o.value">
<label for="type_{{o.value}}">
{{o.name}}
</label>
</div>
</div>
<div class="box-content">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-number">{{ "minNumbers" | i18n }}</label>
<input
id="min-number"
type="number"
min="0"
max="9"
(change)="saveOptions()"
[(ngModel)]="options.minNumber"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-special">{{ "minSpecial" | i18n }}</label>
<input
id="min-special"
type="number"
min="0"
max="9"
(change)="saveOptions()"
[(ngModel)]="options.minSpecial"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{ "avoidAmbChar" | i18n }}</label>
<input
id="ambiguous"
type="checkbox"
(change)="saveOptions()"
[(ngModel)]="avoidAmbiguous"
/>
</div>
</div>
</div>
<div class="box" *ngIf="options.type === 'passphrase'">
<div class="box-content">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="num-words">{{'numWords' | i18n}}</label>
<input id="num-words" type="number" min="3" max="20" (change)="saveOptions()"
[(ngModel)]="options.numWords">
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="word-separator">{{'wordSeparator' | i18n}}</label>
<input id="word-separator" type="text" maxlength="1" (input)="saveOptions()"
[(ngModel)]="options.wordSeparator">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{'capitalize' | i18n}}</label>
<input id="capitalize" type="checkbox" (change)="saveOptions()" [(ngModel)]="options.capitalize"
[disabled]="enforcedPolicyOptions?.capitalize">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="include-number">{{'includeNumber' | i18n}}</label>
<input id="include-number" type="checkbox" (change)="saveOptions()" [(ngModel)]="options.includeNumber"
[disabled]="enforcedPolicyOptions?.includeNumber">
</div>
</div>
</div>
<ng-container *ngIf="options.type === 'password'">
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-slider" appBoxRow>
<label for="length">{{'length' | i18n}}</label>
<input id="length" type="number" min="5" max="128" [(ngModel)]="options.length"
(change)="saveOptions()">
<input id="lengthRange" type="range" min="5" max="128" step="1" [(ngModel)]="options.length"
(change)="sliderChanged()" (input)="sliderInput()">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="uppercase">A-Z</label>
<input id="uppercase" type="checkbox" (change)="saveOptions()" attr.aria-label="{{'uppercase' | i18n}}"
[disabled]="enforcedPolicyOptions.useUppercase" [(ngModel)]="options.uppercase">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label>
<input id="lowercase" type="checkbox" (change)="saveOptions()" attr.aria-label="{{'lowercase' | i18n}}"
[disabled]="enforcedPolicyOptions.useLowercase" [(ngModel)]="options.lowercase">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="numbers">0-9</label>
<input id="numbers" type="checkbox" (change)="saveOptions()" attr.aria-label="{{'numbers' | i18n}}"
[disabled]="enforcedPolicyOptions.useNumbers" [(ngModel)]="options.number">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="special">!@#$%^&*</label>
<input id="special" type="checkbox" (change)="saveOptions()" attr.aria-label="{{'specialCharacters' | i18n}}"
[disabled]="enforcedPolicyOptions.useSpecial" [(ngModel)]="options.special">
</div>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-number">{{'minNumbers' | i18n}}</label>
<input id="min-number" type="number" min="0" max="9" (change)="saveOptions()"
[(ngModel)]="options.minNumber">
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-special">{{'minSpecial' | i18n}}</label>
<input id="min-special" type="number" min="0" max="9" (change)="saveOptions()"
[(ngModel)]="options.minSpecial">
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{'avoidAmbChar' | i18n}}</label>
<input id="ambiguous" type="checkbox" (change)="saveOptions()" [(ngModel)]="avoidAmbiguous">
</div>
</div>
</div>
</ng-container>
</ng-container>
</content>

View File

@ -1,46 +1,48 @@
import { Location } from '@angular/common';
import { Component } from '@angular/core';
import { Location } from "@angular/common";
import { Component } from "@angular/core";
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { StateService } from 'jslib-common/abstractions/state.service';
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherView } from "jslib-common/models/view/cipherView";
import {
PasswordGeneratorComponent as BasePasswordGeneratorComponent,
} from 'jslib-angular/components/password-generator.component';
import { PasswordGeneratorComponent as BasePasswordGeneratorComponent } from "jslib-angular/components/password-generator.component";
@Component({
selector: 'app-password-generator',
templateUrl: 'password-generator.component.html',
selector: "app-password-generator",
templateUrl: "password-generator.component.html",
})
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
private cipherState: CipherView;
private cipherState: CipherView;
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
i18nService: I18nService, private stateService: StateService,
private location: Location) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
constructor(
passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
private stateService: StateService,
private location: Location
) {
super(passwordGenerationService, platformUtilsService, i18nService, window);
}
async ngOnInit() {
await super.ngOnInit();
const addEditCipherInfo = await this.stateService.get<any>('addEditCipherInfo');
if (addEditCipherInfo != null) {
this.cipherState = addEditCipherInfo.cipher;
}
this.showSelect = this.cipherState != null;
async ngOnInit() {
await super.ngOnInit();
const addEditCipherInfo = await this.stateService.get<any>("addEditCipherInfo");
if (addEditCipherInfo != null) {
this.cipherState = addEditCipherInfo.cipher;
}
this.showSelect = this.cipherState != null;
}
select() {
super.select();
this.cipherState.login.password = this.password;
this.close();
}
select() {
super.select();
this.cipherState.login.password = this.password;
this.close();
}
close() {
this.location.back();
}
close() {
this.location.back();
}
}

View File

@ -1,14 +1,14 @@
<!DOCTYPE html>
<html class="__BROWSER__">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Bitwarden</title>
<base href="">
</head>
<body>
<base href="" />
</head>
<body>
<app-root>
<div id="loading"><i class="fa fa-spinner fa-spin fa-3x" aria-hidden="true"></i></div>
<div id="loading"><i class="fa fa-spinner fa-spin fa-3x" aria-hidden="true"></i></div>
</app-root>
</body>
</body>
</html>

View File

@ -1,17 +1,17 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
// tslint:disable-next-line
require('./scss/popup.scss');
require("./scss/popup.scss");
import { AppModule } from './app.module';
import { AppModule } from "./app.module";
if (process.env.ENV === 'production') {
enableProdMode();
if (process.env.ENV === "production") {
enableProdMode();
}
function init() {
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });
}
init();

View File

@ -1,6 +1,6 @@
/* tslint:disable */
import 'core-js/stable';
import 'date-input-polyfill';
import 'web-animations-js';
import 'zone.js/dist/zone';
import "core-js/stable";
import "date-input-polyfill";
import "web-animations-js";
import "zone.js/dist/zone";
/* tslint:enable */

View File

@ -1,6 +1,6 @@
<div class="content">
<p class="text-center">{{privateModeMessage}}</p>
<button type="button" class="btn primary block" (click)="learnMore()">
<b>{{learnMoreMessage}}</b>
</button>
<p class="text-center">{{ privateModeMessage }}</p>
<button type="button" class="btn primary block" (click)="learnMore()">
<b>{{ learnMoreMessage }}</b>
</button>
</div>

View File

@ -1,24 +1,23 @@
import { BrowserApi } from '../browser/browserApi';
import { BrowserApi } from "../browser/browserApi";
import {
Component,
OnInit,
} from '@angular/core';
import { Component, OnInit } from "@angular/core";
@Component({
selector: 'app-private-mode',
templateUrl: 'private-mode.component.html',
selector: "app-private-mode",
templateUrl: "private-mode.component.html",
})
export class PrivateModeComponent implements OnInit {
privateModeMessage: string;
learnMoreMessage: string;
privateModeMessage: string;
learnMoreMessage: string;
ngOnInit() {
this.privateModeMessage = chrome.i18n.getMessage('privateModeMessage');
this.learnMoreMessage = chrome.i18n.getMessage('learnMore');
}
ngOnInit() {
this.privateModeMessage = chrome.i18n.getMessage("privateModeMessage");
this.learnMoreMessage = chrome.i18n.getMessage("learnMore");
}
learnMore() {
BrowserApi.createNewTab('https://help.bitwarden.com/article/extension-wont-load-in-private-mode/');
}
learnMore() {
BrowserApi.createNewTab(
"https://help.bitwarden.com/article/extension-wont-load-in-private-mode/"
);
}
}

View File

@ -1,438 +1,461 @@
@import "variables.scss";
* {
box-sizing: border-box;
padding: 0;
margin: 0;
box-sizing: border-box;
padding: 0;
margin: 0;
}
html, body {
font-family: $font-family-sans-serif;
font-size: $font-size-base;
line-height: $line-height-base;
-webkit-font-smoothing: antialiased;
html,
body {
font-family: $font-family-sans-serif;
font-size: $font-size-base;
line-height: $line-height-base;
-webkit-font-smoothing: antialiased;
}
body {
width: 375px !important;
height: 600px !important;
overflow: hidden;
color: $text-color;
background-color: $background-color;
@include themify($themes) {
color: themed("textColor");
background-color: themed("backgroundColor");
}
&.body-sm {
width: 375px !important;
height: 600px !important;
overflow: hidden;
color: $text-color;
background-color: $background-color;
height: 500px !important;
}
@include themify($themes) {
color: themed('textColor');
background-color: themed('backgroundColor');
}
&.body-xs {
width: 375px !important;
height: 300px !important;
}
&.body-sm {
width: 375px !important;
height: 500px !important;
}
&.body-xs {
width: 375px !important;
height: 300px !important;
}
&.body-full {
width: 100% !important;
height: 100% !important;
}
&.body-full {
width: 100% !important;
height: 100% !important;
}
}
h1, h2, h3, h4, h5, h6 {
font-family: $font-family-sans-serif;
font-size: $font-size-base;
font-weight: normal;
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: $font-family-sans-serif;
font-size: $font-size-base;
font-weight: normal;
}
p {
margin-bottom: 10px;
margin-bottom: 10px;
}
ul, ol {
margin-bottom: 10px;
ul,
ol {
margin-bottom: 10px;
}
img {
border: none;
border: none;
}
a {
text-decoration: none;
text-decoration: none;
@include themify($themes) {
color: themed("primaryColor");
}
&:hover,
&:focus {
@include themify($themes) {
color: themed('primaryColor');
}
&:hover, &:focus {
@include themify($themes) {
color: darken(themed('primaryColor'), 6%);
}
color: darken(themed("primaryColor"), 6%);
}
}
}
input, select, textarea {
@include themify($themes) {
color: themed('textColor');
background-color: themed('inputBackgroundColor');
}
input,
select,
textarea {
@include themify($themes) {
color: themed("textColor");
background-color: themed("inputBackgroundColor");
}
}
input, select, textarea, button {
font-size: $font-size-base;
font-family: $font-family-sans-serif;
input,
select,
textarea,
button {
font-size: $font-size-base;
font-family: $font-family-sans-serif;
}
button {
white-space: nowrap;
cursor: pointer;
white-space: nowrap;
cursor: pointer;
}
textarea {
resize: vertical;
resize: vertical;
}
main {
height: 100%;
height: 100%;
}
content::-webkit-scrollbar, cdk-virtual-scroll-viewport::-webkit-scrollbar {
width: 10px;
height: 10px;
content::-webkit-scrollbar,
cdk-virtual-scroll-viewport::-webkit-scrollbar {
width: 10px;
height: 10px;
}
content::-webkit-scrollbar-track {
background-color: transparent;
background-color: transparent;
}
cdk-virtual-scroll-viewport::-webkit-scrollbar-track {
@include themify($themes) {
background-color: themed('backgroundColor');
}
@include themify($themes) {
background-color: themed("backgroundColor");
}
}
content::-webkit-scrollbar-thumb, cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb {
border-radius: 10px;
margin-right: 1px;
content::-webkit-scrollbar-thumb,
cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb {
border-radius: 10px;
margin-right: 1px;
@include themify($themes) {
background-color: themed("scrollbarColor");
}
&:hover {
@include themify($themes) {
background-color: themed('scrollbarColor');
}
&:hover {
@include themify($themes) {
background-color: themed('scrollbarHoverColor');
}
background-color: themed("scrollbarHoverColor");
}
}
}
header {
min-height: 44px;
max-height: 44px;
min-height: 44px;
max-height: 44px;
display: flex;
border-bottom: 1px solid #000000;
@include themify($themes) {
color: themed("headerColor");
background-color: themed("headerBackgroundColor");
border-bottom-color: themed("headerBorderColor");
}
.left,
.right {
flex: 1;
display: flex;
border-bottom: 1px solid #000000;
min-width: -webkit-min-content; /* Workaround to Chrome bug */
.header-icon {
margin-right: 5px;
}
}
.right {
justify-content: flex-end;
}
.center {
display: flex;
align-items: center;
text-align: center;
min-width: 0;
}
app-pop-out > button,
div > button,
div > a {
border: none;
padding: 0 10px;
text-decoration: none;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
@include themify($themes) {
color: themed('headerColor');
background-color: themed('headerBackgroundColor');
border-bottom-color: themed('headerBorderColor');
color: themed("headerColor");
background-color: themed("headerBackgroundColor");
}
.left, .right {
flex: 1;
display: flex;
min-width: -webkit-min-content; /* Workaround to Chrome bug */
.header-icon {
margin-right: 5px;
}
&:hover,
&:focus {
@include themify($themes) {
background-color: themed("headerBackgroundHoverColor");
color: themed("headerColor");
}
}
.right {
justify-content: flex-end;
&:focus {
text-decoration: underline;
}
.center {
display: flex;
align-items: center;
text-align: center;
min-width: 0;
&[disabled] {
opacity: 0.65;
cursor: default !important;
}
app-pop-out > button, div > button, div > a {
border: none;
padding: 0 10px;
text-decoration: none;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
i + span {
margin-left: 5px;
}
}
app-pop-out {
display: flex;
}
.title {
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.search {
padding: 7px 10px;
width: 100%;
text-align: left;
position: relative;
display: flex;
.fa {
position: absolute;
top: 15px;
left: 20px;
@include themify($themes) {
color: themed("headerInputPlaceholderColor");
}
}
input {
width: 100%;
margin: 0;
border: none;
padding: 5px 10px 5px 30px;
border-radius: $border-radius;
@include themify($themes) {
background-color: themed("headerInputBackgroundColor");
color: themed("headerInputColor");
}
&:focus {
border-radius: $border-radius;
outline: none;
@include themify($themes) {
color: themed('headerColor');
background-color: themed('headerBackgroundColor');
background-color: themed("headerInputBackgroundFocusColor");
}
}
&:hover, &:focus {
@include themify($themes) {
background-color: themed('headerBackgroundHoverColor');
color: themed('headerColor');
}
}
&:focus {
text-decoration: underline;
}
&[disabled] {
opacity: 0.65;
cursor: default !important;
}
i + span {
margin-left: 5px;
&::-webkit-input-placeholder {
@include themify($themes) {
color: themed("headerInputPlaceholderColor");
}
}
}
}
app-pop-out {
display: flex;
.left + .search {
padding-left: 0;
.fa {
left: 10px;
}
}
.title {
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.search {
padding: 7px 10px;
width: 100%;
text-align: left;
position: relative;
display: flex;
.fa {
position: absolute;
top: 15px;
left: 20px;
@include themify($themes) {
color: themed('headerInputPlaceholderColor');
}
}
input {
width: 100%;
margin: 0;
border: none;
padding: 5px 10px 5px 30px;
border-radius: $border-radius;
@include themify($themes) {
background-color: themed('headerInputBackgroundColor');
color: themed('headerInputColor');
}
&:focus {
border-radius: $border-radius;
outline: none;
@include themify($themes) {
background-color: themed('headerInputBackgroundFocusColor');
}
}
&::-webkit-input-placeholder {
@include themify($themes) {
color: themed('headerInputPlaceholderColor');
}
}
}
}
.left + .search {
padding-left: 0;
.fa {
left: 10px;
}
}
.search + .right {
margin-left: -10px;
}
.search + .right {
margin-left: -10px;
}
}
.content {
padding: 15px;
padding: 15px;
}
.tabs {
width: 100%;
height: 55px;
border-top: 1px solid #000000;
position: absolute;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
width: 100%;
height: 55px;
border-top: 1px solid #000000;
position: absolute;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
@include themify($themes) {
background-color: themed('tabBackgroundColor');
border-top-color: themed('borderColor');
}
@include themify($themes) {
background-color: themed("tabBackgroundColor");
border-top-color: themed("borderColor");
}
ul {
display: flex;
list-style: none;
padding: 0;
margin: 0;
ul {
display: flex;
list-style: none;
padding: 0;
margin: 0;
li {
flex: 1;
display: inline-block;
padding: 0;
margin: 0;
li {
flex: 1;
display: inline-block;
padding: 0;
margin: 0;
a {
text-align: center;
display: block;
padding: 7px 0;
text-decoration: none;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
a {
text-align: center;
display: block;
padding: 7px 0;
text-decoration: none;
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@include themify($themes) {
color: themed('mutedColor');
}
&:hover, &:focus {
@include themify($themes) {
background-color: themed('tabBackgroundHoverColor');
}
}
i {
display: block;
margin-bottom: 2px;
text-align: center;
}
}
&.active {
a {
@include themify($themes) {
color: themed('primaryColor');
}
}
}
@include themify($themes) {
color: themed("mutedColor");
}
&:hover,
&:focus {
@include themify($themes) {
background-color: themed("tabBackgroundHoverColor");
}
}
i {
display: block;
margin-bottom: 2px;
text-align: center;
}
}
&.active {
a {
@include themify($themes) {
color: themed("primaryColor");
}
}
}
}
}
}
app-root {
position: absolute;
width: 100%;
height: 100%;
z-index: 980;
@include themify($themes) {
background-color: themed('backgroundColor');
}
position: absolute;
width: 100%;
height: 100%;
z-index: 980;
@include themify($themes) {
background-color: themed("backgroundColor");
}
}
@media only screen and (min-width: 601px) {
app-login header {
padding: 0 calc((100% - 500px) / 2);
}
app-login header {
padding: 0 calc((100% - 500px) / 2);
}
app-login content {
padding: 0 calc((100% - 500px) / 2);
}
app-login content {
padding: 0 calc((100% - 500px) / 2);
}
app-two-factor header {
padding: 0 calc((100% - 500px) / 2);
}
app-two-factor header {
padding: 0 calc((100% - 500px) / 2);
}
app-two-factor content {
padding: 0 calc((100% - 500px) / 2);
}
app-two-factor content {
padding: 0 calc((100% - 500px) / 2);
}
app-lock header {
padding: 0 calc((100% - 500px) / 2);
}
app-lock header {
padding: 0 calc((100% - 500px) / 2);
}
app-lock content {
padding: 0 calc((100% - 500px) / 2);
}
app-lock content {
padding: 0 calc((100% - 500px) / 2);
}
}
content {
position: absolute;
top: 44px;
bottom: 0;
left: 0;
right: 0;
overflow-y: auto;
overflow-x: hidden;
position: absolute;
top: 44px;
bottom: 0;
left: 0;
right: 0;
overflow-y: auto;
overflow-x: hidden;
@include themify($themes) {
background-color: themed('backgroundColor');
}
&.no-header {
top: 0;
}
&.flex {
display: flex;
flex-flow: column;
height: calc(100% - 44px);
&.tab-page {
height: calc(100% - 99px);
}
@include themify($themes) {
background-color: themed("backgroundColor");
}
&.no-header {
top: 0;
}
&.flex {
display: flex;
flex-flow: column;
height: calc(100% - 44px);
&.tab-page {
height: calc(100% - 99px);
}
}
}
.tab-page {
content {
bottom: 55px;
}
content {
bottom: 55px;
}
}
.center-content, .no-items, .full-loading-spinner {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
flex-direction: column;
flex-grow: 1;
.center-content,
.no-items,
.full-loading-spinner {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
flex-direction: column;
flex-grow: 1;
}
.no-items, .full-loading-spinner {
text-align: center;
.no-items,
.full-loading-spinner {
text-align: center;
.fa {
margin-bottom: 10px;
.fa {
margin-bottom: 10px;
@include themify($themes) {
color: themed('disabledIconColor');
}
@include themify($themes) {
color: themed("disabledIconColor");
}
}
}
// cdk-virtual-scroll
.cdk-virtual-scroll-viewport {
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
}
.cdk-virtual-scroll-content-wrapper {
width: 100%;
width: 100%;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,101 +1,101 @@
@import "variables.scss";
.btn {
border-radius: $border-radius;
padding: 7px 15px;
border: 1px solid #000000;
font-size: $font-size-base;
white-space: nowrap;
text-align: center;
border-radius: $border-radius;
padding: 7px 15px;
border: 1px solid #000000;
font-size: $font-size-base;
white-space: nowrap;
text-align: center;
cursor: pointer;
@include themify($themes) {
background-color: themed("buttonBackgroundColor");
border-color: themed("buttonBorderColor");
color: themed("buttonColor");
}
&.primary {
@include themify($themes) {
color: themed("buttonPrimaryColor");
}
}
&.danger {
@include themify($themes) {
color: themed("buttonDangerColor");
}
}
&:hover:not([disabled]) {
cursor: pointer;
@include themify($themes) {
background-color: themed('buttonBackgroundColor');
border-color: themed('buttonBorderColor');
color: themed('buttonColor');
background-color: darken(themed("buttonBackgroundColor"), 1.5%);
border-color: darken(themed("buttonBorderColor"), 17%);
color: darken(themed("buttonColor"), 10%);
}
&.primary {
@include themify($themes) {
color: themed('buttonPrimaryColor');
}
@include themify($themes) {
color: darken(themed("buttonPrimaryColor"), 6%);
}
}
&.danger {
@include themify($themes) {
color: themed('buttonDangerColor');
}
@include themify($themes) {
color: darken(themed("buttonDangerColor"), 6%);
}
}
}
&:hover:not([disabled]) {
cursor: pointer;
&:focus:not([disabled]) {
cursor: pointer;
outline: 0;
@include themify($themes) {
background-color: darken(themed('buttonBackgroundColor'), 1.5%);
border-color: darken(themed('buttonBorderColor'), 17%);
color: darken(themed('buttonColor'), 10%);
}
&.primary {
@include themify($themes) {
color: darken(themed('buttonPrimaryColor'), 6%);
}
}
&.danger {
@include themify($themes) {
color: darken(themed('buttonDangerColor'), 6%);
}
}
@include themify($themes) {
background-color: darken(themed("buttonBackgroundColor"), 6%);
border-color: darken(themed("buttonBorderColor"), 25%);
}
}
&:focus:not([disabled]) {
cursor: pointer;
outline: 0;
&[disabled] {
opacity: 0.65;
cursor: default !important;
}
@include themify($themes) {
background-color: darken(themed('buttonBackgroundColor'), 6%);
border-color: darken(themed('buttonBorderColor'), 25%);
}
}
&[disabled] {
opacity: 0.65;
cursor: default !important;
}
&.block {
display: block;
width: 100%;
}
&.link,
&.neutral {
border: none !important;
background: none !important;
&:focus {
text-decoration: underline;
}
&.block {
display: block;
width: 100%;
}
&.link,
&.neutral {
border: none !important;
background: none !important;
&:focus {
text-decoration: underline;
}
}
}
.action-buttons {
.btn {
&:focus {
outline: auto;
}
.btn {
&:focus {
outline: auto;
}
}
}
button.box-content-row {
display: block;
width: 100%;
text-align: left;
display: block;
width: 100%;
text-align: left;
}
button {
border: none;
background: transparent;
color: inherit;
}
border: none;
background: transparent;
color: inherit;
}

View File

@ -1,39 +1,39 @@
@import "variables.scss";
html.browser_safari {
body {
height: 360px !important;
body {
height: 360px !important;
&.body-xs {
height: 300px !important;
}
&.body-full {
height: 100% !important;
}
&.body-xs {
height: 300px !important;
}
header {
.search .fa {
left: 20px;
}
&.body-full {
height: 100% !important;
}
}
.left + .search .fa {
left: 10px;
}
header {
.search .fa {
left: 20px;
}
app-root {
border-width: 1px;
border-style: solid;
border-color: #000000;
.left + .search .fa {
left: 10px;
}
}
&.theme_light app-root {
border-color: #777777;
}
app-root {
border-width: 1px;
border-style: solid;
border-color: #000000;
}
&.theme_nord app-root {
border-color: #2e3440;
}
&.theme_light app-root {
border-color: #777777;
}
&.theme_nord app-root {
border-color: #2e3440;
}
}

View File

@ -1,11 +1,11 @@
.row {
display: flex;
margin: 0 -15px;
width: 100%;
display: flex;
margin: 0 -15px;
width: 100%;
}
.col {
flex-basis: 0;
flex-grow: 1;
padding: 0 15px;
flex-basis: 0;
flex-grow: 1;
padding: 0 15px;
}

View File

@ -1,375 +1,378 @@
@import "variables.scss";
small, .small {
font-size: $font-size-small;
small,
.small {
font-size: $font-size-small;
}
.bg-primary {
@include themify($themes) {
background-color: themed('primaryColor') !important;
}
@include themify($themes) {
background-color: themed("primaryColor") !important;
}
}
.bg-success {
@include themify($themes) {
background-color: themed('successColor') !important;
}
@include themify($themes) {
background-color: themed("successColor") !important;
}
}
.bg-danger {
@include themify($themes) {
background-color: themed('dangerColor') !important;
}
@include themify($themes) {
background-color: themed("dangerColor") !important;
}
}
.bg-info {
@include themify($themes) {
background-color: themed('infoColor') !important;
}
@include themify($themes) {
background-color: themed("infoColor") !important;
}
}
.bg-warning {
@include themify($themes) {
background-color: themed('warningColor') !important;
}
@include themify($themes) {
background-color: themed("warningColor") !important;
}
}
.text-primary {
@include themify($themes) {
color: themed('primaryColor') !important;
}
@include themify($themes) {
color: themed("primaryColor") !important;
}
}
.text-success {
@include themify($themes) {
color: themed('successColor') !important;
}
@include themify($themes) {
color: themed("successColor") !important;
}
}
.text-muted {
@include themify($themes) {
color: themed('mutedColor') !important;
}
@include themify($themes) {
color: themed("mutedColor") !important;
}
}
.text-default {
@include themify($themes) {
color: themed('textColor') !important;
}
@include themify($themes) {
color: themed("textColor") !important;
}
}
.text-danger {
@include themify($themes) {
color: themed('dangerColor') !important;
}
@include themify($themes) {
color: themed("dangerColor") !important;
}
}
.text-info {
@include themify($themes) {
color: themed('infoColor') !important;
}
@include themify($themes) {
color: themed("infoColor") !important;
}
}
.text-warning {
@include themify($themes) {
color: themed('warningColor') !important;
}
@include themify($themes) {
color: themed("warningColor") !important;
}
}
.text-center {
text-align: center;
text-align: center;
}
.font-weight-semibold {
font-weight: 600;
font-weight: 600;
}
p.lead {
font-size: $font-size-large;
margin-bottom: 20px;
font-weight: normal;
font-size: $font-size-large;
margin-bottom: 20px;
font-weight: normal;
}
.flex-right {
margin-left: auto;
margin-left: auto;
}
.flex-bottom {
margin-top: auto;
margin-top: auto;
}
.no-margin {
margin: 0 !important;
margin: 0 !important;
}
.no-vmargin {
margin-top: 0 !important;
margin-bottom: 0 !important;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.no-vpad {
padding-top: 0 !important;
padding-bottom: 0 !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.display-block {
display: block !important;
display: block !important;
}
.monospaced {
font-family: $font-family-monospace;
font-family: $font-family-monospace;
}
.show-whitespace {
white-space: pre-wrap;
white-space: pre-wrap;
}
.img-responsive {
display: block;
max-width: 100%;
height: auto;
display: block;
max-width: 100%;
height: auto;
}
.img-rounded {
border-radius: 6px;
border-radius: 6px;
}
.sr-only {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
border: 0 !important;
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
border: 0 !important;
}
.password-wrapper {
overflow-wrap: break-word;
white-space: pre-wrap;
min-width: 0;
overflow-wrap: break-word;
white-space: pre-wrap;
min-width: 0;
}
.password-number {
@include themify($themes) {
color: themed('passwordNumberColor');
}
@include themify($themes) {
color: themed("passwordNumberColor");
}
}
.password-special {
@include themify($themes) {
color: themed('passwordSpecialColor');
}
@include themify($themes) {
color: themed("passwordSpecialColor");
}
}
#duo-frame {
background: url('../images/loading.svg') 0 0 no-repeat;
width: 100%;
height: 470px;
margin-bottom: -10px;
background: url("../images/loading.svg") 0 0 no-repeat;
width: 100%;
height: 470px;
margin-bottom: -10px;
iframe {
width: 100%;
height: 100%;
border: none;
}
iframe {
width: 100%;
height: 100%;
border: none;
}
}
#web-authn-frame {
background: url('../images/loading.svg') 0 0 no-repeat;
width: 100%;
height: 310px;
margin-bottom: -10px;
background: url("../images/loading.svg") 0 0 no-repeat;
width: 100%;
height: 310px;
margin-bottom: -10px;
iframe {
width: 100%;
height: 100%;
border: none;
}
iframe {
width: 100%;
height: 100%;
border: none;
}
}
#hcaptcha_iframe {
width: 100%;
border: none;
transition: height 0.25s linear;
width: 100%;
border: none;
transition: height 0.25s linear;
}
body.linux-webauthn {
width: 485px !important;
#web-authn-frame {
iframe {
width: 375px;
margin: 0 55px;
}
width: 485px !important;
#web-authn-frame {
iframe {
width: 375px;
margin: 0 55px;
}
}
}
app-root > #loading {
display: flex;
text-align: center;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
color: $text-muted;
display: flex;
text-align: center;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
color: $text-muted;
@include themify($themes) {
color: themed('mutedColor');
}
@include themify($themes) {
color: themed("mutedColor");
}
}
app-vault-icon {
display: flex;
display: flex;
}
.logo-image {
margin: 0 auto;
width: 142px;
height: 21px;
background-size: 142px 21px;
background-repeat: no-repeat;
@include themify($themes) {
background-image: url('../images/logo-' + themed('logoSuffix') + '@2x.png');
}
@media (min-width: 219px) {
width: 189px;
height: 28px;
background-size: 189px 28px;
}
@media (min-width: 314px) {
width: 284px;
height: 43px;
background-size: 284px 43px;
}
margin: 0 auto;
width: 142px;
height: 21px;
background-size: 142px 21px;
background-repeat: no-repeat;
@include themify($themes) {
background-image: url("../images/logo-" + themed("logoSuffix") + "@2x.png");
}
@media (min-width: 219px) {
width: 189px;
height: 28px;
background-size: 189px 28px;
}
@media (min-width: 314px) {
width: 284px;
height: 43px;
background-size: 284px 43px;
}
}
[hidden] {
display: none !important;
display: none !important;
}
.draggable {
cursor: move;
cursor: move;
}
.callout {
padding: 10px;
margin: 10px;
border: 1px solid #000000;
border-left-width: 5px;
border-radius: 3px;
padding: 10px;
margin: 10px;
border: 1px solid #000000;
border-left-width: 5px;
border-radius: 3px;
@include themify($themes) {
border-color: themed("calloutBorderColor");
background-color: themed("calloutBackgroundColor");
}
.callout-heading {
margin-top: 0;
}
h3.callout-heading {
font-weight: bold;
text-transform: uppercase;
}
&.callout-primary {
@include themify($themes) {
border-color: themed('calloutBorderColor');
background-color: themed('calloutBackgroundColor');
border-left-color: themed("primaryColor");
}
.callout-heading {
margin-top: 0;
@include themify($themes) {
color: themed("primaryColor");
}
}
}
&.callout-info {
@include themify($themes) {
border-left-color: themed("infoColor");
}
h3.callout-heading {
font-weight: bold;
text-transform: uppercase;
.callout-heading {
@include themify($themes) {
color: themed("infoColor");
}
}
}
&.callout-danger {
@include themify($themes) {
border-left-color: themed("dangerColor");
}
&.callout-primary {
@include themify($themes) {
border-left-color: themed('primaryColor');
}
.callout-heading {
@include themify($themes) {
color: themed("dangerColor");
}
}
}
.callout-heading {
@include themify($themes) {
color: themed('primaryColor');
}
}
&.callout-success {
@include themify($themes) {
border-left-color: themed("successColor");
}
&.callout-info {
@include themify($themes) {
border-left-color: themed('infoColor');
}
.callout-heading {
@include themify($themes) {
color: themed("successColor");
}
}
}
.callout-heading {
@include themify($themes) {
color: themed('infoColor');
}
}
&.callout-warning {
@include themify($themes) {
border-left-color: themed("warningColor");
}
&.callout-danger {
@include themify($themes) {
border-left-color: themed('dangerColor');
}
.callout-heading {
@include themify($themes) {
color: themed('dangerColor');
}
}
.callout-heading {
@include themify($themes) {
color: themed("warningColor");
}
}
}
&.callout-success {
@include themify($themes) {
border-left-color: themed('successColor');
}
.callout-heading {
@include themify($themes) {
color: themed('successColor');
}
}
&.clickable {
&:hover,
&:focus,
&.active {
@include themify($themes) {
background-color: themed("boxBackgroundHoverColor");
}
}
}
&.callout-warning {
@include themify($themes) {
border-left-color: themed('warningColor');
}
.callout-heading {
@include themify($themes) {
color: themed('warningColor');
}
}
}
&.clickable {
&:hover, &:focus, &.active {
@include themify($themes) {
background-color: themed('boxBackgroundHoverColor');
}
}
}
.enforced-policy-options ul {
padding-left: 30px;
margin: 0;
}
.enforced-policy-options ul {
padding-left: 30px;
margin: 0;
}
}
input[type="password"]::-ms-reveal {
display: none;
display: none;
}
.flex {
display: flex;
display: flex;
&.flex-grow {
> * {
flex: 1;
}
&.flex-grow {
> * {
flex: 1;
}
}
}
// Workaround for slow performance on external monitors on Chrome + MacOS
// See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64
@keyframes redraw {
0% {
opacity: 0.99;
}
100% {
opacity: 1;
}
0% {
opacity: 0.99;
}
100% {
opacity: 1;
}
}
html.force_redraw {
animation: redraw 1s linear infinite;
animation: redraw 1s linear infinite;
}

Some files were not shown because too many files have changed in this diff Show More