1
0
mirror of https://github.com/bitwarden/web synced 2025-12-11 22:03:21 +00:00

Compare commits

..

55 Commits

Author SHA1 Message Date
Kyle Spearrin
264759cfa0 Version bump and CNAME dist fix 2016-12-03 00:56:41 -05:00
Kyle Spearrin
b5d265526a space between badges 2016-12-01 00:15:35 -05:00
Kyle Spearrin
22290eafb8 update readme with appveyor build badge 2016-12-01 00:14:52 -05:00
Kyle Spearrin
3101e57c36 Reorganization a bit more. Updated readme with build/run instructions. 2016-12-01 00:07:03 -05:00
Kyle Spearrin
b72a52232d reorganize project folder structure and remove asp.net dependency 2016-11-30 23:50:00 -05:00
Kyle Spearrin
a5b8e703fc added node server via gulp serve task 2016-11-30 23:30:08 -05:00
Kyle Spearrin
fb26425f17 tweaks to two factor modal 2016-11-30 23:22:25 -05:00
Kyle Spearrin
3114e20aef remove any spaces from authenticator code input 2016-11-26 18:52:46 -05:00
Kyle Spearrin
a52d2f4b7a added account recovery page 2016-11-14 23:31:54 -05:00
Kyle Spearrin
34e484c377 null check recovery code 2016-11-14 22:45:01 -05:00
Kyle Spearrin
08e8e9ff64 Add recovery code information to two-step login modal, also make verification code required for all actions 2016-11-14 22:38:02 -05:00
Kyle Spearrin
ebf55390eb Added password dragon xml importer #1 2016-11-11 19:21:12 -05:00
Kyle Spearrin
c328144a58 keeper csv import (#1) 2016-11-11 00:10:16 -05:00
Kyle Spearrin
4b583bea9b version bump - 1.4.0 2016-11-09 21:50:04 -05:00
Kyle Spearrin
3f0ca412c6 index checking 2016-11-09 21:49:15 -05:00
Kyle Spearrin
0050b570b4 Added delete option to edit site modal #25 2016-11-09 19:26:02 -05:00
Kyle Spearrin
9405be03b0 Added importer for universal password manager csv format 2016-11-09 18:18:46 -05:00
Kyle Spearrin
b5b706fe06 check badDataLength 2016-11-09 00:42:19 -05:00
Kyle Spearrin
8313d9fa90 Better error handling for imports. Check for bad CSV data on lastpass (when no header row is included). 2016-11-09 00:41:17 -05:00
Kyle Spearrin
93b96b3be7 adjusted add/edit site sort functions for folders 2016-10-22 09:08:17 -04:00
blindly
50e6818f2b Sdd site button to each folder (#17) 2016-10-22 08:43:50 -04:00
Kyle Spearrin
104fb57bd8 Added support for Firefox Password Exporter Addon import. ref #1 2016-10-21 00:10:17 -04:00
Kyle Spearrin
7ba6b2f00a version bump 2016-10-20 08:04:40 -04:00
Kyle Spearrin
d8e8939eca remove old code from 1Password 1pif importer 2016-10-20 08:03:20 -04:00
Kyle Spearrin
26c5f4049d link to help site 2016-10-19 22:52:53 -04:00
Kyle Spearrin
b6c9dba0fc Added Chrome csv importer. Adjustments to 1Password importer in preparation for detecting different formats. 2016-10-19 18:22:18 -04:00
blindly
8badc1a354 Folders should be sorted in alphabetical order when adding or editing a site. 2016-10-19 06:07:10 -04:00
Kyle Spearrin
15097eb1f0 version bump 2016-10-18 22:11:54 -04:00
Kyle Spearrin
986306f811 fix imports error 2016-10-18 21:34:08 -04:00
Kyle Spearrin
c5de290dd4 version bump 2016-10-17 23:26:20 -04:00
Kyle Spearrin
21e9e083f5 nothing was imported error 2016-10-17 23:24:16 -04:00
Kyle Spearrin
517ea65bc9 import validation 2016-10-17 23:18:19 -04:00
Kyle Spearrin
e7a9699226 show import errors as console warning to assist in debugging issues 2016-10-17 23:11:39 -04:00
Kyle Spearrin
52b3dfd0e3 set encoding for to UTF-8 for csv parser 2016-10-17 23:00:44 -04:00
Kyle Spearrin
4c0bde9d87 1.2.0 version bump 2016-10-15 12:30:20 -04:00
Kyle Spearrin
f29bbe4316 adjust password generator to use cryptographically secure RNG 2016-10-15 00:41:03 -04:00
Kyle Spearrin
3c03ed8636 trim url for safeincloud xml import 2016-10-14 23:55:02 -04:00
Kyle Spearrin
71ca4eb84a fix safeincloudcsv value in select option 2016-10-14 23:52:31 -04:00
Kyle Spearrin
30d19b4ee1 import for safeincloud xml 2016-10-14 23:50:39 -04:00
Kyle Spearrin
7b88d30aa8 lowered uri trim to 1000 2016-10-14 23:17:20 -04:00
Kyle Spearrin
0ae11cc40c import for 1password 1pif files 2016-10-14 23:16:37 -04:00
Kyle Spearrin
dd5cda867d trim ridiculously large URLs on import 2016-10-14 21:43:00 -04:00
Kyle Spearrin
2d11bef262 padlock csv import 2016-10-13 23:10:14 -04:00
Kyle Spearrin
5d5d0bfb66 gitter badge 2016-10-13 22:02:46 -04:00
Kyle Spearrin
1b169f368b version bump to 1.1.1 2016-10-13 20:06:55 -04:00
Kyle Spearrin
3375bda789 cache tag for local assets on preprocess build 2016-10-13 19:57:56 -04:00
Kyle Spearrin
6569fbe6aa adjust importers to not require Uri and Passwords 2016-10-13 19:05:53 -04:00
Kyle Spearrin
1d2b82a302 lint fixes for import service. made passwird not required for sites 2016-10-13 18:40:42 -04:00
Kyle Spearrin
004ddb1e75 handle lastpass import when users try to upload html file instead of csv 2016-10-13 00:05:18 -04:00
Kyle Spearrin
400826baf7 minor version bump to 1.1.0 2016-10-12 22:51:59 -04:00
Kyle Spearrin
96ae20d81d fix floating labels for firefox 2016-10-12 22:50:46 -04:00
Kyle Spearrin
ef5f4df30f lint errors in import service 2016-10-12 22:49:04 -04:00
Kyle Spearrin
2ce1b12f6e Handle empty password and uri fields when encrypting/decrypting 2016-10-12 22:26:28 -04:00
Kyle Spearrin
85efba92e6 added importer for keypass 2.x xml 2016-10-12 22:14:39 -04:00
Kyle Spearrin
d17211ff86 added SafeInCloud csv import 2016-10-12 19:02:36 -04:00
99 changed files with 1420 additions and 559 deletions

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

View File

@@ -1,7 +1,26 @@
[![appveyor build](https://ci.appveyor.com/api/projects/status/github/bitwarden/web?branch=master&svg=true)] (https://ci.appveyor.com/project/bitwarden/web) [![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby)
# bitwarden Web # bitwarden Web
The bitwarden Web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/). The bitwarden Web project is an AngularJS application that powers the web vault (https://vault.bitwarden.com/).
# Build/Run
**Requirements**
- Node.js
- Gulp
Unless you are running the [Core](https://github.com/bitwarden/core) API locally, you'll probably need to switch the
application to target the production API. Open `package.json` and set `production` to `true`.
Then run the following commands:
- `gulp build`
- `gulp serve`
You can now access the web vault at `http://localhost:4001`.
# Contribute # Contribute
Code contributions are welcome! Please commit any pull requests against the `master` branch. Code contributions are welcome! Please commit any pull requests against the `master` branch.

View File

@@ -3,34 +3,37 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.25420.1 VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{860863C9-0436-43D4-840D-FE919C9F6FFC}" Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "bitwarden-web", ".", "{25BEDEF4-2CAF-445A-807D-63C17FF85694}"
EndProject ProjectSection(WebsiteProperties) = preProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{14FE7221-D377-4AD5-9A9E-4541577CF05A}" TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.6.1"
ProjectSection(SolutionItems) = preProject Debug.AspNetCompiler.VirtualPath = "/localhost_15509"
.gitignore = .gitignore Debug.AspNetCompiler.PhysicalPath = "."
CNAME = CNAME Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_15509\"
global.json = global.json Debug.AspNetCompiler.Updateable = "true"
NuGet.Config = NuGet.Config Debug.AspNetCompiler.ForceOverwrite = "true"
README.md = README.md Debug.AspNetCompiler.FixedNames = "false"
Debug.AspNetCompiler.Debug = "True"
Release.AspNetCompiler.VirtualPath = "/localhost_15509"
Release.AspNetCompiler.PhysicalPath = "."
Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_15509\"
Release.AspNetCompiler.Updateable = "true"
Release.AspNetCompiler.ForceOverwrite = "true"
Release.AspNetCompiler.FixedNames = "false"
Release.AspNetCompiler.Debug = "False"
VWDPort = "15509"
SlnRelativePath = "."
DefaultWebSiteLanguage = "Visual C#"
EndProjectSection EndProjectSection
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Web", "src\Web\Web.xproj", "{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {25BEDEF4-2CAF-445A-807D-63C17FF85694}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}.Debug|Any CPU.Build.0 = Debug|Any CPU {25BEDEF4-2CAF-445A-807D-63C17FF85694}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5} = {860863C9-0436-43D4-840D-FE919C9F6FFC}
EndGlobalSection
EndGlobal EndGlobal

View File

@@ -1,6 +0,0 @@
{
"projects": [ "src", "test" ],
"sdk": {
"version": "1.0.0-preview2-003121"
}
}

View File

@@ -8,28 +8,31 @@ var gulp = require('gulp'),
uglify = require('gulp-uglify'), uglify = require('gulp-uglify'),
ghPages = require('gulp-gh-pages'), ghPages = require('gulp-gh-pages'),
less = require('gulp-less'), less = require('gulp-less'),
connect = require('gulp-connect'),
ngAnnotate = require('gulp-ng-annotate'), ngAnnotate = require('gulp-ng-annotate'),
preprocess = require('gulp-preprocess'), preprocess = require('gulp-preprocess'),
runSequence = require('run-sequence'), runSequence = require('run-sequence'),
merge = require('merge-stream'), merge = require('merge-stream'),
ngConfig = require('gulp-ng-config'), ngConfig = require('gulp-ng-config'),
settings = require('./settings.json'), settings = require('./settings.json'),
project = require('./project.json'), project = require('./package.json'),
jshint = require('gulp-jshint'), jshint = require('gulp-jshint'),
_ = require('lodash'); _ = require('lodash');
var paths = {}; var paths = {};
paths.dist = '../../dist/'; paths.dist = './dist/';
paths.webroot = './wwwroot/' paths.webroot = './src/'
paths.js = paths.webroot + 'js/**/*.js'; paths.js = paths.webroot + 'js/**/*.js';
paths.minJs = paths.webroot + 'js/**/*.min.js'; paths.minJs = paths.webroot + 'js/**/*.min.js';
paths.concatJsDest = paths.webroot + 'js/bw.min.js'; paths.concatJsDest = paths.webroot + 'js/bw.min.js';
paths.libDir = paths.webroot + 'lib/'; paths.libDir = paths.webroot + 'lib/';
paths.npmDir = 'node_modules/'; paths.npmDir = 'node_modules/';
paths.lessDir = 'less/'; paths.lessDir = paths.webroot + 'less/';
paths.cssDir = paths.webroot + 'css/'; paths.cssDir = paths.webroot + 'css/';
paths.jsDir = paths.webroot + 'js/'; paths.jsDir = paths.webroot + 'js/';
var randomString = Math.random().toString(36).substring(7);
gulp.task('lint', function () { gulp.task('lint', function () {
return gulp.src(paths.webroot + 'app/**/*.js') return gulp.src(paths.webroot + 'app/**/*.js')
.pipe(jshint()) .pipe(jshint())
@@ -187,9 +190,9 @@ function config() {
constants: _.merge({}, { constants: _.merge({}, {
appSettings: { appSettings: {
version: project.version, version: project.version,
environment: project.environment environment: project.production ? 'Production' : 'Development'
} }
}, require('./settings.' + project.environment + '.json') || {}) }, require('./settings' + (project.production ? '.Production' : '') + '.json') || {})
})); }));
} }
@@ -211,7 +214,7 @@ gulp.task('dist:clean', function (cb) {
gulp.task('dist:move', function () { gulp.task('dist:move', function () {
var moves = [ var moves = [
{ {
src: '../../CNAME', src: './CNAME',
dest: paths.dist dest: paths.dist
}, },
{ {
@@ -261,7 +264,7 @@ gulp.task('dist:css', function () {
paths.cssDir + '**/*.css', paths.cssDir + '**/*.css',
'!' + paths.cssDir + '**/*.min.css' '!' + paths.cssDir + '**/*.min.css'
]) ])
.pipe(preprocess({ context: settings })) .pipe(preprocess({ context: { cacheTag: randomString } }))
.pipe(cssmin()) .pipe(cssmin())
.pipe(rename({ suffix: '.min' })) .pipe(rename({ suffix: '.min' }))
.pipe(gulp.dest(paths.dist + 'css')); .pipe(gulp.dest(paths.dist + 'css'));
@@ -277,7 +280,7 @@ gulp.task('dist:js:app', function () {
]); ]);
merge(mainStream, config()) merge(mainStream, config())
.pipe(preprocess({ context: settings })) .pipe(preprocess({ context: { cacheTag: randomString } }))
.pipe(concat(paths.dist + '/js/app.min.js')) .pipe(concat(paths.dist + '/js/app.min.js'))
.pipe(ngAnnotate()) .pipe(ngAnnotate())
.pipe(uglify()) .pipe(uglify())
@@ -306,7 +309,7 @@ gulp.task('dist:preprocess', function () {
.src([ .src([
paths.dist + '/**/*.html' paths.dist + '/**/*.html'
], { base: '.' }) ], { base: '.' })
.pipe(preprocess({ context: settings })) .pipe(preprocess({ context: { cacheTag: randomString }}))
.pipe(gulp.dest('.')); .pipe(gulp.dest('.'));
}); });
@@ -322,3 +325,10 @@ gulp.task('deploy', ['dist'], function () {
return gulp.src(paths.dist + '**/*') return gulp.src(paths.dist + '**/*')
.pipe(ghPages({ cacheDir: paths.dist + '.publish' })); .pipe(ghPages({ cacheDir: paths.dist + '.publish' }));
}); });
gulp.task('serve', function () {
connect.server({
port: 4001,
root: ['src']
});
});

View File

@@ -1,6 +1,7 @@
{ {
"name": "bitwarden", "name": "bitwarden",
"version": "0.0.0", "version": "1.5.0",
"production": false,
"devDependencies": { "devDependencies": {
"connect": "3.4.1", "connect": "3.4.1",
"lodash": "4.13.1", "lodash": "4.13.1",
@@ -14,6 +15,7 @@
"gulp-preprocess": "2.0.0", "gulp-preprocess": "2.0.0",
"gulp-ng-annotate": "2.0.0", "gulp-ng-annotate": "2.0.0",
"gulp-ng-config": "1.3.1", "gulp-ng-config": "1.3.1",
"gulp-connect": "5.0.0",
"jshint": "2.9.2", "jshint": "2.9.2",
"gulp-jshint": "2.0.1", "gulp-jshint": "2.0.1",
"rimraf": "2.5.2", "rimraf": "2.5.2",

6
settings.json Normal file
View File

@@ -0,0 +1,6 @@
{
"appSettings": {
"rememberedEmailCookieName": "bit.rememberedEmail",
"apiUri": "http://localhost:4000"
}
}

View File

@@ -1,20 +0,0 @@
using System.IO;
using Microsoft.AspNetCore.Hosting;
namespace Bit.Web
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

View File

@@ -1,23 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Bit.Web")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("bitwarden Web")]
[assembly: AssemblyProduct("bitwarden Web")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("0bebf47c-ba0b-48ac-b48c-718f94084ad5")]

View File

@@ -1,27 +0,0 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:4001/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Web": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -1,16 +0,0 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services) { }
public void Configure(IApplicationBuilder app)
{
app.UseFileServer();
}
}
}

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>0bebf47c-ba0b-48ac-b48c-718f94084ad5</ProjectGuid>
<RootNamespace>Bit.Vault</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>4001</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -1,47 +0,0 @@
@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700,300italic,400italic,600italic);
@import "../node_modules/toastr/toastr.less";
/* Start AdminLTE */
//Bootstrap Variables & Mixins
//The core bootstrap code have not been modified. These files
//are included only for reference.
@import (reference) "../node_modules/admin-lte/build/bootstrap-less/mixins.less";
@import (reference) "../node_modules/admin-lte/build/bootstrap-less/variables.less";
//MISC
//----
@import "../node_modules/admin-lte/build/less/core.less";
@import "../node_modules/admin-lte/build/less/variables.less";
@import "../node_modules/admin-lte/build/less/mixins.less";
//COMPONENTS
//-----------
@import "../node_modules/admin-lte/build/less/header.less";
@import "../node_modules/admin-lte/build/less/sidebar.less";
@import "../node_modules/admin-lte/build/less/sidebar-mini.less";
@import "../node_modules/admin-lte/build/less/control-sidebar.less";
@import "../node_modules/admin-lte/build/less/dropdown.less";
@import "../node_modules/admin-lte/build/less/forms.less";
@import "../node_modules/admin-lte/build/less/progress-bars.less";
@import "../node_modules/admin-lte/build/less/small-box.less";
@import "../node_modules/admin-lte/build/less/boxes.less";
@import "../node_modules/admin-lte/build/less/info-box.less";
@import "../node_modules/admin-lte/build/less/timeline.less";
@import "../node_modules/admin-lte/build/less/buttons.less";
@import "../node_modules/admin-lte/build/less/callout.less";
@import "../node_modules/admin-lte/build/less/alerts.less";
@import "../node_modules/admin-lte/build/less/navs.less";
@import "../node_modules/admin-lte/build/less/table.less";
@import "../node_modules/admin-lte/build/less/labels.less";
@import "../node_modules/admin-lte/build/less/modal.less";
//PAGES
//------
@import "../node_modules/admin-lte/build/less/login_and_register.less";
@import "../node_modules/admin-lte/build/less/404_500_errors.less";
//Miscellaneous
//-------------
@import "../node_modules/admin-lte/build/less/miscellaneous.less";
@import "../node_modules/admin-lte/build/less/print.less";
/* End AdminLTE */
@import "../node_modules/admin-lte/build/less/skins/skin-blue.less";

View File

@@ -1,56 +0,0 @@
{
"version": "1.0.1",
"environment": "Development",
"dependencies": {
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0"
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": {
"version": "1.0.0-preview2-final",
"imports": "portable-net45+win8+dnxcore50"
}
},
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
}
}
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"gcServer": false,
"gcConcurrent": true
},
"publishOptions": {
"include": [
"wwwroot",
"Views",
"settings.json",
"settings.Development.json",
"settings.Production.json",
"settings.Staging.json",
"web.config"
]
},
"scripts": {
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
},
"userSecretsId": "aspnet-Vault-20160519103145"
}

