Apply Prettier (#2238)
This commit is contained in:
parent
cebee8aa81
commit
8fe821b9a3
|
@ -0,0 +1 @@
|
||||||
|
* text=auto eol=lf
|
|
@ -6,7 +6,7 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Thanks for taking the time to fill out this bug report!
|
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.
|
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
|
- type: textarea
|
||||||
id: reproduce
|
id: reproduce
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
## Type of change
|
## Type of change
|
||||||
|
|
||||||
- [ ] Bug fix
|
- [ ] Bug fix
|
||||||
- [ ] New feature development
|
- [ ] New feature development
|
||||||
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
||||||
|
@ -6,27 +7,26 @@
|
||||||
- [ ] Other
|
- [ ] Other
|
||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
|
|
||||||
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
|
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Code changes
|
## Code changes
|
||||||
|
|
||||||
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your 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-->
|
<!--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
|
## Screenshots
|
||||||
|
|
||||||
<!--Required for any UI changes. Delete if not applicable-->
|
<!--Required for any UI changes. Delete if not applicable-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Testing requirements
|
## Testing requirements
|
||||||
|
|
||||||
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Before you submit
|
## Before you submit
|
||||||
|
|
||||||
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
|
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
|
||||||
- [ ] This change requires a **documentation update** (notify the documentation team)
|
- [ ] This change requires a **documentation update** (notify the documentation team)
|
||||||
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
||||||
|
|
|
@ -104,8 +104,8 @@ jobs:
|
||||||
npm run dist
|
npm run dist
|
||||||
npm run test
|
npm run test
|
||||||
|
|
||||||
# - name: Run linter
|
- name: Run linter
|
||||||
# run: npm run lint
|
run: npm run lint
|
||||||
|
|
||||||
- name: Gulp
|
- name: Gulp
|
||||||
run: gulp ci
|
run: gulp ci
|
||||||
|
|
|
@ -6,17 +6,12 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera
|
||||||
|
|
||||||
Here is how you can get involved:
|
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
|
- **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
|
||||||
* **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)
|
||||||
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
|
- **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
|
||||||
* **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
|
## Contributor Agreement
|
||||||
|
|
||||||
|
@ -24,9 +19,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/brows
|
||||||
|
|
||||||
## Pull Request Guidelines
|
## Pull Request Guidelines
|
||||||
|
|
||||||
* use `npm run lint` and fix any linting suggestions before submitting a pull request
|
- use `npm run lint` and fix any linting suggestions before submitting a pull request
|
||||||
* commit any pull requests against the `master` branch
|
- commit any pull requests against the `master` branch
|
||||||
* include a link to your Community Forums post
|
- include a link to your Community Forums post
|
||||||
|
|
||||||
# Localization (l10n)
|
# Localization (l10n)
|
||||||
|
|
||||||
|
|
|
@ -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
|
- 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.
|
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
|
- 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
|
- 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
|
degradation of our service. Only interact with accounts you own or with explicit permission of the
|
||||||
account holder.
|
account holder.
|
||||||
|
|
345
gulpfile.js
345
gulpfile.js
|
@ -1,223 +1,242 @@
|
||||||
const gulp = require('gulp'),
|
const gulp = require("gulp"),
|
||||||
gulpif = require('gulp-if'),
|
gulpif = require("gulp-if"),
|
||||||
filter = require('gulp-filter'),
|
filter = require("gulp-filter"),
|
||||||
replace = require('gulp-replace'),
|
replace = require("gulp-replace"),
|
||||||
jeditor = require("gulp-json-editor"),
|
jeditor = require("gulp-json-editor"),
|
||||||
child = require('child_process'),
|
child = require("child_process"),
|
||||||
zip = require('gulp-zip'),
|
zip = require("gulp-zip"),
|
||||||
manifest = require('./src/manifest.json'),
|
manifest = require("./src/manifest.json"),
|
||||||
del = require('del'),
|
del = require("del"),
|
||||||
fs = require('fs');
|
fs = require("fs");
|
||||||
|
|
||||||
const paths = {
|
const paths = {
|
||||||
build: './build/',
|
build: "./build/",
|
||||||
dist: './dist/',
|
dist: "./dist/",
|
||||||
coverage: './coverage/',
|
coverage: "./coverage/",
|
||||||
node_modules: './node_modules/',
|
node_modules: "./node_modules/",
|
||||||
popupDir: './src/popup/',
|
popupDir: "./src/popup/",
|
||||||
cssDir: './src/popup/css/',
|
cssDir: "./src/popup/css/",
|
||||||
safari: './src/safari/'
|
safari: "./src/safari/",
|
||||||
};
|
};
|
||||||
|
|
||||||
const filters = {
|
const filters = {
|
||||||
fonts: [
|
fonts: [
|
||||||
'!build/popup/fonts/*',
|
"!build/popup/fonts/*",
|
||||||
'build/popup/fonts/Open_Sans*.woff',
|
"build/popup/fonts/Open_Sans*.woff",
|
||||||
'build/popup/fonts/fontawesome*.woff2',
|
"build/popup/fonts/fontawesome*.woff2",
|
||||||
'build/popup/fonts/fontawesome*.woff'
|
"build/popup/fonts/fontawesome*.woff",
|
||||||
],
|
],
|
||||||
safari: [
|
safari: ["!build/safari/**/*"],
|
||||||
'!build/safari/**/*'
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function buildString() {
|
function buildString() {
|
||||||
var build = '';
|
var build = "";
|
||||||
if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== '') {
|
if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== "") {
|
||||||
build = `-${process.env.BUILD_NUMBER}`;
|
build = `-${process.env.BUILD_NUMBER}`;
|
||||||
}
|
}
|
||||||
return build;
|
return build;
|
||||||
}
|
}
|
||||||
|
|
||||||
function distFileName(browserName, ext) {
|
function distFileName(browserName, ext) {
|
||||||
return `dist-${browserName}${buildString()}.${ext}`;
|
return `dist-${browserName}${buildString()}.${ext}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function dist(browserName, manifest) {
|
function dist(browserName, manifest) {
|
||||||
return gulp.src(paths.build + '**/*')
|
return gulp
|
||||||
.pipe(filter(['**'].concat(filters.fonts).concat(filters.safari)))
|
.src(paths.build + "**/*")
|
||||||
.pipe(gulpif('popup/index.html', replace('__BROWSER__', 'browser_' + browserName)))
|
.pipe(filter(["**"].concat(filters.fonts).concat(filters.safari)))
|
||||||
.pipe(gulpif('manifest.json', jeditor(manifest)))
|
.pipe(gulpif("popup/index.html", replace("__BROWSER__", "browser_" + browserName)))
|
||||||
.pipe(zip(distFileName(browserName, 'zip')))
|
.pipe(gulpif("manifest.json", jeditor(manifest)))
|
||||||
.pipe(gulp.dest(paths.dist));
|
.pipe(zip(distFileName(browserName, "zip")))
|
||||||
|
.pipe(gulp.dest(paths.dist));
|
||||||
}
|
}
|
||||||
|
|
||||||
function distFirefox() {
|
function distFirefox() {
|
||||||
return dist('firefox', (manifest) => {
|
return dist("firefox", (manifest) => {
|
||||||
delete manifest.content_security_policy;
|
delete manifest.content_security_policy;
|
||||||
removeShortcuts(manifest);
|
removeShortcuts(manifest);
|
||||||
return manifest;
|
return manifest;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function distOpera() {
|
function distOpera() {
|
||||||
return dist('opera', (manifest) => {
|
return dist("opera", (manifest) => {
|
||||||
delete manifest.applications;
|
delete manifest.applications;
|
||||||
delete manifest.content_security_policy;
|
delete manifest.content_security_policy;
|
||||||
removeShortcuts(manifest);
|
removeShortcuts(manifest);
|
||||||
return manifest;
|
return manifest;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function distChrome() {
|
function distChrome() {
|
||||||
return dist('chrome', (manifest) => {
|
return dist("chrome", (manifest) => {
|
||||||
delete manifest.applications;
|
delete manifest.applications;
|
||||||
delete manifest.content_security_policy;
|
delete manifest.content_security_policy;
|
||||||
delete manifest.sidebar_action;
|
delete manifest.sidebar_action;
|
||||||
delete manifest.commands._execute_sidebar_action;
|
delete manifest.commands._execute_sidebar_action;
|
||||||
return manifest;
|
return manifest;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function distEdge() {
|
function distEdge() {
|
||||||
return dist('edge', (manifest) => {
|
return dist("edge", (manifest) => {
|
||||||
delete manifest.applications;
|
delete manifest.applications;
|
||||||
delete manifest.content_security_policy;
|
delete manifest.content_security_policy;
|
||||||
delete manifest.sidebar_action;
|
delete manifest.sidebar_action;
|
||||||
delete manifest.commands._execute_sidebar_action;
|
delete manifest.commands._execute_sidebar_action;
|
||||||
return manifest;
|
return manifest;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeShortcuts(manifest) {
|
function removeShortcuts(manifest) {
|
||||||
if (manifest.content_scripts && manifest.content_scripts.length > 1) {
|
if (manifest.content_scripts && manifest.content_scripts.length > 1) {
|
||||||
const shortcutsScript = manifest.content_scripts[1];
|
const shortcutsScript = manifest.content_scripts[1];
|
||||||
if (shortcutsScript.js.indexOf('content/shortcuts.js') > -1) {
|
if (shortcutsScript.js.indexOf("content/shortcuts.js") > -1) {
|
||||||
manifest.content_scripts.splice(1, 1);
|
manifest.content_scripts.splice(1, 1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function distSafariMas(cb) {
|
function distSafariMas(cb) {
|
||||||
return distSafariApp(cb, 'mas');
|
return distSafariApp(cb, "mas");
|
||||||
}
|
}
|
||||||
|
|
||||||
function distSafariMasDev(cb) {
|
function distSafariMasDev(cb) {
|
||||||
return distSafariApp(cb, 'masdev');
|
return distSafariApp(cb, "masdev");
|
||||||
}
|
}
|
||||||
|
|
||||||
function distSafariDmg(cb) {
|
function distSafariDmg(cb) {
|
||||||
return distSafariApp(cb, 'dmg');
|
return distSafariApp(cb, "dmg");
|
||||||
}
|
}
|
||||||
|
|
||||||
function distSafariApp(cb, subBuildPath) {
|
function distSafariApp(cb, subBuildPath) {
|
||||||
const buildPath = paths.dist + 'Safari/' + subBuildPath + '/';
|
const buildPath = paths.dist + "Safari/" + subBuildPath + "/";
|
||||||
const builtAppexPath = buildPath + 'build/Release/safari.appex';
|
const builtAppexPath = buildPath + "build/Release/safari.appex";
|
||||||
const builtAppexFrameworkPath = buildPath + 'build/Release/safari.appex/Contents/Frameworks/';
|
const builtAppexFrameworkPath = buildPath + "build/Release/safari.appex/Contents/Frameworks/";
|
||||||
const entitlementsPath = paths.safari + 'safari/safari.entitlements';
|
const entitlementsPath = paths.safari + "safari/safari.entitlements";
|
||||||
var args = [
|
var args = [
|
||||||
'--verbose',
|
"--verbose",
|
||||||
'--force',
|
"--force",
|
||||||
'-o',
|
"-o",
|
||||||
'runtime',
|
"runtime",
|
||||||
'--sign',
|
"--sign",
|
||||||
'Developer ID Application: 8bit Solutions LLC',
|
"Developer ID Application: 8bit Solutions LLC",
|
||||||
'--entitlements',
|
"--entitlements",
|
||||||
entitlementsPath
|
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 + '**/*'])
|
return del([buildPath + "**/*"])
|
||||||
.then(() => safariCopyAssets(paths.safari + '**/*', buildPath))
|
.then(() => safariCopyAssets(paths.safari + "**/*", buildPath))
|
||||||
.then(() => safariCopyBuild(paths.build + '**/*', buildPath + 'safari/app'))
|
.then(() => safariCopyBuild(paths.build + "**/*", buildPath + "safari/app"))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const proc = child.spawn('xcodebuild', [
|
const proc = child.spawn("xcodebuild", [
|
||||||
'-project',
|
"-project",
|
||||||
buildPath + 'desktop.xcodeproj',
|
buildPath + "desktop.xcodeproj",
|
||||||
'-alltargets',
|
"-alltargets",
|
||||||
'-configuration',
|
"-configuration",
|
||||||
'Release']);
|
"Release",
|
||||||
stdOutProc(proc);
|
]);
|
||||||
return new Promise((resolve) => proc.on('close', resolve));
|
stdOutProc(proc);
|
||||||
}).then(() => {
|
return new Promise((resolve) => proc.on("close", resolve));
|
||||||
const libs = fs.readdirSync(builtAppexFrameworkPath).filter((p) => p.endsWith('.dylib'))
|
})
|
||||||
.map((p) => builtAppexFrameworkPath + p);
|
.then(() => {
|
||||||
const libPromises = [];
|
const libs = fs
|
||||||
libs.forEach((i) => {
|
.readdirSync(builtAppexFrameworkPath)
|
||||||
const proc = child.spawn('codesign', args.concat([i]));
|
.filter((p) => p.endsWith(".dylib"))
|
||||||
stdOutProc(proc);
|
.map((p) => builtAppexFrameworkPath + p);
|
||||||
libPromises.push(new Promise((resolve) => proc.on('close', resolve)));
|
const libPromises = [];
|
||||||
});
|
libs.forEach((i) => {
|
||||||
return Promise.all(libPromises);
|
const proc = child.spawn("codesign", args.concat([i]));
|
||||||
}).then(() => {
|
stdOutProc(proc);
|
||||||
const proc = child.spawn('codesign', args.concat([builtAppexPath]));
|
libPromises.push(new Promise((resolve) => proc.on("close", resolve)));
|
||||||
stdOutProc(proc);
|
});
|
||||||
return new Promise((resolve) => proc.on('close', resolve));
|
return Promise.all(libPromises);
|
||||||
}).then(() => {
|
})
|
||||||
return cb;
|
.then(() => {
|
||||||
}, () => {
|
const proc = child.spawn("codesign", args.concat([builtAppexPath]));
|
||||||
return cb;
|
stdOutProc(proc);
|
||||||
});
|
return new Promise((resolve) => proc.on("close", resolve));
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
() => {
|
||||||
|
return cb;
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
return cb;
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function safariCopyAssets(source, dest) {
|
function safariCopyAssets(source, dest) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
gulp.src(source)
|
gulp
|
||||||
.on('error', reject)
|
.src(source)
|
||||||
.pipe(gulpif('safari/Info.plist', replace('0.0.1', manifest.version)))
|
.on("error", reject)
|
||||||
.pipe(gulpif('safari/Info.plist', replace('0.0.2', process.env.BUILD_NUMBER || manifest.version)))
|
.pipe(gulpif("safari/Info.plist", replace("0.0.1", manifest.version)))
|
||||||
.pipe(gulpif('desktop.xcodeproj/project.pbxproj', replace('../../../build', '../safari/app')))
|
.pipe(
|
||||||
.pipe(gulp.dest(dest))
|
gulpif("safari/Info.plist", replace("0.0.2", process.env.BUILD_NUMBER || manifest.version))
|
||||||
.on('end', resolve);
|
)
|
||||||
});
|
.pipe(gulpif("desktop.xcodeproj/project.pbxproj", replace("../../../build", "../safari/app")))
|
||||||
|
.pipe(gulp.dest(dest))
|
||||||
|
.on("end", resolve);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function safariCopyBuild(source, dest) {
|
function safariCopyBuild(source, dest) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
gulp.src(source)
|
gulp
|
||||||
.on('error', reject)
|
.src(source)
|
||||||
.pipe(filter(['**'].concat(filters.fonts)))
|
.on("error", reject)
|
||||||
.pipe(gulpif('popup/index.html', replace('__BROWSER__', 'browser_safari')))
|
.pipe(filter(["**"].concat(filters.fonts)))
|
||||||
.pipe(gulpif('manifest.json', jeditor((manifest) => {
|
.pipe(gulpif("popup/index.html", replace("__BROWSER__", "browser_safari")))
|
||||||
delete manifest.optional_permissions;
|
.pipe(
|
||||||
manifest.permissions.push("nativeMessaging");
|
gulpif(
|
||||||
return manifest;
|
"manifest.json",
|
||||||
})))
|
jeditor((manifest) => {
|
||||||
.pipe(gulp.dest(dest))
|
delete manifest.optional_permissions;
|
||||||
.on('end', resolve);
|
manifest.permissions.push("nativeMessaging");
|
||||||
});
|
return manifest;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.pipe(gulp.dest(dest))
|
||||||
|
.on("end", resolve);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function stdOutProc(proc) {
|
function stdOutProc(proc) {
|
||||||
proc.stdout.on('data', (data) => console.log(data.toString()));
|
proc.stdout.on("data", (data) => console.log(data.toString()));
|
||||||
proc.stderr.on('data', (data) => console.error(data.toString()));
|
proc.stderr.on("data", (data) => console.error(data.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
function ciCoverage(cb) {
|
function ciCoverage(cb) {
|
||||||
return gulp.src(paths.coverage + '**/*')
|
return gulp
|
||||||
.pipe(filter(['**', '!coverage/coverage*.zip']))
|
.src(paths.coverage + "**/*")
|
||||||
.pipe(zip(`coverage${buildString()}.zip`))
|
.pipe(filter(["**", "!coverage/coverage*.zip"]))
|
||||||
.pipe(gulp.dest(paths.coverage));
|
.pipe(zip(`coverage${buildString()}.zip`))
|
||||||
|
.pipe(gulp.dest(paths.coverage));
|
||||||
}
|
}
|
||||||
|
|
||||||
exports['dist:firefox'] = distFirefox;
|
exports["dist:firefox"] = distFirefox;
|
||||||
exports['dist:chrome'] = distChrome;
|
exports["dist:chrome"] = distChrome;
|
||||||
exports['dist:opera'] = distOpera;
|
exports["dist:opera"] = distOpera;
|
||||||
exports['dist:edge'] = distEdge;
|
exports["dist:edge"] = distEdge;
|
||||||
exports['dist:safari'] = gulp.parallel(distSafariMas, distSafariMasDev, distSafariDmg);
|
exports["dist:safari"] = gulp.parallel(distSafariMas, distSafariMasDev, distSafariDmg);
|
||||||
exports['dist:safari:mas'] = distSafariMas;
|
exports["dist:safari:mas"] = distSafariMas;
|
||||||
exports['dist:safari:masdev'] = distSafariMasDev;
|
exports["dist:safari:masdev"] = distSafariMasDev;
|
||||||
exports['dist:safari:dmg'] = distSafariDmg;
|
exports["dist:safari:dmg"] = distSafariDmg;
|
||||||
exports.dist = gulp.parallel(distFirefox, distChrome, distOpera, distEdge);
|
exports.dist = gulp.parallel(distFirefox, distChrome, distOpera, distEdge);
|
||||||
exports['ci:coverage'] = ciCoverage;
|
exports["ci:coverage"] = ciCoverage;
|
||||||
exports.ci = ciCoverage;
|
exports.ci = ciCoverage;
|
||||||
|
|
133
karma.conf.js
133
karma.conf.js
|
@ -1,76 +1,71 @@
|
||||||
const path = require('path');
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = function(config) {
|
module.exports = function (config) {
|
||||||
config.set({
|
config.set({
|
||||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||||
basePath: '',
|
basePath: "",
|
||||||
|
|
||||||
// frameworks to use
|
// frameworks to use
|
||||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||||
frameworks: ['jasmine', 'webpack'],
|
frameworks: ["jasmine", "webpack"],
|
||||||
|
|
||||||
// list of files / patterns to load in the browser
|
// list of files / patterns to load in the browser
|
||||||
files: [
|
files: [{ pattern: "src/**/*.spec.ts", watch: false }],
|
||||||
{ pattern: 'src/**/*.spec.ts', watch: false },
|
|
||||||
],
|
|
||||||
|
|
||||||
exclude: [
|
exclude: [],
|
||||||
],
|
|
||||||
|
|
||||||
// preprocess matching files before serving them to the browser
|
// preprocess matching files before serving them to the browser
|
||||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
'src/**/*.ts': 'webpack'
|
"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
|
module: {
|
||||||
// possible values: 'dots', 'progress'
|
rules: [{ test: /\.tsx?$/, loader: "ts-loader" }],
|
||||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
},
|
||||||
reporters: ['progress', 'kjhtml'],
|
stats: {
|
||||||
|
|
||||||
// web server port
|
|
||||||
port: 9876,
|
|
||||||
|
|
||||||
// enable / disable colors in the output (reporters and logs)
|
|
||||||
colors: true,
|
colors: true,
|
||||||
|
modules: true,
|
||||||
// level of logging
|
reasons: true,
|
||||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
errorDetails: true,
|
||||||
logLevel: config.LOG_INFO,
|
},
|
||||||
|
devtool: "inline-source-map",
|
||||||
// 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',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body></body>
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -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(() => {
|
bitwardenMain.bootstrap().then(() => {
|
||||||
// Finished bootstrapping
|
// Finished bootstrapping
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
|
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
|
||||||
|
|
||||||
export default class CommandsBackground {
|
export default class CommandsBackground {
|
||||||
private isSafari: boolean;
|
private isSafari: boolean;
|
||||||
private isVivaldi: boolean;
|
private isVivaldi: boolean;
|
||||||
|
|
||||||
constructor(private main: MainBackground, private passwordGenerationService: PasswordGenerationService,
|
constructor(
|
||||||
private platformUtilsService: PlatformUtilsService, private vaultTimeoutService: VaultTimeoutService) {
|
private main: MainBackground,
|
||||||
this.isSafari = this.platformUtilsService.isSafari();
|
private passwordGenerationService: PasswordGenerationService,
|
||||||
this.isVivaldi = this.platformUtilsService.isVivaldi();
|
private platformUtilsService: PlatformUtilsService,
|
||||||
}
|
private vaultTimeoutService: VaultTimeoutService
|
||||||
|
) {
|
||||||
|
this.isSafari = this.platformUtilsService.isSafari();
|
||||||
|
this.isVivaldi = this.platformUtilsService.isVivaldi();
|
||||||
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
BrowserApi.messageListener('commands.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
|
BrowserApi.messageListener(
|
||||||
if (msg.command === 'unlockCompleted' && msg.data.target === 'commands.background') {
|
"commands.background",
|
||||||
await this.processCommand(msg.data.commandToRetry.msg.command, msg.data.commandToRetry.sender);
|
async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
|
||||||
}
|
if (msg.command === "unlockCompleted" && msg.data.target === "commands.background") {
|
||||||
|
await this.processCommand(
|
||||||
if (this.isVivaldi && msg.command === 'keyboardShortcutTriggered' && msg.shortcut) {
|
msg.data.commandToRetry.msg.command,
|
||||||
await this.processCommand(msg.shortcut, sender);
|
msg.data.commandToRetry.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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab == null) {
|
if (this.isVivaldi && msg.command === "keyboardShortcutTriggered" && msg.shortcut) {
|
||||||
return;
|
await this.processCommand(msg.shortcut, sender);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (await this.vaultTimeoutService.isLocked()) {
|
if (!this.isVivaldi && chrome && chrome.commands) {
|
||||||
const retryMessage: LockedVaultPendingNotificationsItem = {
|
chrome.commands.onCommand.addListener(async (command: string) => {
|
||||||
commandToRetry: {
|
await this.processCommand(command);
|
||||||
msg: { command: 'autofill_login' },
|
});
|
||||||
sender: { tab: tab },
|
}
|
||||||
},
|
}
|
||||||
target: 'commands.background',
|
|
||||||
};
|
|
||||||
await BrowserApi.tabSendMessageData(tab, 'addToLockedVaultPendingNotifications', retryMessage);
|
|
||||||
|
|
||||||
BrowserApi.tabSendMessageData(tab, 'promptForLogin');
|
private async processCommand(command: string, sender?: chrome.runtime.MessageSender) {
|
||||||
return;
|
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() {
|
if (tab == null) {
|
||||||
// Chrome APIs cannot open popup
|
return;
|
||||||
if (!this.isSafari) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.main.openPopup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
import { EventService } from "jslib-common/abstractions/event.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { TotpService } from 'jslib-common/abstractions/totp.service';
|
import { TotpService } from "jslib-common/abstractions/totp.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
|
|
||||||
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
|
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
|
||||||
import { EventType } from 'jslib-common/enums/eventType';
|
import { EventType } from "jslib-common/enums/eventType";
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
|
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
|
||||||
|
|
||||||
export default class ContextMenusBackground {
|
export default class ContextMenusBackground {
|
||||||
private readonly noopCommandSuffix = 'noop';
|
private readonly noopCommandSuffix = "noop";
|
||||||
private contextMenus: any;
|
private contextMenus: any;
|
||||||
|
|
||||||
constructor(private main: MainBackground, private cipherService: CipherService,
|
constructor(
|
||||||
private passwordGenerationService: PasswordGenerationService,
|
private main: MainBackground,
|
||||||
private platformUtilsService: PlatformUtilsService, private vaultTimeoutService: VaultTimeoutService,
|
private cipherService: CipherService,
|
||||||
private eventService: EventService, private totpService: TotpService) {
|
private passwordGenerationService: PasswordGenerationService,
|
||||||
this.contextMenus = chrome.contextMenus;
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private vaultTimeoutService: VaultTimeoutService,
|
||||||
|
private eventService: EventService,
|
||||||
|
private totpService: TotpService
|
||||||
|
) {
|
||||||
|
this.contextMenus = chrome.contextMenus;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (!this.contextMenus) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
this.contextMenus.onClicked.addListener(
|
||||||
if (!this.contextMenus) {
|
async (info: chrome.contextMenus.OnClickData, tab: chrome.tabs.Tab) => {
|
||||||
return;
|
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) => {
|
BrowserApi.messageListener(
|
||||||
if (info.menuItemId === 'generate-password') {
|
"contextmenus.background",
|
||||||
await this.generatePasswordToClipboard();
|
async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
|
||||||
} else if (info.menuItemId === 'copy-identifier') {
|
if (msg.command === "unlockCompleted" && msg.data.target === "contextmenus.background") {
|
||||||
await this.getClickedElement(tab, info.frameId);
|
await this.cipherAction(
|
||||||
} else if (info.parentMenuItemId === 'autofill' ||
|
msg.data.commandToRetry.sender.tab,
|
||||||
info.parentMenuItemId === 'copy-username' ||
|
msg.data.commandToRetry.msg.data
|
||||||
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) => {
|
private async generatePasswordToClipboard() {
|
||||||
if (msg.command === 'unlockCompleted' && msg.data.target === 'contextmenus.background') {
|
const options = (await this.passwordGenerationService.getOptions())[0];
|
||||||
await this.cipherAction(msg.data.commandToRetry.sender.tab, msg.data.commandToRetry.msg.data);
|
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() {
|
BrowserApi.tabSendMessage(tab, { command: "getClickedElement" }, { frameId: frameId });
|
||||||
const options = (await this.passwordGenerationService.getOptions())[0];
|
}
|
||||||
const password = await this.passwordGenerationService.generatePassword(options);
|
|
||||||
this.platformUtilsService.copyToClipboard(password, { window: window });
|
private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) {
|
||||||
this.passwordGenerationService.addHistory(password);
|
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) {
|
let cipher: CipherView;
|
||||||
if (tab == null) {
|
if (id === this.noopCommandSuffix) {
|
||||||
return;
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(tab.url);
|
||||||
}
|
cipher = ciphers.find((c) => c.reprompt === CipherRepromptType.None);
|
||||||
|
} else {
|
||||||
BrowserApi.tabSendMessage(tab, { command: 'getClickedElement' }, { frameId: frameId });
|
const ciphers = await this.cipherService.getAllDecrypted();
|
||||||
|
cipher = ciphers.find((c) => c.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async cipherAction(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) {
|
if (cipher == null) {
|
||||||
const id = info.menuItemId.split('_')[1];
|
return;
|
||||||
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async startAutofillPage(tab: chrome.tabs.Tab, cipher: CipherView) {
|
if (info.parentMenuItemId === "autofill") {
|
||||||
this.main.loginToAutoFill = cipher;
|
await this.startAutofillPage(tab, cipher);
|
||||||
if (tab == null) {
|
} else if (info.parentMenuItemId === "copy-username") {
|
||||||
return;
|
this.platformUtilsService.copyToClipboard(cipher.login.username, { window: window });
|
||||||
}
|
} else if (info.parentMenuItemId === "copy-password") {
|
||||||
|
this.platformUtilsService.copyToClipboard(cipher.login.password, { window: window });
|
||||||
BrowserApi.tabSendMessage(tab, {
|
this.eventService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id);
|
||||||
command: 'collectPageDetails',
|
} else if (info.parentMenuItemId === "copy-totp") {
|
||||||
tab: tab,
|
const totpValue = await this.totpService.getCode(cipher.login.totp);
|
||||||
sender: 'contextMenu',
|
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",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +1,75 @@
|
||||||
import { NotificationsService } from 'jslib-common/abstractions/notifications.service';
|
import { NotificationsService } from "jslib-common/abstractions/notifications.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.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
|
const IdleInterval = 60 * 5; // 5 minutes
|
||||||
|
|
||||||
export default class IdleBackground {
|
export default class IdleBackground {
|
||||||
private idle: any;
|
private idle: any;
|
||||||
private idleTimer: number = null;
|
private idleTimer: number = null;
|
||||||
private idleState = 'active';
|
private idleState = "active";
|
||||||
|
|
||||||
constructor(private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService,
|
constructor(
|
||||||
private notificationsService: NotificationsService) {
|
private vaultTimeoutService: VaultTimeoutService,
|
||||||
this.idle = chrome.idle || (browser != null ? browser.idle : null);
|
private storageService: StorageService,
|
||||||
|
private notificationsService: NotificationsService
|
||||||
|
) {
|
||||||
|
this.idle = chrome.idle || (browser != null ? browser.idle : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (!this.idle) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
const idleHandler = (newState: string) => {
|
||||||
if (!this.idle) {
|
if (newState === "active") {
|
||||||
return;
|
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 (this.idle.onStateChanged) {
|
||||||
if (newState === 'active') {
|
this.idle.onStateChanged.addListener(async (newState: string) => {
|
||||||
this.notificationsService.reconnectFromActivity();
|
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 {
|
} 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) {
|
private pollIdle(handler: (newState: string) => void) {
|
||||||
if (this.idleTimer != null) {
|
if (this.idleTimer != null) {
|
||||||
window.clearTimeout(this.idleTimer);
|
window.clearTimeout(this.idleTimer);
|
||||||
this.idleTimer = null;
|
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
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
|
@ -1,6 +1,6 @@
|
||||||
import NotificationQueueMessage from './notificationQueueMessage';
|
import NotificationQueueMessage from "./notificationQueueMessage";
|
||||||
|
|
||||||
export default class AddChangePasswordQueueMessage extends NotificationQueueMessage {
|
export default class AddChangePasswordQueueMessage extends NotificationQueueMessage {
|
||||||
cipherId: string;
|
cipherId: string;
|
||||||
newPassword: string;
|
newPassword: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import NotificationQueueMessage from './notificationQueueMessage';
|
import NotificationQueueMessage from "./notificationQueueMessage";
|
||||||
|
|
||||||
export default class AddLoginQueueMessage extends NotificationQueueMessage {
|
export default class AddLoginQueueMessage extends NotificationQueueMessage {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
uri: string;
|
uri: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export default class AddLoginRuntimeMessage {
|
export default class AddLoginRuntimeMessage {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export default class ChangePasswordRuntimeMessage {
|
export default class ChangePasswordRuntimeMessage {
|
||||||
currentPassword: string;
|
currentPassword: string;
|
||||||
newPassword: string;
|
newPassword: string;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export default class LockedVaultPendingNotificationsItem {
|
export default class LockedVaultPendingNotificationsItem {
|
||||||
commandToRetry: {
|
commandToRetry: {
|
||||||
msg: any;
|
msg: any;
|
||||||
sender: chrome.runtime.MessageSender;
|
sender: chrome.runtime.MessageSender;
|
||||||
};
|
};
|
||||||
target: string;
|
target: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { NotificationQueueMessageType } from './notificationQueueMessageType';
|
import { NotificationQueueMessageType } from "./notificationQueueMessageType";
|
||||||
|
|
||||||
export default class NotificationQueueMessage {
|
export default class NotificationQueueMessage {
|
||||||
type: NotificationQueueMessageType;
|
type: NotificationQueueMessageType;
|
||||||
domain: string;
|
domain: string;
|
||||||
tabId: number;
|
tabId: number;
|
||||||
expires: Date;
|
expires: Date;
|
||||||
wasVaultLocked: boolean;
|
wasVaultLocked: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export enum NotificationQueueMessageType {
|
export enum NotificationQueueMessageType {
|
||||||
addLogin = 'addLogin',
|
addLogin = "addLogin",
|
||||||
changePassword = 'changePassword',
|
changePassword = "changePassword",
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,333 +1,350 @@
|
||||||
import { AppIdService } from 'jslib-common/abstractions/appId.service';
|
import { AppIdService } from "jslib-common/abstractions/appId.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from "jslib-common/abstractions/user.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.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 { Utils } from 'jslib-common/misc/utils';
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
|
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
|
||||||
|
|
||||||
import { BrowserApi } from '../browser/browserApi';
|
import { BrowserApi } from "../browser/browserApi";
|
||||||
import RuntimeBackground from './runtime.background';
|
import RuntimeBackground from "./runtime.background";
|
||||||
|
|
||||||
const MessageValidTimeout = 10 * 1000;
|
const MessageValidTimeout = 10 * 1000;
|
||||||
const EncryptionAlgorithm = 'sha1';
|
const EncryptionAlgorithm = "sha1";
|
||||||
|
|
||||||
export class NativeMessagingBackground {
|
export class NativeMessagingBackground {
|
||||||
private connected = false;
|
private connected = false;
|
||||||
private connecting: boolean;
|
private connecting: boolean;
|
||||||
private port: browser.runtime.Port | chrome.runtime.Port;
|
private port: browser.runtime.Port | chrome.runtime.Port;
|
||||||
|
|
||||||
private resolver: any = null;
|
private resolver: any = null;
|
||||||
private privateKey: ArrayBuffer = null;
|
private privateKey: ArrayBuffer = null;
|
||||||
private publicKey: ArrayBuffer = null;
|
private publicKey: ArrayBuffer = null;
|
||||||
private secureSetupResolve: any = null;
|
private secureSetupResolve: any = null;
|
||||||
private sharedSecret: SymmetricCryptoKey;
|
private sharedSecret: SymmetricCryptoKey;
|
||||||
private appId: string;
|
private appId: string;
|
||||||
private validatingFingerprint: boolean;
|
private validatingFingerprint: boolean;
|
||||||
|
|
||||||
constructor(private storageService: StorageService, private cryptoService: CryptoService,
|
constructor(
|
||||||
private cryptoFunctionService: CryptoFunctionService, private vaultTimeoutService: VaultTimeoutService,
|
private storageService: StorageService,
|
||||||
private runtimeBackground: RuntimeBackground, private i18nService: I18nService, private userService: UserService,
|
private cryptoService: CryptoService,
|
||||||
private messagingService: MessagingService, private appIdService: AppIdService,
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
private platformUtilsService: PlatformUtilsService) {
|
private vaultTimeoutService: VaultTimeoutService,
|
||||||
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
|
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) {
|
if (chrome?.permissions?.onAdded) {
|
||||||
// Reload extension to activate nativeMessaging
|
// Reload extension to activate nativeMessaging
|
||||||
chrome.permissions.onAdded.addListener(permissions => {
|
chrome.permissions.onAdded.addListener((permissions) => {
|
||||||
BrowserApi.reloadExtension(null);
|
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();
|
||||||
}
|
}
|
||||||
}
|
this.connected = false;
|
||||||
|
this.port.disconnect();
|
||||||
async connect() {
|
break;
|
||||||
this.appId = await this.appIdService.getAppId();
|
case "setupEncryption":
|
||||||
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
|
// Ignore since it belongs to another device
|
||||||
|
if (message.appId !== this.appId) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return;
|
||||||
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) => {
|
const encrypted = Utils.fromB64ToArray(message.sharedSecret);
|
||||||
switch (message.command) {
|
const decrypted = await this.cryptoFunctionService.rsaDecrypt(
|
||||||
case 'connected':
|
encrypted.buffer,
|
||||||
connectedCallback();
|
this.privateKey,
|
||||||
break;
|
EncryptionAlgorithm
|
||||||
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);
|
if (this.validatingFingerprint) {
|
||||||
const decrypted = await this.cryptoFunctionService.rsaDecrypt(encrypted.buffer, this.privateKey, EncryptionAlgorithm);
|
this.validatingFingerprint = false;
|
||||||
|
this.storageService.save(ConstantsService.biometricFingerprintValidated, true);
|
||||||
if (this.validatingFingerprint) {
|
}
|
||||||
this.validatingFingerprint = false;
|
this.sharedSecret = new SymmetricCryptoKey(decrypted);
|
||||||
this.storageService.save(ConstantsService.biometricFingerprintValidated, true);
|
this.secureSetupResolve();
|
||||||
}
|
break;
|
||||||
this.sharedSecret = new SymmetricCryptoKey(decrypted);
|
case "invalidateEncryption":
|
||||||
this.secureSetupResolve();
|
// Ignore since it belongs to another device
|
||||||
break;
|
if (message.appId !== this.appId) {
|
||||||
case 'invalidateEncryption':
|
return;
|
||||||
// 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.");
|
|
||||||
|
|
||||||
this.sharedSecret = null;
|
this.sharedSecret = null;
|
||||||
this.privateKey = null;
|
this.privateKey = null;
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
|
|
||||||
this.messagingService.send('showDialog', {
|
this.messagingService.send("showDialog", {
|
||||||
text: this.i18nService.t('nativeMessagingInvalidEncryptionDesc'),
|
text: this.i18nService.t("nativeMessagingInvalidEncryptionDesc"),
|
||||||
title: this.i18nService.t('nativeMessagingInvalidEncryptionTitle'),
|
title: this.i18nService.t("nativeMessagingInvalidEncryptionTitle"),
|
||||||
confirmText: this.i18nService.t('ok'),
|
confirmText: this.i18nService.t("ok"),
|
||||||
type: 'error',
|
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) {
|
if (this.platformUtilsService.isSafari()) {
|
||||||
let message = rawMessage;
|
this.postMessage(message);
|
||||||
if (!this.platformUtilsService.isSafari()) {
|
} else {
|
||||||
message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret));
|
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
|
// tslint:disable-next-line
|
||||||
console.error('NativeMessage is to old, ignoring.');
|
console.error("Unable to verify key:", e);
|
||||||
return;
|
await this.cryptoService.clearKey();
|
||||||
}
|
this.showWrongUserDialog();
|
||||||
|
|
||||||
switch (message.command) {
|
message = false;
|
||||||
case 'biometricUnlock':
|
break;
|
||||||
await this.storageService.remove(ConstantsService.biometricAwaitingAcceptance);
|
}
|
||||||
|
|
||||||
if (message.response === 'not enabled') {
|
this.vaultTimeoutService.biometricLocked = false;
|
||||||
this.messagingService.send('showDialog', {
|
this.runtimeBackground.processMessage({ command: "unlocked" }, null, null);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// tslint:disable-next-line
|
||||||
|
console.error("NativeMessage, got unknown command: ", message.command);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async secureCommunication() {
|
if (this.resolver) {
|
||||||
const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048);
|
this.resolver(message);
|
||||||
this.publicKey = publicKey;
|
}
|
||||||
this.privateKey = privateKey;
|
}
|
||||||
|
|
||||||
this.sendUnencrypted({
|
private async secureCommunication() {
|
||||||
command: 'setupEncryption',
|
const [publicKey, privateKey] = await this.cryptoFunctionService.rsaGenerateKeyPair(2048);
|
||||||
publicKey: Utils.fromBufferToB64(publicKey),
|
this.publicKey = publicKey;
|
||||||
userId: await this.userService.getUserId(),
|
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) {
|
message.timestamp = Date.now();
|
||||||
if (!this.connected) {
|
|
||||||
await this.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
this.messagingService.send("showDialog", {
|
||||||
const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), this.publicKey)).join(' ');
|
html: `${this.i18nService.t(
|
||||||
|
"desktopIntegrationVerificationText"
|
||||||
this.messagingService.send('showDialog', {
|
)}<br><br><strong>${fingerprint}</strong>`,
|
||||||
html: `${this.i18nService.t('desktopIntegrationVerificationText')}<br><br><strong>${fingerprint}</strong>`,
|
title: this.i18nService.t("desktopSyncVerificationTitle"),
|
||||||
title: this.i18nService.t('desktopSyncVerificationTitle'),
|
confirmText: this.i18nService.t("ok"),
|
||||||
confirmText: this.i18nService.t('ok'),
|
type: "warning",
|
||||||
type: 'warning',
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
import { LoginUriView } from 'jslib-common/models/view/loginUriView';
|
import { LoginUriView } from "jslib-common/models/view/loginUriView";
|
||||||
import { LoginView } from 'jslib-common/models/view/loginView';
|
import { LoginView } from "jslib-common/models/view/loginView";
|
||||||
|
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
import { FolderService } from "jslib-common/abstractions/folder.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from "jslib-common/abstractions/user.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.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 AddChangePasswordQueueMessage from "./models/addChangePasswordQueueMessage";
|
||||||
import AddLoginQueueMessage from './models/addLoginQueueMessage';
|
import AddLoginQueueMessage from "./models/addLoginQueueMessage";
|
||||||
import AddLoginRuntimeMessage from './models/addLoginRuntimeMessage';
|
import AddLoginRuntimeMessage from "./models/addLoginRuntimeMessage";
|
||||||
import ChangePasswordRuntimeMessage from './models/changePasswordRuntimeMessage';
|
import ChangePasswordRuntimeMessage from "./models/changePasswordRuntimeMessage";
|
||||||
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
|
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
|
||||||
import { NotificationQueueMessageType } from './models/notificationQueueMessageType';
|
import { NotificationQueueMessageType } from "./models/notificationQueueMessageType";
|
||||||
|
|
||||||
export default class NotificationBackground {
|
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,
|
async init() {
|
||||||
private cipherService: CipherService, private storageService: StorageService,
|
if (chrome.runtime == null) {
|
||||||
private vaultTimeoutService: VaultTimeoutService, private policyService: PolicyService,
|
return;
|
||||||
private folderService: FolderService, private userService: UserService) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
BrowserApi.messageListener(
|
||||||
if (chrome.runtime == null) {
|
"notification.background",
|
||||||
return;
|
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);
|
||||||
BrowserApi.messageListener('notification.background', async (msg: any, sender: chrome.runtime.MessageSender) => {
|
break;
|
||||||
await this.processMessage(msg, sender);
|
case "bgGetDataForTab":
|
||||||
});
|
await this.getDataForTab(sender.tab, msg.responseCommand);
|
||||||
|
break;
|
||||||
this.cleanupNotificationQueue();
|
case "bgCloseNotificationBar":
|
||||||
}
|
await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar");
|
||||||
|
break;
|
||||||
async processMessage(msg: any, sender: chrome.runtime.MessageSender) {
|
case "bgAdjustNotificationBar":
|
||||||
switch (msg.command) {
|
await BrowserApi.tabSendMessageData(sender.tab, "adjustNotificationBar", msg.data);
|
||||||
case 'unlockCompleted':
|
break;
|
||||||
if (msg.data.target !== 'notification.background') {
|
case "bgAddLogin":
|
||||||
return;
|
await this.addLogin(msg.login, sender.tab);
|
||||||
}
|
break;
|
||||||
await this.processMessage(msg.data.commandToRetry.msg, msg.data.commandToRetry.sender);
|
case "bgChangedPassword":
|
||||||
break;
|
await this.changedPassword(msg.data, sender.tab);
|
||||||
case 'bgGetDataForTab':
|
break;
|
||||||
await this.getDataForTab(sender.tab, msg.responseCommand);
|
case "bgAddClose":
|
||||||
break;
|
case "bgChangeClose":
|
||||||
case 'bgCloseNotificationBar':
|
this.removeTabFromNotificationQueue(sender.tab);
|
||||||
await BrowserApi.tabSendMessageData(sender.tab, 'closeNotificationBar');
|
break;
|
||||||
break;
|
case "bgAddSave":
|
||||||
case 'bgAdjustNotificationBar':
|
case "bgChangeSave":
|
||||||
await BrowserApi.tabSendMessageData(sender.tab, 'adjustNotificationBar', msg.data);
|
if (await this.vaultTimeoutService.isLocked()) {
|
||||||
break;
|
const retryMessage: LockedVaultPendingNotificationsItem = {
|
||||||
case 'bgAddLogin':
|
commandToRetry: {
|
||||||
await this.addLogin(msg.login, sender.tab);
|
msg: msg,
|
||||||
break;
|
sender: sender,
|
||||||
case 'bgChangedPassword':
|
},
|
||||||
await this.changedPassword(msg.data, sender.tab);
|
target: "notification.background",
|
||||||
break;
|
};
|
||||||
case 'bgAddClose':
|
await BrowserApi.tabSendMessageData(
|
||||||
case 'bgChangeClose':
|
sender.tab,
|
||||||
this.removeTabFromNotificationQueue(sender.tab);
|
"addToLockedVaultPendingNotifications",
|
||||||
break;
|
retryMessage
|
||||||
case 'bgAddSave':
|
);
|
||||||
case 'bgChangeSave':
|
await BrowserApi.tabSendMessageData(sender.tab, "promptForLogin");
|
||||||
if (await this.vaultTimeoutService.isLocked()) {
|
return;
|
||||||
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.saveOrUpdateCredentials(sender.tab, msg.folder);
|
||||||
|
break;
|
||||||
async checkNotificationQueue(tab: chrome.tabs.Tab = null): Promise<void> {
|
case "bgNeverSave":
|
||||||
if (this.notificationQueue.length === 0) {
|
await this.saveNever(sender.tab);
|
||||||
return;
|
break;
|
||||||
}
|
case "collectPageDetailsResponse":
|
||||||
|
switch (msg.sender) {
|
||||||
if (tab != null) {
|
case "notificationBar":
|
||||||
this.doNotificationQueueCheck(tab);
|
const forms = this.autofillService.getFormsWithPasswordFields(msg.details);
|
||||||
return;
|
await BrowserApi.tabSendMessageData(msg.tab, "notificationBarPageDetails", {
|
||||||
}
|
details: msg.details,
|
||||||
|
forms: forms,
|
||||||
const currentTab = await BrowserApi.getTabFromCurrentWindow();
|
});
|
||||||
if (currentTab != null) {
|
break;
|
||||||
this.doNotificationQueueCheck(currentTab);
|
default:
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
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) {
|
if (tab != null) {
|
||||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
this.doNotificationQueueCheck(tab);
|
||||||
if (this.notificationQueue[i].tabId === tab.id) {
|
return;
|
||||||
this.notificationQueue.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async addLogin(loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab) {
|
const currentTab = await BrowserApi.getTabFromCurrentWindow();
|
||||||
if (!await this.userService.isAuthenticated()) {
|
if (currentTab != null) {
|
||||||
return;
|
this.doNotificationQueueCheck(currentTab);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loginDomain = Utils.getDomain(loginInfo.url);
|
private cleanupNotificationQueue() {
|
||||||
if (loginDomain == null) {
|
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||||
return;
|
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;
|
private doNotificationQueueCheck(tab: chrome.tabs.Tab): void {
|
||||||
if (normalizedUsername != null) {
|
if (tab == null) {
|
||||||
normalizedUsername = normalizedUsername.toLowerCase();
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
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 async pushAddLoginToQueue(loginDomain: string, loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab, isVaultLocked: boolean = false) {
|
const tabDomain = Utils.getDomain(tab.url);
|
||||||
// remove any old messages for this tab
|
if (tabDomain == null) {
|
||||||
this.removeTabFromNotificationQueue(tab);
|
return;
|
||||||
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) {
|
for (let i = 0; i < this.notificationQueue.length; i++) {
|
||||||
const loginDomain = Utils.getDomain(changeData.url);
|
if (
|
||||||
if (loginDomain == null) {
|
this.notificationQueue[i].tabId !== tab.id ||
|
||||||
return;
|
this.notificationQueue[i].domain !== tabDomain
|
||||||
}
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (await this.vaultTimeoutService.isLocked()) {
|
if (this.notificationQueue[i].type === NotificationQueueMessageType.addLogin) {
|
||||||
this.pushChangePasswordToQueue(null, loginDomain, changeData.newPassword, tab, true);
|
BrowserApi.tabSendMessageData(tab, "openNotificationBar", {
|
||||||
return;
|
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;
|
private removeTabFromNotificationQueue(tab: chrome.tabs.Tab) {
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url);
|
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||||
if (changeData.currentPassword != null) {
|
if (this.notificationQueue[i].tabId === tab.id) {
|
||||||
const passwordMatches = ciphers.filter(c => c.login.password === changeData.currentPassword);
|
this.notificationQueue.splice(i, 1);
|
||||||
if (passwordMatches.length === 1) {
|
}
|
||||||
id = passwordMatches[0].id;
|
}
|
||||||
}
|
}
|
||||||
} else if (ciphers.length === 1) {
|
|
||||||
id = ciphers[0].id;
|
private async addLogin(loginInfo: AddLoginRuntimeMessage, tab: chrome.tabs.Tab) {
|
||||||
}
|
if (!(await this.userService.isAuthenticated())) {
|
||||||
if (id != null) {
|
return;
|
||||||
this.pushChangePasswordToQueue(id, loginDomain, changeData.newPassword, tab);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async pushChangePasswordToQueue(cipherId: string, loginDomain: string, newPassword: string, tab: chrome.tabs.Tab, isVaultLocked: boolean = false) {
|
const loginDomain = Utils.getDomain(loginInfo.url);
|
||||||
// remove any old messages for this tab
|
if (loginDomain == null) {
|
||||||
this.removeTabFromNotificationQueue(tab);
|
return;
|
||||||
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) {
|
let normalizedUsername = loginInfo.username;
|
||||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
if (normalizedUsername != null) {
|
||||||
const queueMessage = this.notificationQueue[i];
|
normalizedUsername = normalizedUsername.toLowerCase();
|
||||||
if (queueMessage.tabId !== tab.id ||
|
}
|
||||||
(queueMessage.type !== NotificationQueueMessageType.addLogin && queueMessage.type !== NotificationQueueMessageType.changePassword)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabDomain = Utils.getDomain(tab.url);
|
const disabledAddLogin = await this.storageService.get<boolean>(
|
||||||
if (tabDomain != null && tabDomain !== queueMessage.domain) {
|
ConstantsService.disableAddLoginNotificationKey
|
||||||
continue;
|
);
|
||||||
}
|
if (await this.vaultTimeoutService.isLocked()) {
|
||||||
|
if (disabledAddLogin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.notificationQueue.splice(i, 1);
|
if (!(await this.allowPersonalOwnership())) {
|
||||||
BrowserApi.tabSendMessageData(tab, 'closeNotificationBar');
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (queueMessage.type === NotificationQueueMessageType.changePassword) {
|
this.pushAddLoginToQueue(loginDomain, loginInfo, tab, true);
|
||||||
const message = (queueMessage as AddChangePasswordQueueMessage);
|
return;
|
||||||
const cipher = await this.getDecryptedCipherById(message.cipherId);
|
}
|
||||||
if (cipher == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.updateCipher(cipher, message.newPassword);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!queueMessage.wasVaultLocked) {
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url);
|
||||||
await this.createNewCipher(queueMessage as AddLoginQueueMessage, folderId);
|
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 (!(await this.allowPersonalOwnership())) {
|
||||||
if (queueMessage.type === NotificationQueueMessageType.addLogin && queueMessage.wasVaultLocked === true) {
|
return;
|
||||||
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) {
|
this.pushAddLoginToQueue(loginDomain, loginInfo, tab);
|
||||||
await this.updateCipher(usernameMatches[0], message.password);
|
} else if (
|
||||||
return;
|
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) {
|
if (!queueMessage.wasVaultLocked) {
|
||||||
const loginModel = new LoginView();
|
await this.createNewCipher(queueMessage as AddLoginQueueMessage, folderId);
|
||||||
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)) {
|
// If the vault was locked, check if a cipher needs updating instead of creating a new one
|
||||||
const folders = await this.folderService.getAllDecrypted();
|
if (
|
||||||
if (folders.some(x => x.id === folderId)) {
|
queueMessage.type === NotificationQueueMessageType.addLogin &&
|
||||||
model.folderId = folderId;
|
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.createNewCipher(message, folderId);
|
||||||
await this.cipherService.saveWithServer(cipher);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.encrypt(model);
|
||||||
const cipher = await this.cipherService.get(cipherId);
|
await this.cipherService.saveWithServer(cipher);
|
||||||
if (cipher != null && cipher.type === CipherType.Login) {
|
}
|
||||||
return await cipher.decrypt();
|
|
||||||
}
|
private async getDecryptedCipherById(cipherId: string) {
|
||||||
return null;
|
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) {
|
await BrowserApi.tabSendMessageData(tab, responseCommand, responseData);
|
||||||
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) {
|
private async allowPersonalOwnership(): Promise<boolean> {
|
||||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
return !(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,219 +1,248 @@
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { NotificationsService } from 'jslib-common/abstractions/notifications.service';
|
import { NotificationsService } from "jslib-common/abstractions/notifications.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
import { SystemService } from 'jslib-common/abstractions/system.service';
|
import { SystemService } from "jslib-common/abstractions/system.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 BrowserPlatformUtilsService from '../services/browserPlatformUtils.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 { Utils } from "jslib-common/misc/utils";
|
||||||
import LockedVaultPendingNotificationsItem from './models/lockedVaultPendingNotificationsItem';
|
import LockedVaultPendingNotificationsItem from "./models/lockedVaultPendingNotificationsItem";
|
||||||
|
|
||||||
export default class RuntimeBackground {
|
export default class RuntimeBackground {
|
||||||
private autofillTimeout: any;
|
private autofillTimeout: any;
|
||||||
private pageDetailsToAutoFill: any[] = [];
|
private pageDetailsToAutoFill: any[] = [];
|
||||||
private onInstalledReason: string = null;
|
private onInstalledReason: string = null;
|
||||||
private lockedVaultPendingNotifications: LockedVaultPendingNotificationsItem[] = [];
|
private lockedVaultPendingNotifications: LockedVaultPendingNotificationsItem[] = [];
|
||||||
|
|
||||||
constructor(private main: MainBackground, private autofillService: AutofillService,
|
constructor(
|
||||||
private platformUtilsService: BrowserPlatformUtilsService,
|
private main: MainBackground,
|
||||||
private storageService: StorageService, private i18nService: I18nService,
|
private autofillService: AutofillService,
|
||||||
private notificationsService: NotificationsService, private systemService: SystemService,
|
private platformUtilsService: BrowserPlatformUtilsService,
|
||||||
private environmentService: EnvironmentService, private messagingService: MessagingService,
|
private storageService: StorageService,
|
||||||
private logService: LogService) {
|
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
|
async init() {
|
||||||
chrome.runtime.onInstalled.addListener((details: any) => {
|
if (!chrome.runtime) {
|
||||||
this.onInstalledReason = details.reason;
|
return;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
await this.checkOnInstalled();
|
||||||
if (!chrome.runtime) {
|
BrowserApi.messageListener(
|
||||||
return;
|
"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();
|
await this.main.setIcon();
|
||||||
BrowserApi.messageListener('runtime.background', async (msg: any, sender: chrome.runtime.MessageSender, sendResponse: any) => {
|
await this.main.refreshBadgeAndMenu(false);
|
||||||
await this.processMessage(msg, sender, sendResponse);
|
this.notificationsService.updateConnection(msg.command === "unlocked");
|
||||||
});
|
this.systemService.cancelProcessReload();
|
||||||
}
|
|
||||||
|
|
||||||
async processMessage(msg: any, sender: any, sendResponse: any) {
|
if (item) {
|
||||||
switch (msg.command) {
|
await BrowserApi.tabSendMessageData(
|
||||||
case 'loggedIn':
|
item.commandToRetry.sender.tab,
|
||||||
case 'unlocked':
|
"unlockCompleted",
|
||||||
let item: LockedVaultPendingNotificationsItem;
|
item
|
||||||
|
);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
|
case "addToLockedVaultPendingNotifications":
|
||||||
private async autofillPage() {
|
this.lockedVaultPendingNotifications.push(msg.data);
|
||||||
const totpCode = await this.autofillService.doAutoFill({
|
break;
|
||||||
cipher: this.main.loginToAutoFill,
|
case "logout":
|
||||||
pageDetails: this.pageDetailsToAutoFill,
|
await this.main.logout(msg.expired);
|
||||||
fillNewPassword: true,
|
break;
|
||||||
});
|
case "syncCompleted":
|
||||||
|
if (msg.successfully) {
|
||||||
if (totpCode != null) {
|
setTimeout(async () => await this.main.refreshBadgeAndMenu(), 2000);
|
||||||
this.platformUtilsService.copyToClipboard(totpCode, { window: window });
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
// reset
|
case "openPopup":
|
||||||
this.main.loginToAutoFill = null;
|
await this.main.openPopup();
|
||||||
this.pageDetailsToAutoFill = [];
|
break;
|
||||||
}
|
case "promptForLogin":
|
||||||
|
await BrowserApi.createNewTab("popup/index.html?uilocation=popout", true, true);
|
||||||
private async checkOnInstalled() {
|
break;
|
||||||
setTimeout(async () => {
|
case "showDialogResolve":
|
||||||
if (this.onInstalledReason != null) {
|
this.platformUtilsService.resolveDialogPromise(msg.dialogId, msg.confirmed);
|
||||||
if (this.onInstalledReason === 'install') {
|
break;
|
||||||
BrowserApi.createNewTab('https://bitwarden.com/browser-start/');
|
case "bgCollectPageDetails":
|
||||||
await this.setDefaultSettings();
|
await this.main.collectPageDetailsForContentScript(sender.tab, msg.sender, sender.frameId);
|
||||||
}
|
break;
|
||||||
|
case "bgUpdateContextMenu":
|
||||||
this.onInstalledReason = null;
|
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() {
|
if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) {
|
||||||
// Default timeout option to "on restart".
|
return;
|
||||||
const currentVaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
|
|
||||||
if (currentVaultTimeout == null) {
|
|
||||||
await this.storageService.save(ConstantsService.vaultTimeoutKey, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default action to "lock".
|
try {
|
||||||
const currentVaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
|
BrowserApi.createNewTab(
|
||||||
if (currentVaultTimeoutAction == null) {
|
"popup/index.html?uilocation=popout#/sso?code=" +
|
||||||
await this.storageService.save(ConstantsService.vaultTimeoutActionKey, 'lock');
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,45 @@
|
||||||
import MainBackground from './main.background';
|
import MainBackground from "./main.background";
|
||||||
import NotificationBackground from './notification.background';
|
import NotificationBackground from "./notification.background";
|
||||||
|
|
||||||
export default class TabsBackground {
|
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() {
|
chrome.tabs.onActivated.addListener(async (activeInfo: chrome.tabs.TabActiveInfo) => {
|
||||||
if (!chrome.tabs) {
|
await this.main.refreshBadgeAndMenu();
|
||||||
return;
|
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;
|
||||||
chrome.tabs.onActivated.addListener(async (activeInfo: chrome.tabs.TabActiveInfo) => {
|
await this.notificationBackground.checkNotificationQueue(tab);
|
||||||
await this.main.refreshBadgeAndMenu();
|
await this.main.refreshBadgeAndMenu();
|
||||||
this.main.messagingService.send('tabActivated');
|
this.main.messagingService.send("tabUpdated");
|
||||||
this.main.messagingService.send('tabChanged');
|
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');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,78 +1,94 @@
|
||||||
import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.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 {
|
export default class WebRequestBackground {
|
||||||
private pendingAuthRequests: any[] = [];
|
private pendingAuthRequests: any[] = [];
|
||||||
private webRequest: any;
|
private webRequest: any;
|
||||||
private isFirefox: boolean;
|
private isFirefox: boolean;
|
||||||
|
|
||||||
constructor(platformUtilsService: PlatformUtilsService, private cipherService: CipherService,
|
constructor(
|
||||||
private vaultTimeoutService: VaultTimeoutService) {
|
platformUtilsService: PlatformUtilsService,
|
||||||
this.webRequest = (window as any).chrome.webRequest;
|
private cipherService: CipherService,
|
||||||
this.isFirefox = platformUtilsService.isFirefox();
|
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() {
|
this.webRequest.onAuthRequired.addListener(
|
||||||
if (!this.webRequest || !this.webRequest.onAuthRequired) {
|
async (details: any, callback: any) => {
|
||||||
return;
|
if (!details.url || this.pendingAuthRequests.indexOf(details.requestId) !== -1) {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.webRequest.onAuthRequired.addListener(async (details: any, callback: any) => {
|
this.pendingAuthRequests.push(details.requestId);
|
||||||
if (!details.url || this.pendingAuthRequests.indexOf(details.requestId) !== -1) {
|
|
||||||
if (callback) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
this.webRequest.onCompleted.addListener((details: any) => this.completeAuthRequest(details), {
|
||||||
return new Promise(async (resolve, reject) => {
|
urls: ["http://*/*"],
|
||||||
await this.resolveAuthCredentials(details.url, resolve, reject);
|
});
|
||||||
});
|
this.webRequest.onErrorOccurred.addListener(
|
||||||
} else {
|
(details: any) => this.completeAuthRequest(details),
|
||||||
await this.resolveAuthCredentials(details.url, callback, callback);
|
{
|
||||||
}
|
urls: ["http://*/*"],
|
||||||
}, { urls: ['http://*/*', 'https://*/*'] }, [this.isFirefox ? 'blocking' : 'asyncBlocking']);
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.webRequest.onCompleted.addListener(
|
private async resolveAuthCredentials(domain: string, success: Function, error: Function) {
|
||||||
(details: any) => this.completeAuthRequest(details), { urls: ['http://*/*'] });
|
if (await this.vaultTimeoutService.isLocked()) {
|
||||||
this.webRequest.onErrorOccurred.addListener(
|
error();
|
||||||
(details: any) => this.completeAuthRequest(details), { urls: ['http://*/*'] });
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async resolveAuthCredentials(domain: string, success: Function, error: Function) {
|
try {
|
||||||
if (await this.vaultTimeoutService.isLocked()) {
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(
|
||||||
error();
|
domain,
|
||||||
return;
|
null,
|
||||||
}
|
UriMatchType.Host
|
||||||
|
);
|
||||||
|
if (ciphers == null || ciphers.length !== 1) {
|
||||||
|
error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
success({
|
||||||
const ciphers = await this.cipherService.getAllDecryptedForUrl(domain, null, UriMatchType.Host);
|
authCredentials: {
|
||||||
if (ciphers == null || ciphers.length !== 1) {
|
username: ciphers[0].login.username,
|
||||||
error();
|
password: ciphers[0].login.password,
|
||||||
return;
|
},
|
||||||
}
|
});
|
||||||
|
} catch {
|
||||||
success({
|
error();
|
||||||
authCredentials: {
|
|
||||||
username: ciphers[0].login.username,
|
|
||||||
password: ciphers[0].login.password,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
error();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private completeAuthRequest(details: any) {
|
private completeAuthRequest(details: any) {
|
||||||
const i = this.pendingAuthRequests.indexOf(details.requestId);
|
const i = this.pendingAuthRequests.indexOf(details.requestId);
|
||||||
if (i > -1) {
|
if (i > -1) {
|
||||||
this.pendingAuthRequests.splice(i, 1);
|
this.pendingAuthRequests.splice(i, 1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
import MainBackground from './main.background';
|
import MainBackground from "./main.background";
|
||||||
|
|
||||||
export default class WindowsBackground {
|
export default class WindowsBackground {
|
||||||
private windows: any;
|
private windows: any;
|
||||||
|
|
||||||
constructor(private main: MainBackground) {
|
constructor(private main: MainBackground) {
|
||||||
this.windows = chrome.windows;
|
this.windows = chrome.windows;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (!this.windows) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
this.windows.onFocusChanged.addListener(async (windowId: any) => {
|
||||||
if (!this.windows) {
|
if (windowId === null || windowId < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.windows.onFocusChanged.addListener(async (windowId: any) => {
|
await this.main.refreshBadgeAndMenu();
|
||||||
if (windowId === null || windowId < 0) {
|
this.main.messagingService.send("windowFocused");
|
||||||
return;
|
this.main.messagingService.send("windowChanged");
|
||||||
}
|
});
|
||||||
|
}
|
||||||
await this.main.refreshBadgeAndMenu();
|
|
||||||
this.main.messagingService.send('windowFocused');
|
|
||||||
this.main.messagingService.send('windowChanged');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
export class BrowserApi {
|
||||||
static isWebExtensionsApi: boolean = (typeof browser !== 'undefined');
|
static isWebExtensionsApi: boolean = typeof browser !== "undefined";
|
||||||
static isSafariApi: boolean = navigator.userAgent.indexOf(' Safari/') !== -1 &&
|
static isSafariApi: boolean =
|
||||||
navigator.userAgent.indexOf(' Chrome/') === -1 &&
|
navigator.userAgent.indexOf(" Safari/") !== -1 &&
|
||||||
navigator.userAgent.indexOf(' Chromium/') === -1;
|
navigator.userAgent.indexOf(" Chrome/") === -1 &&
|
||||||
static isChromeApi: boolean = !BrowserApi.isSafariApi && (typeof chrome !== 'undefined');
|
navigator.userAgent.indexOf(" Chromium/") === -1;
|
||||||
static isFirefoxOnAndroid: boolean = navigator.userAgent.indexOf('Firefox/') !== -1 &&
|
static isChromeApi: boolean = !BrowserApi.isSafariApi && typeof chrome !== "undefined";
|
||||||
navigator.userAgent.indexOf('Android') !== -1;
|
static isFirefoxOnAndroid: boolean =
|
||||||
|
navigator.userAgent.indexOf("Firefox/") !== -1 && navigator.userAgent.indexOf("Android") !== -1;
|
||||||
|
|
||||||
static async getTabFromCurrentWindowId(): Promise<chrome.tabs.Tab> | null {
|
static async getTabFromCurrentWindowId(): Promise<chrome.tabs.Tab> | null {
|
||||||
return await BrowserApi.tabsQueryFirst({
|
return await BrowserApi.tabsQueryFirst({
|
||||||
active: true,
|
active: true,
|
||||||
windowId: chrome.windows.WINDOW_ID_CURRENT,
|
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 null;
|
||||||
return await BrowserApi.tabsQueryFirst({
|
}
|
||||||
active: true,
|
|
||||||
currentWindow: true,
|
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 BrowserApi.tabSendMessage(tab, obj);
|
||||||
return await BrowserApi.tabsQuery({
|
}
|
||||||
active: true,
|
|
||||||
});
|
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<void>((resolve) => {
|
||||||
return new Promise(resolve => {
|
chrome.tabs.sendMessage(tab.id, obj, options, () => {
|
||||||
chrome.tabs.query(options, (tabs: any[]) => {
|
if (chrome.runtime.lastError) {
|
||||||
resolve(tabs);
|
// Some error happened
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
|
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 tabToClose = tabs[tabs.length - 1].id;
|
||||||
const obj: any = {
|
chrome.tabs.remove(tabToClose);
|
||||||
command: command,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (data != null) {
|
static async focusSpecifiedTab(tabId: number) {
|
||||||
obj.data = data;
|
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> {
|
static downloadFile(win: Window, blobData: any, blobOptions: any, fileName: string) {
|
||||||
if (!tab || !tab.id) {
|
if (BrowserApi.isSafariApi) {
|
||||||
return;
|
const type = blobOptions != null ? blobOptions.type : null;
|
||||||
}
|
let data: string = null;
|
||||||
|
if (type === "text/plain" && typeof blobData === "string") {
|
||||||
return new Promise<void>(resolve => {
|
data = blobData;
|
||||||
chrome.tabs.sendMessage(tab.id, obj, options, () => {
|
} else {
|
||||||
if (chrome.runtime.lastError) {
|
data = Utils.fromBufferToB64(blobData);
|
||||||
// Some error happened
|
}
|
||||||
}
|
SafariApp.sendMessageToApp(
|
||||||
resolve();
|
"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 {
|
static gaFilter() {
|
||||||
return chrome.extension.getBackgroundPage();
|
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 {
|
static reloadOpenWindows() {
|
||||||
return chrome.runtime.getManifest().version;
|
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> {
|
static requestPermission(permission: any) {
|
||||||
return Promise.resolve(chrome.extension.getViews({ type: 'popup' }).length > 0);
|
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) {
|
static getPlatformInfo(): Promise<browser.runtime.PlatformInfo | chrome.runtime.PlatformInfo> {
|
||||||
chrome.tabs.create({ url: url, active: active });
|
if (BrowserApi.isWebExtensionsApi) {
|
||||||
}
|
return browser.runtime.getPlatformInfo();
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.runtime.getPlatformInfo(resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,26 @@
|
||||||
import { BrowserApi } from './browserApi';
|
import { BrowserApi } from "./browserApi";
|
||||||
|
|
||||||
export class SafariApp {
|
export class SafariApp {
|
||||||
static sendMessageToApp(command: string, data: any = null, resolveNow = false): Promise<any> {
|
static sendMessageToApp(command: string, data: any = null, resolveNow = false): Promise<any> {
|
||||||
if (!BrowserApi.isSafariApi) {
|
if (!BrowserApi.isSafariApi) {
|
||||||
return Promise.resolve(null);
|
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
@-webkit-keyframes bitwardenfill {
|
@-webkit-keyframes bitwardenfill {
|
||||||
0% {
|
0% {
|
||||||
-webkit-transform: scale(1.0, 1.0);
|
-webkit-transform: scale(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
-webkit-transform: scale(1.2, 1.2);
|
-webkit-transform: scale(1.2, 1.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
-webkit-transform: scale(1.0, 1.0);
|
-webkit-transform: scale(1, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@-moz-keyframes bitwardenfill {
|
@-moz-keyframes bitwardenfill {
|
||||||
0% {
|
0% {
|
||||||
transform: scale(1.0, 1.0);
|
transform: scale(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
transform: scale(1.2, 1.2);
|
transform: scale(1.2, 1.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
transform: scale(1.0, 1.0);
|
transform: scale(1, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
span[data-bwautofill].com-bitwarden-browser-animated-fill {
|
span[data-bwautofill].com-bitwarden-browser-animated-fill {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.com-bitwarden-browser-animated-fill {
|
.com-bitwarden-browser-animated-fill {
|
||||||
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;
|
-webkit-animation: bitwardenfill 200ms ease-in-out 0ms 1;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,43 +1,43 @@
|
||||||
document.addEventListener('DOMContentLoaded', event => {
|
document.addEventListener("DOMContentLoaded", (event) => {
|
||||||
let pageHref: string = null;
|
let pageHref: string = null;
|
||||||
let filledThisHref = false;
|
let filledThisHref = false;
|
||||||
let delayFillTimeout: number;
|
let delayFillTimeout: number;
|
||||||
|
|
||||||
const enabledKey = 'enableAutoFillOnPageLoad';
|
const enabledKey = "enableAutoFillOnPageLoad";
|
||||||
chrome.storage.local.get(enabledKey, (obj: any) => {
|
chrome.storage.local.get(enabledKey, (obj: any) => {
|
||||||
if (obj != null && obj[enabledKey] === true) {
|
if (obj != null && obj[enabledKey] === true) {
|
||||||
setInterval(() => doFillIfNeeded(), 500);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,66 +1,66 @@
|
||||||
const inputTags = ['input', 'textarea', 'select'];
|
const inputTags = ["input", "textarea", "select"];
|
||||||
const labelTags = ['label', 'span'];
|
const labelTags = ["label", "span"];
|
||||||
const attributes = ['id', 'name', 'label-aria', 'placeholder'];
|
const attributes = ["id", "name", "label-aria", "placeholder"];
|
||||||
const invalidElement = chrome.i18n.getMessage('copyCustomFieldNameInvalidElement');
|
const invalidElement = chrome.i18n.getMessage("copyCustomFieldNameInvalidElement");
|
||||||
const noUniqueIdentifier = chrome.i18n.getMessage('copyCustomFieldNameNotUnique');
|
const noUniqueIdentifier = chrome.i18n.getMessage("copyCustomFieldNameNotUnique");
|
||||||
|
|
||||||
let clickedEl: HTMLElement = null;
|
let clickedEl: HTMLElement = null;
|
||||||
|
|
||||||
// Find the best attribute to be used as the Name for an element in a custom field.
|
// Find the best attribute to be used as the Name for an element in a custom field.
|
||||||
function getClickedElementIdentifier() {
|
function getClickedElementIdentifier() {
|
||||||
if (clickedEl == null) {
|
if (clickedEl == null) {
|
||||||
return invalidElement;
|
return invalidElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clickedTag = clickedEl.nodeName.toLowerCase();
|
const clickedTag = clickedEl.nodeName.toLowerCase();
|
||||||
let inputEl = null;
|
let inputEl = null;
|
||||||
|
|
||||||
// Try to identify the input element (which may not be the clicked element)
|
// Try to identify the input element (which may not be the clicked element)
|
||||||
if (labelTags.includes(clickedTag)) {
|
if (labelTags.includes(clickedTag)) {
|
||||||
let inputId = null;
|
let inputId = null;
|
||||||
if (clickedTag === 'label') {
|
if (clickedTag === "label") {
|
||||||
inputId = clickedEl.getAttribute('for');
|
inputId = clickedEl.getAttribute("for");
|
||||||
} else {
|
|
||||||
inputId = clickedEl.closest('label')?.getAttribute('for');
|
|
||||||
}
|
|
||||||
|
|
||||||
inputEl = document.getElementById(inputId);
|
|
||||||
} else {
|
} else {
|
||||||
inputEl = clickedEl;
|
inputId = clickedEl.closest("label")?.getAttribute("for");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputEl == null || !inputTags.includes(inputEl.nodeName.toLowerCase())) {
|
inputEl = document.getElementById(inputId);
|
||||||
return invalidElement;
|
} else {
|
||||||
}
|
inputEl = clickedEl;
|
||||||
|
}
|
||||||
|
|
||||||
for (const attr of attributes) {
|
if (inputEl == null || !inputTags.includes(inputEl.nodeName.toLowerCase())) {
|
||||||
const attributeValue = inputEl.getAttribute(attr);
|
return invalidElement;
|
||||||
const selector = '[' + attr + '="' + attributeValue + '"]';
|
}
|
||||||
if (!isNullOrEmpty(attributeValue) && document.querySelectorAll(selector)?.length === 1) {
|
|
||||||
return attributeValue;
|
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) {
|
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.
|
// We only have access to the element that's been clicked when the context menu is first opened.
|
||||||
// Remember it for use later.
|
// Remember it for use later.
|
||||||
document.addEventListener('contextmenu', event => {
|
document.addEventListener("contextmenu", (event) => {
|
||||||
clickedEl = event.target as HTMLElement;
|
clickedEl = event.target as HTMLElement;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Runs when the 'Copy Custom Field Name' context menu item is actually clicked.
|
// Runs when the 'Copy Custom Field Name' context menu item is actually clicked.
|
||||||
chrome.runtime.onMessage.addListener(event => {
|
chrome.runtime.onMessage.addListener((event) => {
|
||||||
if (event.command === 'getClickedElement') {
|
if (event.command === "getClickedElement") {
|
||||||
const identifier = getClickedElementIdentifier();
|
const identifier = getClickedElementIdentifier();
|
||||||
chrome.runtime.sendMessage({
|
chrome.runtime.sendMessage({
|
||||||
command: 'getClickedElementResponse',
|
command: "getClickedElementResponse",
|
||||||
sender: 'contextMenuHandler',
|
sender: "contextMenuHandler",
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,30 +1,37 @@
|
||||||
window.addEventListener('message', event => {
|
window.addEventListener(
|
||||||
if (event.source !== window)
|
"message",
|
||||||
return;
|
(event) => {
|
||||||
|
if (event.source !== window) return;
|
||||||
|
|
||||||
if (event.data.command && (event.data.command === 'authResult')) {
|
if (event.data.command && event.data.command === "authResult") {
|
||||||
chrome.runtime.sendMessage({
|
chrome.runtime.sendMessage({
|
||||||
command: event.data.command,
|
command: event.data.command,
|
||||||
code: event.data.code,
|
code: event.data.code,
|
||||||
state: event.data.state,
|
state: event.data.state,
|
||||||
referrer: event.source.location.hostname,
|
referrer: event.source.location.hostname,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.data.command && (event.data.command === 'webAuthnResult')) {
|
if (event.data.command && event.data.command === "webAuthnResult") {
|
||||||
chrome.runtime.sendMessage({
|
chrome.runtime.sendMessage({
|
||||||
command: event.data.command,
|
command: event.data.command,
|
||||||
data: event.data.data,
|
data: event.data.data,
|
||||||
remember: event.data.remember,
|
remember: event.data.remember,
|
||||||
referrer: event.source.location.hostname,
|
referrer: event.source.location.hostname,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, false);
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
const forwardCommands = ['promptForLogin', 'addToLockedVaultPendingNotifications', 'unlockCompleted'];
|
const forwardCommands = [
|
||||||
|
"promptForLogin",
|
||||||
|
"addToLockedVaultPendingNotifications",
|
||||||
|
"unlockCompleted",
|
||||||
|
];
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(event => {
|
chrome.runtime.onMessage.addListener((event) => {
|
||||||
if (forwardCommands.includes(event.command)) {
|
if (forwardCommands.includes(event.command)) {
|
||||||
chrome.runtime.sendMessage(event);
|
chrome.runtime.sendMessage(event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,50 +1,52 @@
|
||||||
import * as Mousetrap from 'mousetrap';
|
import * as Mousetrap from "mousetrap";
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', event => {
|
document.addEventListener("DOMContentLoaded", (event) => {
|
||||||
const isSafari = (typeof safari !== 'undefined') && navigator.userAgent.indexOf(' Safari/') !== -1 &&
|
const isSafari =
|
||||||
navigator.userAgent.indexOf('Chrome') === -1;
|
typeof safari !== "undefined" &&
|
||||||
const isVivaldi = !isSafari && navigator.userAgent.indexOf(' Vivaldi/') !== -1;
|
navigator.userAgent.indexOf(" Safari/") !== -1 &&
|
||||||
|
navigator.userAgent.indexOf("Chrome") === -1;
|
||||||
|
const isVivaldi = !isSafari && navigator.userAgent.indexOf(" Vivaldi/") !== -1;
|
||||||
|
|
||||||
if (!isSafari && !isVivaldi) {
|
if (!isSafari && !isVivaldi) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSafari && (window as any).__bitwardenFrameId == null) {
|
if (isSafari && (window as any).__bitwardenFrameId == null) {
|
||||||
(window as any).__bitwardenFrameId = Math.floor(Math.random() * Math.floor(99999999));
|
(window as any).__bitwardenFrameId = Math.floor(Math.random() * Math.floor(99999999));
|
||||||
}
|
}
|
||||||
|
|
||||||
Mousetrap.prototype.stopCallback = () => {
|
Mousetrap.prototype.stopCallback = () => {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let autofillCommand = ['mod+shift+l'];
|
let autofillCommand = ["mod+shift+l"];
|
||||||
if (isSafari) {
|
if (isSafari) {
|
||||||
autofillCommand = ['mod+\\', 'mod+8', 'mod+shift+p'];
|
autofillCommand = ["mod+\\", "mod+8", "mod+shift+p"];
|
||||||
}
|
}
|
||||||
Mousetrap.bind(autofillCommand, () => {
|
Mousetrap.bind(autofillCommand, () => {
|
||||||
sendMessage('autofill_login');
|
sendMessage("autofill_login");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isSafari) {
|
||||||
|
Mousetrap.bind("mod+shift+y", () => {
|
||||||
|
sendMessage("open_popup");
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isSafari) {
|
Mousetrap.bind("mod+shift+s", () => {
|
||||||
Mousetrap.bind('mod+shift+y', () => {
|
sendMessage("lock_vault");
|
||||||
sendMessage('open_popup');
|
});
|
||||||
});
|
} else {
|
||||||
|
Mousetrap.bind("mod+shift+9", () => {
|
||||||
|
sendMessage("generate_password");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Mousetrap.bind('mod+shift+s', () => {
|
function sendMessage(shortcut: string) {
|
||||||
sendMessage('lock_vault');
|
const msg: any = {
|
||||||
});
|
command: "keyboardShortcutTriggered",
|
||||||
} else {
|
shortcut: shortcut,
|
||||||
Mousetrap.bind('mod+shift+9', () => {
|
};
|
||||||
sendMessage('generate_password');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMessage(shortcut: string) {
|
chrome.runtime.sendMessage(msg);
|
||||||
const msg: any = {
|
}
|
||||||
command: 'keyboardShortcutTriggered',
|
|
||||||
shortcut: shortcut,
|
|
||||||
};
|
|
||||||
|
|
||||||
chrome.runtime.sendMessage(msg);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,47 +23,25 @@
|
||||||
"content/notificationBar.js",
|
"content/notificationBar.js",
|
||||||
"content/contextMenuHandler.js"
|
"content/contextMenuHandler.js"
|
||||||
],
|
],
|
||||||
"matches": [
|
"matches": ["http://*/*", "https://*/*", "file:///*"],
|
||||||
"http://*/*",
|
|
||||||
"https://*/*",
|
|
||||||
"file:///*"
|
|
||||||
],
|
|
||||||
"run_at": "document_start"
|
"run_at": "document_start"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"all_frames": false,
|
"all_frames": false,
|
||||||
"js": [
|
"js": ["content/shortcuts.js"],
|
||||||
"content/shortcuts.js"
|
"matches": ["http://*/*", "https://*/*", "file:///*"],
|
||||||
],
|
|
||||||
"matches": [
|
|
||||||
"http://*/*",
|
|
||||||
"https://*/*",
|
|
||||||
"file:///*"
|
|
||||||
],
|
|
||||||
"run_at": "document_start"
|
"run_at": "document_start"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"all_frames": false,
|
"all_frames": false,
|
||||||
"js": [
|
"js": ["content/message_handler.js"],
|
||||||
"content/message_handler.js"
|
"matches": ["http://*/*", "https://*/*", "file:///*"],
|
||||||
],
|
|
||||||
"matches": [
|
|
||||||
"http://*/*",
|
|
||||||
"https://*/*",
|
|
||||||
"file:///*"
|
|
||||||
],
|
|
||||||
"run_at": "document_start"
|
"run_at": "document_start"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"all_frames": true,
|
"all_frames": true,
|
||||||
"css": [
|
"css": ["content/autofill.css"],
|
||||||
"content/autofill.css"
|
"matches": ["http://*/*", "https://*/*", "file:///*"],
|
||||||
],
|
|
||||||
"matches": [
|
|
||||||
"http://*/*",
|
|
||||||
"https://*/*",
|
|
||||||
"file:///*"
|
|
||||||
],
|
|
||||||
"run_at": "document_end"
|
"run_at": "document_end"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -92,9 +70,7 @@
|
||||||
"webRequest",
|
"webRequest",
|
||||||
"webRequestBlocking"
|
"webRequestBlocking"
|
||||||
],
|
],
|
||||||
"optional_permissions": [
|
"optional_permissions": ["nativeMessaging"],
|
||||||
"nativeMessaging"
|
|
||||||
],
|
|
||||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
|
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
|
||||||
"commands": {
|
"commands": {
|
||||||
"_execute_browser_action": {
|
"_execute_browser_action": {
|
||||||
|
@ -144,4 +120,4 @@
|
||||||
"default_panel": "popup/index.html?uilocation=sidebar",
|
"default_panel": "popup/index.html?uilocation=sidebar",
|
||||||
"default_icon": "images/icon19.png"
|
"default_icon": "images/icon19.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
export default class AutofillField {
|
export default class AutofillField {
|
||||||
opid: string;
|
opid: string;
|
||||||
elementNumber: number;
|
elementNumber: number;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
viewable: boolean;
|
viewable: boolean;
|
||||||
htmlID: string;
|
htmlID: string;
|
||||||
htmlName: string;
|
htmlName: string;
|
||||||
htmlClass: string;
|
htmlClass: string;
|
||||||
'label-left': string;
|
"label-left": string;
|
||||||
'label-right': string;
|
"label-right": string;
|
||||||
'label-top': string;
|
"label-top": string;
|
||||||
'label-tag': string;
|
"label-tag": string;
|
||||||
'label-aria': string;
|
"label-aria": string;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
type: string;
|
type: string;
|
||||||
value: string;
|
value: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
readonly: boolean;
|
readonly: boolean;
|
||||||
onePasswordFieldType: string;
|
onePasswordFieldType: string;
|
||||||
form: string;
|
form: string;
|
||||||
autoCompleteType: string;
|
autoCompleteType: string;
|
||||||
selectInfo: any;
|
selectInfo: any;
|
||||||
maxLength: number;
|
maxLength: number;
|
||||||
tagName: string;
|
tagName: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export default class AutofillForm {
|
export default class AutofillForm {
|
||||||
opid: string;
|
opid: string;
|
||||||
htmlName: string;
|
htmlName: string;
|
||||||
htmlID: string;
|
htmlID: string;
|
||||||
htmlAction: string;
|
htmlAction: string;
|
||||||
htmlMethod: string;
|
htmlMethod: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import AutofillField from './autofillField';
|
import AutofillField from "./autofillField";
|
||||||
import AutofillForm from './autofillForm';
|
import AutofillForm from "./autofillForm";
|
||||||
|
|
||||||
export default class AutofillPageDetails {
|
export default class AutofillPageDetails {
|
||||||
documentUUID: string;
|
documentUUID: string;
|
||||||
title: string;
|
title: string;
|
||||||
url: string;
|
url: string;
|
||||||
documentUrl: string;
|
documentUrl: string;
|
||||||
tabUrl: string;
|
tabUrl: string;
|
||||||
forms: { [id: string]: AutofillForm; };
|
forms: { [id: string]: AutofillForm };
|
||||||
fields: AutofillField[];
|
fields: AutofillField[];
|
||||||
collectedTimestamp: number;
|
collectedTimestamp: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
export default class AutofillScript {
|
export default class AutofillScript {
|
||||||
script: string[][] = [];
|
script: string[][] = [];
|
||||||
documentUUID: any = {};
|
documentUUID: any = {};
|
||||||
properties: any = {};
|
properties: any = {};
|
||||||
options: any = {};
|
options: any = {};
|
||||||
metadata: any = {};
|
metadata: any = {};
|
||||||
autosubmit: any = null;
|
autosubmit: any = null;
|
||||||
|
|
||||||
constructor(documentUUID: string) {
|
constructor(documentUUID: string) {
|
||||||
this.documentUUID = documentUUID;
|
this.documentUUID = documentUUID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,39 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
<head>
|
||||||
<head>
|
|
||||||
<title>Bitwarden</title>
|
<title>Bitwarden</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="outer-wrapper">
|
<div class="outer-wrapper">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<a href="https://vault.bitwarden.com" target="_blank" id="logo-link">
|
<a href="https://vault.bitwarden.com" target="_blank" id="logo-link">
|
||||||
<img id="logo" alt="Bitwarden" />
|
<img id="logo" alt="Bitwarden" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="content"></div>
|
<div id="content"></div>
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="neutral" id="close-button">
|
<button type="button" class="neutral" id="close-button">
|
||||||
<img id="close" alt="Close" />
|
<img id="close" alt="Close" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="templates" style="display: none;">
|
<div id="templates" style="display: none">
|
||||||
<div class="inner-wrapper" id="template-add">
|
<div class="inner-wrapper" id="template-add">
|
||||||
<div class="add-text"></div>
|
<div class="add-text"></div>
|
||||||
<div class="add-buttons">
|
<div class="add-buttons">
|
||||||
<button type="button" class="never-save link"></button>
|
<button type="button" class="never-save link"></button>
|
||||||
<select class="select-folder" isVaultLocked="false"></select>
|
<select class="select-folder" isVaultLocked="false"></select>
|
||||||
<button type="button" class="add-save"></button>
|
<button type="button" class="add-save"></button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="inner-wrapper" id="template-change">
|
</div>
|
||||||
<div class="change-text"></div>
|
<div class="inner-wrapper" id="template-change">
|
||||||
<div class="change-buttons">
|
<div class="change-text"></div>
|
||||||
<button type="button" class="change-save"></button>
|
<div class="change-buttons">
|
||||||
</div>
|
<button type="button" class="change-save"></button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,159 +1,160 @@
|
||||||
require('./bar.scss');
|
require("./bar.scss");
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
var i18n = {};
|
var i18n = {};
|
||||||
var lang = window.navigator.language;
|
var lang = window.navigator.language;
|
||||||
|
|
||||||
i18n.appName = chrome.i18n.getMessage('appName');
|
i18n.appName = chrome.i18n.getMessage("appName");
|
||||||
i18n.close = chrome.i18n.getMessage('close');
|
i18n.close = chrome.i18n.getMessage("close");
|
||||||
i18n.never = chrome.i18n.getMessage('never');
|
i18n.never = chrome.i18n.getMessage("never");
|
||||||
i18n.folder = chrome.i18n.getMessage('folder');
|
i18n.folder = chrome.i18n.getMessage("folder");
|
||||||
i18n.notificationAddSave = chrome.i18n.getMessage('notificationAddSave');
|
i18n.notificationAddSave = chrome.i18n.getMessage("notificationAddSave");
|
||||||
i18n.notificationAddDesc = chrome.i18n.getMessage('notificationAddDesc');
|
i18n.notificationAddDesc = chrome.i18n.getMessage("notificationAddDesc");
|
||||||
i18n.notificationChangeSave = chrome.i18n.getMessage('notificationChangeSave');
|
i18n.notificationChangeSave = chrome.i18n.getMessage("notificationChangeSave");
|
||||||
i18n.notificationChangeDesc = chrome.i18n.getMessage('notificationChangeDesc');
|
i18n.notificationChangeDesc = chrome.i18n.getMessage("notificationChangeDesc");
|
||||||
lang = chrome.i18n.getUILanguage();
|
lang = chrome.i18n.getUILanguage();
|
||||||
|
|
||||||
// delay 50ms so that we get proper body dimensions
|
// delay 50ms so that we get proper body dimensions
|
||||||
setTimeout(load, 50);
|
setTimeout(load, 50);
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
const isVaultLocked = getQueryVariable('isVaultLocked') == 'true';
|
const isVaultLocked = getQueryVariable("isVaultLocked") == "true";
|
||||||
document.getElementById('logo').src = isVaultLocked
|
document.getElementById("logo").src = isVaultLocked
|
||||||
? chrome.runtime.getURL('images/icon38_locked.png')
|
? chrome.runtime.getURL("images/icon38_locked.png")
|
||||||
: chrome.runtime.getURL('images/icon38.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');
|
var neverButton = document.querySelector("#template-add .never-save");
|
||||||
neverButton.textContent = i18n.never;
|
neverButton.textContent = i18n.never;
|
||||||
|
|
||||||
var selectFolder = document.querySelector('#template-add .select-folder');
|
var selectFolder = document.querySelector("#template-add .select-folder");
|
||||||
selectFolder.setAttribute('aria-label', i18n.folder);
|
selectFolder.setAttribute("aria-label", i18n.folder);
|
||||||
selectFolder.setAttribute('isVaultLocked', isVaultLocked.toString());
|
selectFolder.setAttribute("isVaultLocked", isVaultLocked.toString());
|
||||||
|
|
||||||
var addButton = document.querySelector('#template-add .add-save');
|
var addButton = document.querySelector("#template-add .add-save");
|
||||||
addButton.textContent = i18n.notificationAddSave;
|
addButton.textContent = i18n.notificationAddSave;
|
||||||
|
|
||||||
var changeButton = document.querySelector('#template-change .change-save');
|
var changeButton = document.querySelector("#template-change .change-save");
|
||||||
changeButton.textContent = i18n.notificationChangeSave;
|
changeButton.textContent = i18n.notificationChangeSave;
|
||||||
|
|
||||||
var closeIcon = document.getElementById('close');
|
var closeIcon = document.getElementById("close");
|
||||||
closeIcon.src = chrome.runtime.getURL('images/close.png');
|
closeIcon.src = chrome.runtime.getURL("images/close.png");
|
||||||
closeIcon.alt = i18n.close;
|
closeIcon.alt = i18n.close;
|
||||||
|
|
||||||
var closeButton = document.getElementById('close-button')
|
var closeButton = document.getElementById("close-button");
|
||||||
closeButton.title = i18n.close;
|
closeButton.title = i18n.close;
|
||||||
closeButton.setAttribute('aria-label', i18n.close);
|
closeButton.setAttribute("aria-label", i18n.close);
|
||||||
|
|
||||||
document.querySelector('#template-add .add-text').textContent = i18n.notificationAddDesc;
|
document.querySelector("#template-add .add-text").textContent = i18n.notificationAddDesc;
|
||||||
document.querySelector('#template-change .change-text').textContent = i18n.notificationChangeDesc;
|
document.querySelector("#template-change .change-text").textContent =
|
||||||
|
i18n.notificationChangeDesc;
|
||||||
|
|
||||||
if (getQueryVariable('add')) {
|
if (getQueryVariable("add")) {
|
||||||
setContent(document.getElementById('template-add'));
|
setContent(document.getElementById("template-add"));
|
||||||
|
|
||||||
var addButton = document.querySelector('#template-add-clone .add-save'),
|
var addButton = document.querySelector("#template-add-clone .add-save"),
|
||||||
neverButton = document.querySelector('#template-add-clone .never-save');
|
neverButton = document.querySelector("#template-add-clone .never-save");
|
||||||
|
|
||||||
addButton.addEventListener('click', (e) => {
|
addButton.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const folderId = document.querySelector('#template-add-clone .select-folder').value;
|
const folderId = document.querySelector("#template-add-clone .select-folder").value;
|
||||||
|
|
||||||
const bgAddSaveMessage = {
|
const bgAddSaveMessage = {
|
||||||
command: 'bgAddSave',
|
command: "bgAddSave",
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
};
|
};
|
||||||
sendPlatformMessage(bgAddSaveMessage);
|
sendPlatformMessage(bgAddSaveMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
neverButton.addEventListener('click', (e) => {
|
neverButton.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
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() {
|
|
||||||
sendPlatformMessage({
|
sendPlatformMessage({
|
||||||
command: 'bgAdjustNotificationBar',
|
command: "bgNeverSave",
|
||||||
data: {
|
|
||||||
height: document.querySelector('body').scrollHeight
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,94 +1,94 @@
|
||||||
body {
|
body {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.outer-wrapper {
|
.outer-wrapper {
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
border-bottom: 2px solid #175ddc;
|
border-bottom: 2px solid #175ddc;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 24px auto 55px;
|
grid-template-columns: 24px auto 55px;
|
||||||
grid-column-gap: 10px;
|
grid-column-gap: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
min-height: 42px;
|
min-height: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner-wrapper {
|
.inner-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto max-content;
|
grid-template-columns: auto max-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.outer-wrapper > *, .inner-wrapper > * {
|
.outer-wrapper > *,
|
||||||
align-self: center;
|
.inner-wrapper > * {
|
||||||
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border: 0;
|
border: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#logo {
|
#logo {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#close {
|
#close {
|
||||||
width: 15px;
|
width: 15px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
display: block;
|
display: block;
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:not(.link),
|
button:not(.link),
|
||||||
button:not(.neutral) {
|
button:not(.neutral) {
|
||||||
background-color: #175DDC;
|
background-color: #175ddc;
|
||||||
padding: 5px 15px;
|
padding: 5px 15px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #1751bd;
|
background-color: #1751bd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button.link,
|
button.link,
|
||||||
button.neutral {
|
button.neutral {
|
||||||
background: none;
|
background: none;
|
||||||
padding: 5px 15px;
|
padding: 5px 15px;
|
||||||
color: #175DDC;
|
color: #175ddc;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: none;
|
background: none;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-folder[isVaultLocked="true"] {
|
.select-folder[isVaultLocked="true"] {
|
||||||
display: none
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.select-folder {
|
.select-folder {
|
||||||
display: none
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (print) {
|
@media (print) {
|
||||||
body {
|
body {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,67 +1,104 @@
|
||||||
<form #form (ngSubmit)="submit()">
|
<form #form (ngSubmit)="submit()">
|
||||||
<header>
|
<header>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<a routerLink="/home">{{'close' | i18n}}</a>
|
<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>
|
</div>
|
||||||
<h1 class="center">
|
</div>
|
||||||
<span class="title">{{'appName' | i18n}}</span>
|
<div class="box-footer">
|
||||||
</h1>
|
{{ "selfHostedEnvironmentFooter" | i18n }}
|
||||||
<div class="right">
|
</div>
|
||||||
<button type="submit" appBlurClick [disabled]="form.loading">
|
</div>
|
||||||
<span [hidden]="form.loading">{{'save' | i18n}}</span>
|
<div class="box">
|
||||||
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
<h2 class="box-header">
|
||||||
</button>
|
{{ "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>
|
||||||
</header>
|
<div class="box-content-row" appBoxRow>
|
||||||
<content>
|
<label for="apiUrl">{{ "apiUrl" | i18n }}</label>
|
||||||
<div class="box">
|
<input
|
||||||
<h2 class="box-header">
|
id="apiUrl"
|
||||||
{{'selfHostedEnvironment' | i18n}}
|
type="text"
|
||||||
</h2>
|
name="ApiUrl"
|
||||||
<div class="box-content">
|
[(ngModel)]="apiUrl"
|
||||||
<div class="box-content-row" appBoxRow>
|
inputmode="url"
|
||||||
<label for="baseUrl">{{'baseUrl' | i18n}}</label>
|
appInputVerbatim
|
||||||
<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>
|
</div>
|
||||||
<div class="box">
|
<div class="box-content-row" appBoxRow>
|
||||||
<h2 class="box-header">
|
<label for="identityUrl">{{ "identityUrl" | i18n }}</label>
|
||||||
{{'customEnvironment' | i18n}}
|
<input
|
||||||
</h2>
|
id="identityUrl"
|
||||||
<div class="box-content" [hidden]="!showCustom">
|
type="text"
|
||||||
<div class="box-content-row" appBoxRow>
|
name="IdentityUrl"
|
||||||
<label for="webVaultUrl">{{'webVaultUrl' | i18n}}</label>
|
[(ngModel)]="identityUrl"
|
||||||
<input id="webVaultUrl" type="text" name="WebVaultUrl" [(ngModel)]="webVaultUrl" inputmode="url"
|
inputmode="url"
|
||||||
appInputVerbatim>
|
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>
|
</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>
|
</form>
|
||||||
|
|
|
@ -1,25 +1,29 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import { Router } from '@angular/router';
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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({
|
@Component({
|
||||||
selector: 'app-environment',
|
selector: "app-environment",
|
||||||
templateUrl: 'environment.component.html',
|
templateUrl: "environment.component.html",
|
||||||
})
|
})
|
||||||
export class EnvironmentComponent extends BaseEnvironmentComponent {
|
export class EnvironmentComponent extends BaseEnvironmentComponent {
|
||||||
constructor(platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
constructor(
|
||||||
i18nService: I18nService, private router: Router) {
|
platformUtilsService: PlatformUtilsService,
|
||||||
super(platformUtilsService, environmentService, i18nService);
|
environmentService: EnvironmentService,
|
||||||
this.showCustom = true;
|
i18nService: I18nService,
|
||||||
}
|
private router: Router
|
||||||
|
) {
|
||||||
|
super(platformUtilsService, environmentService, i18nService);
|
||||||
|
this.showCustom = true;
|
||||||
|
}
|
||||||
|
|
||||||
saved() {
|
saved() {
|
||||||
super.saved();
|
super.saved();
|
||||||
this.router.navigate(['']);
|
this.router.navigate([""]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,38 @@
|
||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
<header>
|
<header>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<a routerLink="/login">{{'cancel' | i18n}}</a>
|
<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>
|
</div>
|
||||||
<h1 class="center">
|
</div>
|
||||||
<span class="title">{{'passwordHint' | i18n}}</span>
|
<div class="box-footer">
|
||||||
</h1>
|
{{ "enterEmailToGetHint" | i18n }}
|
||||||
<div class="right">
|
</div>
|
||||||
<button type="submit" appBlurClick [disabled]="form.loading">
|
</div>
|
||||||
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
|
</content>
|
||||||
<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>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import { Router } from '@angular/router';
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.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({
|
@Component({
|
||||||
selector: 'app-hint',
|
selector: "app-hint",
|
||||||
templateUrl: 'hint.component.html',
|
templateUrl: "hint.component.html",
|
||||||
})
|
})
|
||||||
export class HintComponent extends BaseHintComponent {
|
export class HintComponent extends BaseHintComponent {
|
||||||
constructor(router: Router, platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
i18nService: I18nService, apiService: ApiService, logService: LogService) {
|
router: Router,
|
||||||
super(router, i18nService, apiService, platformUtilsService, logService);
|
platformUtilsService: PlatformUtilsService,
|
||||||
}
|
i18nService: I18nService,
|
||||||
|
apiService: ApiService,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(router, i18nService, apiService, platformUtilsService, logService);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
<div class="center-content">
|
<div class="center-content">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="logo-image"></div>
|
<div class="logo-image"></div>
|
||||||
<p class="lead text-center">{{'loginOrCreateNewAccount' | i18n}}</p>
|
<p class="lead text-center">{{ "loginOrCreateNewAccount" | i18n }}</p>
|
||||||
<a class="btn primary block" routerLink="/login"><b>{{'login' | i18n}}</b></a>
|
<a class="btn primary block" routerLink="/login"
|
||||||
<button type="button" (click)="launchSsoBrowser()" class="btn block">
|
><b>{{ "login" | i18n }}</b></a
|
||||||
<i class="fa fa-bank" aria-hidden="true"></i> {{'enterpriseSingleSignOn' | i18n}}
|
>
|
||||||
</button>
|
<button type="button" (click)="launchSsoBrowser()" class="btn block">
|
||||||
<a class="btn block" routerLink="/register">{{'createAccount' | i18n}}</a>
|
<i class="fa fa-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
|
||||||
</div>
|
</button>
|
||||||
|
<a class="btn block" routerLink="/register">{{ "createAccount" | i18n }}</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a routerLink="/environment" class="settings-icon">
|
<a routerLink="/environment" class="settings-icon">
|
||||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i><span> {{'settings' | i18n}}</span>
|
<i class="fa fa-cog fa-lg" aria-hidden="true"></i><span> {{ "settings" | i18n }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -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 { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
|
|
||||||
import { Utils } from 'jslib-common/misc/utils';
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: "app-home",
|
||||||
templateUrl: 'home.component.html',
|
templateUrl: "home.component.html",
|
||||||
})
|
})
|
||||||
export class HomeComponent {
|
export class HomeComponent {
|
||||||
constructor(protected platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
private passwordGenerationService: PasswordGenerationService, private storageService: StorageService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
private cryptoFunctionService: CryptoFunctionService, private environmentService: EnvironmentService) { }
|
private passwordGenerationService: PasswordGenerationService,
|
||||||
|
private storageService: StorageService,
|
||||||
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
|
private environmentService: EnvironmentService
|
||||||
|
) {}
|
||||||
|
|
||||||
async launchSsoBrowser() {
|
async launchSsoBrowser() {
|
||||||
// Generate necessary sso params
|
// Generate necessary sso params
|
||||||
const passwordOptions: any = {
|
const passwordOptions: any = {
|
||||||
type: 'password',
|
type: "password",
|
||||||
length: 64,
|
length: 64,
|
||||||
uppercase: true,
|
uppercase: true,
|
||||||
lowercase: true,
|
lowercase: true,
|
||||||
numbers: true,
|
numbers: true,
|
||||||
special: false,
|
special: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = (await this.passwordGenerationService.generatePassword(passwordOptions)) + ':clientId=browser';
|
const state =
|
||||||
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
|
(await this.passwordGenerationService.generatePassword(passwordOptions)) +
|
||||||
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256');
|
":clientId=browser";
|
||||||
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
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.ssoCodeVerifierKey, codeVerifier);
|
||||||
await this.storageService.save(ConstantsService.ssoStateKey, state);
|
await this.storageService.save(ConstantsService.ssoStateKey, state);
|
||||||
|
|
||||||
let url = this.environmentService.getWebVaultUrl();
|
let url = this.environmentService.getWebVaultUrl();
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
url = 'https://vault.bitwarden.com';
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const redirectUri = url + "/sso-connector.html";
|
||||||
|
|
||||||
|
// Launch browser
|
||||||
|
this.platformUtilsService.launchUri(
|
||||||
|
url +
|
||||||
|
"/#/sso?clientId=browser" +
|
||||||
|
"&redirectUri=" +
|
||||||
|
encodeURIComponent(redirectUri) +
|
||||||
|
"&state=" +
|
||||||
|
state +
|
||||||
|
"&codeChallenge=" +
|
||||||
|
codeChallenge
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +1,74 @@
|
||||||
<form (ngSubmit)="submit()">
|
<form (ngSubmit)="submit()">
|
||||||
<header>
|
<header>
|
||||||
<div class="left"></div>
|
<div class="left"></div>
|
||||||
<h1 class="center">
|
<h1 class="center">
|
||||||
<span class="title">{{'verifyIdentity' | i18n}}</span>
|
<span class="title">{{ "verifyIdentity" | i18n }}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<button type="submit" appBlurClick *ngIf="!hideInput">{{'unlock' | i18n}}</button>
|
<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>
|
</div>
|
||||||
</header>
|
</div>
|
||||||
<content>
|
<div class="box-footer">
|
||||||
<div class="box">
|
<p>{{ "yourVaultIsLocked" | i18n }}</p>
|
||||||
<div class="box-content">
|
{{ "loggedInAsOn" | i18n: email:webVaultHostname }}
|
||||||
<div class="box-content-row box-content-row-flex" appBoxRow *ngIf="!hideInput">
|
</div>
|
||||||
<div class="row-main" *ngIf="pinLock">
|
</div>
|
||||||
<label for="pin">{{'pin' | i18n}}</label>
|
<div class="box" *ngIf="biometricLock">
|
||||||
<input id="pin" type="{{showPassword ? 'text' : 'password'}}" name="PIN" class="monospaced"
|
<div class="box-footer">
|
||||||
[(ngModel)]="pin" required appInputVerbatim>
|
<button type="button" class="btn primary block" (click)="unlockBiometric()" appStopClick>
|
||||||
</div>
|
{{ "unlockWithBiometrics" | i18n }}
|
||||||
<div class="row-main" *ngIf="!pinLock">
|
</button>
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
</div>
|
||||||
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
|
</div>
|
||||||
class="monospaced" [(ngModel)]="masterPassword" required appInputVerbatim>
|
<p class="text-center">
|
||||||
</div>
|
<button type="button" appStopClick (click)="logOut()">{{ "logOut" | i18n }}</button>
|
||||||
<div class="action-buttons">
|
</p>
|
||||||
<button type="button" class="row-btn" appStopClick appBlurClick
|
</content>
|
||||||
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>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,87 +1,107 @@
|
||||||
import {
|
import { Component, NgZone } from "@angular/core";
|
||||||
Component,
|
import { Router } from "@angular/router";
|
||||||
NgZone,
|
import Swal from "sweetalert2";
|
||||||
} 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 { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from "jslib-common/abstractions/user.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.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({
|
@Component({
|
||||||
selector: 'app-lock',
|
selector: "app-lock",
|
||||||
templateUrl: 'lock.component.html',
|
templateUrl: "lock.component.html",
|
||||||
})
|
})
|
||||||
export class LockComponent extends BaseLockComponent {
|
export class LockComponent extends BaseLockComponent {
|
||||||
private isInitialLockScreen: boolean;
|
private isInitialLockScreen: boolean;
|
||||||
|
|
||||||
constructor(router: Router, i18nService: I18nService,
|
constructor(
|
||||||
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
|
router: Router,
|
||||||
userService: UserService, cryptoService: CryptoService,
|
i18nService: I18nService,
|
||||||
storageService: StorageService, vaultTimeoutService: VaultTimeoutService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
environmentService: EnvironmentService, stateService: StateService,
|
messagingService: MessagingService,
|
||||||
apiService: ApiService, logService: LogService, keyConnectorService: KeyConnectorService,
|
userService: UserService,
|
||||||
ngZone: NgZone) {
|
cryptoService: CryptoService,
|
||||||
super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService,
|
storageService: StorageService,
|
||||||
storageService, vaultTimeoutService, environmentService, stateService, apiService, logService,
|
vaultTimeoutService: VaultTimeoutService,
|
||||||
keyConnectorService, ngZone);
|
environmentService: EnvironmentService,
|
||||||
this.successRoute = '/tabs/current';
|
stateService: StateService,
|
||||||
this.isInitialLockScreen = (window as any).previousPopupUrl == null;
|
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() {
|
async ngOnInit() {
|
||||||
await super.ngOnInit();
|
await super.ngOnInit();
|
||||||
const disableAutoBiometricsPrompt = await this.storageService.get<boolean>(
|
const disableAutoBiometricsPrompt =
|
||||||
ConstantsService.disableAutoBiometricsPromptKey) ?? true;
|
(await this.storageService.get<boolean>(ConstantsService.disableAutoBiometricsPromptKey)) ??
|
||||||
|
true;
|
||||||
|
|
||||||
window.setTimeout(async () => {
|
window.setTimeout(async () => {
|
||||||
document.getElementById(this.pinLock ? 'pin' : 'masterPassword').focus();
|
document.getElementById(this.pinLock ? "pin" : "masterPassword").focus();
|
||||||
if (this.biometricLock && !disableAutoBiometricsPrompt && this.isInitialLockScreen) {
|
if (this.biometricLock && !disableAutoBiometricsPrompt && this.isInitialLockScreen) {
|
||||||
if (await this.vaultTimeoutService.isLocked()) {
|
if (await this.vaultTimeoutService.isLocked()) {
|
||||||
await this.unlockBiometric();
|
await this.unlockBiometric();
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
async unlockBiometric(): Promise<boolean> {
|
|
||||||
if (!this.biometricLock) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
const div = document.createElement('div');
|
async unlockBiometric(): Promise<boolean> {
|
||||||
div.innerHTML = `<div class="swal2-text">${this.i18nService.t('awaitDesktop')}</div>`;
|
if (!this.biometricLock) {
|
||||||
|
return;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +1,71 @@
|
||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
<header>
|
<header>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<a routerLink="/home">{{'cancel' | i18n}}</a>
|
<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>
|
</div>
|
||||||
<h1 class="center">
|
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||||
<span class="title">{{'appName' | i18n}}</span>
|
<div class="row-main">
|
||||||
</h1>
|
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||||
<div class="right">
|
<input
|
||||||
<button type="submit" appBlurClick [disabled]="form.loading">
|
id="masterPassword"
|
||||||
<span [hidden]="form.loading">{{'login' | i18n}}</span>
|
type="{{ showPassword ? 'text' : 'password' }}"
|
||||||
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
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>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
<div class="box-content-row" [hidden]="!showCaptcha()">
|
||||||
<content>
|
<iframe id="hcaptcha_iframe" height="80"></iframe>
|
||||||
<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>
|
</div>
|
||||||
<p class="text-center">
|
</div>
|
||||||
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
|
</div>
|
||||||
</p>
|
<p class="text-center">
|
||||||
</content>
|
<a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
|
||||||
|
</p>
|
||||||
|
</content>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,42 +1,58 @@
|
||||||
import {
|
import { Component, NgZone } from "@angular/core";
|
||||||
Component,
|
import { Router } from "@angular/router";
|
||||||
NgZone,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.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({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: "app-login",
|
||||||
templateUrl: 'login.component.html',
|
templateUrl: "login.component.html",
|
||||||
})
|
})
|
||||||
export class LoginComponent extends BaseLoginComponent {
|
export class LoginComponent extends BaseLoginComponent {
|
||||||
constructor(authService: AuthService, router: Router,
|
constructor(
|
||||||
protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService,
|
authService: AuthService,
|
||||||
protected stateService: StateService, protected environmentService: EnvironmentService,
|
router: Router,
|
||||||
protected passwordGenerationService: PasswordGenerationService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
protected cryptoFunctionService: CryptoFunctionService, storageService: StorageService,
|
protected i18nService: I18nService,
|
||||||
syncService: SyncService, logService: LogService, ngZone: NgZone) {
|
protected stateService: StateService,
|
||||||
super(authService, router, platformUtilsService, i18nService, stateService, environmentService,
|
protected environmentService: EnvironmentService,
|
||||||
passwordGenerationService, cryptoFunctionService, storageService, logService, ngZone);
|
protected passwordGenerationService: PasswordGenerationService,
|
||||||
super.onSuccessfulLogin = async () => {
|
protected cryptoFunctionService: CryptoFunctionService,
|
||||||
await syncService.fullSync(true);
|
storageService: StorageService,
|
||||||
};
|
syncService: SyncService,
|
||||||
super.successRoute = '/tabs/vault';
|
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() {
|
settings() {
|
||||||
this.router.navigate(['environment']);
|
this.router.navigate(["environment"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,100 +1,160 @@
|
||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
<header>
|
<header>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<a routerLink="/home">{{'cancel' | i18n}}</a>
|
<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>
|
</div>
|
||||||
<h1 class="center">
|
<div class="box-content-row" appBoxRow>
|
||||||
<span class="title">{{'createAccount' | i18n}}</span>
|
<div class="box-content-row-flex">
|
||||||
</h1>
|
<div class="row-main">
|
||||||
<div class="right">
|
<label for="masterPassword">
|
||||||
<button type="submit" appBlurClick [disabled]="form.loading">
|
{{ "masterPass" | i18n }}
|
||||||
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
|
<strong
|
||||||
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
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>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
<div class="box-content-row" appBoxRow>
|
||||||
<content>
|
<label for="hint">{{ "masterPassHint" | i18n }}</label>
|
||||||
<div class="box">
|
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
|
||||||
<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>
|
</div>
|
||||||
<div class="box">
|
</div>
|
||||||
<div class="box-content">
|
<div class="box-footer">
|
||||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
{{ "masterPassHintDesc" | i18n }}
|
||||||
<div class="row-main">
|
</div>
|
||||||
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
|
</div>
|
||||||
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
|
<div [hidden]="!showCaptcha()"><iframe id="hcaptcha_iframe" height="80"></iframe></div>
|
||||||
name="MasterPasswordRetype" class="monospaced" [(ngModel)]="confirmMasterPassword" required
|
<div class="box last" *ngIf="showTerms">
|
||||||
appInputVerbatim>
|
<div class="box-content">
|
||||||
</div>
|
<div
|
||||||
<div class="action-buttons">
|
class="box-content-row box-content-row-checkbox box-content-row-checkbox-left box-content-row-word-break"
|
||||||
<button type="button" class="row-btn" appStopClick appBlurClick
|
appBoxRow
|
||||||
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword(true)" [attr.aria-pressed]="showPassword">
|
>
|
||||||
<i class="fa fa-lg" aria-hidden="true"
|
<input
|
||||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
type="checkbox"
|
||||||
</button>
|
id="acceptPolicies"
|
||||||
</div>
|
[(ngModel)]="acceptPolicies"
|
||||||
</div>
|
name="AcceptPolicies"
|
||||||
<div class="box-content-row" appBoxRow>
|
/>
|
||||||
<label for="hint">{{'masterPassHint' | i18n}}</label>
|
<label for="acceptPolicies">
|
||||||
<input id="hint" type="text" name="Hint" [(ngModel)]="hint">
|
{{ "acceptPolicies" | i18n }}<br />
|
||||||
</div>
|
<a href="https://bitwarden.com/terms/" target="_blank" rel="noopener">{{
|
||||||
</div>
|
"termsOfService" | i18n
|
||||||
<div class="box-footer">
|
}}</a
|
||||||
{{'masterPassHintDesc' | i18n}}
|
>,
|
||||||
</div>
|
<a href="https://bitwarden.com/privacy/" target="_blank" rel="noopener">{{
|
||||||
|
"privacyPolicy" | i18n
|
||||||
|
}}</a>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="!showCaptcha()"><iframe id="hcaptcha_iframe" height="80"></iframe></div>
|
</div>
|
||||||
<div class="box last" *ngIf="showTerms">
|
</div>
|
||||||
<div class="box-content">
|
</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>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,29 +1,46 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import { Router } from '@angular/router';
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
|
|
||||||
import { RegisterComponent as BaseRegisterComponent } from 'jslib-angular/components/register.component';
|
import { RegisterComponent as BaseRegisterComponent } from "jslib-angular/components/register.component";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-register',
|
selector: "app-register",
|
||||||
templateUrl: 'register.component.html',
|
templateUrl: "register.component.html",
|
||||||
})
|
})
|
||||||
export class RegisterComponent extends BaseRegisterComponent {
|
export class RegisterComponent extends BaseRegisterComponent {
|
||||||
constructor(authService: AuthService, router: Router,
|
constructor(
|
||||||
i18nService: I18nService, cryptoService: CryptoService,
|
authService: AuthService,
|
||||||
apiService: ApiService, stateService: StateService, platformUtilsService: PlatformUtilsService,
|
router: Router,
|
||||||
passwordGenerationService: PasswordGenerationService, environmentService: EnvironmentService,
|
i18nService: I18nService,
|
||||||
logService: LogService) {
|
cryptoService: CryptoService,
|
||||||
super(authService, router, i18nService, cryptoService, apiService, stateService, platformUtilsService,
|
apiService: ApiService,
|
||||||
passwordGenerationService, environmentService, logService);
|
stateService: StateService,
|
||||||
}
|
platformUtilsService: PlatformUtilsService,
|
||||||
|
passwordGenerationService: PasswordGenerationService,
|
||||||
|
environmentService: EnvironmentService,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
authService,
|
||||||
|
router,
|
||||||
|
i18nService,
|
||||||
|
cryptoService,
|
||||||
|
apiService,
|
||||||
|
stateService,
|
||||||
|
platformUtilsService,
|
||||||
|
passwordGenerationService,
|
||||||
|
environmentService,
|
||||||
|
logService
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,49 @@
|
||||||
<header>
|
<header>
|
||||||
<div class="left"></div>
|
<div class="left"></div>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<span class="title">{{'removeMasterPassword' | i18n}}</span>
|
<span class="title">{{ "removeMasterPassword" | i18n }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="right"></div>
|
<div class="right"></div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<content>
|
<content>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="box-content-row" appBoxRow>
|
<div class="box-content-row" appBoxRow>
|
||||||
<p>{{'convertOrganizationEncryptionDesc' | i18n : organization.name}}</p>
|
<p>{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-content-row">
|
<div class="box-content-row">
|
||||||
<button type="button" class="btn block primary" (click)="convert()" [disabled]="actionPromise">
|
<button
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="continuing"></i>
|
type="button"
|
||||||
{{'removeMasterPassword' | i18n}}
|
class="btn block primary"
|
||||||
</button>
|
(click)="convert()"
|
||||||
</div>
|
[disabled]="actionPromise"
|
||||||
<div class="box-content-row">
|
>
|
||||||
<button type="button" class="btn btn-outline-secondary block" (click)="leave()" [disabled]="actionPromise">
|
<i
|
||||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true" *ngIf="leaving"></i>
|
class="fa fa-spinner fa-spin"
|
||||||
{{'leaveOrganization' | i18n}}
|
title="{{ 'loading' | i18n }}"
|
||||||
</button>
|
aria-hidden="true"
|
||||||
</div>
|
*ngIf="continuing"
|
||||||
</div>
|
></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>
|
||||||
|
</div>
|
||||||
</content>
|
</content>
|
||||||
|
|
|
@ -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({
|
@Component({
|
||||||
selector: 'app-remove-password',
|
selector: "app-remove-password",
|
||||||
templateUrl: 'remove-password.component.html',
|
templateUrl: "remove-password.component.html",
|
||||||
})
|
})
|
||||||
export class RemovePasswordComponent extends BaseRemovePasswordComponent {
|
export class RemovePasswordComponent extends BaseRemovePasswordComponent {}
|
||||||
}
|
|
||||||
|
|
|
@ -1,100 +1,150 @@
|
||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
<header>
|
<header>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<a routerLink="/home">{{'cancel' | i18n}}</a>
|
<a routerLink="/home">{{ "cancel" | i18n }}</a>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="center">
|
<h1 class="center">
|
||||||
<span class="title">{{'setMasterPassword' | i18n}}</span>
|
<span class="title">{{ "setMasterPassword" | i18n }}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<button type="submit" appBlurClick [disabled]="form.loading">
|
<button type="submit" appBlurClick [disabled]="form.loading">
|
||||||
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
|
<span [hidden]="form.loading">{{ "submit" | i18n }}</span>
|
||||||
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<content>
|
<content>
|
||||||
<div class="full-loading-spinner" *ngIf="syncLoading">
|
<div class="full-loading-spinner" *ngIf="syncLoading">
|
||||||
<i class="fa fa-spinner fa-spin fa-3x" aria-hidden="true"></i>
|
<i class="fa fa-spinner fa-spin fa-3x" aria-hidden="true"></i>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!syncLoading">
|
<div *ngIf="!syncLoading">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<app-callout type="tip">{{'ssoCompleteRegistration' | i18n}}</app-callout>
|
<app-callout type="tip">{{ "ssoCompleteRegistration" | i18n }}</app-callout>
|
||||||
<app-callout type="warning" title="{{'resetPasswordPolicyAutoEnroll' | i18n}}"
|
<app-callout
|
||||||
*ngIf="resetPasswordAutoEnroll">
|
type="warning"
|
||||||
{{'resetPasswordAutoEnrollInviteWarning' | i18n}}
|
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
|
||||||
</app-callout>
|
*ngIf="resetPasswordAutoEnroll"
|
||||||
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
|
>
|
||||||
</app-callout>
|
{{ "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>
|
||||||
<div class="box">
|
<div class="progress">
|
||||||
<div class="box-content">
|
<div
|
||||||
<div class="box-content-row" appBoxRow>
|
class="progress-bar bg-{{ masterPasswordScoreColor }}"
|
||||||
<div class="box-content-row-flex">
|
role="progressbar"
|
||||||
<div class="row-main">
|
aria-valuenow="0"
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}
|
aria-valuemin="0"
|
||||||
<strong class="sub-label text-{{masterPasswordScoreColor}}"
|
aria-valuemax="100"
|
||||||
*ngIf="masterPasswordScoreText">
|
[ngStyle]="{ width: masterPasswordScoreWidth + '%' }"
|
||||||
{{masterPasswordScoreText}}
|
attr.aria-valuenow="{{ masterPasswordScoreWidth }}"
|
||||||
</strong>
|
></div>
|
||||||
</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>
|
</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>
|
</form>
|
||||||
|
|
|
@ -1,65 +1,79 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from "jslib-common/abstractions/user.service";
|
||||||
|
|
||||||
import {
|
import { SetPasswordComponent as BaseSetPasswordComponent } from "jslib-angular/components/set-password.component";
|
||||||
SetPasswordComponent as BaseSetPasswordComponent,
|
|
||||||
} from 'jslib-angular/components/set-password.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-set-password',
|
selector: "app-set-password",
|
||||||
templateUrl: 'set-password.component.html',
|
templateUrl: "set-password.component.html",
|
||||||
})
|
})
|
||||||
export class SetPasswordComponent extends BaseSetPasswordComponent {
|
export class SetPasswordComponent extends BaseSetPasswordComponent {
|
||||||
constructor(apiService: ApiService, i18nService: I18nService,
|
constructor(
|
||||||
cryptoService: CryptoService, messagingService: MessagingService,
|
apiService: ApiService,
|
||||||
userService: UserService, passwordGenerationService: PasswordGenerationService,
|
i18nService: I18nService,
|
||||||
platformUtilsService: PlatformUtilsService, policyService: PolicyService, router: Router,
|
cryptoService: CryptoService,
|
||||||
syncService: SyncService, route: ActivatedRoute) {
|
messagingService: MessagingService,
|
||||||
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
|
userService: UserService,
|
||||||
platformUtilsService, policyService, router, apiService, syncService, route);
|
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() {
|
get masterPasswordScoreWidth() {
|
||||||
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
|
return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
get masterPasswordScoreColor() {
|
get masterPasswordScoreColor() {
|
||||||
switch (this.masterPasswordScore) {
|
switch (this.masterPasswordScore) {
|
||||||
case 4:
|
case 4:
|
||||||
return 'success';
|
return "success";
|
||||||
case 3:
|
case 3:
|
||||||
return 'primary';
|
return "primary";
|
||||||
case 2:
|
case 2:
|
||||||
return 'warning';
|
return "warning";
|
||||||
default:
|
default:
|
||||||
return 'danger';
|
return "danger";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get masterPasswordScoreText() {
|
get masterPasswordScoreText() {
|
||||||
switch (this.masterPasswordScore) {
|
switch (this.masterPasswordScore) {
|
||||||
case 4:
|
case 4:
|
||||||
return this.i18nService.t('strong');
|
return this.i18nService.t("strong");
|
||||||
case 3:
|
case 3:
|
||||||
return this.i18nService.t('good');
|
return this.i18nService.t("good");
|
||||||
case 2:
|
case 2:
|
||||||
return this.i18nService.t('weak');
|
return this.i18nService.t("weak");
|
||||||
default:
|
default:
|
||||||
return this.masterPasswordScore != null ? this.i18nService.t('weak') : null;
|
return this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +1,73 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
ActivatedRoute,
|
|
||||||
Router,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
||||||
|
|
||||||
import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component';
|
import { SsoComponent as BaseSsoComponent } from "jslib-angular/components/sso.component";
|
||||||
import { BrowserApi } from '../../browser/browserApi';
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sso',
|
selector: "app-sso",
|
||||||
templateUrl: 'sso.component.html',
|
templateUrl: "sso.component.html",
|
||||||
})
|
})
|
||||||
export class SsoComponent extends BaseSsoComponent {
|
export class SsoComponent extends BaseSsoComponent {
|
||||||
constructor(authService: AuthService, router: Router,
|
constructor(
|
||||||
i18nService: I18nService, route: ActivatedRoute,
|
authService: AuthService,
|
||||||
storageService: StorageService, stateService: StateService,
|
router: Router,
|
||||||
platformUtilsService: PlatformUtilsService, apiService: ApiService,
|
i18nService: I18nService,
|
||||||
cryptoFunctionService: CryptoFunctionService, passwordGenerationService: PasswordGenerationService,
|
route: ActivatedRoute,
|
||||||
syncService: SyncService, environmentService: EnvironmentService, logService: LogService,
|
storageService: StorageService,
|
||||||
private vaultTimeoutService: VaultTimeoutService) {
|
stateService: StateService,
|
||||||
super(authService, router, i18nService, route, storageService, stateService, platformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService);
|
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.redirectUri = url + "/sso-connector.html";
|
||||||
this.clientId = 'browser';
|
this.clientId = "browser";
|
||||||
|
|
||||||
super.onSuccessfulLogin = async () => {
|
super.onSuccessfulLogin = async () => {
|
||||||
await syncService.fullSync(true);
|
await syncService.fullSync(true);
|
||||||
if (await this.vaultTimeoutService.isLocked()) {
|
if (await this.vaultTimeoutService.isLocked()) {
|
||||||
// If the vault is unlocked then this will clear keys from memory, which we don't want to do
|
// If the vault is unlocked then this will clear keys from memory, which we don't want to do
|
||||||
BrowserApi.reloadOpenWindows();
|
BrowserApi.reloadOpenWindows();
|
||||||
}
|
}
|
||||||
|
|
||||||
const thisWindow = window.open('', '_self');
|
const thisWindow = window.open("", "_self");
|
||||||
thisWindow.close();
|
thisWindow.close();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,29 @@
|
||||||
<header>
|
<header>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<a routerLink="/2fa">{{'close' | i18n}}</a>
|
<a routerLink="/2fa">{{ "close" | i18n }}</a>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="center">
|
<h1 class="center">
|
||||||
<span class="title">{{'twoStepOptions' | i18n}}</span>
|
<span class="title">{{ "twoStepOptions" | i18n }}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="right"></div>
|
<div class="right"></div>
|
||||||
</header>
|
</header>
|
||||||
<content>
|
<content>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<button type="button" appStopClick *ngFor="let p of providers" class="box-content-row" (click)="choose(p)">
|
<button
|
||||||
<span class="text">{{p.name}}</span>
|
type="button"
|
||||||
<span class="detail">{{p.description}}</span>
|
appStopClick
|
||||||
</button>
|
*ngFor="let p of providers"
|
||||||
<button type="button" appStopClick class="box-content-row" (click)="recover()">
|
class="box-content-row"
|
||||||
<span class="text">{{'recoveryCodeTitle' | i18n}}</span>
|
(click)="choose(p)"
|
||||||
<span class="detail">{{'recoveryCodeDesc' | i18n}}</span>
|
>
|
||||||
</button>
|
<span class="text">{{ p.name }}</span>
|
||||||
</div>
|
<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>
|
||||||
|
</div>
|
||||||
</content>
|
</content>
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import { Router } from '@angular/router';
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
import {
|
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "jslib-angular/components/two-factor-options.component";
|
||||||
TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent,
|
|
||||||
} from 'jslib-angular/components/two-factor-options.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-two-factor-options',
|
selector: "app-two-factor-options",
|
||||||
templateUrl: 'two-factor-options.component.html',
|
templateUrl: "two-factor-options.component.html",
|
||||||
})
|
})
|
||||||
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
|
export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
|
||||||
constructor(authService: AuthService, router: Router,
|
constructor(
|
||||||
i18nService: I18nService, platformUtilsService: PlatformUtilsService) {
|
authService: AuthService,
|
||||||
super(authService, router, i18nService, platformUtilsService, window);
|
router: Router,
|
||||||
}
|
i18nService: I18nService,
|
||||||
|
platformUtilsService: PlatformUtilsService
|
||||||
|
) {
|
||||||
|
super(authService, router, i18nService, platformUtilsService, window);
|
||||||
|
}
|
||||||
|
|
||||||
choose(p: any) {
|
choose(p: any) {
|
||||||
super.choose(p);
|
super.choose(p);
|
||||||
this.authService.selectedTwoFactorProviderType = p.type;
|
this.authService.selectedTwoFactorProviderType = p.type;
|
||||||
this.router.navigate(['2fa']);
|
this.router.navigate(["2fa"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,106 +1,141 @@
|
||||||
<form id="two-factor-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
<form id="two-factor-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
<header>
|
<header>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<a routerLink="/login">{{'back' | i18n}}</a>
|
<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>
|
</div>
|
||||||
<h1 class="center">
|
</div>
|
||||||
<span class="title">{{title}}</span>
|
</ng-container>
|
||||||
</h1>
|
<ng-container *ngIf="selectedProviderType === providerType.Yubikey">
|
||||||
<div class="right">
|
<div class="content text-center">
|
||||||
<button type="submit" appBlurClick [disabled]="form.loading" *ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
|
<p class="text-center">{{ "insertYubiKey" | i18n }}</p>
|
||||||
selectedProviderType !== providerType.OrganizationDuo &&
|
<img src="../images/yubikey.jpg" class="img-rounded img-responsive" alt="" />
|
||||||
(selectedProviderType !== providerType.WebAuthn || form.loading)">
|
</div>
|
||||||
<span [hidden]="form.loading">{{'continue' | i18n}}</span>
|
<div class="box first">
|
||||||
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
<div class="box-content">
|
||||||
</button>
|
<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>
|
||||||
</header>
|
</div>
|
||||||
<content>
|
</ng-container>
|
||||||
<ng-container *ngIf="selectedProviderType === providerType.Authenticator ||
|
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn && !webAuthnNewTab">
|
||||||
selectedProviderType === providerType.Email">
|
<div id="web-authn-frame"><iframe id="webauthn_iframe" [allow]="webAuthnAllow"></iframe></div>
|
||||||
<div class="content text-center">
|
<div class="box">
|
||||||
<span *ngIf="selectedProviderType === providerType.Authenticator">
|
<div class="box-content">
|
||||||
{{'enterVerificationCodeApp' | i18n}}
|
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||||
</span>
|
<label for="remember">{{ "rememberMe" | i18n }}</label>
|
||||||
<span *ngIf="selectedProviderType === providerType.Email">
|
<input id="remember" type="checkbox" name="Remember" [(ngModel)]="remember" />
|
||||||
{{'enterVerificationCodeEmail' | i18n : twoFactorEmail}}
|
</div>
|
||||||
</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>
|
</div>
|
||||||
<div class="content no-vpad" *ngIf="selectedProviderType != null">
|
</div>
|
||||||
<p class="text-center">
|
</ng-container>
|
||||||
<button type="button" appStopClick (click)="anotherMethod()">{{'useAnotherTwoStepMethod' | i18n}}</button>
|
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn && webAuthnNewTab">
|
||||||
</p>
|
<div class="content text-center" *ngIf="webAuthnNewTab">
|
||||||
<p *ngIf="selectedProviderType === providerType.Email" class="text-center">
|
<p class="text-center">{{ "webAuthnNewTab" | i18n }}</p>
|
||||||
<button type="button" appStopClick (click)="sendEmail(true)" [appApiAction]="emailPromise">
|
<button type="button" class="btn primary block" (click)="authWebAuthn()" appStopClick>
|
||||||
{{'sendVerificationCodeEmailAgain' | i18n}}
|
{{ "webAuthnNewTabOpen" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</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>
|
||||||
</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>
|
</form>
|
||||||
|
|
|
@ -1,116 +1,140 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import {
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
ActivatedRoute,
|
import { first } from "rxjs/operators";
|
||||||
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 { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
|
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
import { StorageService } from "jslib-common/abstractions/storage.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.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({
|
@Component({
|
||||||
selector: 'app-two-factor',
|
selector: "app-two-factor",
|
||||||
templateUrl: 'two-factor.component.html',
|
templateUrl: "two-factor.component.html",
|
||||||
})
|
})
|
||||||
export class TwoFactorComponent extends BaseTwoFactorComponent {
|
export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||||
showNewWindowMessage = false;
|
showNewWindowMessage = false;
|
||||||
|
|
||||||
constructor(authService: AuthService, router: Router,
|
constructor(
|
||||||
i18nService: I18nService, apiService: ApiService,
|
authService: AuthService,
|
||||||
platformUtilsService: PlatformUtilsService, private syncService: SyncService,
|
router: Router,
|
||||||
environmentService: EnvironmentService, private broadcasterService: BroadcasterService,
|
i18nService: I18nService,
|
||||||
private popupUtilsService: PopupUtilsService, stateService: StateService,
|
apiService: ApiService,
|
||||||
storageService: StorageService, route: ActivatedRoute, private messagingService: MessagingService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
logService: LogService) {
|
private syncService: SyncService,
|
||||||
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
|
environmentService: EnvironmentService,
|
||||||
stateService, storageService, route, logService);
|
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 = () => {
|
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() {
|
anotherMethod() {
|
||||||
if (this.route.snapshot.paramMap.has('webAuthnResponse')) {
|
this.router.navigate(["2fa-options"]);
|
||||||
// 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();
|
async isLinux() {
|
||||||
if (this.selectedProviderType == null) {
|
return (await BrowserApi.getPlatformInfo()).os === "linux";
|
||||||
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';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,86 +1,130 @@
|
||||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||||
<header>
|
<header>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<a (click)="logOut()">{{'logOut' | i18n}}</a>
|
<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>
|
</div>
|
||||||
<h1 class="center">
|
</div>
|
||||||
<span class="title">{{'updateMasterPassword' | i18n}}</span>
|
</div>
|
||||||
</h1>
|
<div class="box">
|
||||||
<div class="right">
|
<div class="box-content">
|
||||||
<button type="submit" appBlurClick [disabled]="form.loading">
|
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||||
<span [hidden]="form.loading">{{'submit' | i18n}}</span>
|
<div class="row-main">
|
||||||
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
<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>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</div>
|
||||||
<content>
|
</div>
|
||||||
<app-callout type="warning" title="{{'updateMasterPassword' | i18n}}">
|
<div class="box">
|
||||||
{{'updateMasterPasswordWarning' | i18n}}
|
<div class="box-content">
|
||||||
</app-callout>
|
<div class="box-content-row" appBoxRow>
|
||||||
<app-callout type="info" [enforcedPolicyOptions]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
|
<label for="hint">{{ "masterPassHint" | i18n }}</label>
|
||||||
</app-callout>
|
<input id="hint" type="text" name="Hint" [(ngModel)]="hint" />
|
||||||
<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>
|
||||||
<div class="box-content">
|
<div class="box-footer">
|
||||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
{{ "masterPassHintDesc" | i18n }}
|
||||||
<div class="row-main">
|
</div>
|
||||||
<label for="masterPasswordRetype">{{'reTypeMasterPass' | i18n}}</label>
|
</div>
|
||||||
<input id="masterPasswordRetype" type="{{showPassword ? 'text' : 'password'}}"
|
</content>
|
||||||
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>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,65 +1,82 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
import { SyncService } from "jslib-common/abstractions/sync.service";
|
||||||
import { UserService } from 'jslib-common/abstractions/user.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 {
|
interface MasterPasswordScore {
|
||||||
Color: string;
|
Color: string;
|
||||||
Text: string;
|
Text: string;
|
||||||
Width: number;
|
Width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-update-temp-password',
|
selector: "app-update-temp-password",
|
||||||
templateUrl: 'update-temp-password.component.html',
|
templateUrl: "update-temp-password.component.html",
|
||||||
})
|
})
|
||||||
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
|
export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {
|
||||||
get masterPasswordScoreStyle(): MasterPasswordScore {
|
get masterPasswordScoreStyle(): MasterPasswordScore {
|
||||||
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
|
const scoreWidth = this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
|
||||||
switch (this.masterPasswordScore) {
|
switch (this.masterPasswordScore) {
|
||||||
case 4:
|
case 4:
|
||||||
return {
|
return {
|
||||||
Color: 'bg-success',
|
Color: "bg-success",
|
||||||
Text: 'strong',
|
Text: "strong",
|
||||||
Width: scoreWidth,
|
Width: scoreWidth,
|
||||||
};
|
};
|
||||||
case 3:
|
case 3:
|
||||||
return {
|
return {
|
||||||
Color: 'bg-primary',
|
Color: "bg-primary",
|
||||||
Text: 'good',
|
Text: "good",
|
||||||
Width: scoreWidth,
|
Width: scoreWidth,
|
||||||
};
|
};
|
||||||
case 2:
|
case 2:
|
||||||
return {
|
return {
|
||||||
Color: 'bg-warning',
|
Color: "bg-warning",
|
||||||
Text: 'weak',
|
Text: "weak",
|
||||||
Width: scoreWidth,
|
Width: scoreWidth,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
Color: 'bg-danger',
|
Color: "bg-danger",
|
||||||
Text: 'weak',
|
Text: "weak",
|
||||||
Width: scoreWidth,
|
Width: scoreWidth,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
|
i18nService: I18nService,
|
||||||
cryptoService: CryptoService, userService: UserService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
messagingService: MessagingService, apiService: ApiService,
|
passwordGenerationService: PasswordGenerationService,
|
||||||
syncService: SyncService, logService: LogService) {
|
policyService: PolicyService,
|
||||||
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
|
cryptoService: CryptoService,
|
||||||
userService, messagingService, apiService, syncService, logService);
|
userService: UserService,
|
||||||
}
|
messagingService: MessagingService,
|
||||||
|
apiService: ApiService,
|
||||||
|
syncService: SyncService,
|
||||||
|
logService: LogService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
i18nService,
|
||||||
|
platformUtilsService,
|
||||||
|
passwordGenerationService,
|
||||||
|
policyService,
|
||||||
|
cryptoService,
|
||||||
|
userService,
|
||||||
|
messagingService,
|
||||||
|
apiService,
|
||||||
|
syncService,
|
||||||
|
logService
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,199 +1,213 @@
|
||||||
import {
|
import { animate, group, query, style, transition, trigger } from "@angular/animations";
|
||||||
animate,
|
|
||||||
group,
|
|
||||||
query,
|
|
||||||
style,
|
|
||||||
transition,
|
|
||||||
trigger,
|
|
||||||
} from '@angular/animations';
|
|
||||||
|
|
||||||
import { BrowserApi } from '../browser/browserApi';
|
import { BrowserApi } from "../browser/browserApi";
|
||||||
|
|
||||||
const queryShown = query(':enter, :leave', [
|
const queryShown = query(
|
||||||
style({ position: 'fixed', width: '100%', height: '100%' }),
|
":enter, :leave",
|
||||||
], { optional: true });
|
[style({ position: "fixed", width: "100%", height: "100%" })],
|
||||||
|
{
|
||||||
|
optional: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// ref: https://github.com/angular/angular/issues/15477
|
// ref: https://github.com/angular/angular/issues/15477
|
||||||
const queryChildRoute = query('router-outlet ~ *', [
|
const queryChildRoute = query("router-outlet ~ *", [style({}), animate(1, style({}))], {
|
||||||
style({}),
|
optional: true,
|
||||||
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) {
|
export function queryTranslate(
|
||||||
return query(':' + direction, [
|
direction: string,
|
||||||
style({ transform: 'translate' + axis + '(' + from + '%)', zIndex: zIndex, boxShadow: '0 3px 2px -2px gray' }),
|
axis: string,
|
||||||
animate(speed + ' ease-in-out', style({ transform: 'translate' + axis + '(' + to + '%)' })),
|
from: number,
|
||||||
], { optional: true });
|
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) {
|
export function queryTranslateX(
|
||||||
return queryTranslate(direction, 'X', from, to, zIndex);
|
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) {
|
export function queryTranslateY(
|
||||||
return queryTranslate(direction, 'Y', from, to, zIndex);
|
direction: string,
|
||||||
|
from: number,
|
||||||
|
to: number,
|
||||||
|
zIndex: number = 1000
|
||||||
|
) {
|
||||||
|
return queryTranslate(direction, "Y", from, to, zIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inSlideLeft = [
|
const inSlideLeft = [
|
||||||
queryShown,
|
queryShown,
|
||||||
group([
|
group([queryTranslateX("enter", 100, 0), queryTranslateX("leave", 0, -100), queryChildRoute]),
|
||||||
queryTranslateX('enter', 100, 0),
|
|
||||||
queryTranslateX('leave', 0, -100),
|
|
||||||
queryChildRoute,
|
|
||||||
]),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const outSlideRight = [
|
const outSlideRight = [
|
||||||
queryShown,
|
queryShown,
|
||||||
group([
|
group([queryTranslateX("enter", -100, 0), queryTranslateX("leave", 0, 100)]),
|
||||||
queryTranslateX('enter', -100, 0),
|
|
||||||
queryTranslateX('leave', 0, 100),
|
|
||||||
]),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const inSlideUp = [
|
const inSlideUp = [
|
||||||
queryShown,
|
queryShown,
|
||||||
group([
|
group([queryTranslateY("enter", 100, 0, 1010), queryTranslateY("leave", 0, 0), queryChildRoute]),
|
||||||
queryTranslateY('enter', 100, 0, 1010),
|
|
||||||
queryTranslateY('leave', 0, 0),
|
|
||||||
queryChildRoute,
|
|
||||||
]),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const outSlideDown = [
|
const outSlideDown = [
|
||||||
queryShown,
|
queryShown,
|
||||||
group([
|
group([queryTranslateY("enter", 0, 0), queryTranslateY("leave", 0, 100, 1010)]),
|
||||||
queryTranslateY('enter', 0, 0),
|
|
||||||
queryTranslateY('leave', 0, 100, 1010),
|
|
||||||
]),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const inSlideDown = [
|
const inSlideDown = [
|
||||||
queryShown,
|
queryShown,
|
||||||
group([
|
group([queryTranslateY("enter", -100, 0, 1010), queryTranslateY("leave", 0, 0), queryChildRoute]),
|
||||||
queryTranslateY('enter', -100, 0, 1010),
|
|
||||||
queryTranslateY('leave', 0, 0),
|
|
||||||
queryChildRoute,
|
|
||||||
]),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const outSlideUp = [
|
const outSlideUp = [
|
||||||
queryShown,
|
queryShown,
|
||||||
group([
|
group([queryTranslateY("enter", 0, 0), queryTranslateY("leave", 0, -100, 1010)]),
|
||||||
queryTranslateY('enter', 0, 0),
|
|
||||||
queryTranslateY('leave', 0, -100, 1010),
|
|
||||||
]),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function tabsToCiphers(fromState: string, toState: string) {
|
export function tabsToCiphers(fromState: string, toState: string) {
|
||||||
if (fromState == null || toState === null || toState.indexOf('ciphers_') === -1) {
|
if (fromState == null || toState === null || toState.indexOf("ciphers_") === -1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (fromState.indexOf('ciphers_') === 0 && fromState.indexOf('ciphers_direction=b') === -1) ||
|
return (
|
||||||
fromState === 'tabs';
|
(fromState.indexOf("ciphers_") === 0 && fromState.indexOf("ciphers_direction=b") === -1) ||
|
||||||
|
fromState === "tabs"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ciphersToTabs(fromState: string, toState: string) {
|
export function ciphersToTabs(fromState: string, toState: string) {
|
||||||
if (fromState == null || toState === null || fromState.indexOf('ciphers_') === -1) {
|
if (fromState == null || toState === null || fromState.indexOf("ciphers_") === -1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return toState.indexOf('ciphers_direction=b') === 0 || toState === 'tabs';
|
return toState.indexOf("ciphers_direction=b") === 0 || toState === "tabs";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ciphersToView(fromState: string, toState: string) {
|
export function ciphersToView(fromState: string, toState: string) {
|
||||||
if (fromState == null || toState === null) {
|
if (fromState == null || toState === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return fromState.indexOf('ciphers_') === 0 &&
|
return (
|
||||||
(toState === 'view-cipher' || toState === 'add-cipher' || toState === 'clone-cipher');
|
fromState.indexOf("ciphers_") === 0 &&
|
||||||
|
(toState === "view-cipher" || toState === "add-cipher" || toState === "clone-cipher")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function viewToCiphers(fromState: string, toState: string) {
|
export function viewToCiphers(fromState: string, toState: string) {
|
||||||
if (fromState == null || toState === null) {
|
if (fromState == null || toState === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (fromState === 'view-cipher' || fromState === 'add-cipher' || fromState === 'clone-cipher') &&
|
return (
|
||||||
toState.indexOf('ciphers_') === 0;
|
(fromState === "view-cipher" || fromState === "add-cipher" || fromState === "clone-cipher") &&
|
||||||
|
toState.indexOf("ciphers_") === 0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const routerTransition = trigger('routerTransition', [
|
export const routerTransition = trigger("routerTransition", [
|
||||||
transition('void => home', inSlideLeft),
|
transition("void => home", inSlideLeft),
|
||||||
transition('void => tabs', 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 => home", outSlideDown),
|
||||||
transition('login => hint', inSlideUp),
|
transition("login => hint", inSlideUp),
|
||||||
transition('login => tabs, login => 2fa', inSlideLeft),
|
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 => login", outSlideRight),
|
||||||
transition('2fa => 2fa-options', inSlideUp),
|
transition("2fa => 2fa-options", inSlideUp),
|
||||||
transition('2fa-options => 2fa', outSlideDown),
|
transition("2fa-options => 2fa", outSlideDown),
|
||||||
transition('2fa => tabs', inSlideLeft),
|
transition("2fa => tabs", inSlideLeft),
|
||||||
|
|
||||||
transition(tabsToCiphers, inSlideLeft),
|
transition(tabsToCiphers, inSlideLeft),
|
||||||
transition(ciphersToTabs, outSlideRight),
|
transition(ciphersToTabs, outSlideRight),
|
||||||
|
|
||||||
transition(ciphersToView, inSlideUp),
|
transition(ciphersToView, inSlideUp),
|
||||||
transition(viewToCiphers, outSlideDown),
|
transition(viewToCiphers, outSlideDown),
|
||||||
|
|
||||||
transition('tabs => view-cipher', inSlideUp),
|
transition("tabs => view-cipher", inSlideUp),
|
||||||
transition('view-cipher => tabs', outSlideDown),
|
transition("view-cipher => tabs", outSlideDown),
|
||||||
|
|
||||||
transition('view-cipher => edit-cipher, view-cipher => cipher-password-history', inSlideUp),
|
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(
|
||||||
|
"edit-cipher => view-cipher, cipher-password-history => view-cipher, edit-cipher => tabs",
|
||||||
|
outSlideDown
|
||||||
|
),
|
||||||
|
|
||||||
transition('view-cipher => clone-cipher', inSlideUp),
|
transition("view-cipher => clone-cipher", inSlideUp),
|
||||||
transition('clone-cipher => view-cipher, clone-cipher => tabs', outSlideDown),
|
transition("clone-cipher => view-cipher, clone-cipher => tabs", outSlideDown),
|
||||||
|
|
||||||
transition('view-cipher => share-cipher', inSlideUp),
|
transition("view-cipher => share-cipher", inSlideUp),
|
||||||
transition('share-cipher => view-cipher', outSlideDown),
|
transition("share-cipher => view-cipher", outSlideDown),
|
||||||
|
|
||||||
transition('tabs => add-cipher', inSlideUp),
|
transition("tabs => add-cipher", inSlideUp),
|
||||||
transition('add-cipher => tabs', outSlideDown),
|
transition("add-cipher => tabs", outSlideDown),
|
||||||
|
|
||||||
transition('generator => generator-history, tabs => generator-history', inSlideLeft),
|
transition("generator => generator-history, tabs => generator-history", inSlideLeft),
|
||||||
transition('generator-history => generator, generator-history => tabs', outSlideRight),
|
transition("generator-history => generator, generator-history => tabs", outSlideRight),
|
||||||
|
|
||||||
transition('add-cipher => generator, edit-cipher => generator, clone-cipher => generator', inSlideUp),
|
transition(
|
||||||
transition('generator => add-cipher, generator => edit-cipher, generator => clone-cipher', outSlideDown),
|
"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("edit-cipher => attachments, edit-cipher => collections", inSlideLeft),
|
||||||
transition('attachments => edit-cipher, collections => edit-cipher', outSlideRight),
|
transition("attachments => edit-cipher, collections => edit-cipher", outSlideRight),
|
||||||
|
|
||||||
transition('clone-cipher => attachments, clone-cipher => collections', inSlideLeft),
|
transition("clone-cipher => attachments, clone-cipher => collections", inSlideLeft),
|
||||||
transition('attachments => clone-cipher, collections => clone-cipher', outSlideRight),
|
transition("attachments => clone-cipher, collections => clone-cipher", outSlideRight),
|
||||||
|
|
||||||
transition('tabs => export', inSlideLeft),
|
transition("tabs => export", inSlideLeft),
|
||||||
transition('export => tabs', outSlideRight),
|
transition("export => tabs", outSlideRight),
|
||||||
|
|
||||||
transition('tabs => folders', inSlideLeft),
|
transition("tabs => folders", inSlideLeft),
|
||||||
transition('folders => tabs', outSlideRight),
|
transition("folders => tabs", outSlideRight),
|
||||||
|
|
||||||
transition('folders => edit-folder, folders => add-folder', inSlideUp),
|
transition("folders => edit-folder, folders => add-folder", inSlideUp),
|
||||||
transition('edit-folder => folders, add-folder => folders', outSlideDown),
|
transition("edit-folder => folders, add-folder => folders", outSlideDown),
|
||||||
|
|
||||||
transition('tabs => sync', inSlideLeft),
|
transition("tabs => sync", inSlideLeft),
|
||||||
transition('sync => tabs', outSlideRight),
|
transition("sync => tabs", outSlideRight),
|
||||||
|
|
||||||
transition('tabs => options', inSlideLeft),
|
transition("tabs => options", inSlideLeft),
|
||||||
transition('options => tabs', outSlideRight),
|
transition("options => tabs", outSlideRight),
|
||||||
|
|
||||||
transition('tabs => premium', inSlideLeft),
|
transition("tabs => premium", inSlideLeft),
|
||||||
transition('premium => tabs', outSlideRight),
|
transition("premium => tabs", outSlideRight),
|
||||||
|
|
||||||
transition('tabs => lock', inSlideDown),
|
transition("tabs => lock", inSlideDown),
|
||||||
|
|
||||||
transition('tabs => send-type', inSlideLeft),
|
transition("tabs => send-type", inSlideLeft),
|
||||||
transition('send-type => tabs', outSlideRight),
|
transition("send-type => tabs", outSlideRight),
|
||||||
|
|
||||||
transition('tabs => add-send, send-type => add-send', inSlideUp),
|
transition("tabs => add-send, send-type => add-send", inSlideUp),
|
||||||
transition('add-send => tabs, add-send => send-type', outSlideDown),
|
transition("add-send => tabs, add-send => send-type", outSlideDown),
|
||||||
|
|
||||||
transition('tabs => edit-send, send-type => edit-send', inSlideUp),
|
transition("tabs => edit-send, send-type => edit-send", inSlideUp),
|
||||||
transition('edit-send => tabs, edit-send => send-type', outSlideDown),
|
transition("edit-send => tabs, edit-send => send-type", outSlideDown),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -1,355 +1,352 @@
|
||||||
import { Injectable, NgModule } from '@angular/core';
|
import { Injectable, NgModule } from "@angular/core";
|
||||||
import {
|
import { ActivatedRouteSnapshot, RouteReuseStrategy, RouterModule, Routes } from "@angular/router";
|
||||||
ActivatedRouteSnapshot,
|
|
||||||
RouteReuseStrategy,
|
|
||||||
RouterModule,
|
|
||||||
Routes,
|
|
||||||
} from '@angular/router';
|
|
||||||
|
|
||||||
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service';
|
import { AuthGuardService } from "jslib-angular/services/auth-guard.service";
|
||||||
import { LockGuardService } from 'jslib-angular/services/lock-guard.service';
|
import { LockGuardService } from "jslib-angular/services/lock-guard.service";
|
||||||
|
|
||||||
import { DebounceNavigationService } from './services/debounceNavigationService';
|
import { DebounceNavigationService } from "./services/debounceNavigationService";
|
||||||
import { LaunchGuardService } from './services/launch-guard.service';
|
import { LaunchGuardService } from "./services/launch-guard.service";
|
||||||
|
|
||||||
import { EnvironmentComponent } from './accounts/environment.component';
|
import { EnvironmentComponent } from "./accounts/environment.component";
|
||||||
import { HintComponent } from './accounts/hint.component';
|
import { HintComponent } from "./accounts/hint.component";
|
||||||
import { HomeComponent } from './accounts/home.component';
|
import { HomeComponent } from "./accounts/home.component";
|
||||||
import { LockComponent } from './accounts/lock.component';
|
import { LockComponent } from "./accounts/lock.component";
|
||||||
import { LoginComponent } from './accounts/login.component';
|
import { LoginComponent } from "./accounts/login.component";
|
||||||
import { RegisterComponent } from './accounts/register.component';
|
import { RegisterComponent } from "./accounts/register.component";
|
||||||
import { RemovePasswordComponent } from './accounts/remove-password.component';
|
import { RemovePasswordComponent } from "./accounts/remove-password.component";
|
||||||
import { SetPasswordComponent } from './accounts/set-password.component';
|
import { SetPasswordComponent } from "./accounts/set-password.component";
|
||||||
import { SsoComponent } from './accounts/sso.component';
|
import { SsoComponent } from "./accounts/sso.component";
|
||||||
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
|
import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
|
||||||
import { TwoFactorComponent } from './accounts/two-factor.component';
|
import { TwoFactorComponent } from "./accounts/two-factor.component";
|
||||||
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
|
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
|
||||||
|
|
||||||
import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component';
|
import { PasswordGeneratorHistoryComponent } from "./generator/password-generator-history.component";
|
||||||
import { PasswordGeneratorComponent } from './generator/password-generator.component';
|
import { PasswordGeneratorComponent } from "./generator/password-generator.component";
|
||||||
|
|
||||||
import { PrivateModeComponent } from './private-mode.component';
|
import { PrivateModeComponent } from "./private-mode.component";
|
||||||
import { TabsComponent } from './tabs.component';
|
import { TabsComponent } from "./tabs.component";
|
||||||
|
|
||||||
import { ExcludedDomainsComponent } from './settings/excluded-domains.component';
|
import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
|
||||||
import { ExportComponent } from './settings/export.component';
|
import { ExportComponent } from "./settings/export.component";
|
||||||
import { FolderAddEditComponent } from './settings/folder-add-edit.component';
|
import { FolderAddEditComponent } from "./settings/folder-add-edit.component";
|
||||||
import { FoldersComponent } from './settings/folders.component';
|
import { FoldersComponent } from "./settings/folders.component";
|
||||||
import { OptionsComponent } from './settings/options.component';
|
import { OptionsComponent } from "./settings/options.component";
|
||||||
import { PremiumComponent } from './settings/premium.component';
|
import { PremiumComponent } from "./settings/premium.component";
|
||||||
import { SettingsComponent } from './settings/settings.component';
|
import { SettingsComponent } from "./settings/settings.component";
|
||||||
import { SyncComponent } from './settings/sync.component';
|
import { SyncComponent } from "./settings/sync.component";
|
||||||
|
|
||||||
import { AddEditComponent } from './vault/add-edit.component';
|
import { AddEditComponent } from "./vault/add-edit.component";
|
||||||
import { AttachmentsComponent } from './vault/attachments.component';
|
import { AttachmentsComponent } from "./vault/attachments.component";
|
||||||
import { CiphersComponent } from './vault/ciphers.component';
|
import { CiphersComponent } from "./vault/ciphers.component";
|
||||||
import { CollectionsComponent } from './vault/collections.component';
|
import { CollectionsComponent } from "./vault/collections.component";
|
||||||
import { CurrentTabComponent } from './vault/current-tab.component';
|
import { CurrentTabComponent } from "./vault/current-tab.component";
|
||||||
import { GroupingsComponent } from './vault/groupings.component';
|
import { GroupingsComponent } from "./vault/groupings.component";
|
||||||
import { PasswordHistoryComponent } from './vault/password-history.component';
|
import { PasswordHistoryComponent } from "./vault/password-history.component";
|
||||||
import { ShareComponent } from './vault/share.component';
|
import { ShareComponent } from "./vault/share.component";
|
||||||
import { ViewComponent } from './vault/view.component';
|
import { ViewComponent } from "./vault/view.component";
|
||||||
|
|
||||||
import { SendAddEditComponent } from './send/send-add-edit.component';
|
import { SendAddEditComponent } from "./send/send-add-edit.component";
|
||||||
import { SendGroupingsComponent } from './send/send-groupings.component';
|
import { SendGroupingsComponent } from "./send/send-groupings.component";
|
||||||
import { SendTypeComponent } from './send/send-type.component';
|
import { SendTypeComponent } from "./send/send-type.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: "",
|
||||||
redirectTo: 'home',
|
redirectTo: "home",
|
||||||
pathMatch: 'full',
|
pathMatch: "full",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'vault',
|
path: "vault",
|
||||||
redirectTo: '/tabs/vault',
|
redirectTo: "/tabs/vault",
|
||||||
pathMatch: 'full',
|
pathMatch: "full",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'home',
|
path: "home",
|
||||||
component: HomeComponent,
|
component: HomeComponent,
|
||||||
canActivate: [LaunchGuardService],
|
canActivate: [LaunchGuardService],
|
||||||
data: { state: 'home' },
|
data: { state: "home" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'login',
|
path: "login",
|
||||||
component: LoginComponent,
|
component: LoginComponent,
|
||||||
canActivate: [LaunchGuardService],
|
canActivate: [LaunchGuardService],
|
||||||
data: { state: 'login' },
|
data: { state: "login" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'lock',
|
path: "lock",
|
||||||
component: LockComponent,
|
component: LockComponent,
|
||||||
canActivate: [LockGuardService],
|
canActivate: [LockGuardService],
|
||||||
data: { state: 'lock' },
|
data: { state: "lock" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '2fa',
|
path: "2fa",
|
||||||
component: TwoFactorComponent,
|
component: TwoFactorComponent,
|
||||||
canActivate: [LaunchGuardService],
|
canActivate: [LaunchGuardService],
|
||||||
data: { state: '2fa' },
|
data: { state: "2fa" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '2fa-options',
|
path: "2fa-options",
|
||||||
component: TwoFactorOptionsComponent,
|
component: TwoFactorOptionsComponent,
|
||||||
canActivate: [LaunchGuardService],
|
canActivate: [LaunchGuardService],
|
||||||
data: { state: '2fa-options' },
|
data: { state: "2fa-options" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'sso',
|
path: "sso",
|
||||||
component: SsoComponent,
|
component: SsoComponent,
|
||||||
canActivate: [LaunchGuardService],
|
canActivate: [LaunchGuardService],
|
||||||
data: { state: 'sso' },
|
data: { state: "sso" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'set-password',
|
path: "set-password",
|
||||||
component: SetPasswordComponent,
|
component: SetPasswordComponent,
|
||||||
data: { state: 'set-password' },
|
data: { state: "set-password" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'remove-password',
|
path: "remove-password",
|
||||||
component: RemovePasswordComponent,
|
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],
|
canActivate: [AuthGuardService],
|
||||||
data: { state: 'remove-password' },
|
data: { state: "tabs_current" },
|
||||||
},
|
runGuardsAndResolvers: "always",
|
||||||
{
|
},
|
||||||
path: 'register',
|
{
|
||||||
component: RegisterComponent,
|
path: "vault",
|
||||||
canActivate: [LaunchGuardService],
|
component: GroupingsComponent,
|
||||||
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],
|
canActivate: [AuthGuardService],
|
||||||
data: { state: 'ciphers' },
|
data: { state: "tabs_vault" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'view-cipher',
|
path: "generator",
|
||||||
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,
|
component: PasswordGeneratorComponent,
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuardService],
|
||||||
data: { state: 'generator' },
|
data: { state: "tabs_generator" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'generator-history',
|
path: "settings",
|
||||||
component: PasswordGeneratorHistoryComponent,
|
component: SettingsComponent,
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuardService],
|
||||||
data: { state: 'generator-history' },
|
data: { state: "tabs_settings" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'export',
|
path: "send",
|
||||||
component: ExportComponent,
|
component: SendGroupingsComponent,
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuardService],
|
||||||
data: { state: 'export' },
|
data: { state: "tabs_send" },
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
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' },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NoRouteReuseStrategy implements RouteReuseStrategy {
|
export class NoRouteReuseStrategy implements RouteReuseStrategy {
|
||||||
shouldDetach(route: ActivatedRouteSnapshot) {
|
shouldDetach(route: ActivatedRouteSnapshot) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
store(route: ActivatedRouteSnapshot, handle: {}) { /* Nothing */ }
|
store(route: ActivatedRouteSnapshot, handle: {}) {
|
||||||
|
/* Nothing */
|
||||||
|
}
|
||||||
|
|
||||||
shouldAttach(route: ActivatedRouteSnapshot) {
|
shouldAttach(route: ActivatedRouteSnapshot) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
retrieve(route: ActivatedRouteSnapshot): any {
|
retrieve(route: ActivatedRouteSnapshot): any {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) {
|
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes, {
|
imports: [
|
||||||
useHash: true,
|
RouterModule.forRoot(routes, {
|
||||||
onSameUrlNavigation: 'reload',
|
useHash: true,
|
||||||
/*enableTracing: true,*/
|
onSameUrlNavigation: "reload",
|
||||||
})],
|
/*enableTracing: true,*/
|
||||||
exports: [RouterModule],
|
}),
|
||||||
providers: [
|
],
|
||||||
{ provide: RouteReuseStrategy, useClass: NoRouteReuseStrategy },
|
exports: [RouterModule],
|
||||||
],
|
providers: [{ provide: RouteReuseStrategy, useClass: NoRouteReuseStrategy }],
|
||||||
})
|
})
|
||||||
export class AppRoutingModule { }
|
export class AppRoutingModule {}
|
||||||
|
|
|
@ -1,243 +1,254 @@
|
||||||
import {
|
import { ChangeDetectorRef, Component, NgZone, OnInit, SecurityContext } from "@angular/core";
|
||||||
ChangeDetectorRef,
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
Component,
|
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
||||||
NgZone,
|
import { IndividualConfig, ToastrService } from "ngx-toastr";
|
||||||
OnInit,
|
import Swal, { SweetAlertIcon } from "sweetalert2/src/sweetalert2.js";
|
||||||
SecurityContext,
|
import { BrowserApi } from "../browser/browserApi";
|
||||||
} 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 { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
import { StateService } from "jslib-common/abstractions/state.service";
|
||||||
import { StorageService } from 'jslib-common/abstractions/storage.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({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: "app-root",
|
||||||
styles: [],
|
styles: [],
|
||||||
animations: [routerTransition],
|
animations: [routerTransition],
|
||||||
template: `
|
template: ` <main [@routerTransition]="getState(o)">
|
||||||
<main [@routerTransition]="getState(o)">
|
<router-outlet #o="outlet"></router-outlet>
|
||||||
<router-outlet #o="outlet"></router-outlet>
|
</main>`,
|
||||||
</main>`,
|
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
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,
|
ngOnInit() {
|
||||||
private broadcasterService: BroadcasterService, private authService: AuthService,
|
if (BrowserApi.getBackgroundPage() == null) {
|
||||||
private i18nService: I18nService, private router: Router,
|
return;
|
||||||
private stateService: StateService, private messagingService: MessagingService,
|
}
|
||||||
private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone,
|
|
||||||
private sanitizer: DomSanitizer, private platformUtilsService: PlatformUtilsService,
|
|
||||||
private keyConnectoService: KeyConnectorService) { }
|
|
||||||
|
|
||||||
ngOnInit() {
|
this.ngZone.runOutsideAngular(() => {
|
||||||
if (BrowserApi.getBackgroundPage() == null) {
|
window.onmousemove = () => this.recordActivity();
|
||||||
return;
|
window.onmousedown = () => this.recordActivity();
|
||||||
}
|
window.ontouchstart = () => this.recordActivity();
|
||||||
|
window.onclick = () => this.recordActivity();
|
||||||
|
window.onscroll = () => this.recordActivity();
|
||||||
|
window.onkeypress = () => this.recordActivity();
|
||||||
|
});
|
||||||
|
|
||||||
this.ngZone.runOutsideAngular(() => {
|
(window as any).bitwardenPopupMainMessageListener = async (
|
||||||
window.onmousemove = () => this.recordActivity();
|
msg: any,
|
||||||
window.onmousedown = () => this.recordActivity();
|
sender: any,
|
||||||
window.ontouchstart = () => this.recordActivity();
|
sendResponse: any
|
||||||
window.onclick = () => this.recordActivity();
|
) => {
|
||||||
window.onscroll = () => this.recordActivity();
|
if (msg.command === "doneLoggingOut") {
|
||||||
window.onkeypress = () => this.recordActivity();
|
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") {
|
||||||
(window as any).bitwardenPopupMainMessageListener = async (msg: any, sender: any, sendResponse: any) => {
|
this.ngZone.run(() => {
|
||||||
if (msg.command === 'doneLoggingOut') {
|
this.router.navigate(["home"]);
|
||||||
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 === "locked") {
|
||||||
|
this.stateService.purge();
|
||||||
getState(outlet: RouterOutlet) {
|
this.ngZone.run(() => {
|
||||||
if (outlet.activatedRouteData.state === 'ciphers') {
|
this.router.navigate(["lock"]);
|
||||||
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 === "showDialog") {
|
||||||
this.messagingService.send('showDialogResolve', {
|
await this.showDialog(msg);
|
||||||
dialogId: msg.dialogId,
|
} else if (msg.command === "showToast") {
|
||||||
confirmed: confirmed.value,
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,272 +1,265 @@
|
||||||
import { A11yModule } from '@angular/cdk/a11y';
|
import { A11yModule } from "@angular/cdk/a11y";
|
||||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
import { DragDropModule } from "@angular/cdk/drag-drop";
|
||||||
import { ScrollingModule } from '@angular/cdk/scrolling';
|
import { ScrollingModule } from "@angular/cdk/scrolling";
|
||||||
|
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from "./app-routing.module";
|
||||||
import { ServicesModule } from './services/services.module';
|
import { ServicesModule } from "./services/services.module";
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from "@angular/core";
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from "@angular/platform-browser";
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
|
|
||||||
import { EnvironmentComponent } from './accounts/environment.component';
|
import { EnvironmentComponent } from "./accounts/environment.component";
|
||||||
import { HintComponent } from './accounts/hint.component';
|
import { HintComponent } from "./accounts/hint.component";
|
||||||
import { HomeComponent } from './accounts/home.component';
|
import { HomeComponent } from "./accounts/home.component";
|
||||||
import { LockComponent } from './accounts/lock.component';
|
import { LockComponent } from "./accounts/lock.component";
|
||||||
import { LoginComponent } from './accounts/login.component';
|
import { LoginComponent } from "./accounts/login.component";
|
||||||
import { RegisterComponent } from './accounts/register.component';
|
import { RegisterComponent } from "./accounts/register.component";
|
||||||
import { RemovePasswordComponent } from './accounts/remove-password.component';
|
import { RemovePasswordComponent } from "./accounts/remove-password.component";
|
||||||
import { SetPasswordComponent } from './accounts/set-password.component';
|
import { SetPasswordComponent } from "./accounts/set-password.component";
|
||||||
import { SsoComponent } from './accounts/sso.component';
|
import { SsoComponent } from "./accounts/sso.component";
|
||||||
import { TwoFactorOptionsComponent } from './accounts/two-factor-options.component';
|
import { TwoFactorOptionsComponent } from "./accounts/two-factor-options.component";
|
||||||
import { TwoFactorComponent } from './accounts/two-factor.component';
|
import { TwoFactorComponent } from "./accounts/two-factor.component";
|
||||||
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
|
import { UpdateTempPasswordComponent } from "./accounts/update-temp-password.component";
|
||||||
|
|
||||||
import { PasswordGeneratorHistoryComponent } from './generator/password-generator-history.component';
|
import { PasswordGeneratorHistoryComponent } from "./generator/password-generator-history.component";
|
||||||
import { PasswordGeneratorComponent } from './generator/password-generator.component';
|
import { PasswordGeneratorComponent } from "./generator/password-generator.component";
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from "./app.component";
|
||||||
import { PrivateModeComponent } from './private-mode.component';
|
import { PrivateModeComponent } from "./private-mode.component";
|
||||||
import { TabsComponent } from './tabs.component';
|
import { TabsComponent } from "./tabs.component";
|
||||||
|
|
||||||
import { ExcludedDomainsComponent } from './settings/excluded-domains.component';
|
import { ExcludedDomainsComponent } from "./settings/excluded-domains.component";
|
||||||
import { ExportComponent } from './settings/export.component';
|
import { ExportComponent } from "./settings/export.component";
|
||||||
import { FolderAddEditComponent } from './settings/folder-add-edit.component';
|
import { FolderAddEditComponent } from "./settings/folder-add-edit.component";
|
||||||
import { FoldersComponent } from './settings/folders.component';
|
import { FoldersComponent } from "./settings/folders.component";
|
||||||
import { OptionsComponent } from './settings/options.component';
|
import { OptionsComponent } from "./settings/options.component";
|
||||||
import { PremiumComponent } from './settings/premium.component';
|
import { PremiumComponent } from "./settings/premium.component";
|
||||||
import { SettingsComponent } from './settings/settings.component';
|
import { SettingsComponent } from "./settings/settings.component";
|
||||||
import { SyncComponent } from './settings/sync.component';
|
import { SyncComponent } from "./settings/sync.component";
|
||||||
import { VaultTimeoutInputComponent } from './settings/vault-timeout-input.component';
|
import { VaultTimeoutInputComponent } from "./settings/vault-timeout-input.component";
|
||||||
|
|
||||||
import { AddEditCustomFieldsComponent } from './vault/add-edit-custom-fields.component';
|
import { AddEditCustomFieldsComponent } from "./vault/add-edit-custom-fields.component";
|
||||||
import { AddEditComponent } from './vault/add-edit.component';
|
import { AddEditComponent } from "./vault/add-edit.component";
|
||||||
import { AttachmentsComponent } from './vault/attachments.component';
|
import { AttachmentsComponent } from "./vault/attachments.component";
|
||||||
import { CiphersComponent } from './vault/ciphers.component';
|
import { CiphersComponent } from "./vault/ciphers.component";
|
||||||
import { CollectionsComponent } from './vault/collections.component';
|
import { CollectionsComponent } from "./vault/collections.component";
|
||||||
import { CurrentTabComponent } from './vault/current-tab.component';
|
import { CurrentTabComponent } from "./vault/current-tab.component";
|
||||||
import { GroupingsComponent } from './vault/groupings.component';
|
import { GroupingsComponent } from "./vault/groupings.component";
|
||||||
import { PasswordHistoryComponent } from './vault/password-history.component';
|
import { PasswordHistoryComponent } from "./vault/password-history.component";
|
||||||
import { ShareComponent } from './vault/share.component';
|
import { ShareComponent } from "./vault/share.component";
|
||||||
import { ViewCustomFieldsComponent } from './vault/view-custom-fields.component';
|
import { ViewCustomFieldsComponent } from "./vault/view-custom-fields.component";
|
||||||
import { ViewComponent } from './vault/view.component';
|
import { ViewComponent } from "./vault/view.component";
|
||||||
|
|
||||||
import { EffluxDatesComponent as SendEffluxDatesComponent } from './send/efflux-dates.component';
|
import { EffluxDatesComponent as SendEffluxDatesComponent } from "./send/efflux-dates.component";
|
||||||
import { SendAddEditComponent } from './send/send-add-edit.component';
|
import { SendAddEditComponent } from "./send/send-add-edit.component";
|
||||||
import { SendGroupingsComponent } from './send/send-groupings.component';
|
import { SendGroupingsComponent } from "./send/send-groupings.component";
|
||||||
import { SendTypeComponent } from './send/send-type.component';
|
import { SendTypeComponent } from "./send/send-type.component";
|
||||||
|
|
||||||
import { A11yTitleDirective } from 'jslib-angular/directives/a11y-title.directive';
|
import { A11yTitleDirective } from "jslib-angular/directives/a11y-title.directive";
|
||||||
import { ApiActionDirective } from 'jslib-angular/directives/api-action.directive';
|
import { ApiActionDirective } from "jslib-angular/directives/api-action.directive";
|
||||||
import { AutofocusDirective } from 'jslib-angular/directives/autofocus.directive';
|
import { AutofocusDirective } from "jslib-angular/directives/autofocus.directive";
|
||||||
import { BlurClickDirective } from 'jslib-angular/directives/blur-click.directive';
|
import { BlurClickDirective } from "jslib-angular/directives/blur-click.directive";
|
||||||
import { BoxRowDirective } from 'jslib-angular/directives/box-row.directive';
|
import { BoxRowDirective } from "jslib-angular/directives/box-row.directive";
|
||||||
import { CipherListVirtualScroll } from 'jslib-angular/directives/cipherListVirtualScroll.directive';
|
import { CipherListVirtualScroll } from "jslib-angular/directives/cipherListVirtualScroll.directive";
|
||||||
import { FallbackSrcDirective } from 'jslib-angular/directives/fallback-src.directive';
|
import { FallbackSrcDirective } from "jslib-angular/directives/fallback-src.directive";
|
||||||
import { InputVerbatimDirective } from 'jslib-angular/directives/input-verbatim.directive';
|
import { InputVerbatimDirective } from "jslib-angular/directives/input-verbatim.directive";
|
||||||
import { SelectCopyDirective } from 'jslib-angular/directives/select-copy.directive';
|
import { SelectCopyDirective } from "jslib-angular/directives/select-copy.directive";
|
||||||
import { StopClickDirective } from 'jslib-angular/directives/stop-click.directive';
|
import { StopClickDirective } from "jslib-angular/directives/stop-click.directive";
|
||||||
import { StopPropDirective } from 'jslib-angular/directives/stop-prop.directive';
|
import { StopPropDirective } from "jslib-angular/directives/stop-prop.directive";
|
||||||
import { TrueFalseValueDirective } from 'jslib-angular/directives/true-false-value.directive';
|
import { TrueFalseValueDirective } from "jslib-angular/directives/true-false-value.directive";
|
||||||
|
|
||||||
import { ColorPasswordPipe } from 'jslib-angular/pipes/color-password.pipe';
|
import { ColorPasswordPipe } from "jslib-angular/pipes/color-password.pipe";
|
||||||
import { I18nPipe } from 'jslib-angular/pipes/i18n.pipe';
|
import { I18nPipe } from "jslib-angular/pipes/i18n.pipe";
|
||||||
import { SearchCiphersPipe } from 'jslib-angular/pipes/search-ciphers.pipe';
|
import { SearchCiphersPipe } from "jslib-angular/pipes/search-ciphers.pipe";
|
||||||
|
|
||||||
import { ActionButtonsComponent } from './components/action-buttons.component';
|
import { ActionButtonsComponent } from "./components/action-buttons.component";
|
||||||
import { CipherRowComponent } from './components/cipher-row.component';
|
import { CipherRowComponent } from "./components/cipher-row.component";
|
||||||
import { PasswordRepromptComponent } from './components/password-reprompt.component';
|
import { PasswordRepromptComponent } from "./components/password-reprompt.component";
|
||||||
import { PopOutComponent } from './components/pop-out.component';
|
import { PopOutComponent } from "./components/pop-out.component";
|
||||||
import { SendListComponent } from './components/send-list.component';
|
import { SendListComponent } from "./components/send-list.component";
|
||||||
import { SetPinComponent } from './components/set-pin.component';
|
import { SetPinComponent } from "./components/set-pin.component";
|
||||||
import { VerifyMasterPasswordComponent } from './components/verify-master-password.component';
|
import { VerifyMasterPasswordComponent } from "./components/verify-master-password.component";
|
||||||
|
|
||||||
import { CalloutComponent } from 'jslib-angular/components/callout.component';
|
import { CalloutComponent } from "jslib-angular/components/callout.component";
|
||||||
import { IconComponent } from 'jslib-angular/components/icon.component';
|
import { IconComponent } from "jslib-angular/components/icon.component";
|
||||||
import { BitwardenToastModule } from 'jslib-angular/components/toastr.component';
|
import { BitwardenToastModule } from "jslib-angular/components/toastr.component";
|
||||||
|
|
||||||
import {
|
import { CurrencyPipe, DatePipe, registerLocaleData } from "@angular/common";
|
||||||
CurrencyPipe,
|
import localeAz from "@angular/common/locales/az";
|
||||||
DatePipe,
|
import localeBe from "@angular/common/locales/be";
|
||||||
registerLocaleData,
|
import localeBg from "@angular/common/locales/bg";
|
||||||
} from '@angular/common';
|
import localeBn from "@angular/common/locales/bn";
|
||||||
import localeAz from '@angular/common/locales/az';
|
import localeCa from "@angular/common/locales/ca";
|
||||||
import localeBe from '@angular/common/locales/be';
|
import localeCs from "@angular/common/locales/cs";
|
||||||
import localeBg from '@angular/common/locales/bg';
|
import localeDa from "@angular/common/locales/da";
|
||||||
import localeBn from '@angular/common/locales/bn';
|
import localeDe from "@angular/common/locales/de";
|
||||||
import localeCa from '@angular/common/locales/ca';
|
import localeEl from "@angular/common/locales/el";
|
||||||
import localeCs from '@angular/common/locales/cs';
|
import localeEnGb from "@angular/common/locales/en-GB";
|
||||||
import localeDa from '@angular/common/locales/da';
|
import localeEnIn from "@angular/common/locales/en-IN";
|
||||||
import localeDe from '@angular/common/locales/de';
|
import localeEs from "@angular/common/locales/es";
|
||||||
import localeEl from '@angular/common/locales/el';
|
import localeEt from "@angular/common/locales/et";
|
||||||
import localeEnGb from '@angular/common/locales/en-GB';
|
import localeFa from "@angular/common/locales/fa";
|
||||||
import localeEnIn from '@angular/common/locales/en-IN';
|
import localeFi from "@angular/common/locales/fi";
|
||||||
import localeEs from '@angular/common/locales/es';
|
import localeFr from "@angular/common/locales/fr";
|
||||||
import localeEt from '@angular/common/locales/et';
|
import localeHe from "@angular/common/locales/he";
|
||||||
import localeFa from '@angular/common/locales/fa';
|
import localeHr from "@angular/common/locales/hr";
|
||||||
import localeFi from '@angular/common/locales/fi';
|
import localeHu from "@angular/common/locales/hu";
|
||||||
import localeFr from '@angular/common/locales/fr';
|
import localeId from "@angular/common/locales/id";
|
||||||
import localeHe from '@angular/common/locales/he';
|
import localeIt from "@angular/common/locales/it";
|
||||||
import localeHr from '@angular/common/locales/hr';
|
import localeJa from "@angular/common/locales/ja";
|
||||||
import localeHu from '@angular/common/locales/hu';
|
import localeKn from "@angular/common/locales/kn";
|
||||||
import localeId from '@angular/common/locales/id';
|
import localeKo from "@angular/common/locales/ko";
|
||||||
import localeIt from '@angular/common/locales/it';
|
import localeLv from "@angular/common/locales/lv";
|
||||||
import localeJa from '@angular/common/locales/ja';
|
import localeMl from "@angular/common/locales/ml";
|
||||||
import localeKn from '@angular/common/locales/kn';
|
import localeNb from "@angular/common/locales/nb";
|
||||||
import localeKo from '@angular/common/locales/ko';
|
import localeNl from "@angular/common/locales/nl";
|
||||||
import localeLv from '@angular/common/locales/lv';
|
import localePl from "@angular/common/locales/pl";
|
||||||
import localeMl from '@angular/common/locales/ml';
|
import localePtBr from "@angular/common/locales/pt";
|
||||||
import localeNb from '@angular/common/locales/nb';
|
import localePtPt from "@angular/common/locales/pt-PT";
|
||||||
import localeNl from '@angular/common/locales/nl';
|
import localeRo from "@angular/common/locales/ro";
|
||||||
import localePl from '@angular/common/locales/pl';
|
import localeRu from "@angular/common/locales/ru";
|
||||||
import localePtBr from '@angular/common/locales/pt';
|
import localeSk from "@angular/common/locales/sk";
|
||||||
import localePtPt from '@angular/common/locales/pt-PT';
|
import localeSr from "@angular/common/locales/sr";
|
||||||
import localeRo from '@angular/common/locales/ro';
|
import localeSv from "@angular/common/locales/sv";
|
||||||
import localeRu from '@angular/common/locales/ru';
|
import localeTh from "@angular/common/locales/th";
|
||||||
import localeSk from '@angular/common/locales/sk';
|
import localeTr from "@angular/common/locales/tr";
|
||||||
import localeSr from '@angular/common/locales/sr';
|
import localeUk from "@angular/common/locales/uk";
|
||||||
import localeSv from '@angular/common/locales/sv';
|
import localeVi from "@angular/common/locales/vi";
|
||||||
import localeTh from '@angular/common/locales/th';
|
import localeZhCn from "@angular/common/locales/zh-Hans";
|
||||||
import localeTr from '@angular/common/locales/tr';
|
import localeZhTw from "@angular/common/locales/zh-Hant";
|
||||||
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(localeAz, "az");
|
||||||
registerLocaleData(localeBe, 'be');
|
registerLocaleData(localeBe, "be");
|
||||||
registerLocaleData(localeBg, 'bg');
|
registerLocaleData(localeBg, "bg");
|
||||||
registerLocaleData(localeBn, 'bn');
|
registerLocaleData(localeBn, "bn");
|
||||||
registerLocaleData(localeCa, 'ca');
|
registerLocaleData(localeCa, "ca");
|
||||||
registerLocaleData(localeCs, 'cs');
|
registerLocaleData(localeCs, "cs");
|
||||||
registerLocaleData(localeDa, 'da');
|
registerLocaleData(localeDa, "da");
|
||||||
registerLocaleData(localeDe, 'de');
|
registerLocaleData(localeDe, "de");
|
||||||
registerLocaleData(localeEl, 'el');
|
registerLocaleData(localeEl, "el");
|
||||||
registerLocaleData(localeEnGb, 'en-GB');
|
registerLocaleData(localeEnGb, "en-GB");
|
||||||
registerLocaleData(localeEnIn, 'en-IN');
|
registerLocaleData(localeEnIn, "en-IN");
|
||||||
registerLocaleData(localeEs, 'es');
|
registerLocaleData(localeEs, "es");
|
||||||
registerLocaleData(localeEt, 'et');
|
registerLocaleData(localeEt, "et");
|
||||||
registerLocaleData(localeFa, 'fa');
|
registerLocaleData(localeFa, "fa");
|
||||||
registerLocaleData(localeFi, 'fi');
|
registerLocaleData(localeFi, "fi");
|
||||||
registerLocaleData(localeFr, 'fr');
|
registerLocaleData(localeFr, "fr");
|
||||||
registerLocaleData(localeHe, 'he');
|
registerLocaleData(localeHe, "he");
|
||||||
registerLocaleData(localeHr, 'hr');
|
registerLocaleData(localeHr, "hr");
|
||||||
registerLocaleData(localeHu, 'hu');
|
registerLocaleData(localeHu, "hu");
|
||||||
registerLocaleData(localeId, 'id');
|
registerLocaleData(localeId, "id");
|
||||||
registerLocaleData(localeIt, 'it');
|
registerLocaleData(localeIt, "it");
|
||||||
registerLocaleData(localeJa, 'ja');
|
registerLocaleData(localeJa, "ja");
|
||||||
registerLocaleData(localeKo, 'ko');
|
registerLocaleData(localeKo, "ko");
|
||||||
registerLocaleData(localeKn, 'kn');
|
registerLocaleData(localeKn, "kn");
|
||||||
registerLocaleData(localeLv, 'lv');
|
registerLocaleData(localeLv, "lv");
|
||||||
registerLocaleData(localeMl, 'ml');
|
registerLocaleData(localeMl, "ml");
|
||||||
registerLocaleData(localeNb, 'nb');
|
registerLocaleData(localeNb, "nb");
|
||||||
registerLocaleData(localeNl, 'nl');
|
registerLocaleData(localeNl, "nl");
|
||||||
registerLocaleData(localePl, 'pl');
|
registerLocaleData(localePl, "pl");
|
||||||
registerLocaleData(localePtBr, 'pt-BR');
|
registerLocaleData(localePtBr, "pt-BR");
|
||||||
registerLocaleData(localePtPt, 'pt-PT');
|
registerLocaleData(localePtPt, "pt-PT");
|
||||||
registerLocaleData(localeRo, 'ro');
|
registerLocaleData(localeRo, "ro");
|
||||||
registerLocaleData(localeRu, 'ru');
|
registerLocaleData(localeRu, "ru");
|
||||||
registerLocaleData(localeSk, 'sk');
|
registerLocaleData(localeSk, "sk");
|
||||||
registerLocaleData(localeSr, 'sr');
|
registerLocaleData(localeSr, "sr");
|
||||||
registerLocaleData(localeSv, 'sv');
|
registerLocaleData(localeSv, "sv");
|
||||||
registerLocaleData(localeTh, 'th');
|
registerLocaleData(localeTh, "th");
|
||||||
registerLocaleData(localeTr, 'tr');
|
registerLocaleData(localeTr, "tr");
|
||||||
registerLocaleData(localeUk, 'uk');
|
registerLocaleData(localeUk, "uk");
|
||||||
registerLocaleData(localeVi, 'vi');
|
registerLocaleData(localeVi, "vi");
|
||||||
registerLocaleData(localeZhCn, 'zh-CN');
|
registerLocaleData(localeZhCn, "zh-CN");
|
||||||
registerLocaleData(localeZhTw, 'zh-TW');
|
registerLocaleData(localeZhTw, "zh-TW");
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
A11yModule,
|
A11yModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
DragDropModule,
|
DragDropModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
ScrollingModule,
|
ScrollingModule,
|
||||||
ServicesModule,
|
ServicesModule,
|
||||||
BitwardenToastModule.forRoot({
|
BitwardenToastModule.forRoot({
|
||||||
maxOpened: 2,
|
maxOpened: 2,
|
||||||
autoDismiss: true,
|
autoDismiss: true,
|
||||||
closeButton: true,
|
closeButton: true,
|
||||||
positionClass: 'toast-bottom-full-width',
|
positionClass: "toast-bottom-full-width",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
A11yTitleDirective,
|
A11yTitleDirective,
|
||||||
ActionButtonsComponent,
|
ActionButtonsComponent,
|
||||||
AddEditComponent,
|
AddEditComponent,
|
||||||
AddEditCustomFieldsComponent,
|
AddEditCustomFieldsComponent,
|
||||||
ApiActionDirective,
|
ApiActionDirective,
|
||||||
AppComponent,
|
AppComponent,
|
||||||
AttachmentsComponent,
|
AttachmentsComponent,
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
BlurClickDirective,
|
BlurClickDirective,
|
||||||
BoxRowDirective,
|
BoxRowDirective,
|
||||||
CalloutComponent,
|
CalloutComponent,
|
||||||
CipherListVirtualScroll,
|
CipherListVirtualScroll,
|
||||||
CipherRowComponent,
|
CipherRowComponent,
|
||||||
CiphersComponent,
|
CiphersComponent,
|
||||||
CollectionsComponent,
|
CollectionsComponent,
|
||||||
ColorPasswordPipe,
|
ColorPasswordPipe,
|
||||||
CurrentTabComponent,
|
CurrentTabComponent,
|
||||||
EnvironmentComponent,
|
EnvironmentComponent,
|
||||||
ExcludedDomainsComponent,
|
ExcludedDomainsComponent,
|
||||||
ExportComponent,
|
ExportComponent,
|
||||||
FallbackSrcDirective,
|
FallbackSrcDirective,
|
||||||
FolderAddEditComponent,
|
FolderAddEditComponent,
|
||||||
FoldersComponent,
|
FoldersComponent,
|
||||||
GroupingsComponent,
|
GroupingsComponent,
|
||||||
HintComponent,
|
HintComponent,
|
||||||
HomeComponent,
|
HomeComponent,
|
||||||
I18nPipe,
|
I18nPipe,
|
||||||
IconComponent,
|
IconComponent,
|
||||||
InputVerbatimDirective,
|
InputVerbatimDirective,
|
||||||
LockComponent,
|
LockComponent,
|
||||||
LoginComponent,
|
LoginComponent,
|
||||||
OptionsComponent,
|
OptionsComponent,
|
||||||
PasswordGeneratorComponent,
|
PasswordGeneratorComponent,
|
||||||
PasswordGeneratorHistoryComponent,
|
PasswordGeneratorHistoryComponent,
|
||||||
PasswordHistoryComponent,
|
PasswordHistoryComponent,
|
||||||
PasswordRepromptComponent,
|
PasswordRepromptComponent,
|
||||||
PopOutComponent,
|
PopOutComponent,
|
||||||
PremiumComponent,
|
PremiumComponent,
|
||||||
PrivateModeComponent,
|
PrivateModeComponent,
|
||||||
RegisterComponent,
|
RegisterComponent,
|
||||||
SearchCiphersPipe,
|
SearchCiphersPipe,
|
||||||
SelectCopyDirective,
|
SelectCopyDirective,
|
||||||
SendAddEditComponent,
|
SendAddEditComponent,
|
||||||
SendEffluxDatesComponent,
|
SendEffluxDatesComponent,
|
||||||
SendGroupingsComponent,
|
SendGroupingsComponent,
|
||||||
SendListComponent,
|
SendListComponent,
|
||||||
SendTypeComponent,
|
SendTypeComponent,
|
||||||
SetPasswordComponent,
|
SetPasswordComponent,
|
||||||
SetPinComponent,
|
SetPinComponent,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
ShareComponent,
|
ShareComponent,
|
||||||
SsoComponent,
|
SsoComponent,
|
||||||
StopClickDirective,
|
StopClickDirective,
|
||||||
StopPropDirective,
|
StopPropDirective,
|
||||||
SyncComponent,
|
SyncComponent,
|
||||||
TabsComponent,
|
TabsComponent,
|
||||||
TrueFalseValueDirective,
|
TrueFalseValueDirective,
|
||||||
TwoFactorComponent,
|
TwoFactorComponent,
|
||||||
TwoFactorOptionsComponent,
|
TwoFactorOptionsComponent,
|
||||||
UpdateTempPasswordComponent,
|
UpdateTempPasswordComponent,
|
||||||
VaultTimeoutInputComponent,
|
VaultTimeoutInputComponent,
|
||||||
VerifyMasterPasswordComponent,
|
VerifyMasterPasswordComponent,
|
||||||
ViewComponent,
|
ViewComponent,
|
||||||
ViewCustomFieldsComponent,
|
ViewCustomFieldsComponent,
|
||||||
RemovePasswordComponent,
|
RemovePasswordComponent,
|
||||||
],
|
],
|
||||||
entryComponents: [],
|
entryComponents: [],
|
||||||
providers: [
|
providers: [CurrencyPipe, DatePipe],
|
||||||
CurrencyPipe,
|
bootstrap: [AppComponent],
|
||||||
DatePipe,
|
|
||||||
],
|
|
||||||
bootstrap: [AppComponent],
|
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule {}
|
||||||
|
|
|
@ -1,41 +1,87 @@
|
||||||
<span class="row-btn" (click)="view()" appStopClick appStopProp appA11yTitle="{{'view' | i18n}}" *ngIf="showView">
|
<span
|
||||||
<i class="fa fa-lg fa-list-alt" aria-hidden="true"></i>
|
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>
|
</span>
|
||||||
<ng-container *ngIf="cipher.type === cipherType.Login">
|
<ng-container *ngIf="cipher.type === cipherType.Login">
|
||||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'launch' | i18n}}" (click)="launchCipher()"
|
<span
|
||||||
*ngIf="!showView" [ngClass]="{disabled: !cipher.login.canLaunch}">
|
class="row-btn"
|
||||||
<i class="fa fa-lg fa-share-square-o" aria-hidden="true"></i>
|
appStopClick
|
||||||
</span>
|
appStopProp
|
||||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyUsername' | i18n}}"
|
appA11yTitle="{{ 'launch' | i18n }}"
|
||||||
(click)="copy(cipher, cipher.login.username, 'username', 'Username')"
|
(click)="launchCipher()"
|
||||||
[ngClass]="{disabled: !cipher.login.username}">
|
*ngIf="!showView"
|
||||||
<i class="fa fa-lg fa-user" aria-hidden="true"></i>
|
[ngClass]="{ disabled: !cipher.login.canLaunch }"
|
||||||
</span>
|
>
|
||||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyPassword' | i18n}}"
|
<i class="fa fa-lg fa-share-square-o" aria-hidden="true"></i>
|
||||||
(click)="copy(cipher, cipher.login.password, 'password', 'Password')"
|
</span>
|
||||||
[ngClass]="{disabled: (!cipher.login.password || !cipher.viewPassword)}">
|
<span
|
||||||
<i class="fa fa-lg fa-key" aria-hidden="true"></i>
|
class="row-btn"
|
||||||
</span>
|
appStopClick
|
||||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyVerificationCode' | i18n}}"
|
appStopProp
|
||||||
(click)="copy(cipher, cipher.login.totp, 'verificationCodeTotp', 'TOTP')"
|
appA11yTitle="{{ 'copyUsername' | i18n }}"
|
||||||
[ngClass]="{disabled: (!displayTotpCopyButton(cipher))}">
|
(click)="copy(cipher, cipher.login.username, 'username', 'Username')"
|
||||||
<i class="fa fa-lg fa-clock-o" aria-hidden="true"></i>
|
[ngClass]="{ disabled: !cipher.login.username }"
|
||||||
</span>
|
>
|
||||||
|
<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>
|
||||||
<ng-container *ngIf="cipher.type === cipherType.Card">
|
<ng-container *ngIf="cipher.type === cipherType.Card">
|
||||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyNumber' | i18n}}"
|
<span
|
||||||
(click)="copy(cipher, cipher.card.number, 'number', 'Card Number')" [ngClass]="{disabled: !cipher.card.number}">
|
class="row-btn"
|
||||||
<i class="fa fa-lg fa-hashtag" aria-hidden="true"></i>
|
appStopClick
|
||||||
</span>
|
appStopProp
|
||||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copySecurityCode' | i18n}}"
|
appA11yTitle="{{ 'copyNumber' | i18n }}"
|
||||||
(click)="copy(cipher, cipher.card.code, 'securityCode', 'Security Code')"
|
(click)="copy(cipher, cipher.card.number, 'number', 'Card Number')"
|
||||||
[ngClass]="{disabled: !cipher.card.code}">
|
[ngClass]="{ disabled: !cipher.card.number }"
|
||||||
<i class="fa fa-lg fa-key" aria-hidden="true"></i>
|
>
|
||||||
</span>
|
<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>
|
||||||
<ng-container *ngIf="cipher.type === cipherType.SecureNote">
|
<ng-container *ngIf="cipher.type === cipherType.SecureNote">
|
||||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copyNote' | i18n}}"
|
<span
|
||||||
(click)="copy(cipher, cipher.notes, 'note', 'Note')" [ngClass]="{disabled: !cipher.notes}">
|
class="row-btn"
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
appStopClick
|
||||||
</span>
|
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>
|
</ng-container>
|
||||||
|
|
|
@ -1,82 +1,88 @@
|
||||||
import {
|
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
Output,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType';
|
import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType";
|
||||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
import { CipherType } from "jslib-common/enums/cipherType";
|
||||||
import { EventType } from 'jslib-common/enums/eventType';
|
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 { EventService } from "jslib-common/abstractions/event.service";
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { TotpService } from 'jslib-common/abstractions/totp.service';
|
import { TotpService } from "jslib-common/abstractions/totp.service";
|
||||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
import { UserService } from "jslib-common/abstractions/user.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-action-buttons',
|
selector: "app-action-buttons",
|
||||||
templateUrl: 'action-buttons.component.html',
|
templateUrl: "action-buttons.component.html",
|
||||||
})
|
})
|
||||||
export class ActionButtonsComponent {
|
export class ActionButtonsComponent {
|
||||||
@Output() onView = new EventEmitter<CipherView>();
|
@Output() onView = new EventEmitter<CipherView>();
|
||||||
@Output() launchEvent = new EventEmitter<CipherView>();
|
@Output() launchEvent = new EventEmitter<CipherView>();
|
||||||
@Input() cipher: CipherView;
|
@Input() cipher: CipherView;
|
||||||
@Input() showView = false;
|
@Input() showView = false;
|
||||||
|
|
||||||
cipherType = CipherType;
|
cipherType = CipherType;
|
||||||
userHasPremiumAccess = false;
|
userHasPremiumAccess = false;
|
||||||
|
|
||||||
constructor(private i18nService: I18nService,
|
constructor(
|
||||||
private platformUtilsService: PlatformUtilsService, private eventService: EventService,
|
private i18nService: I18nService,
|
||||||
private totpService: TotpService, private userService: UserService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private passwordRepromptService: PasswordRepromptService) { }
|
private eventService: EventService,
|
||||||
|
private totpService: TotpService,
|
||||||
|
private userService: UserService,
|
||||||
|
private passwordRepromptService: PasswordRepromptService
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.userHasPremiumAccess = await this.userService.canAccessPremium();
|
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() {
|
if (value == null || (aType === "TOTP" && !this.displayTotpCopyButton(cipher))) {
|
||||||
this.launchEvent.emit(this.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 (!cipher.viewPassword) {
|
||||||
if (this.cipher.reprompt !== CipherRepromptType.None && this.passwordRepromptService.protectedFields().includes(aType) &&
|
return;
|
||||||
!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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
displayTotpCopyButton(cipher: CipherView) {
|
this.platformUtilsService.copyToClipboard(value, { window: window });
|
||||||
return (cipher?.login?.hasTotp ?? false) &&
|
this.platformUtilsService.showToast(
|
||||||
(cipher.organizationUseTotp || this.userHasPremiumAccess);
|
"info",
|
||||||
}
|
null,
|
||||||
|
this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey))
|
||||||
|
);
|
||||||
|
|
||||||
view() {
|
if (typeI18nKey === "password" || typeI18nKey === "verificationCodeTotp") {
|
||||||
this.onView.emit(this.cipher);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,38 @@
|
||||||
<button type="button" (click)="selectCipher(cipher)" (dblclick)="launchCipher(cipher)" appStopClick
|
<button
|
||||||
title="{{title}} - {{cipher.name}}" class="box-content-row box-content-row-flex virtual-scroll-item">
|
type="button"
|
||||||
<div class="row-main">
|
(click)="selectCipher(cipher)"
|
||||||
<app-vault-icon [cipher]="cipher"></app-vault-icon>
|
(dblclick)="launchCipher(cipher)"
|
||||||
<div class="row-main-content">
|
appStopClick
|
||||||
<span class="text">
|
title="{{ title }} - {{ cipher.name }}"
|
||||||
{{cipher.name}}
|
class="box-content-row box-content-row-flex virtual-scroll-item"
|
||||||
<ng-container *ngIf="cipher.organizationId">
|
>
|
||||||
<i class="fa fa-cube text-muted" title="{{'shared' | i18n}}" aria-hidden="true"></i>
|
<div class="row-main">
|
||||||
<span class="sr-only">{{'shared' | i18n}}</span>
|
<app-vault-icon [cipher]="cipher"></app-vault-icon>
|
||||||
</ng-container>
|
<div class="row-main-content">
|
||||||
<ng-container *ngIf="cipher.hasAttachments">
|
<span class="text">
|
||||||
<i class="fa fa-paperclip text-muted" title="{{'attachments' | i18n}}" aria-hidden="true"></i>
|
{{ cipher.name }}
|
||||||
<span class="sr-only">{{'attachments' | i18n}}</span>
|
<ng-container *ngIf="cipher.organizationId">
|
||||||
</ng-container>
|
<i class="fa fa-cube text-muted" title="{{ 'shared' | i18n }}" aria-hidden="true"></i>
|
||||||
</span>
|
<span class="sr-only">{{ "shared" | i18n }}</span>
|
||||||
<span class="detail">{{cipher.subTitle}}</span>
|
</ng-container>
|
||||||
</div>
|
<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>
|
</div>
|
||||||
<app-action-buttons [cipher]="cipher" [showView]="showView" (onView)="viewCipher(cipher)" (launchEvent)="launchCipher(cipher)"
|
</div>
|
||||||
class="action-buttons">
|
<app-action-buttons
|
||||||
</app-action-buttons>
|
[cipher]="cipher"
|
||||||
|
[showView]="showView"
|
||||||
|
(onView)="viewCipher(cipher)"
|
||||||
|
(launchEvent)="launchCipher(cipher)"
|
||||||
|
class="action-buttons"
|
||||||
|
>
|
||||||
|
</app-action-buttons>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,33 +1,28 @@
|
||||||
import {
|
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
Output,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-cipher-row',
|
selector: "app-cipher-row",
|
||||||
templateUrl: 'cipher-row.component.html',
|
templateUrl: "cipher-row.component.html",
|
||||||
})
|
})
|
||||||
export class CipherRowComponent {
|
export class CipherRowComponent {
|
||||||
@Output() onSelected = new EventEmitter<CipherView>();
|
@Output() onSelected = new EventEmitter<CipherView>();
|
||||||
@Output() launchEvent = new EventEmitter<CipherView>();
|
@Output() launchEvent = new EventEmitter<CipherView>();
|
||||||
@Output() onView = new EventEmitter<CipherView>();
|
@Output() onView = new EventEmitter<CipherView>();
|
||||||
@Input() cipher: CipherView;
|
@Input() cipher: CipherView;
|
||||||
@Input() showView = false;
|
@Input() showView = false;
|
||||||
@Input() title: string;
|
@Input() title: string;
|
||||||
|
|
||||||
selectCipher(c: CipherView) {
|
selectCipher(c: CipherView) {
|
||||||
this.onSelected.emit(c);
|
this.onSelected.emit(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
launchCipher(c: CipherView) {
|
launchCipher(c: CipherView) {
|
||||||
this.launchEvent.emit(c);
|
this.launchEvent.emit(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
viewCipher(c: CipherView) {
|
viewCipher(c: CipherView) {
|
||||||
this.onView.emit(c);
|
this.onView.emit(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,56 @@
|
||||||
<div class="modal fade" role="dialog" aria-modal="true">
|
<div class="modal fade" role="dialog" aria-modal="true">
|
||||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||||
<form class="modal-content" #form (ngSubmit)="submit()">
|
<form class="modal-content" #form (ngSubmit)="submit()">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h1 class="box-header">{{'passwordConfirmation' | i18n}}</h1>
|
<h1 class="box-header">{{ "passwordConfirmation" | i18n }}</h1>
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||||
<div class="row-main">
|
<div class="row-main">
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||||
<input id="masterPassword" type="{{showPassword ? 'text' : 'password'}}" name="MasterPassword"
|
<input
|
||||||
class="monospaced" [(ngModel)]="masterPassword" required appAutofocus>
|
id="masterPassword"
|
||||||
</div>
|
type="{{ showPassword ? 'text' : 'password' }}"
|
||||||
<div class="action-buttons">
|
name="MasterPassword"
|
||||||
<button type="button" class="row-btn" appStopClick appBlurClick role="button"
|
class="monospaced"
|
||||||
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePassword()" [attr.aria-pressed]="showPassword">
|
[(ngModel)]="masterPassword"
|
||||||
<i class="fa fa-lg" aria-hidden="true"
|
required
|
||||||
[ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"></i>
|
appAutofocus
|
||||||
</button>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="action-buttons">
|
||||||
</div>
|
<button
|
||||||
<div class="box-footer">
|
type="button"
|
||||||
{{'passwordConfirmationDesc' | i18n}}
|
class="row-btn"
|
||||||
</div>
|
appStopClick
|
||||||
</div>
|
appBlurClick
|
||||||
</div>
|
role="button"
|
||||||
<div class="modal-footer">
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
|
(click)="togglePassword()"
|
||||||
<span>{{'ok' | i18n}}</span>
|
[attr.aria-pressed]="showPassword"
|
||||||
</button>
|
>
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
<i
|
||||||
{{'cancel' | i18n}}
|
class="fa fa-lg"
|
||||||
|
aria-hidden="true"
|
||||||
|
[ngClass]="{ 'fa-eye': !showPassword, 'fa-eye-slash': showPassword }"
|
||||||
|
></i>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
|
@ -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({
|
@Component({
|
||||||
templateUrl: 'password-reprompt.component.html',
|
templateUrl: "password-reprompt.component.html",
|
||||||
})
|
})
|
||||||
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}
|
export class PasswordRepromptComponent extends BasePasswordRepromptComponent {}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<button type="button" (click)="expand()" appA11yTitle="{{'popOutNewWindow' | i18n}}">
|
<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>
|
<i class="fa fa-external-link fa-rotate-270 fa-lg fa-fw" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
|
@ -1,32 +1,30 @@
|
||||||
import {
|
import { Component, Input, OnInit } from "@angular/core";
|
||||||
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({
|
@Component({
|
||||||
selector: 'app-pop-out',
|
selector: "app-pop-out",
|
||||||
templateUrl: 'pop-out.component.html',
|
templateUrl: "pop-out.component.html",
|
||||||
})
|
})
|
||||||
export class PopOutComponent implements OnInit {
|
export class PopOutComponent implements OnInit {
|
||||||
@Input() show = true;
|
@Input() show = true;
|
||||||
|
|
||||||
constructor(private platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
private popupUtilsService: PopupUtilsService) { }
|
private platformUtilsService: PlatformUtilsService,
|
||||||
|
private popupUtilsService: PopupUtilsService
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.show) {
|
if (this.show) {
|
||||||
if (this.popupUtilsService.inSidebar(window) && this.platformUtilsService.isFirefox()) {
|
if (this.popupUtilsService.inSidebar(window) && this.platformUtilsService.isFirefox()) {
|
||||||
this.show = false;
|
this.show = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
expand() {
|
expand() {
|
||||||
this.popupUtilsService.popOut(window);
|
this.popupUtilsService.popOut(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +1,90 @@
|
||||||
<button type="button" *ngFor="let s of sends" (click)="selectSend(s)" appStopClick title="{{title}} - {{s.name}}"
|
<button
|
||||||
class="box-content-row box-content-row-flex">
|
type="button"
|
||||||
<div class="row-main">
|
*ngFor="let s of sends"
|
||||||
<div class="app-vault-icon">
|
(click)="selectSend(s)"
|
||||||
<div class="icon" aria-hidden="true">
|
appStopClick
|
||||||
<i class="fa fa-fw fa-lg fa-file-text-o" *ngIf="s.type === sendType.Text"></i>
|
title="{{ title }} - {{ s.name }}"
|
||||||
<i class="fa fa-fw fa-lg fa-file-o" *ngIf="s.type === sendType.File"></i>
|
class="box-content-row box-content-row-flex"
|
||||||
</div>
|
>
|
||||||
</div>
|
<div class="row-main">
|
||||||
<div class="row-main-content">
|
<div class="app-vault-icon">
|
||||||
<span class="text">
|
<div class="icon" aria-hidden="true">
|
||||||
{{s.name}}
|
<i class="fa fa-fw fa-lg fa-file-text-o" *ngIf="s.type === sendType.Text"></i>
|
||||||
<ng-container *ngIf="s.disabled">
|
<i class="fa fa-fw fa-lg fa-file-o" *ngIf="s.type === sendType.File"></i>
|
||||||
<i class="fa fa-warning text-muted" title="{{'disabled' | i18n}}" aria-hidden="true"></i>
|
</div>
|
||||||
<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>
|
||||||
<div class="action-buttons">
|
<div class="row-main-content">
|
||||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'copySendLink' | i18n}}"
|
<span class="text">
|
||||||
(click)="copySendLink(s)">
|
{{ s.name }}
|
||||||
<i class="fa fa-lg fa-copy" aria-hidden="true"></i>
|
<ng-container *ngIf="s.disabled">
|
||||||
</span>
|
<i
|
||||||
<span class="row-btn" [ngClass]="{'disabled' : disabledByPolicy}" appStopClick appStopProp
|
class="fa fa-warning text-muted"
|
||||||
appA11yTitle="{{'removePassword' | i18n}}" (click)="removePassword(s)" *ngIf="s.password">
|
title="{{ 'disabled' | i18n }}"
|
||||||
<i class="fa fa-lg fa-undo" aria-hidden="true"></i>
|
aria-hidden="true"
|
||||||
</span>
|
></i>
|
||||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'delete' | i18n}}" (click)="delete(s)">
|
<span class="sr-only">{{ "disabled" | i18n }}</span>
|
||||||
<i class="fa fa-lg fa-trash-o" aria-hidden="true"></i>
|
</ng-container>
|
||||||
</span>
|
<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>
|
||||||
|
<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>
|
</button>
|
||||||
|
|
|
@ -1,42 +1,37 @@
|
||||||
import {
|
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
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({
|
@Component({
|
||||||
selector: 'app-send-list',
|
selector: "app-send-list",
|
||||||
templateUrl: 'send-list.component.html',
|
templateUrl: "send-list.component.html",
|
||||||
})
|
})
|
||||||
export class SendListComponent {
|
export class SendListComponent {
|
||||||
@Input() sends: SendView[];
|
@Input() sends: SendView[];
|
||||||
@Input() title: string;
|
@Input() title: string;
|
||||||
@Input() disabledByPolicy = false;
|
@Input() disabledByPolicy = false;
|
||||||
@Output() onSelected = new EventEmitter<SendView>();
|
@Output() onSelected = new EventEmitter<SendView>();
|
||||||
@Output() onCopySendLink = new EventEmitter<SendView>();
|
@Output() onCopySendLink = new EventEmitter<SendView>();
|
||||||
@Output() onRemovePassword = new EventEmitter<SendView>();
|
@Output() onRemovePassword = new EventEmitter<SendView>();
|
||||||
@Output() onDeleteSend = new EventEmitter<SendView>();
|
@Output() onDeleteSend = new EventEmitter<SendView>();
|
||||||
|
|
||||||
sendType = SendType;
|
sendType = SendType;
|
||||||
|
|
||||||
selectSend(s: SendView) {
|
selectSend(s: SendView) {
|
||||||
this.onSelected.emit(s);
|
this.onSelected.emit(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
copySendLink(s: SendView) {
|
copySendLink(s: SendView) {
|
||||||
this.onCopySendLink.emit(s);
|
this.onCopySendLink.emit(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
removePassword(s: SendView) {
|
removePassword(s: SendView) {
|
||||||
this.onRemovePassword.emit(s);
|
this.onRemovePassword.emit(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(s: SendView) {
|
delete(s: SendView) {
|
||||||
this.onDeleteSend.emit(s);
|
this.onDeleteSend.emit(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,65 @@
|
||||||
<div class="modal fade" role="dialog" aria-modal="true">
|
<div class="modal fade" role="dialog" aria-modal="true">
|
||||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||||
<form class="modal-content" #form (ngSubmit)="submit()">
|
<form class="modal-content" #form (ngSubmit)="submit()">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div>
|
<div>
|
||||||
{{'setYourPinCode' | i18n}}
|
{{ "setYourPinCode" | i18n }}
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||||
<div class="row-main">
|
<div class="row-main">
|
||||||
<label for="pin">{{'pin' | i18n}}</label>
|
<label for="pin">{{ "pin" | i18n }}</label>
|
||||||
<input id="pin" type="{{showPin ? 'text' : 'password'}}" name="Pin"
|
<input
|
||||||
class="monospaced" [(ngModel)]="pin" required appInputVerbatim>
|
id="pin"
|
||||||
</div>
|
type="{{ showPin ? 'text' : 'password' }}"
|
||||||
<div class="action-buttons">
|
name="Pin"
|
||||||
<button type="button" class="row-btn" appStopClick appBlurClick appA11yTitle="{{'toggleVisibility' | i18n}}"
|
class="monospaced"
|
||||||
(click)="toggleVisibility()" [attr.aria-pressed]="showPin">
|
[(ngModel)]="pin"
|
||||||
<i class="fa fa-lg" aria-hidden="true"
|
required
|
||||||
[ngClass]="{'fa-eye': !showPin, 'fa-eye-slash': showPin}"></i>
|
appInputVerbatim
|
||||||
</button>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="action-buttons">
|
||||||
</div>
|
<button
|
||||||
</div>
|
type="button"
|
||||||
<div class="checkbox" *ngIf="showMasterPassOnRestart">
|
class="row-btn"
|
||||||
<label for="masterPasswordOnRestart">
|
appStopClick
|
||||||
<input type="checkbox" id="masterPasswordOnRestart" name="MasterPasswordOnRestart"
|
appBlurClick
|
||||||
[(ngModel)]="masterPassOnRestart">
|
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||||
<span>{{'lockWithMasterPassOnRestart' | i18n}}</span>
|
(click)="toggleVisibility()"
|
||||||
</label>
|
[attr.aria-pressed]="showPin"
|
||||||
</div>
|
>
|
||||||
</div>
|
<i
|
||||||
<div class="modal-footer">
|
class="fa fa-lg"
|
||||||
<button type="submit" class="btn btn-primary btn-submit" appBlurClick>
|
aria-hidden="true"
|
||||||
<span>{{'ok' | i18n}}</span>
|
[ngClass]="{ 'fa-eye': !showPin, 'fa-eye-slash': showPin }"
|
||||||
</button>
|
></i>
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
|
||||||
{{'cancel' | i18n}}
|
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
|
@ -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({
|
@Component({
|
||||||
templateUrl: 'set-pin.component.html',
|
templateUrl: "set-pin.component.html",
|
||||||
})
|
})
|
||||||
export class SetPinComponent extends BaseSetPinComponent {}
|
export class SetPinComponent extends BaseSetPinComponent {}
|
||||||
|
|
|
@ -1,25 +1,46 @@
|
||||||
<ng-container *ngIf="!usesKeyConnector">
|
<ng-container *ngIf="!usesKeyConnector">
|
||||||
<div class="box-content-row" appBoxRow>
|
<div class="box-content-row" appBoxRow>
|
||||||
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||||
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control"
|
<input
|
||||||
[formControl]="secret" required appAutofocus appInputVerbatim>
|
id="masterPassword"
|
||||||
</div>
|
type="password"
|
||||||
|
name="MasterPasswordHash"
|
||||||
|
class="form-control"
|
||||||
|
[formControl]="secret"
|
||||||
|
required
|
||||||
|
appAutofocus
|
||||||
|
appInputVerbatim
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="usesKeyConnector">
|
<ng-container *ngIf="usesKeyConnector">
|
||||||
<div class="box-content-row" appBoxRow>
|
<div class="box-content-row" appBoxRow>
|
||||||
<label class="d-block">{{'sendVerificationCode' | i18n}}</label>
|
<label class="d-block">{{ "sendVerificationCode" | i18n }}</label>
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="requestOTP()" [disabled]="disableRequestOTP">
|
<button
|
||||||
{{'sendCode' | i18n}}
|
type="button"
|
||||||
</button>
|
class="btn btn-outline-secondary"
|
||||||
<span class="ml-2 text-success" role="alert" @sent *ngIf="sentCode">
|
(click)="requestOTP()"
|
||||||
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
|
[disabled]="disableRequestOTP"
|
||||||
{{'codeSent' | i18n}}
|
>
|
||||||
</span>
|
{{ "sendCode" | i18n }}
|
||||||
</div>
|
</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>
|
<div class="box-content-row" appBoxRow>
|
||||||
<label for="verificationCode">{{'verificationCode' | i18n}}</label>
|
<label for="verificationCode">{{ "verificationCode" | i18n }}</label>
|
||||||
<input id="verificationCode" type="input" name="verificationCode" class="form-control"
|
<input
|
||||||
[formControl]="secret" required appAutofocus appInputVerbatim>
|
id="verificationCode"
|
||||||
</div>
|
type="input"
|
||||||
|
name="verificationCode"
|
||||||
|
class="form-control"
|
||||||
|
[formControl]="secret"
|
||||||
|
required
|
||||||
|
appAutofocus
|
||||||
|
appInputVerbatim
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
|
@ -1,31 +1,23 @@
|
||||||
import {
|
import { animate, style, transition, trigger } from "@angular/animations";
|
||||||
animate,
|
import { Component } from "@angular/core";
|
||||||
style,
|
import { NG_VALUE_ACCESSOR } from "@angular/forms";
|
||||||
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({
|
@Component({
|
||||||
selector: 'app-verify-master-password',
|
selector: "app-verify-master-password",
|
||||||
templateUrl: 'verify-master-password.component.html',
|
templateUrl: "verify-master-password.component.html",
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: NG_VALUE_ACCESSOR,
|
provide: NG_VALUE_ACCESSOR,
|
||||||
multi: true,
|
multi: true,
|
||||||
useExisting: VerifyMasterPasswordComponent,
|
useExisting: VerifyMasterPasswordComponent,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
animations: [
|
animations: [
|
||||||
trigger('sent', [
|
trigger("sent", [
|
||||||
transition(':enter', [
|
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
|
||||||
style({ opacity: 0 }),
|
]),
|
||||||
animate('100ms', style({ opacity: 1 })),
|
],
|
||||||
]),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class VerifyMasterPasswordComponent extends BaseComponent { }
|
export class VerifyMasterPasswordComponent extends BaseComponent {}
|
||||||
|
|
|
@ -1,40 +1,48 @@
|
||||||
<header>
|
<header>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<button type="button" appBlurClick type="button" (click)="close()">
|
<button type="button" appBlurClick type="button" (click)="close()">
|
||||||
<span class="header-icon" aria-hidden="true"><i class="fa fa-chevron-left"></i></span>
|
<span class="header-icon" aria-hidden="true"><i class="fa fa-chevron-left"></i></span>
|
||||||
<span>{{'back' | i18n}}</span>
|
<span>{{ "back" | i18n }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="center">
|
<h1 class="center">
|
||||||
<span class="title">{{'passwordHistory' | i18n}}</span>
|
<span class="title">{{ "passwordHistory" | i18n }}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<button type="button" appBlurClick type="button" (click)="clear()">
|
<button type="button" appBlurClick type="button" (click)="clear()">
|
||||||
{{'clear' | i18n}}
|
{{ "clear" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<content>
|
<content>
|
||||||
<div class="box list full-list" *ngIf="history && history.length">
|
<div class="box list full-list" *ngIf="history && history.length">
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
|
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
|
||||||
<div class="row-main">
|
<div class="row-main">
|
||||||
<div class="row-main-content">
|
<div class="row-main-content">
|
||||||
<div class="monospaced password-wrapper" appSelectCopy
|
<div
|
||||||
[innerHTML]="h.password | colorPassword"></div>
|
class="monospaced password-wrapper"
|
||||||
<span class="detail">{{h.date | date:'medium'}}</span>
|
appSelectCopy
|
||||||
</div>
|
[innerHTML]="h.password | colorPassword"
|
||||||
</div>
|
></div>
|
||||||
<div class="action-buttons">
|
<span class="detail">{{ h.date | date: "medium" }}</span>
|
||||||
<button type="button" class="row-btn" appStopClick appA11yTitle="{{'copyPassword' | i18n}}"
|
</div>
|
||||||
(click)="copy(h.password)">
|
|
||||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</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>
|
||||||
<div class="no-items" *ngIf="!history || !history.length">
|
</div>
|
||||||
<p>{{'noPasswordsInList' | i18n}}</p>
|
<div class="no-items" *ngIf="!history || !history.length">
|
||||||
</div>
|
<p>{{ "noPasswordsInList" | i18n }}</p>
|
||||||
|
</div>
|
||||||
</content>
|
</content>
|
||||||
|
|
|
@ -1,25 +1,27 @@
|
||||||
import { Location } from '@angular/common';
|
import { Location } from "@angular/common";
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
|
|
||||||
import {
|
import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "jslib-angular/components/password-generator-history.component";
|
||||||
PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent,
|
|
||||||
} from 'jslib-angular/components/password-generator-history.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-password-generator-history',
|
selector: "app-password-generator-history",
|
||||||
templateUrl: 'password-generator-history.component.html',
|
templateUrl: "password-generator-history.component.html",
|
||||||
})
|
})
|
||||||
export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
|
export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
|
||||||
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
i18nService: I18nService, private location: Location) {
|
passwordGenerationService: PasswordGenerationService,
|
||||||
super(passwordGenerationService, platformUtilsService, i18nService, window);
|
platformUtilsService: PlatformUtilsService,
|
||||||
}
|
i18nService: I18nService,
|
||||||
|
private location: Location
|
||||||
|
) {
|
||||||
|
super(passwordGenerationService, platformUtilsService, i18nService, window);
|
||||||
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.location.back();
|
this.location.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,128 +1,229 @@
|
||||||
<header>
|
<header>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<app-pop-out [show]="!showSelect"></app-pop-out>
|
<app-pop-out [show]="!showSelect"></app-pop-out>
|
||||||
<button type="button" appBlurClick (click)="close()" *ngIf="showSelect">{{'cancel' | i18n}}</button>
|
<button type="button" appBlurClick (click)="close()" *ngIf="showSelect">
|
||||||
</div>
|
{{ "cancel" | i18n }}
|
||||||
<h1 class="center">
|
</button>
|
||||||
<span class="title">{{'passGen' | i18n}}</span>
|
</div>
|
||||||
</h1>
|
<h1 class="center">
|
||||||
<div class="right">
|
<span class="title">{{ "passGen" | i18n }}</span>
|
||||||
<button type="button" appBlurClick (click)="select()" *ngIf="showSelect">{{'select' | i18n}}</button>
|
</h1>
|
||||||
</div>
|
<div class="right">
|
||||||
|
<button type="button" appBlurClick (click)="select()" *ngIf="showSelect">
|
||||||
|
{{ "select" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<content>
|
<content>
|
||||||
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
|
<app-callout type="info" *ngIf="enforcedPolicyOptions?.inEffect()">
|
||||||
{{'passwordGeneratorPolicyInEffect' | i18n}}
|
{{ "passwordGeneratorPolicyInEffect" | i18n }}
|
||||||
</app-callout>
|
</app-callout>
|
||||||
<div class="password-block">
|
<div class="password-block">
|
||||||
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
|
<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>
|
||||||
<div class="box list">
|
</div>
|
||||||
<div class="box-content single-line">
|
<div class="box list">
|
||||||
<button type="button" class="box-content-row text-primary" appStopClick appBlurClick
|
<div class="box-content single-line">
|
||||||
(click)="regenerate()">{{'regeneratePassword' | i18n}}</button>
|
<a class="box-content-row box-content-row-flex" routerLink="/generator-history">
|
||||||
<button type="button" class="box-content-row text-primary" appStopClick appBlurClick
|
<div class="row-main">{{ "passwordHistory" | i18n }}</div>
|
||||||
(click)="copy()">{{'copyPassword' | i18n}}</button>
|
<i class="fa fa-chevron-right fa-lg row-sub-icon" aria-hidden="true"></i>
|
||||||
</div>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="box list">
|
</div>
|
||||||
<div class="box-content single-line">
|
<div class="box">
|
||||||
<a class="box-content-row box-content-row-flex" routerLink="/generator-history">
|
<h2 class="box-header">
|
||||||
<div class="row-main">{{'passwordHistory' | i18n}}</div>
|
{{ "options" | i18n }}
|
||||||
<i class="fa fa-chevron-right fa-lg row-sub-icon" aria-hidden="true"></i>
|
</h2>
|
||||||
</a>
|
<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>
|
||||||
|
<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>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h2 class="box-header">
|
<div class="box-content">
|
||||||
{{'options' | i18n}}
|
<div class="box-content-row box-content-row-input" appBoxRow>
|
||||||
</h2>
|
<label for="min-number">{{ "minNumbers" | i18n }}</label>
|
||||||
<div class="box-content">
|
<input
|
||||||
<div class="box-content-row">
|
id="min-number"
|
||||||
<label class="sr-only radio-header">{{'type' | i18n}}</label>
|
type="number"
|
||||||
<div class="radio-group text-default" appBoxRow *ngFor="let o of passTypeOptions">
|
min="0"
|
||||||
<input type="radio" [(ngModel)]="options.type" name="Type_{{o.value}}" id="type_{{o.value}}"
|
max="9"
|
||||||
[value]="o.value" (change)="saveOptions()" [checked]="options.type === o.value">
|
(change)="saveOptions()"
|
||||||
<label for="type_{{o.value}}">
|
[(ngModel)]="options.minNumber"
|
||||||
{{o.name}}
|
/>
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</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>
|
||||||
<div class="box" *ngIf="options.type === 'passphrase'">
|
</ng-container>
|
||||||
<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>
|
|
||||||
</content>
|
</content>
|
||||||
|
|
|
@ -1,46 +1,48 @@
|
||||||
import { Location } from '@angular/common';
|
import { Location } from "@angular/common";
|
||||||
import { Component } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { StateService } from 'jslib-common/abstractions/state.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 {
|
import { PasswordGeneratorComponent as BasePasswordGeneratorComponent } from "jslib-angular/components/password-generator.component";
|
||||||
PasswordGeneratorComponent as BasePasswordGeneratorComponent,
|
|
||||||
} from 'jslib-angular/components/password-generator.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-password-generator',
|
selector: "app-password-generator",
|
||||||
templateUrl: 'password-generator.component.html',
|
templateUrl: "password-generator.component.html",
|
||||||
})
|
})
|
||||||
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
|
export class PasswordGeneratorComponent extends BasePasswordGeneratorComponent {
|
||||||
private cipherState: CipherView;
|
private cipherState: CipherView;
|
||||||
|
|
||||||
constructor(passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
|
constructor(
|
||||||
i18nService: I18nService, private stateService: StateService,
|
passwordGenerationService: PasswordGenerationService,
|
||||||
private location: Location) {
|
platformUtilsService: PlatformUtilsService,
|
||||||
super(passwordGenerationService, platformUtilsService, i18nService, window);
|
i18nService: I18nService,
|
||||||
}
|
private stateService: StateService,
|
||||||
|
private location: Location
|
||||||
|
) {
|
||||||
|
super(passwordGenerationService, platformUtilsService, i18nService, window);
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await super.ngOnInit();
|
await super.ngOnInit();
|
||||||
const addEditCipherInfo = await this.stateService.get<any>('addEditCipherInfo');
|
const addEditCipherInfo = await this.stateService.get<any>("addEditCipherInfo");
|
||||||
if (addEditCipherInfo != null) {
|
if (addEditCipherInfo != null) {
|
||||||
this.cipherState = addEditCipherInfo.cipher;
|
this.cipherState = addEditCipherInfo.cipher;
|
||||||
}
|
|
||||||
this.showSelect = this.cipherState != null;
|
|
||||||
}
|
}
|
||||||
|
this.showSelect = this.cipherState != null;
|
||||||
|
}
|
||||||
|
|
||||||
select() {
|
select() {
|
||||||
super.select();
|
super.select();
|
||||||
this.cipherState.login.password = this.password;
|
this.cipherState.login.password = this.password;
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.location.back();
|
this.location.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="__BROWSER__">
|
<html class="__BROWSER__">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Bitwarden</title>
|
<title>Bitwarden</title>
|
||||||
<base href="">
|
<base href="" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root>
|
<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>
|
</app-root>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import { enableProdMode } from '@angular/core';
|
import { enableProdMode } from "@angular/core";
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
|
||||||
|
|
||||||
// tslint:disable-next-line
|
// 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') {
|
if (process.env.ENV === "production") {
|
||||||
enableProdMode();
|
enableProdMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });
|
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
import 'core-js/stable';
|
import "core-js/stable";
|
||||||
import 'date-input-polyfill';
|
import "date-input-polyfill";
|
||||||
import 'web-animations-js';
|
import "web-animations-js";
|
||||||
import 'zone.js/dist/zone';
|
import "zone.js/dist/zone";
|
||||||
/* tslint:enable */
|
/* tslint:enable */
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<p class="text-center">{{privateModeMessage}}</p>
|
<p class="text-center">{{ privateModeMessage }}</p>
|
||||||
<button type="button" class="btn primary block" (click)="learnMore()">
|
<button type="button" class="btn primary block" (click)="learnMore()">
|
||||||
<b>{{learnMoreMessage}}</b>
|
<b>{{ learnMoreMessage }}</b>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
import { BrowserApi } from '../browser/browserApi';
|
import { BrowserApi } from "../browser/browserApi";
|
||||||
|
|
||||||
import {
|
import { Component, OnInit } from "@angular/core";
|
||||||
Component,
|
|
||||||
OnInit,
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-private-mode',
|
selector: "app-private-mode",
|
||||||
templateUrl: 'private-mode.component.html',
|
templateUrl: "private-mode.component.html",
|
||||||
})
|
})
|
||||||
export class PrivateModeComponent implements OnInit {
|
export class PrivateModeComponent implements OnInit {
|
||||||
privateModeMessage: string;
|
privateModeMessage: string;
|
||||||
learnMoreMessage: string;
|
learnMoreMessage: string;
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.privateModeMessage = chrome.i18n.getMessage('privateModeMessage');
|
this.privateModeMessage = chrome.i18n.getMessage("privateModeMessage");
|
||||||
this.learnMoreMessage = chrome.i18n.getMessage('learnMore');
|
this.learnMoreMessage = chrome.i18n.getMessage("learnMore");
|
||||||
}
|
}
|
||||||
|
|
||||||
learnMore() {
|
learnMore() {
|
||||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/extension-wont-load-in-private-mode/');
|
BrowserApi.createNewTab(
|
||||||
}
|
"https://help.bitwarden.com/article/extension-wont-load-in-private-mode/"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,438 +1,461 @@
|
||||||
@import "variables.scss";
|
@import "variables.scss";
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html,
|
||||||
font-family: $font-family-sans-serif;
|
body {
|
||||||
font-size: $font-size-base;
|
font-family: $font-family-sans-serif;
|
||||||
line-height: $line-height-base;
|
font-size: $font-size-base;
|
||||||
-webkit-font-smoothing: antialiased;
|
line-height: $line-height-base;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
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;
|
width: 375px !important;
|
||||||
height: 600px !important;
|
height: 500px !important;
|
||||||
overflow: hidden;
|
}
|
||||||
color: $text-color;
|
|
||||||
background-color: $background-color;
|
|
||||||
|
|
||||||
@include themify($themes) {
|
&.body-xs {
|
||||||
color: themed('textColor');
|
width: 375px !important;
|
||||||
background-color: themed('backgroundColor');
|
height: 300px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.body-sm {
|
&.body-full {
|
||||||
width: 375px !important;
|
width: 100% !important;
|
||||||
height: 500px !important;
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.body-xs {
|
|
||||||
width: 375px !important;
|
|
||||||
height: 300px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.body-full {
|
|
||||||
width: 100% !important;
|
|
||||||
height: 100% !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1,
|
||||||
font-family: $font-family-sans-serif;
|
h2,
|
||||||
font-size: $font-size-base;
|
h3,
|
||||||
font-weight: normal;
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-family: $font-family-sans-serif;
|
||||||
|
font-size: $font-size-base;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul, ol {
|
ul,
|
||||||
margin-bottom: 10px;
|
ol {
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
|
@include themify($themes) {
|
||||||
|
color: themed("primaryColor");
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('primaryColor');
|
color: darken(themed("primaryColor"), 6%);
|
||||||
}
|
|
||||||
|
|
||||||
&:hover, &:focus {
|
|
||||||
@include themify($themes) {
|
|
||||||
color: darken(themed('primaryColor'), 6%);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input, select, textarea {
|
input,
|
||||||
@include themify($themes) {
|
select,
|
||||||
color: themed('textColor');
|
textarea {
|
||||||
background-color: themed('inputBackgroundColor');
|
@include themify($themes) {
|
||||||
}
|
color: themed("textColor");
|
||||||
|
background-color: themed("inputBackgroundColor");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input, select, textarea, button {
|
input,
|
||||||
font-size: $font-size-base;
|
select,
|
||||||
font-family: $font-family-sans-serif;
|
textarea,
|
||||||
|
button {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
font-family: $font-family-sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
content::-webkit-scrollbar, cdk-virtual-scroll-viewport::-webkit-scrollbar {
|
content::-webkit-scrollbar,
|
||||||
width: 10px;
|
cdk-virtual-scroll-viewport::-webkit-scrollbar {
|
||||||
height: 10px;
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
content::-webkit-scrollbar-track {
|
content::-webkit-scrollbar-track {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
cdk-virtual-scroll-viewport::-webkit-scrollbar-track {
|
cdk-virtual-scroll-viewport::-webkit-scrollbar-track {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: themed('backgroundColor');
|
background-color: themed("backgroundColor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content::-webkit-scrollbar-thumb, cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb {
|
content::-webkit-scrollbar-thumb,
|
||||||
border-radius: 10px;
|
cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb {
|
||||||
margin-right: 1px;
|
border-radius: 10px;
|
||||||
|
margin-right: 1px;
|
||||||
|
|
||||||
|
@include themify($themes) {
|
||||||
|
background-color: themed("scrollbarColor");
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: themed('scrollbarColor');
|
background-color: themed("scrollbarHoverColor");
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
@include themify($themes) {
|
|
||||||
background-color: themed('scrollbarHoverColor');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
max-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;
|
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) {
|
@include themify($themes) {
|
||||||
color: themed('headerColor');
|
color: themed("headerColor");
|
||||||
background-color: themed('headerBackgroundColor');
|
background-color: themed("headerBackgroundColor");
|
||||||
border-bottom-color: themed('headerBorderColor');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.left, .right {
|
&:hover,
|
||||||
flex: 1;
|
&:focus {
|
||||||
display: flex;
|
@include themify($themes) {
|
||||||
min-width: -webkit-min-content; /* Workaround to Chrome bug */
|
background-color: themed("headerBackgroundHoverColor");
|
||||||
.header-icon {
|
color: themed("headerColor");
|
||||||
margin-right: 5px;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
&:focus {
|
||||||
justify-content: flex-end;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.center {
|
&[disabled] {
|
||||||
display: flex;
|
opacity: 0.65;
|
||||||
align-items: center;
|
cursor: default !important;
|
||||||
text-align: center;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app-pop-out > button, div > button, div > a {
|
i + span {
|
||||||
border: none;
|
margin-left: 5px;
|
||||||
padding: 0 10px;
|
}
|
||||||
text-decoration: none;
|
}
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
app-pop-out {
|
||||||
justify-content: center;
|
display: flex;
|
||||||
align-items: center;
|
}
|
||||||
|
|
||||||
|
.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) {
|
@include themify($themes) {
|
||||||
color: themed('headerColor');
|
background-color: themed("headerInputBackgroundFocusColor");
|
||||||
background-color: themed('headerBackgroundColor');
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover, &:focus {
|
&::-webkit-input-placeholder {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: themed('headerBackgroundHoverColor');
|
color: themed("headerInputPlaceholderColor");
|
||||||
color: themed('headerColor');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[disabled] {
|
|
||||||
opacity: 0.65;
|
|
||||||
cursor: default !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
i + span {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
app-pop-out {
|
.left + .search {
|
||||||
display: flex;
|
padding-left: 0;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
left: 10px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.search + .right {
|
||||||
font-weight: bold;
|
margin-left: -10px;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 55px;
|
height: 55px;
|
||||||
border-top: 1px solid #000000;
|
border-top: 1px solid #000000;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: themed('tabBackgroundColor');
|
background-color: themed("tabBackgroundColor");
|
||||||
border-top-color: themed('borderColor');
|
border-top-color: themed("borderColor");
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
display: flex;
|
display: flex;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: block;
|
display: block;
|
||||||
padding: 7px 0;
|
padding: 7px 0;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('mutedColor');
|
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&: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 {
|
app-root {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 980;
|
z-index: 980;
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: themed('backgroundColor');
|
background-color: themed("backgroundColor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (min-width: 601px) {
|
@media only screen and (min-width: 601px) {
|
||||||
app-login header {
|
app-login header {
|
||||||
padding: 0 calc((100% - 500px) / 2);
|
padding: 0 calc((100% - 500px) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
app-login content {
|
app-login content {
|
||||||
padding: 0 calc((100% - 500px) / 2);
|
padding: 0 calc((100% - 500px) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
app-two-factor header {
|
app-two-factor header {
|
||||||
padding: 0 calc((100% - 500px) / 2);
|
padding: 0 calc((100% - 500px) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
app-two-factor content {
|
app-two-factor content {
|
||||||
padding: 0 calc((100% - 500px) / 2);
|
padding: 0 calc((100% - 500px) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
app-lock header {
|
app-lock header {
|
||||||
padding: 0 calc((100% - 500px) / 2);
|
padding: 0 calc((100% - 500px) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
app-lock content {
|
app-lock content {
|
||||||
padding: 0 calc((100% - 500px) / 2);
|
padding: 0 calc((100% - 500px) / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content {
|
content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 44px;
|
top: 44px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: themed('backgroundColor');
|
background-color: themed("backgroundColor");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.no-header {
|
&.no-header {
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.flex {
|
&.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
height: calc(100% - 44px);
|
height: calc(100% - 44px);
|
||||||
|
|
||||||
&.tab-page {
|
&.tab-page {
|
||||||
height: calc(100% - 99px);
|
height: calc(100% - 99px);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-page {
|
.tab-page {
|
||||||
content {
|
content {
|
||||||
bottom: 55px;
|
bottom: 55px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.center-content, .no-items, .full-loading-spinner {
|
.center-content,
|
||||||
display: flex;
|
.no-items,
|
||||||
justify-content: center;
|
.full-loading-spinner {
|
||||||
align-items: center;
|
display: flex;
|
||||||
height: 100%;
|
justify-content: center;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
flex-grow: 1;
|
height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-items, .full-loading-spinner {
|
.no-items,
|
||||||
text-align: center;
|
.full-loading-spinner {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
.fa {
|
.fa {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('disabledIconColor');
|
color: themed("disabledIconColor");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cdk-virtual-scroll
|
// cdk-virtual-scroll
|
||||||
.cdk-virtual-scroll-viewport {
|
.cdk-virtual-scroll-viewport {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cdk-virtual-scroll-content-wrapper {
|
.cdk-virtual-scroll-content-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,101 +1,101 @@
|
||||||
@import "variables.scss";
|
@import "variables.scss";
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
padding: 7px 15px;
|
padding: 7px 15px;
|
||||||
border: 1px solid #000000;
|
border: 1px solid #000000;
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-align: center;
|
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;
|
cursor: pointer;
|
||||||
|
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: themed('buttonBackgroundColor');
|
background-color: darken(themed("buttonBackgroundColor"), 1.5%);
|
||||||
border-color: themed('buttonBorderColor');
|
border-color: darken(themed("buttonBorderColor"), 17%);
|
||||||
color: themed('buttonColor');
|
color: darken(themed("buttonColor"), 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.primary {
|
&.primary {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('buttonPrimaryColor');
|
color: darken(themed("buttonPrimaryColor"), 6%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.danger {
|
&.danger {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('buttonDangerColor');
|
color: darken(themed("buttonDangerColor"), 6%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover:not([disabled]) {
|
&:focus:not([disabled]) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
outline: 0;
|
||||||
|
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: darken(themed('buttonBackgroundColor'), 1.5%);
|
background-color: darken(themed("buttonBackgroundColor"), 6%);
|
||||||
border-color: darken(themed('buttonBorderColor'), 17%);
|
border-color: darken(themed("buttonBorderColor"), 25%);
|
||||||
color: darken(themed('buttonColor'), 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.primary {
|
|
||||||
@include themify($themes) {
|
|
||||||
color: darken(themed('buttonPrimaryColor'), 6%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.danger {
|
|
||||||
@include themify($themes) {
|
|
||||||
color: darken(themed('buttonDangerColor'), 6%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:focus:not([disabled]) {
|
&[disabled] {
|
||||||
cursor: pointer;
|
opacity: 0.65;
|
||||||
outline: 0;
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
|
||||||
@include themify($themes) {
|
&.block {
|
||||||
background-color: darken(themed('buttonBackgroundColor'), 6%);
|
display: block;
|
||||||
border-color: darken(themed('buttonBorderColor'), 25%);
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
&.link,
|
||||||
&[disabled] {
|
&.neutral {
|
||||||
opacity: 0.65;
|
border: none !important;
|
||||||
cursor: default !important;
|
background: none !important;
|
||||||
}
|
|
||||||
|
&:focus {
|
||||||
&.block {
|
text-decoration: underline;
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.link,
|
|
||||||
&.neutral {
|
|
||||||
border: none !important;
|
|
||||||
background: none !important;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
.btn {
|
.btn {
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: auto;
|
outline: auto;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button.box-content-row {
|
button.box-content-row {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
@import "variables.scss";
|
@import "variables.scss";
|
||||||
|
|
||||||
html.browser_safari {
|
html.browser_safari {
|
||||||
body {
|
body {
|
||||||
height: 360px !important;
|
height: 360px !important;
|
||||||
|
|
||||||
&.body-xs {
|
&.body-xs {
|
||||||
height: 300px !important;
|
height: 300px !important;
|
||||||
}
|
|
||||||
|
|
||||||
&.body-full {
|
|
||||||
height: 100% !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
&.body-full {
|
||||||
.search .fa {
|
height: 100% !important;
|
||||||
left: 20px;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.left + .search .fa {
|
header {
|
||||||
left: 10px;
|
.search .fa {
|
||||||
}
|
left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
app-root {
|
.left + .search .fa {
|
||||||
border-width: 1px;
|
left: 10px;
|
||||||
border-style: solid;
|
|
||||||
border-color: #000000;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.theme_light app-root {
|
app-root {
|
||||||
border-color: #777777;
|
border-width: 1px;
|
||||||
}
|
border-style: solid;
|
||||||
|
border-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
&.theme_nord app-root {
|
&.theme_light app-root {
|
||||||
border-color: #2e3440;
|
border-color: #777777;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.theme_nord app-root {
|
||||||
|
border-color: #2e3440;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0 -15px;
|
margin: 0 -15px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col {
|
.col {
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,375 +1,378 @@
|
||||||
@import "variables.scss";
|
@import "variables.scss";
|
||||||
|
|
||||||
small, .small {
|
small,
|
||||||
font-size: $font-size-small;
|
.small {
|
||||||
|
font-size: $font-size-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-primary {
|
.bg-primary {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: themed('primaryColor') !important;
|
background-color: themed("primaryColor") !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-success {
|
.bg-success {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: themed('successColor') !important;
|
background-color: themed("successColor") !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-danger {
|
.bg-danger {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: themed('dangerColor') !important;
|
background-color: themed("dangerColor") !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-info {
|
.bg-info {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: themed('infoColor') !important;
|
background-color: themed("infoColor") !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-warning {
|
.bg-warning {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-color: themed('warningColor') !important;
|
background-color: themed("warningColor") !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-primary {
|
.text-primary {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('primaryColor') !important;
|
color: themed("primaryColor") !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-success {
|
.text-success {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('successColor') !important;
|
color: themed("successColor") !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-muted {
|
.text-muted {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('mutedColor') !important;
|
color: themed("mutedColor") !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-default {
|
.text-default {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('textColor') !important;
|
color: themed("textColor") !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-danger {
|
.text-danger {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('dangerColor') !important;
|
color: themed("dangerColor") !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-info {
|
.text-info {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('infoColor') !important;
|
color: themed("infoColor") !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-warning {
|
.text-warning {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('warningColor') !important;
|
color: themed("warningColor") !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-weight-semibold {
|
.font-weight-semibold {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.lead {
|
p.lead {
|
||||||
font-size: $font-size-large;
|
font-size: $font-size-large;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-right {
|
.flex-right {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-bottom {
|
.flex-bottom {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-margin {
|
.no-margin {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-vmargin {
|
.no-vmargin {
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-vpad {
|
.no-vpad {
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
padding-bottom: 0 !important;
|
padding-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.display-block {
|
.display-block {
|
||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monospaced {
|
.monospaced {
|
||||||
font-family: $font-family-monospace;
|
font-family: $font-family-monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.show-whitespace {
|
.show-whitespace {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.img-responsive {
|
.img-responsive {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.img-rounded {
|
.img-rounded {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sr-only {
|
.sr-only {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
width: 1px !important;
|
width: 1px !important;
|
||||||
height: 1px !important;
|
height: 1px !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
margin: -1px !important;
|
margin: -1px !important;
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
clip: rect(0, 0, 0, 0) !important;
|
clip: rect(0, 0, 0, 0) !important;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-wrapper {
|
.password-wrapper {
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-number {
|
.password-number {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('passwordNumberColor');
|
color: themed("passwordNumberColor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-special {
|
.password-special {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('passwordSpecialColor');
|
color: themed("passwordSpecialColor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#duo-frame {
|
#duo-frame {
|
||||||
background: url('../images/loading.svg') 0 0 no-repeat;
|
background: url("../images/loading.svg") 0 0 no-repeat;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 470px;
|
height: 470px;
|
||||||
margin-bottom: -10px;
|
margin-bottom: -10px;
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#web-authn-frame {
|
#web-authn-frame {
|
||||||
background: url('../images/loading.svg') 0 0 no-repeat;
|
background: url("../images/loading.svg") 0 0 no-repeat;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 310px;
|
height: 310px;
|
||||||
margin-bottom: -10px;
|
margin-bottom: -10px;
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#hcaptcha_iframe {
|
#hcaptcha_iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
transition: height 0.25s linear;
|
transition: height 0.25s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.linux-webauthn {
|
body.linux-webauthn {
|
||||||
width: 485px !important;
|
width: 485px !important;
|
||||||
#web-authn-frame {
|
#web-authn-frame {
|
||||||
iframe {
|
iframe {
|
||||||
width: 375px;
|
width: 375px;
|
||||||
margin: 0 55px;
|
margin: 0 55px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app-root > #loading {
|
app-root > #loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: $text-muted;
|
color: $text-muted;
|
||||||
|
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('mutedColor');
|
color: themed("mutedColor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app-vault-icon {
|
app-vault-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-image {
|
.logo-image {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 142px;
|
width: 142px;
|
||||||
height: 21px;
|
height: 21px;
|
||||||
background-size: 142px 21px;
|
background-size: 142px 21px;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
background-image: url('../images/logo-' + themed('logoSuffix') + '@2x.png');
|
background-image: url("../images/logo-" + themed("logoSuffix") + "@2x.png");
|
||||||
}
|
}
|
||||||
@media (min-width: 219px) {
|
@media (min-width: 219px) {
|
||||||
width: 189px;
|
width: 189px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
background-size: 189px 28px;
|
background-size: 189px 28px;
|
||||||
}
|
}
|
||||||
@media (min-width: 314px) {
|
@media (min-width: 314px) {
|
||||||
width: 284px;
|
width: 284px;
|
||||||
height: 43px;
|
height: 43px;
|
||||||
background-size: 284px 43px;
|
background-size: 284px 43px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[hidden] {
|
[hidden] {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.draggable {
|
.draggable {
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
|
|
||||||
.callout {
|
.callout {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border: 1px solid #000000;
|
border: 1px solid #000000;
|
||||||
border-left-width: 5px;
|
border-left-width: 5px;
|
||||||
border-radius: 3px;
|
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) {
|
@include themify($themes) {
|
||||||
border-color: themed('calloutBorderColor');
|
border-left-color: themed("primaryColor");
|
||||||
background-color: themed('calloutBackgroundColor');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.callout-heading {
|
.callout-heading {
|
||||||
margin-top: 0;
|
@include themify($themes) {
|
||||||
|
color: themed("primaryColor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.callout-info {
|
||||||
|
@include themify($themes) {
|
||||||
|
border-left-color: themed("infoColor");
|
||||||
}
|
}
|
||||||
|
|
||||||
h3.callout-heading {
|
.callout-heading {
|
||||||
font-weight: bold;
|
@include themify($themes) {
|
||||||
text-transform: uppercase;
|
color: themed("infoColor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.callout-danger {
|
||||||
|
@include themify($themes) {
|
||||||
|
border-left-color: themed("dangerColor");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.callout-primary {
|
.callout-heading {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
border-left-color: themed('primaryColor');
|
color: themed("dangerColor");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.callout-heading {
|
&.callout-success {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('primaryColor');
|
border-left-color: themed("successColor");
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.callout-info {
|
.callout-heading {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
border-left-color: themed('infoColor');
|
color: themed("successColor");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.callout-heading {
|
&.callout-warning {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed('infoColor');
|
border-left-color: themed("warningColor");
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.callout-danger {
|
.callout-heading {
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
border-left-color: themed('dangerColor');
|
color: themed("warningColor");
|
||||||
}
|
}
|
||||||
|
|
||||||
.callout-heading {
|
|
||||||
@include themify($themes) {
|
|
||||||
color: themed('dangerColor');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.callout-success {
|
&.clickable {
|
||||||
@include themify($themes) {
|
&:hover,
|
||||||
border-left-color: themed('successColor');
|
&:focus,
|
||||||
}
|
&.active {
|
||||||
|
@include themify($themes) {
|
||||||
.callout-heading {
|
background-color: themed("boxBackgroundHoverColor");
|
||||||
@include themify($themes) {
|
}
|
||||||
color: themed('successColor');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.callout-warning {
|
.enforced-policy-options ul {
|
||||||
@include themify($themes) {
|
padding-left: 30px;
|
||||||
border-left-color: themed('warningColor');
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="password"]::-ms-reveal {
|
input[type="password"]::-ms-reveal {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
&.flex-grow {
|
&.flex-grow {
|
||||||
> * {
|
> * {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround for slow performance on external monitors on Chrome + MacOS
|
// Workaround for slow performance on external monitors on Chrome + MacOS
|
||||||
// See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64
|
// See: https://bugs.chromium.org/p/chromium/issues/detail?id=971701#c64
|
||||||
@keyframes redraw {
|
@keyframes redraw {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0.99;
|
opacity: 0.99;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
html.force_redraw {
|
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
Loading…
Reference in New Issue