View File

@@ -1,5 +0,0 @@
{
"appSettings": {
"apiUri": "http://localhost:4000"
}
}

View File

@@ -1,5 +0,0 @@
{
"appSettings": {
"apiUri": "https://api.bitwarden.com"
}
}

View File

@@ -1,5 +0,0 @@
{
"appSettings": {
"rememberedEmailCookieName": "bit.rememberedEmail"
}
}

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
</system.webServer>
</configuration>

View File

@@ -1,70 +0,0 @@
/// <autosync enabled="true" />
/// <reference path="../gulpfile.js" />
/// <reference path="app/accounts/accountsLoginController.js" />
/// <reference path="app/accounts/accountsLogoutController.js" />
/// <reference path="app/accounts/accountsmodule.js" />
/// <reference path="app/accounts/accountspasswordhintcontroller.js" />
/// <reference path="app/accounts/accountsRegisterController.js" />
/// <reference path="app/apiInterceptor.js" />
/// <reference path="app/app.js" />
/// <reference path="app/config.js" />
/// <reference path="app/directives/apiFieldDirective.js" />
/// <reference path="app/directives/apiFormDirective.js" />
/// <reference path="app/directives/directivesModule.js" />
/// <reference path="app/directives/masterPasswordDirective.js" />
/// <reference path="app/directives/pageTitleDirective.js" />
/// <reference path="app/directives/passwordmeterdirective.js" />
/// <reference path="app/directives/passwordviewerdirective.js" />
/// <reference path="app/global/globalModule.js" />
/// <reference path="app/global/mainController.js" />
/// <reference path="app/global/sideNavController.js" />
/// <reference path="app/global/topNavController.js" />
/// <reference path="app/services/apiService.js" />
/// <reference path="app/services/authService.js" />
/// <reference path="app/services/cipherService.js" />
/// <reference path="app/services/cryptoService.js" />
/// <reference path="app/services/importservice.js" />
/// <reference path="app/services/passwordservice.js" />
/// <reference path="app/services/servicesModule.js" />
/// <reference path="app/services/tokenService.js" />
/// <reference path="app/services/validationservice.js" />
/// <reference path="app/settings.js" />
/// <reference path="app/settings/settingsChangeEmailController.js" />
/// <reference path="app/settings/settingsChangePasswordController.js" />
/// <reference path="app/settings/settingsController.js" />
/// <reference path="app/settings/settingsdeletecontroller.js" />
/// <reference path="app/settings/settingsmodule.js" />
/// <reference path="app/settings/settingsSessionsController.js" />
/// <reference path="app/settings/settingsTwoFactorController.js" />
/// <reference path="app/tools/toolsAuditsController.js" />
/// <reference path="app/tools/toolsController.js" />
/// <reference path="app/tools/toolsExportController.js" />
/// <reference path="app/tools/toolsImportController.js" />
/// <reference path="app/tools/toolsmodule.js" />
/// <reference path="app/vault/vaultAddFolderController.js" />
/// <reference path="app/vault/vaultAddSiteController.js" />
/// <reference path="app/vault/vaultController.js" />
/// <reference path="app/vault/vaultEditFolderController.js" />
/// <reference path="app/vault/vaultEditSiteController.js" />
/// <reference path="app/vault/vaultmodule.js" />
/// <reference path="lib/admin-lte/js/app.js" />
/// <reference path="lib/angular/angular.js" />
/// <reference path="lib/angular-bootstrap/angular-bootstrap-tpls.js" />
/// <reference path="lib/angular-bootstrap-show-errors/showErrors.js" />
/// <reference path="lib/angular-cookies/angular-cookies.js" />
/// <reference path="lib/angular-jwt/angular-jwt.js" />
/// <reference path="lib/angular-md5/angular-md5.js" />
/// <reference path="lib/angular-messages/angular-messages.js" />
/// <reference path="lib/angular-resource/angular-resource.js" />
/// <reference path="lib/angular-toastr/angular-toastr.js" />
/// <reference path="lib/angular-toastr/angular-toastr.tpls.js" />
/// <reference path="lib/angular-ui-router/angular-ui-router.js" />
/// <reference path="lib/bootstrap/js/bootstrap.min.js" />
/// <reference path="lib/clipboard/clipboard.js" />
/// <reference path="lib/jquery/jquery.js" />
/// <reference path="lib/ngclipboard/ngclipboard.js" />
/// <reference path="lib/ngstorage/ngStorage.js" />
/// <reference path="lib/papaparse/papaparse.js" />
/// <reference path="lib/sjcl/bitArray.js" />
/// <reference path="lib/sjcl/cbc.js" />
/// <reference path="lib/sjcl/sjcl.js" />

View File

@@ -1,136 +0,0 @@
angular
.module('bit.services')
.factory('importService', function () {
var _service = {};
_service.import = function (source, file, success, error) {
switch (source) {
case 'local':
importLocal(file, success, error);
break;
case 'lastpass':
importLastPass(file, success, error);
break;
default:
error();
break;
}
};
function importLocal(file, success, error) {
Papa.parse(file, {
header: true,
complete: function (results) {
var folders = [],
sites = [],
folderRelationships = [];
angular.forEach(results.data, function (value, key) {
if (!value.uri || value.uri === '') {
return;
}
var folderIndex = folders.length,
siteIndex = sites.length,
hasFolder = value.folder && value.folder !== '',
addFolder = hasFolder;
if (hasFolder) {
for (var i = 0; i < folders.length; i++) {
if (folders[i].name == value.folder) {
addFolder = false;
folderIndex = i;
break;
}
}
}
sites.push({
favorite: value.favorite !== null ? value.favorite : false,
uri: value.uri,
username: value.username && value.username !== '' ? value.username : null,
password: value.password,
notes: value.notes && value.notes !== '' ? value.notes : null,
name: value.name
});
if (addFolder) {
folders.push({
name: value.folder
});
}
if (hasFolder) {
var relationship = {
key: siteIndex,
value: folderIndex
};
folderRelationships.push(relationship);
}
});
success(folders, sites, folderRelationships);
}
});
}
function importLastPass(file, success, error) {
Papa.parse(file, {
header: true,
complete: function (results) {
var folders = [],
sites = [],
siteRelationships = [];
angular.forEach(results.data, function (value, key) {
if (!value.url || value.url === '') {
return;
}
var folderIndex = folders.length,
siteIndex = sites.length,
hasFolder = value.grouping && value.grouping !== '' && value.grouping != '(none)',
addFolder = hasFolder;
if (hasFolder) {
for (var i = 0; i < folders.length; i++) {
if (folders[i].name == value.grouping) {
addFolder = false;
folderIndex = i;
break;
}
}
}
sites.push({
favorite: value.fav == '1',
uri: value.url,
username: value.username && value.username !== '' ? value.username : null,
password: value.password,
notes: value.extra && value.extra !== '' ? value.extra : null,
name: value.name
});
if (addFolder) {
folders.push({
name: value.grouping
});
}
if (hasFolder) {
var relationship = {
key: siteIndex,
value: folderIndex
};
siteRelationships.push(relationship);
}
});
success(folders, sites, siteRelationships);
}
});
}
return _service;
});

View File

@@ -1,2 +0,0 @@
angular.module("bit")
.constant("appSettings", {"rememberedEmailCookieName":"bit.rememberedEmail","version":"1.0.1","environment":"Development","apiUri":"http://localhost:4000"});

View File

@@ -0,0 +1,21 @@
angular
.module('bit.accounts')
.controller('accountsRecoverController', function ($scope, apiService, cryptoService) {
$scope.success = false;
$scope.submit = function (model) {
var email = model.email.toLowerCase();
var key = cryptoService.makeKey(model.masterPassword, email);
var request = {
email: email,
masterPasswordHash: cryptoService.hashPassword(model.masterPassword, key),
recoveryCode: model.code.replace(/\s/g, '').toLowerCase()
};
$scope.submitPromise = apiService.accounts.postTwoFactorRecover(request, function () {
$scope.success = true;
}).$promise;
};
});

View File

@@ -13,7 +13,10 @@
<span class="fa fa-lock form-control-feedback"></span> <span class="fa fa-lock form-control-feedback"></span>
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-offset-7 col-xs-5"> <div class="col-xs-7">
<a ui-sref="frontend.recover">Lost authenticator app?</a>
</div>
<div class="col-xs-5">
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="twoFactorForm.$loading"> <button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="twoFactorForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="twoFactorForm.$loading"></i>Log In <i class="fa fa-refresh fa-spin loading-icon" ng-show="twoFactorForm.$loading"></i>Log In
</button> </button>

View File

@@ -0,0 +1,52 @@
<div class="login-box">
<div class="login-logo">
<i class="fa fa-shield"></i> <b>bit</b>warden
</div>
<div class="login-box-body">
<p class="login-box-msg">Lost your authenticator app?</p>
<div class="text-center" ng-show="success">
<div class="callout callout-success">
Two-step login has been successfully disabled on your account.
</div>
<a ui-sref="frontend.login.info">Ready to log in?</a>
</div>
<form name="recoverForm" ng-submit="recoverForm.$valid && submit(model)" ng-show="!success"
api-form="submitPromise">
<div class="callout callout-danger validation-errors" ng-show="recoverForm.$errors">
<h4>Errors have occured</h4>
<ul>
<li ng-repeat="e in recoverForm.$errors">{{e}}</li>
</ul>
</div>
<div class="form-group has-feedback" show-errors>
<label for="email" class="sr-only">Email</label>
<input type="email" id="email" name="Email" class="form-control" placeholder="Email" ng-model="model.email"
required api-field />
<span class="fa fa-envelope form-control-feedback"></span>
</div>
<div class="form-group has-feedback" show-errors>
<label for="masterPassword" class="sr-only">Master Password</label>
<input type="password" id="masterPassword" name="MasterPasswordHash" class="form-control" placeholder="Master Password"
ng-model="model.masterPassword"
required api-field />
<span class="fa fa-lock form-control-feedback"></span>
</div>
<div class="form-group has-feedback" show-errors>
<label for="code" class="sr-only">Recovery code</label>
<input type="text" id="code" name="RecoveryCode" class="form-control" placeholder="Recovery code"
ng-model="model.code" required api-field />
<span class="fa fa-key form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-7">
<a ui-sref="frontend.login.info">Ready to log in?</a>
</div>
<div class="col-xs-5">
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="recoverForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="recoverForm.$loading"></i>Submit
</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -105,6 +105,15 @@ angular
bodyClass: 'login-page' bodyClass: 'login-page'
} }
}) })
.state('frontend.recover', {
url: '^/recover',
templateUrl: 'app/accounts/views/accountsRecover.html',
controller: 'accountsRecoverController',
data: {
pageTitle: 'Recover Account',
bodyClass: 'login-page'
}
})
.state('frontend.register', { .state('frontend.register', {
url: '^/register', url: '^/register',
templateUrl: 'app/accounts/views/accountsRegister.html', templateUrl: 'app/accounts/views/accountsRegister.html',

View File

@@ -38,6 +38,7 @@
putProfile: { url: _apiUri + '/accounts/profile', method: 'POST', params: {} }, putProfile: { url: _apiUri + '/accounts/profile', method: 'POST', params: {} },
getTwoFactor: { url: _apiUri + '/accounts/two-factor', method: 'GET', params: {} }, getTwoFactor: { url: _apiUri + '/accounts/two-factor', method: 'GET', params: {} },
putTwoFactor: { url: _apiUri + '/accounts/two-factor', method: 'POST', params: {} }, putTwoFactor: { url: _apiUri + '/accounts/two-factor', method: 'POST', params: {} },
postTwoFactorRecover: { url: _apiUri + '/accounts/two-factor-recover', method: 'POST', params: {} },
postPasswordHint: { url: _apiUri + '/accounts/password-hint', method: 'POST', params: {} }, postPasswordHint: { url: _apiUri + '/accounts/password-hint', method: 'POST', params: {} },
putSecurityStamp: { url: _apiUri + '/accounts/security-stamp', method: 'POST', params: {} }, putSecurityStamp: { url: _apiUri + '/accounts/security-stamp', method: 'POST', params: {} },
'import': { url: _apiUri + '/accounts/import', method: 'POST', params: {} }, 'import': { url: _apiUri + '/accounts/import', method: 'POST', params: {} },

View File

@@ -34,7 +34,7 @@ angular
_service.logInTwoFactor = function (code, provider) { _service.logInTwoFactor = function (code, provider) {
var request = { var request = {
code: code, code: code.replace(' ', ''),
provider: provider provider: provider
}; };

View File

@@ -24,9 +24,9 @@ angular
folderId: encryptedSite.FolderId, folderId: encryptedSite.FolderId,
favorite: encryptedSite.Favorite, favorite: encryptedSite.Favorite,
name: cryptoService.decrypt(encryptedSite.Name), name: cryptoService.decrypt(encryptedSite.Name),
uri: cryptoService.decrypt(encryptedSite.Uri), uri: encryptedSite.Uri && encryptedSite.Uri !== '' ? cryptoService.decrypt(encryptedSite.Uri) : null,
username: encryptedSite.Username && encryptedSite.Username !== '' ? cryptoService.decrypt(encryptedSite.Username) : null, username: encryptedSite.Username && encryptedSite.Username !== '' ? cryptoService.decrypt(encryptedSite.Username) : null,
password: cryptoService.decrypt(encryptedSite.Password), password: encryptedSite.Password && encryptedSite.Password !== '' ? cryptoService.decrypt(encryptedSite.Password) : null,
notes: encryptedSite.Notes && encryptedSite.Notes !== '' ? cryptoService.decrypt(encryptedSite.Notes) : null notes: encryptedSite.Notes && encryptedSite.Notes !== '' ? cryptoService.decrypt(encryptedSite.Notes) : null
}; };
@@ -79,10 +79,10 @@ angular
'type': 1, 'type': 1,
folderId: unencryptedSite.folderId === '' ? null : unencryptedSite.folderId, folderId: unencryptedSite.folderId === '' ? null : unencryptedSite.folderId,
favorite: unencryptedSite.favorite !== null ? unencryptedSite.favorite : false, favorite: unencryptedSite.favorite !== null ? unencryptedSite.favorite : false,
uri: cryptoService.encrypt(unencryptedSite.uri, key), uri: !unencryptedSite.uri || unencryptedSite.uri === '' ? null : cryptoService.encrypt(unencryptedSite.uri, key),
name: cryptoService.encrypt(unencryptedSite.name, key), name: cryptoService.encrypt(unencryptedSite.name, key),
username: !unencryptedSite.username || unencryptedSite.username === '' ? null : cryptoService.encrypt(unencryptedSite.username, key), username: !unencryptedSite.username || unencryptedSite.username === '' ? null : cryptoService.encrypt(unencryptedSite.username, key),
password: cryptoService.encrypt(unencryptedSite.password, key), password: !unencryptedSite.password || unencryptedSite.password === '' ? null : cryptoService.encrypt(unencryptedSite.password, key),
notes: !unencryptedSite.notes || unencryptedSite.notes === '' ? null : cryptoService.encrypt(unencryptedSite.notes, key) notes: !unencryptedSite.notes || unencryptedSite.notes === '' ? null : cryptoService.encrypt(unencryptedSite.notes, key)
}; };
}; };

View File

@@ -0,0 +1,986 @@
angular
.module('bit.services')
.factory('importService', function () {
var _service = {};
_service.import = function (source, file, success, error) {
if (!file) {
error();
return;
}
switch (source) {
case 'local':
importLocal(file, success, error);
break;
case 'lastpass':
importLastPass(file, success, error);
break;
case 'safeincloudcsv':
importSafeInCloudCsv(file, success, error);
break;
case 'safeincloudxml':
importSafeInCloudXml(file, success, error);
break;
case 'keypassxml':
importKeyPassXml(file, success, error);
break;
case 'padlockcsv':
importPadlockCsv(file, success, error);
break;
case '1password1pif':
import1Password1Pif(file, success, error);
break;
case 'chromecsv':
importChromeCsv(file, success, error);
break;
case 'firefoxpasswordexportercsvxml':
importFirefoxPasswordExporterCsvXml(file, success, error);
break;
case 'upmcsv':
importUpmCsv(file, success, error);
break;
case 'keepercsv':
importKeeperCsv(file, success, error);
break;
case 'passworddragonxml':
importPasswordDragonXml(file, success, error);
break;
default:
error();
break;
}
};
function trimUri(uri) {
if (uri.length > 1000) {
return uri.substring(0, 1000);
}
return uri;
}
function parseCsvErrors(results) {
if (results.errors && results.errors.length) {
for (var i = 0; i < results.errors.length; i++) {
console.warn('Error parsing row ' + results.errors[i].row + ': ' + results.errors[i].message);
}
}
}
function importLocal(file, success, error) {
Papa.parse(file, {
header: true,
encoding: 'UTF-8',
complete: function (results) {
parseCsvErrors(results);
var folders = [],
sites = [],
folderRelationships = [];
angular.forEach(results.data, function (value, key) {
var folderIndex = folders.length,
siteIndex = sites.length,
hasFolder = value.folder && value.folder !== '',
addFolder = hasFolder;
if (hasFolder) {
for (var i = 0; i < folders.length; i++) {
if (folders[i].name === value.folder) {
addFolder = false;
folderIndex = i;
break;
}
}
}
sites.push({
favorite: value.favorite !== null ? value.favorite : false,
uri: value.uri && value.uri !== '' ? trimUri(value.uri) : null,
username: value.username && value.username !== '' ? value.username : null,
password: value.password && value.password !== '' ? value.password : null,
notes: value.notes && value.notes !== '' ? value.notes : null,
name: value.name && value.name !== '' ? value.name : '--',
});
if (addFolder) {
folders.push({
name: value.folder
});
}
if (hasFolder) {
var relationship = {
key: siteIndex,
value: folderIndex
};
folderRelationships.push(relationship);
}
});
success(folders, sites, folderRelationships);
}
});
}
function importLastPass(file, success, error) {
if (file.type === 'text/html') {
var reader = new FileReader();
reader.readAsText(file, 'utf-8');
reader.onload = function (evt) {
var doc = $(evt.target.result);
var pre = doc.find('pre');
var csv, results;
if (pre.length === 1) {
csv = pre.text().trim();
results = Papa.parse(csv, {
header: true,
encoding: 'UTF-8'
});
parseData(results.data);
}
else {
var foundPre = false;
for (var i = 0; i < doc.length; i++) {
if (doc[i].tagName.toLowerCase() === 'pre') {
foundPre = true;
csv = doc[i].outerText.trim();
results = Papa.parse(csv, {
header: true,
encoding: 'UTF-8'
});
parseData(results.data);
break;
}
}
if (!foundPre) {
error();
}
}
};
reader.onerror = function (evt) {
error();
};
}
else {
Papa.parse(file, {
header: true,
encoding: 'UTF-8',
complete: function (results) {
parseCsvErrors(results);
parseData(results.data);
}
});
}
function parseData(data) {
var folders = [],
sites = [],
siteRelationships = [],
badDataSites = 0;
angular.forEach(data, function (value, key) {
var folderIndex = folders.length,
siteIndex = sites.length,
hasFolder = value.grouping && value.grouping !== '' && value.grouping !== '(none)',
addFolder = hasFolder;
if (hasFolder) {
for (var i = 0; i < folders.length; i++) {
if (folders[i].name === value.grouping) {
addFolder = false;
folderIndex = i;
break;
}
}
}
if ((!value.name || value.name === '') && (!value.password || value.password === '')) {
badDataSites++;
}
sites.push({
favorite: value.fav === '1',
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
username: value.username && value.username !== '' ? value.username : null,
password: value.password && value.password !== '' ? value.password : null,
notes: value.extra && value.extra !== '' ? value.extra : null,
name: value.name && value.name !== '' ? value.name : '--',
});
if (addFolder) {
folders.push({
name: value.grouping
});
}
if (hasFolder) {
var relationship = {
key: siteIndex,
value: folderIndex
};
siteRelationships.push(relationship);
}
});
if (badDataSites && badDataSites > (data.length / 2)) {
error('CSV data is not formatted correctly from LastPass. Please check your import file and try again.');
}
else {
success(folders, sites, siteRelationships);
}
}
}
function importSafeInCloudCsv(file, success, error) {
Papa.parse(file, {
header: true,
encoding: 'UTF-8',
complete: function (results) {
parseCsvErrors(results);
var folders = [],
sites = [],
siteRelationships = [];
angular.forEach(results.data, function (value, key) {
sites.push({
favorite: false,
uri: value.URL && value.URL !== '' ? trimUri(value.URL) : null,
username: value.Login && value.Login !== '' ? value.Login : null,
password: value.Password && value.Password !== '' ? value.Password : null,
notes: value.Notes && value.Notes !== '' ? value.Notes : null,
name: value.Title && value.Title !== '' ? value.Title : '--',
});
});
success(folders, sites, siteRelationships);
}
});
}
function importSafeInCloudXml(file, success, error) {
var folders = [],
sites = [],
siteRelationships = [],
foldersIndex = [];
var i = 0,
j = 0;
var reader = new FileReader();
reader.readAsText(file, 'utf-8');
reader.onload = function (evt) {
var xmlDoc = $.parseXML(evt.target.result),
xml = $(xmlDoc);
var db = xml.find('database');
if (db.length) {
var labels = db.find('> label');
if (labels.length) {
for (i = 0; i < labels.length; i++) {
var label = $(labels[i]);
foldersIndex[label.attr('id')] = folders.length;
folders.push({
name: label.attr('name')
});
}
}
var cards = db.find('> card');
if (cards.length) {
for (i = 0; i < cards.length; i++) {
var card = $(cards[i]);
if (card.attr('template') === 'true') {
continue;
}
var site = {
favorite: false,
uri: null,
username: null,
password: null,
notes: null,
name: card.attr('title'),
};
var fields = card.find('> field');
for (j = 0; j < fields.length; j++) {
var field = $(fields[j]);
var text = field.text();
var type = field.attr('type');
if (text && text !== '') {
if (type === 'login') {
site.username = text;
}
else if (type === 'password') {
site.password = text;
}
else if (type === 'notes') {
site.notes = text;
}
else if (type === 'website') {
site.uri = trimUri(text);
}
}
}
sites.push(site);
labels = card.find('> label_id');
if (labels.length) {
var labelId = $(labels[0]).text();
var folderIndex = foldersIndex[labelId];
if (labelId !== null && labelId !== '' && folderIndex !== null) {
siteRelationships.push({
key: sites.length - 1,
value: folderIndex
});
}
}
}
}
success(folders, sites, siteRelationships);
}
else {
error();
}
};
reader.onerror = function (evt) {
error();
};
}
function importPadlockCsv(file, success, error) {
Papa.parse(file, {
encoding: 'UTF-8',
complete: function (results) {
parseCsvErrors(results);
var folders = [],
sites = [],
folderRelationships = [];
var customFieldHeaders = [];
// CSV index ref: 0 = name, 1 = category, 2 = username, 3 = password, 4+ = custom fields
var i = 0,
j = 0;
for (i = 0; i < results.data.length; i++) {
var value = results.data[i];
if (i === 0) {
// header row
for (j = 4; j < value.length; j++) {
customFieldHeaders.push(value[j]);
}
continue;
}
var folderIndex = folders.length,
siteIndex = sites.length,
hasFolder = value[1] && value[1] !== '',
addFolder = hasFolder;
if (hasFolder) {
for (j = 0; j < folders.length; j++) {
if (folders[j].name === value[1]) {
addFolder = false;
folderIndex = j;
break;
}
}
}
var site = {
favorite: false,
uri: null,
username: value[2] && value[2] !== '' ? value[2] : null,
password: value[3] && value[3] !== '' ? value[3] : null,
notes: null,
name: value[0] && value[0] !== '' ? value[0] : '--',
};
if (customFieldHeaders.length) {
for (j = 4; j < value.length; j++) {
var cf = value[j];
if (!cf || cf === '') {
continue;
}
var cfHeader = customFieldHeaders[j - 4];
if (cfHeader.toLowerCase() === 'url' || cfHeader.toLowerCase() === 'uri') {
site.uri = trimUri(cf);
}
else {
if (site.notes === null) {
site.notes = '';
}
site.notes += cfHeader + ': ' + cf + '\n';
}
}
}
sites.push(site);
if (addFolder) {
folders.push({
name: value[1]
});
}
if (hasFolder) {
folderRelationships.push({
key: siteIndex,
value: folderIndex
});
}
}
success(folders, sites, folderRelationships);
}
});
}
function importKeyPassXml(file, success, error) {
var folders = [],
sites = [],
siteRelationships = [];
var reader = new FileReader();
reader.readAsText(file, 'utf-8');
reader.onload = function (evt) {
var xmlDoc = $.parseXML(evt.target.result),
xml = $(xmlDoc);
var root = xml.find('Root');
if (root.length) {
var group = root.find('> Group');
if (group.length) {
traverse($(group[0]), true, '');
success(folders, sites, siteRelationships);
}
}
else {
error();
}
};
reader.onerror = function (evt) {
error();
};
function traverse(node, isRootNode, groupNamePrefix) {
var nodeEntries = [];
var folderIndex = folders.length;
var groupName = groupNamePrefix;
if (!isRootNode) {
if (groupName !== '') {
groupName += ' > ';
}
groupName += node.find('> Name').text();
folders.push({
name: groupName
});
}
var entries = node.find('> Entry');
if (entries.length) {
for (var i = 0; i < entries.length; i++) {
var entry = $(entries[i]);
var siteIndex = sites.length;
var site = {
favorite: false,
uri: null,
username: null,
password: null,
notes: null,
name: null
};
var entryStrings = entry.find('> String');
for (var j = 0; j < entryStrings.length; j++) {
var entryString = $(entryStrings[j]);
var key = entryString.find('> Key').text();
var value = entryString.find('> Value').text();
if (value === '') {
continue;
}
switch (key) {
case 'URL':
site.uri = trimUri(value);
break;
case 'UserName':
site.username = value;
break;
case 'Password':
site.password = value;
break;
case 'Title':
site.name = value;
break;
case 'Notes':
site.notes = site.notes === null ? value + '\n' : site.notes + value + '\n';
break;
default:
// other custom fields
site.notes = site.notes === null ? key + ': ' + value + '\n'
: site.notes + key + ': ' + value + '\n';
break;
}
}
if (site.name === null) {
site.name = '--';
}
sites.push(site);
if (!isRootNode) {
siteRelationships.push({
key: siteIndex,
value: folderIndex
});
}
}
}
var groups = node.find('> Group');
if (groups.length) {
for (var k = 0; k < groups.length; k++) {
traverse($(groups[k]), false, groupName);
}
}
}
}
function import1Password1Pif(file, success, error) {
var folders = [],
sites = [],
siteRelationships = [];
var i = 0,
j = 0;
var reader = new FileReader();
reader.readAsText(file, 'utf-8');
reader.onload = function (evt) {
var fileContent = evt.target.result;
var fileLines = fileContent.split(/(?:\r\n|\r|\n)/);
for (i = 0; i < fileLines.length; i++) {
var line = fileLines[i];
if (!line.length || line[0] !== '{') {
continue;
}
var item = JSON.parse(line);
if (item.typeName !== 'webforms.WebForm') {
continue;
}
var site = {
favorite: item.openContents && item.openContents.faveIndex ? true : false,
uri: item.location && item.location !== '' ? trimUri(item.location) : null,
username: null,
password: null,
notes: null,
name: item.title && item.title !== '' ? item.title : '--',
};
if (item.secureContents) {
if (item.secureContents.notesPlain && item.secureContents.notesPlain !== '') {
site.notes = item.secureContents.notesPlain;
}
if (item.secureContents.fields) {
for (j = 0; j < item.secureContents.fields.length; j++) {
var field = item.secureContents.fields[j];
if (field.designation === 'username') {
site.username = field.value;
}
else if (field.designation === 'password') {
site.password = field.value;
}
else {
if (site.notes === null) {
site.notes = '';
}
else {
site.notes += '\n';
}
site.notes += (field.name + ': ' + field.value + '\n');
}
}
}
}
sites.push(site);
}
success(folders, sites, siteRelationships);
};
reader.onerror = function (evt) {
error();
};
}
function importChromeCsv(file, success, error) {
Papa.parse(file, {
header: true,
encoding: 'UTF-8',
complete: function (results) {
parseCsvErrors(results);
var folders = [],
sites = [],
siteRelationships = [];
angular.forEach(results.data, function (value, key) {
sites.push({
favorite: false,
uri: value.url && value.url !== '' ? trimUri(value.url) : null,
username: value.username && value.username !== '' ? value.username : null,
password: value.password && value.password !== '' ? value.password : null,
notes: null,
name: value.name && value.name !== '' ? value.name : '--',
});
});
success(folders, sites, siteRelationships);
}
});
}
function importFirefoxPasswordExporterCsvXml(file, success, error) {
var folders = [],
sites = [],
siteRelationships = [];
function getNameFromHost(host) {
var name = '--';
try {
if (host && host !== '') {
var parser = document.createElement('a');
parser.href = host;
if (parser.hostname) {
name = parser.hostname;
}
}
}
catch (e) {
// do nothing
}
return name;
}
if (file.type === 'text/xml') {
var reader = new FileReader();
reader.readAsText(file, 'utf-8');
reader.onload = function (evt) {
var xmlDoc = $.parseXML(evt.target.result),
xml = $(xmlDoc);
var entries = xml.find('entry');
for (var i = 0; i < entries.length; i++) {
var entry = $(entries[i]);
if (!entry) {
continue;
}
var host = entry.attr('host'),
user = entry.attr('user'),
password = entry.attr('password');
sites.push({
favorite: false,
uri: host && host !== '' ? trimUri(host) : null,
username: user && user !== '' ? user : null,
password: password && password !== '' ? password : null,
notes: null,
name: getNameFromHost(host),
});
}
success(folders, sites, siteRelationships);
};
reader.onerror = function (evt) {
error();
};
}
else {
// currently bugged due to the comment
// ref: https://github.com/mholt/PapaParse/issues/351
error('Only .xml exports are supported.');
return;
//Papa.parse(file, {
// comments: '#',
// header: true,
// encoding: 'UTF-8',
// complete: function (results) {
// parseCsvErrors(results);
// angular.forEach(results.data, function (value, key) {
// sites.push({
// favorite: false,
// uri: value.hostname && value.hostname !== '' ? trimUri(value.hostname) : null,
// username: value.username && value.username !== '' ? value.username : null,
// password: value.password && value.password !== '' ? value.password : null,
// notes: null,
// name: getNameFromHost(value.hostname),
// });
// });
// success(folders, sites, siteRelationships);
// }
//});
}
}
function importUpmCsv(file, success, error) {
Papa.parse(file, {
encoding: 'UTF-8',
complete: function (results) {
parseCsvErrors(results);
var folders = [],
sites = [],
siteRelationships = [];
angular.forEach(results.data, function (value, key) {
if (value.length === 5) {
sites.push({
favorite: false,
uri: value[3] && value[3] !== '' ? trimUri(value[3]) : null,
username: value[1] && value[1] !== '' ? value[1] : null,
password: value[2] && value[2] !== '' ? value[2] : null,
notes: value[4] && value[4] !== '' ? value[4] : null,
name: value[0] && value[0] !== '' ? value[0] : '--',
});
}
});
success(folders, sites, siteRelationships);
}
});
}
function importKeeperCsv(file, success, error) {
Papa.parse(file, {
encoding: 'UTF-8',
complete: function (results) {
parseCsvErrors(results);
var folders = [],
sites = [],
folderRelationships = [];
angular.forEach(results.data, function (value, key) {
if (value.length >= 6) {
var folderIndex = folders.length,
siteIndex = sites.length,
hasFolder = value[0] && value[0] !== '',
addFolder = hasFolder,
i = 0;
if (hasFolder) {
for (i = 0; i < folders.length; i++) {
if (folders[i].name === value[0]) {
addFolder = false;
folderIndex = i;
break;
}
}
}
var site = {
favorite: false,
uri: value[4] && value[4] !== '' ? trimUri(value[4]) : null,
username: value[2] && value[2] !== '' ? value[2] : null,
password: value[3] && value[3] !== '' ? value[3] : null,
notes: value[5] && value[5] !== '' ? value[5] : null,
name: value[1] && value[1] !== '' ? value[1] : '--',
};
if (value.length > 6) {
// we have some custom fields. add them to notes.
if (site.notes === null) {
site.notes = '';
}
else {
site.notes += '\n';
}
for (i = 6; i < value.length; i = i + 2) {
var cfName = value[i];
var cfValue = value[i + 1];
site.notes += (cfName + ': ' + cfValue + '\n');
}
}
sites.push(site);
if (addFolder) {
folders.push({
name: value[0]
});
}
if (hasFolder) {
var relationship = {
key: siteIndex,
value: folderIndex
};
folderRelationships.push(relationship);
}
}
});
success(folders, sites, folderRelationships);
}
});
}
function importPasswordDragonXml(file, success, error) {
var folders = [],
sites = [],
folderRelationships = [],
foldersIndex = [],
j = 0;
var reader = new FileReader();
reader.readAsText(file, 'utf-8');
reader.onload = function (evt) {
var xmlDoc = $.parseXML(evt.target.result),
xml = $(xmlDoc);
var pwManager = xml.find('PasswordManager');
if (pwManager.length) {
var records = pwManager.find('> record');
if (records.length) {
for (var i = 0; i < records.length; i++) {
var record = $(records[i]);
var accountNameNode = record.find('> Account-Name'),
accountName = accountNameNode.length ? $(accountNameNode) : null,
userIdNode = record.find('> User-Id'),
userId = userIdNode.length ? $(userIdNode) : null,
passwordNode = record.find('> Password'),
password = passwordNode.length ? $(passwordNode) : null,
urlNode = record.find('> URL'),
url = urlNode.length ? $(urlNode) : null,
notesNode = record.find('> Notes'),
notes = notesNode.length ? $(notesNode) : null,
categoryNode = record.find('> Category'),
category = categoryNode.length ? $(categoryNode) : null,
categoryText = category ? category.text() : null;
var folderIndex = folders.length,
siteIndex = sites.length,
hasFolder = categoryText && categoryText !== '' && categoryText !== 'Unfiled',
addFolder = hasFolder;
if (hasFolder) {
for (j = 0; j < folders.length; j++) {
if (folders[j].name === categoryText) {
addFolder = false;
folderIndex = j;
break;
}
}
}
var site = {
favorite: false,
uri: url && url.text() !== '' ? trimUri(url.text()) : null,
username: userId && userId.text() !== '' ? userId.text() : null,
password: password && password.text() !== '' ? password.text() : null,
notes: notes && notes.text() !== '' ? notes.text() : null,
name: accountName && accountName.text() !== '' ? accountName.text() : '--',
};
var attributesSelector = '';
for (j = 1; j <= 10; j++) {
attributesSelector += '> Attribute-' + j;
if (j < 10) {
attributesSelector += ', ';
}
}
var attributes = record.find(attributesSelector);
if (attributes.length) {
// we have some attributes. add them to notes.
for (j = 0; j < attributes.length; j++) {
var attr = $(attributes[j]),
attrName = attr.prop('tagName'),
attrValue = attr.text();
if (!attrValue || attrValue === '' || attrValue === 'null') {
continue;
}
if (site.notes === null) {
site.notes = '';
}
else {
site.notes += '\n';
}
site.notes += (attrName + ': ' + attrValue);
}
}
sites.push(site);
if (addFolder) {
folders.push({
name: categoryText
});
}
if (hasFolder) {
var relationship = {
key: siteIndex,
value: folderIndex
};
folderRelationships.push(relationship);
}
}
}
success(folders, sites, folderRelationships);
}
else {
error();
}
};
reader.onerror = function (evt) {
error();
};
}
return _service;
});

View File

@@ -97,8 +97,39 @@ angular
return password; return password;
}; };
// EFForg/OpenWireless
// ref https://github.com/EFForg/OpenWireless/blob/master/app/js/diceware.js
function randomInt(min, max) { function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min; var rval = 0;
var range = max - min;
var bits_needed = Math.ceil(Math.log2(range));
if (bits_needed > 53) {
throw new Exception("We cannot generate numbers larger than 53 bits.");
}
var bytes_needed = Math.ceil(bits_needed / 8);
var mask = Math.pow(2, bits_needed) - 1;
// 7776 -> (2^13 = 8192) -1 == 8191 or 0x00001111 11111111
// Create byte array and fill with N random numbers
var byteArray = new Uint8Array(bytes_needed);
window.crypto.getRandomValues(byteArray);
var p = (bytes_needed - 1) * 8;
for (var i = 0; i < bytes_needed; i++) {
rval += byteArray[i] * Math.pow(2, p);
p -= 8;
}
// Use & to apply the mask and reduce the number of recursive lookups
rval = rval & mask;
if (rval >= range) {
// Integer out of acceptable range
return randomInt(min, max);
}
// Return an integer that falls within the range
return min + rval;
} }
return _service; return _service;

2
src/app/settings.js Normal file
View File

@@ -0,0 +1,2 @@
angular.module("bit")
.constant("appSettings", {"rememberedEmailCookieName":"bit.rememberedEmail","apiUri":"http://localhost:4000","version":"1.5.0","environment":"Development"});

View File

@@ -19,18 +19,31 @@
masterPasswordHash: _masterPasswordHash, masterPasswordHash: _masterPasswordHash,
provider: 0 /* Only authenticator provider for now. */ provider: 0 /* Only authenticator provider for now. */
}, function (response) { }, function (response) {
var key = response.AuthenticatorKey; processResponse(response);
$scope.twoFactorModel = {
enabled: response.TwoFactorEnabled,
key: key.replace(/(.{4})/g, '$1 ').trim(),
qr: 'https://chart.googleapis.com/chart?chs=120x120&chld=L|0&cht=qr&chl=otpauth://totp/' +
_issuer + ':' + encodeURIComponent(_profile.email) +
'%3Fsecret=' + encodeURIComponent(key) +
'%26issuer=' + _issuer
};
}).$promise; }).$promise;
}; };
function formatString(s) {
if (!s) {
return null;
}
return s.replace(/(.{4})/g, '$1 ').trim().toUpperCase();
}
function processResponse(response) {
var key = response.AuthenticatorKey;
$scope.twoFactorModel = {
enabled: response.TwoFactorEnabled,
key: formatString(key),
recovery: formatString(response.TwoFactorRecoveryCode),
qr: 'https://chart.googleapis.com/chart?chs=120x120&chld=L|0&cht=qr&chl=otpauth://totp/' +
_issuer + ':' + encodeURIComponent(_profile.email) +
'%3Fsecret=' + encodeURIComponent(key) +
'%26issuer=' + _issuer
};
}
$scope.update = function (model) { $scope.update = function (model) {
var currentlyEnabled = $scope.twoFactorModel.enabled; var currentlyEnabled = $scope.twoFactorModel.enabled;
if (currentlyEnabled && !confirm('Are you sure you want to disable two-step login?')) { if (currentlyEnabled && !confirm('Are you sure you want to disable two-step login?')) {
@@ -39,7 +52,7 @@
var request = { var request = {
enabled: !currentlyEnabled, enabled: !currentlyEnabled,
token: model ? model.token : null, token: model.token.replace(' ', ''),
masterPasswordHash: _masterPasswordHash masterPasswordHash: _masterPasswordHash
}; };
@@ -48,14 +61,16 @@
$analytics.eventTrack('Enabled Two-step Login'); $analytics.eventTrack('Enabled Two-step Login');
toastr.success('Two-step login has been enabled.'); toastr.success('Two-step login has been enabled.');
if (_profile.extended) _profile.extended.twoFactorEnabled = true; if (_profile.extended) _profile.extended.twoFactorEnabled = true;
processResponse(response);
$('#token').blur();
model.token = null;
} }
else { else {
$analytics.eventTrack('Disabled Two-step Login'); $analytics.eventTrack('Disabled Two-step Login');
toastr.success('Two-step login has been disabled.'); toastr.success('Two-step login has been disabled.');
if (_profile.extended) _profile.extended.twoFactorEnabled = false; if (_profile.extended) _profile.extended.twoFactorEnabled = false;
$scope.close();
} }
$scope.close();
}).$promise; }).$promise;
}; };

View File

@@ -29,7 +29,7 @@
<form name="updateTwoStepForm" ng-submit="updateTwoStepForm.$valid && update(updateModel)" api-form="updatePromise" ng-if="twoFactorModel"> <form name="updateTwoStepForm" ng-submit="updateTwoStepForm.$valid && update(updateModel)" api-form="updatePromise" ng-if="twoFactorModel">
<div class="modal-body"> <div class="modal-body">
<div ng-show="enabled()"> <div ng-show="enabled()">
<p>Two-step login is enabled on your account. Below is the code required by your verification app.</p> <p>Two-step login is <strong class="text-green">enabled</strong> on your account. Below is the code required by your verification app.</p>
<p>Need a two-step verification app? Download one of the following:</p> <p>Need a two-step verification app? Download one of the following:</p>
</div> </div>
<div ng-show="!enabled()"> <div ng-show="!enabled()">
@@ -37,7 +37,7 @@
<h4>1. Download a two-step verification app</h4> <h4>1. Download a two-step verification app</h4>
</div> </div>
<ul class="fa-ul"> <ul class="fa-ul">
<li><i class="fa-li fa fa-apple"></i> iOS devices: <a href="https://itunes.apple.com/en/app/authy/id494168017" target="_blank">Authy for iOS</a></li> <li><i class="fa-li fa fa-apple"></i> iOS devices: <a href="https://itunes.apple.com/us/app/authy/id494168017?mt=8" target="_blank">Authy for iOS</a></li>
<li><i class="fa-li fa fa-android"></i> Android devices: <a href="https://play.google.com/store/apps/details?id=com.authy.authy" target="_blank">Authy for Android</a></li> <li><i class="fa-li fa fa-android"></i> Android devices: <a href="https://play.google.com/store/apps/details?id=com.authy.authy" target="_blank">Authy for Android</a></li>
<li><i class="fa-li fa fa-windows"></i> Windows devices: <a href="https://www.microsoft.com/en-us/store/apps/authenticator/9wzdncrfj3rj" target="_blank">Microsoft Authenticator </a></li> <li><i class="fa-li fa fa-windows"></i> Windows devices: <a href="https://www.microsoft.com/en-us/store/apps/authenticator/9wzdncrfj3rj" target="_blank">Microsoft Authenticator </a></li>
</ul> </ul>
@@ -47,35 +47,48 @@
<div class="col-md-4 text-center"> <div class="col-md-4 text-center">
<p><img ng-src="{{twoFactorModel.qr}}" alt="QR" class="img-thumbnail" /></p> <p><img ng-src="{{twoFactorModel.qr}}" alt="QR" class="img-thumbnail" /></p>
</div> </div>
<div class="col-sm-8"> <div class="col-md-8">
<p><strong>Can't scan the code?</strong> You can add the code to your application manually using the following details:</p> <p><strong>Can't scan the code?</strong> You can add the code to your application manually using the following details:</p>
<ul class="list-unstyled"> <ul class="list-unstyled">
<li><strong>Key:</strong> <samp>{{twoFactorModel.key}}</samp></li> <li><strong>Key:</strong> <code>{{twoFactorModel.key}}</code></li>
<li><strong>Account:</strong> {{account}}</li> <li><strong>Account:</strong> {{account}}</li>
<li><strong>Time based:</strong> Yes</li> <li><strong>Time based:</strong> Yes</li>
</ul> </ul>
</div> </div>
</div> </div>
<div ng-show="!enabled()"> <div ng-show="enabled()">
<div class="callout callout-danger validation-errors" ng-show="updateTwoStepForm.$errors"> <hr />
<h4>Errors have occured</h4> <h4>Recovery Code</h4>
<ul> <p>
<li ng-repeat="e in updateTwoStepForm.$errors">{{e}}</li> The recovery code allows you to access your account in the event that you lose your authenticator app.
</ul> bitwarden support won't be able to assist you if you lose access to your account. We recommend you write down or
</div> print the recovery code below and keep it in a safe place.
<h4 style="margin-top: 30px;">3. Enter the resulting verification code from the app</h4> </p>
<div class="form-group" show-errors ng-show="!twoFactorModel.enabled"> <ul class="list-unstyled">
<label for="token" class="sr-only">Verification Code</label> <li>
<input type="number" id="token" name="Token" placeholder="Verification Code" ng-model="updateModel.token" class="form-control" ng-required="!twoFactorModel.enabled" api-field /> <strong>Recovery Code:</strong> <code>{{twoFactorModel.recovery}}</code>
</div> </li>
<p>NOTE: After enabling two-step login, you will be required to enter the current code generated by your verification app each time you log in.</p> </ul>
</div> </div>
<div class="callout callout-danger validation-errors" ng-show="updateTwoStepForm.$errors">
<h4>Errors have occured</h4>
<ul>
<li ng-repeat="e in updateTwoStepForm.$errors">{{e}}</li>
</ul>
</div>
<hr ng-show="enabled()" />
<h4 style="margin-top: 30px;"><span ng-show="!enabled()">3. </span>Enter the resulting verification code from the app</h4>
<div class="form-group" show-errors>
<label for="token" class="sr-only">Verification Code</label>
<input type="text" id="token" name="Token" placeholder="Verification Code" ng-model="updateModel.token" class="form-control" required api-field />
</div>
<p ng-show="!enabled()">NOTE: After enabling two-step login, you will be required to enter the current code generated by your verification app each time you log in.</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="updateTwoStepForm.$loading"> <button type="submit" class="btn btn-primary btn-flat" ng-disabled="updateTwoStepForm.$loading">
<i class="fa fa-refresh fa-spin loading-icon" ng-show="updateTwoStepForm.$loading"></i> <i class="fa fa-refresh fa-spin loading-icon" ng-show="updateTwoStepForm.$loading"></i>
<span ng-show="twoFactorModel.enabled">Disable Two-step</span> <span ng-show="enabled()">Disable Two-step</span>
<span ng-show="!twoFactorModel.enabled">Enable Two-step</span> <span ng-show="!enabled()">Enable Two-step</span>
</button> </button>
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button> <button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
</div> </div>

View File

@@ -12,6 +12,12 @@
}; };
function importSuccess(folders, sites, folderRelationships) { function importSuccess(folders, sites, folderRelationships) {
if (!folders.length && !sites.length) {
$uibModalInstance.dismiss('cancel');
toastr.error('Nothing was imported.');
return;
}
apiService.ciphers.import({ apiService.ciphers.import({
folders: cipherService.encryptFolders(folders, cryptoService.getKey()), folders: cipherService.encryptFolders(folders, cryptoService.getKey()),
sites: cipherService.encryptSites(sites, cryptoService.getKey()), sites: cipherService.encryptSites(sites, cryptoService.getKey()),
@@ -19,14 +25,45 @@
}, function () { }, function () {
$uibModalInstance.dismiss('cancel'); $uibModalInstance.dismiss('cancel');
$state.go('backend.vault').then(function () { $state.go('backend.vault').then(function () {
$analytics.eventTrack('Imported Data', { label: model.source }); $analytics.eventTrack('Imported Data', { label: $scope.model.source });
toastr.success('Data has been successfully imported into your vault.', 'Import Success'); toastr.success('Data has been successfully imported into your vault.', 'Import Success');
}); });
}, importError); }, importError);
} }
function importError() { function importError(error) {
$analytics.eventTrack('Import Data Failed', { label: $scope.model.source });
$uibModalInstance.dismiss('cancel'); $uibModalInstance.dismiss('cancel');
if (error) {
var data = error.data;
if (data && data.ValidationErrors) {
var message = '';
for (var key in data.ValidationErrors) {
if (!data.ValidationErrors.hasOwnProperty(key)) {
continue;
}
for (var i = 0; i < data.ValidationErrors[key].length; i++) {
message += (key + ': ' + data.ValidationErrors[key][i] + ' ');
}
}
if (message !== '') {
toastr.error(message);
return;
}
}
else if (data && data.Message) {
toastr.error(data.Message);
return;
}
else {
toastr.error(error);
return;
}
}
toastr.error('Something went wrong. Try again.', 'Oh No!'); toastr.error('Something went wrong. Try again.', 'Oh No!');
} }

View File

@@ -7,13 +7,23 @@
<div class="form-group"> <div class="form-group">
<label for="source">Source</label> <label for="source">Source</label>
<select id="source" name="source" class="form-control" ng-model="model.source"> <select id="source" name="source" class="form-control" ng-model="model.source">
<option value="local">bitwarden</option> <option value="local">bitwarden (csv)</option>
<option value="lastpass">LastPass</option> <option value="lastpass">LastPass (csv)</option>
<option value="chromecsv">Chrome (csv)</option>
<option value="firefoxpasswordexportercsvxml">Firefox Password Exporter (xml)</option>
<option value="safeincloudxml">SafeInCloud (xml)</option>
<option value="safeincloudcsv">SafeInCloud (csv)</option>
<option value="keypassxml">KeyPass (xml)</option>
<option value="padlockcsv">Padlock (csv)</option>
<option value="1password1pif">1Password (1pif)</option>
<option value="upmcsv">Universal Password Manager (csv)</option>
<option value="keepercsv">Keeper (csv)</option>
<option value="passworddragonxml">Password Dragon (xml)</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="file">File</label> <label for="file">File</label>
<input type="file" id="file" name="file" /> <input type="file" id="file" name="file" required />
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View File

@@ -37,6 +37,10 @@
alert('Your web browser does not support easy clipboard copying. Copy it manually instead.'); alert('Your web browser does not support easy clipboard copying. Copy it manually instead.');
}; };
$scope.folderSort = function (item) {
return item.name.toLowerCase();
};
function selectPassword(e) { function selectPassword(e) {
var target = $(e.trigger).parent().prev(); var target = $(e.trigger).parent().prev();
if (target.attr('type') === 'text') { if (target.attr('type') === 'text') {

View File

@@ -77,13 +77,25 @@
} }
}); });
editModel.result.then(function (editedSite) { editModel.result.then(function (returnVal) {
var site = $filter('filter')($scope.sites, { id: editedSite.id }, true); if (returnVal.action === 'edit') {
if (site && site.length > 0) { var siteToUpdate = $filter('filter')($scope.sites, { id: returnVal.data.id }, true);
site[0].folderId = editedSite.folderId;
site[0].name = editedSite.name; if (siteToUpdate && siteToUpdate.length > 0) {
site[0].username = editedSite.username; siteToUpdate[0].folderId = returnVal.data.folderId;
site[0].favorite = editedSite.favorite; siteToUpdate[0].name = returnVal.data.name;
siteToUpdate[0].username = returnVal.data.username;
siteToUpdate[0].favorite = returnVal.data.favorite;
}
}
else if (returnVal.action === 'delete') {
var siteToDelete = $filter('filter')($scope.sites, { id: returnVal.data }, true);
if (siteToDelete && siteToDelete.length > 0) {
var index = $scope.sites.indexOf(siteToDelete[0]);
if (index > -1) {
$scope.sites.splice(index, 1);
}
}
} }
}); });
}; };
@@ -115,7 +127,9 @@
apiService.sites.del({ id: site.id }, function () { apiService.sites.del({ id: site.id }, function () {
var index = $scope.sites.indexOf(site); var index = $scope.sites.indexOf(site);
$scope.sites.splice(index, 1); if (index > -1) {
$scope.sites.splice(index, 1);
}
}); });
}; };
@@ -162,7 +176,9 @@
apiService.folders.del({ id: folder.id }, function () { apiService.folders.del({ id: folder.id }, function () {
var index = $scope.folders.indexOf(folder); var index = $scope.folders.indexOf(folder);
$scope.folders.splice(index, 1); if (index > -1) {
$scope.folders.splice(index, 1);
}
}); });
}; };

View File

@@ -15,7 +15,10 @@
$scope.savePromise = apiService.sites.put({ id: siteId }, site, function (siteResponse) { $scope.savePromise = apiService.sites.put({ id: siteId }, site, function (siteResponse) {
$analytics.eventTrack('Edited Site'); $analytics.eventTrack('Edited Site');
var decSite = cipherService.decryptSite(siteResponse); var decSite = cipherService.decryptSite(siteResponse);
$uibModalInstance.close(decSite); $uibModalInstance.close({
action: 'edit',
data: decSite
});
}).$promise; }).$promise;
}; };
@@ -38,6 +41,10 @@
alert('Your web browser does not support easy clipboard copying. Copy it manually instead.'); alert('Your web browser does not support easy clipboard copying. Copy it manually instead.');
}; };
$scope.folderSort = function (item) {
return item.name.toLowerCase();
};
function selectPassword(e) { function selectPassword(e) {
var target = $(e.trigger).parent().prev(); var target = $(e.trigger).parent().prev();
if (target.attr('type') === 'text') { if (target.attr('type') === 'text') {
@@ -45,6 +52,19 @@
} }
} }
$scope.delete = function () {
if (!confirm('Are you sure you want to delete this site (' + $scope.site.name + ')?')) {
return;
}
apiService.sites.del({ id: $scope.site.id }, function () {
$uibModalInstance.close({
action: 'delete',
data: $scope.site.id
});
});
};
$scope.close = function () { $scope.close = function () {
$uibModalInstance.dismiss('cancel'); $uibModalInstance.dismiss('cancel');
}; };

View File

@@ -12,6 +12,9 @@
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title"><i class="fa fa-folder-open"></i> {{folder.name}}</h3> <h3 class="box-title"><i class="fa fa-folder-open"></i> {{folder.name}}</h3>
<div class="box-tools pull-right"> <div class="box-tools pull-right">
<button type="button" class="btn btn-box-tool" ng-click="addSite(folder)" uib-tooltip="Add Site">
<i class="fa fa-plus"></i>
</button>
<button type="button" class="btn btn-box-tool" ng-click="deleteFolder(folder)" ng-show="canDeleteFolder(folder)" uib-tooltip="Delete"> <button type="button" class="btn btn-box-tool" ng-click="deleteFolder(folder)" ng-show="canDeleteFolder(folder)" uib-tooltip="Delete">
<i class="fa fa-trash"></i> <i class="fa fa-trash"></i>
</button> </button>
@@ -54,4 +57,4 @@
</div> </div>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -34,7 +34,7 @@
<div class="form-group" show-errors> <div class="form-group" show-errors>
<label for="folder">Folder</label> <label for="folder">Folder</label>
<select id="folder" name="FolderId" ng-model="site.folderId" class="form-control" api-field> <select id="folder" name="FolderId" ng-model="site.folderId" class="form-control" api-field>
<option ng-repeat="folder in folders" value="{{folder.id}}">{{folder.name}}</option> <option ng-repeat="folder in folders | orderBy: folderSort" value="{{folder.id}}">{{folder.name}}</option>
</select> </select>
</div> </div>
</div> </div>
@@ -64,7 +64,7 @@
<label for="password">Password</label> <label for="password">Password</label>
<div class="input-group"> <div class="input-group">
<input tabindex="-1" type="text" id="password-text" value="{{site.password}}" style="margin-left: -9999px;" /> <input tabindex="-1" type="text" id="password-text" value="{{site.password}}" style="margin-left: -9999px;" />
<input type="password" id="password" name="Password" ng-model="site.password" class="form-control" required api-field /> <input type="password" id="password" name="Password" ng-model="site.password" class="form-control" api-field />
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left"> <span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard <button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
ngclipboard-success="clipboardSuccess(e)" ngclipboard-success="clipboardSuccess(e)"
@@ -95,4 +95,4 @@
</button> </button>
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button> <button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
</div> </div>
</form> </form>

View File

@@ -37,7 +37,7 @@
<div class="form-group" show-errors> <div class="form-group" show-errors>
<label for="folder">Folder</label> <label for="folder">Folder</label>
<select id="folder" name="FolderId" ng-model="site.folderId" class="form-control" api-field> <select id="folder" name="FolderId" ng-model="site.folderId" class="form-control" api-field>
<option ng-repeat="folder in folders" value="{{folder.id}}">{{folder.name}}</option> <option ng-repeat="folder in folders | orderBy: folderSort" value="{{folder.id}}">{{folder.name}}</option>
</select> </select>
</div> </div>
</div> </div>
@@ -67,7 +67,7 @@
<label for="password">Password</label> <label for="password">Password</label>
<div class="input-group"> <div class="input-group">
<input type="text" id="password-text" value="{{site.password}}" style="margin-left: -9999px;" /> <input type="text" id="password-text" value="{{site.password}}" style="margin-left: -9999px;" />
<input type="password" id="password" name="Password" ng-model="site.password" class="form-control" required api-field /> <input type="password" id="password" name="Password" ng-model="site.password" class="form-control" api-field />
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left"> <span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard <button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
ngclipboard-success="clipboardSuccess(e)" ngclipboard-success="clipboardSuccess(e)"
@@ -97,5 +97,8 @@
<i class="fa fa-refresh fa-spin loading-icon" ng-show="editSiteForm.$loading"></i>Save <i class="fa fa-refresh fa-spin loading-icon" ng-show="editSiteForm.$loading"></i>Save
</button> </button>
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button> <button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
<button type="button" class="btn btn-link pull-right" ng-click="delete()" uib-tooltip="Delete">
<i class="fa fa-trash fa-lg"></i>
</button>
</div> </div>
</form> </form>

View File

@@ -107,13 +107,14 @@
</ul> </ul>
</li> </li>
<li> <li>
<a href="https://bitwarden.com/contact/" target="_blank" <a href="https://help.bitwarden.com/" target="_blank"
analytics-on="click" analytics-event="Clicked Get Help"> analytics-on="click" analytics-event="Clicked Get Help">
<i class="fa fa-info-circle"></i> <span>Get Help</span> <i class="fa fa-info-circle"></i> <span>Get Help</span>
</a> </a>
</li> </li>
<li class="header"> <li class="header">
MOBILE APPS<small class="label pull-right bg-green">FREE</small> <small class="label pull-right bg-green">FREE</small>
MOBILE APPS
</li> </li>
<li> <li>
<a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8"
@@ -128,7 +129,8 @@
</a> </a>
</li> </li>
<li class="header"> <li class="header">
BROWSER EXTENSIONS <small class="label pull-right bg-green">FREE</small> <small class="label pull-right bg-green">FREE</small>
BROWSER EXTENSIONS
</li> </li>
<li> <li>
<a href="https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb" <a href="https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb"
@@ -139,22 +141,22 @@
<li> <li>
<a href="javascript:void(0)" <a href="javascript:void(0)"
target="_blank" analytics-on="click" analytics-event="Clicked Firefox"> target="_blank" analytics-on="click" analytics-event="Clicked Firefox">
<i class="fa fa-firefox"></i> <span>Firefox</span>
<small class="label pull-right bg-gray">coming very soon</small> <small class="label pull-right bg-gray">coming very soon</small>
<i class="fa fa-firefox"></i> <span>Firefox</span>
</a> </a>
</li> </li>
<li> <li>
<a href="javascript:void(0)" <a href="javascript:void(0)"
target="_blank" analytics-on="click" analytics-event="Clicked Opera"> target="_blank" analytics-on="click" analytics-event="Clicked Opera">
<i class="fa fa-opera"></i> <span>Opera</span>
<small class="label pull-right bg-gray">coming very soon</small> <small class="label pull-right bg-gray">coming very soon</small>
<i class="fa fa-opera"></i> <span>Opera</span>
</a> </a>
</li> </li>
<li> <li>
<a href="javascript:void(0)" <a href="javascript:void(0)"
target="_blank" analytics-on="click" analytics-event="Clicked Edge"> target="_blank" analytics-on="click" analytics-event="Clicked Edge">
<i class="fa fa-edge"></i> <span>Edge</span>
<small class="label pull-right bg-gray">coming soon</small> <small class="label pull-right bg-gray">coming soon</small>
<i class="fa fa-edge"></i> <span>Edge</span>
</a> </a>
</li> </li>
</ul> </ul>

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -10,13 +10,13 @@
<!-- @if true !> <!-- @if true !>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" /> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
<meta name="x-stylesheet-test-bs" content="" class="invisible" /> <meta name="x-stylesheet-test-bs" content="" class="invisible" />
<script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName('SCRIPT'),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}('visibility','hidden',['lib\/bootstrap\/css\/bootstrap.min.css']);</script> <script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName('SCRIPT'),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}('visibility','hidden',['lib\/bootstrap\/css\/bootstrap.min.css?v=<!-- @echo cacheTag !>']);</script>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" /> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" />
<meta name="x-stylesheet-test-fa" content="" class="fa" /> <meta name="x-stylesheet-test-fa" content="" class="fa" />
<script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName('SCRIPT'),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}('font-family','FontAwesome',['lib\/font-awesome\/css\/font-awesome.min.css']);</script> <script>!function(a,b,c){var d,e=document,f=e.getElementsByTagName('SCRIPT'),g=f[f.length-1].previousElementSibling,h=e.defaultView&&e.defaultView.getComputedStyle?e.defaultView.getComputedStyle(g):g.currentStyle;if(h&&h[a]!==b)for(d=0;d<c.length;d++)e.write('<link rel="stylesheet" href="'+c[d]+'"/>')}('font-family','FontAwesome',['lib\/font-awesome\/css\/font-awesome.min.css?v=<!-- @echo cacheTag !>']);</script>
<link rel="stylesheet" href="css/vault.min.css" /> <link rel="stylesheet" href="css/vault.min.css?v=<!-- @echo cacheTag !>" />
<!-- @endif --> <!-- @endif -->
<!-- @exclude --> <!-- @exclude -->
<link rel="stylesheet" href="lib/bootstrap/css/bootstrap.css" /> <link rel="stylesheet" href="lib/bootstrap/css/bootstrap.css" />
@@ -30,16 +30,16 @@
<!-- @if true !> <!-- @if true !>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>(window.jQuery||document.write('<script src="lib\/jquery\/jquery.min.js"><\/script>'));</script> <script>(window.jQuery||document.write('<script src="lib\/jquery\/jquery.min.js?v=<!-- @echo cacheTag !>"><\/script>'));</script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script> <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script>((window.jQuery&&window.jQuery.fn&&window.jQuery.fn.modal)||document.write('<script src="lib\/bootstrap\/js\/bootstrap.min.js"><\/script>'));</script> <script>((window.jQuery&&window.jQuery.fn&&window.jQuery.fn.modal)||document.write('<script src="lib\/bootstrap\/js\/bootstrap.min.js?v=<!-- @echo cacheTag !>"><\/script>'));</script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script>(window.angular||document.write('<script src="lib\/angular\/angular.min.js"><\/script>'));</script> <script>(window.angular||document.write('<script src="lib\/angular\/angular.min.js?v=<!-- @echo cacheTag !>"><\/script>'));</script>
<script src="js/lib.min.js"></script> <script src="js/lib.min.js?v=<!-- @echo cacheTag !>"></script>
<script src="js/app.min.js"></script> <script src="js/app.min.js?v=<!-- @echo cacheTag !>"></script>
<script> <script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
@@ -110,6 +110,7 @@
<script src="app/accounts/accountsLogoutController.js"></script> <script src="app/accounts/accountsLogoutController.js"></script>
<script src="app/accounts/accountsRegisterController.js"></script> <script src="app/accounts/accountsRegisterController.js"></script>
<script src="app/accounts/accountsPasswordHintController.js"></script> <script src="app/accounts/accountsPasswordHintController.js"></script>
<script src="app/accounts/accountsRecoverController.js"></script>
<script src="app/vault/vaultModule.js"></script> <script src="app/vault/vaultModule.js"></script>
<script src="app/vault/vaultController.js"></script> <script src="app/vault/vaultController.js"></script>

47
src/less/theme.less Normal file
View File

@@ -0,0 +1,47 @@
@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700,300italic,400italic,600italic);
@import "../../node_modules/toastr/toastr.less";
/* Start AdminLTE */
//Bootstrap Variables & Mixins
//The core bootstrap code have not been modified. These files
//are included only for reference.
@import (reference) "../../node_modules/admin-lte/build/bootstrap-less/mixins.less";
@import (reference) "../../node_modules/admin-lte/build/bootstrap-less/variables.less";
//MISC
//----
@import "../../node_modules/admin-lte/build/less/core.less";
@import "../../node_modules/admin-lte/build/less/variables.less";
@import "../../node_modules/admin-lte/build/less/mixins.less";
//COMPONENTS
//-----------
@import "../../node_modules/admin-lte/build/less/header.less";
@import "../../node_modules/admin-lte/build/less/sidebar.less";
@import "../../node_modules/admin-lte/build/less/sidebar-mini.less";
@import "../../node_modules/admin-lte/build/less/control-sidebar.less";
@import "../../node_modules/admin-lte/build/less/dropdown.less";
@import "../../node_modules/admin-lte/build/less/forms.less";
@import "../../node_modules/admin-lte/build/less/progress-bars.less";
@import "../../node_modules/admin-lte/build/less/small-box.less";
@import "../../node_modules/admin-lte/build/less/boxes.less";
@import "../../node_modules/admin-lte/build/less/info-box.less";
@import "../../node_modules/admin-lte/build/less/timeline.less";
@import "../../node_modules/admin-lte/build/less/buttons.less";
@import "../../node_modules/admin-lte/build/less/callout.less";
@import "../../node_modules/admin-lte/build/less/alerts.less";
@import "../../node_modules/admin-lte/build/less/navs.less";
@import "../../node_modules/admin-lte/build/less/table.less";
@import "../../node_modules/admin-lte/build/less/labels.less";
@import "../../node_modules/admin-lte/build/less/modal.less";
//PAGES
//------
@import "../../node_modules/admin-lte/build/less/login_and_register.less";
@import "../../node_modules/admin-lte/build/less/404_500_errors.less";
//Miscellaneous
//-------------
@import "../../node_modules/admin-lte/build/less/miscellaneous.less";
@import "../../node_modules/admin-lte/build/less/print.less";
/* End AdminLTE */
@import "../../node_modules/admin-lte/build/less/skins/skin-blue.less";