mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-21 16:18:28 +01:00
initial commit of source
This commit is contained in:
parent
1fe75015be
commit
df69bdbc21
202
.gitignore
vendored
Normal file
202
.gitignore
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
build/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
|
||||||
|
# Visual Studo 2015 cache/options directory
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# JustCode is a .NET coding addin-in
|
||||||
|
.JustCode
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/packages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/packages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/packages/repositories.config
|
||||||
|
|
||||||
|
# Windows Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Windows Store app package directory
|
||||||
|
AppPackages/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
*.[Cc]ache
|
||||||
|
ClientBin/
|
||||||
|
[Ss]tyle[Cc]op.*
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
node_modules/
|
||||||
|
bower_components/
|
||||||
|
lib/
|
||||||
|
css/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Other
|
||||||
|
project.lock.json
|
8
NuGet.Config
Normal file
8
NuGet.Config
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?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>
|
35
bitwarden-vault.sln
Normal file
35
bitwarden-vault.sln
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 14
|
||||||
|
VisualStudioVersion = 14.0.23107.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{860863C9-0436-43D4-840D-FE919C9F6FFC}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{14FE7221-D377-4AD5-9A9E-4541577CF05A}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.gitignore = .gitignore
|
||||||
|
global.json = global.json
|
||||||
|
NuGet.Config = NuGet.Config
|
||||||
|
README.md = README.md
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Vault", "src\Vault\Vault.xproj", "{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5}.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
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{0BEBF47C-BA0B-48AC-B48C-718F94084AD5} = {860863C9-0436-43D4-840D-FE919C9F6FFC}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
6
global.json
Normal file
6
global.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"projects": [ "src", "test" ],
|
||||||
|
"sdk": {
|
||||||
|
"version": "1.0.0-rc1-final"
|
||||||
|
}
|
||||||
|
}
|
23
src/Vault/Properties/AssemblyInfo.cs
Normal file
23
src/Vault/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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.Vault")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("bitwarden Vault")]
|
||||||
|
[assembly: AssemblyProduct("bitwarden Vault")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||||
|
[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")]
|
25
src/Vault/Properties/launchSettings.json
Normal file
25
src/Vault/Properties/launchSettings.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:4001/",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"Hosting:Environment": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"commandName": "web",
|
||||||
|
"environmentVariables": {
|
||||||
|
"Hosting:Environment": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
src/Vault/Startup.cs
Normal file
21
src/Vault/Startup.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Bit.Vault
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public void ConfigureServices(IServiceCollection services) { }
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseIISPlatformHandler();
|
||||||
|
app.UseFileServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry point for the application.
|
||||||
|
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
|
||||||
|
}
|
||||||
|
}
|
19
src/Vault/Vault.xproj
Normal file
19
src/Vault/Vault.xproj
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?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)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>0bebf47c-ba0b-48ac-b48c-718f94084ad5</ProjectGuid>
|
||||||
|
<RootNamespace>Bit.Vault</RootNamespace>
|
||||||
|
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||||
|
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<DevelopmentServerPort>4001</DevelopmentServerPort>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
</Project>
|
312
src/Vault/gulpfile.js
Normal file
312
src/Vault/gulpfile.js
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
/// <binding BeforeBuild='build, dist' Clean='clean' ProjectOpened='build. dist' />
|
||||||
|
|
||||||
|
var gulp = require('gulp'),
|
||||||
|
rimraf = require('rimraf'),
|
||||||
|
concat = require('gulp-concat'),
|
||||||
|
rename = require('gulp-rename'),
|
||||||
|
cssmin = require('gulp-cssmin'),
|
||||||
|
uglify = require('gulp-uglify'),
|
||||||
|
ghPages = require('gulp-gh-pages'),
|
||||||
|
less = require('gulp-less'),
|
||||||
|
ngAnnotate = require('gulp-ng-annotate'),
|
||||||
|
preprocess = require('gulp-preprocess'),
|
||||||
|
runSequence = require('run-sequence'),
|
||||||
|
merge = require('merge-stream'),
|
||||||
|
ngConfig = require('gulp-ng-config'),
|
||||||
|
settings = require('./settings.json'),
|
||||||
|
project = require('./project.json'),
|
||||||
|
jshint = require('gulp-jshint'),
|
||||||
|
_ = require('lodash');
|
||||||
|
|
||||||
|
var paths = {};
|
||||||
|
paths.dist = '../../dist/';
|
||||||
|
paths.webroot = './wwwroot/'
|
||||||
|
paths.js = paths.webroot + 'js/**/*.js';
|
||||||
|
paths.minJs = paths.webroot + 'js/**/*.min.js';
|
||||||
|
paths.concatJsDest = paths.webroot + 'js/bw.min.js';
|
||||||
|
paths.libDir = paths.webroot + 'lib/';
|
||||||
|
paths.npmDir = 'node_modules/';
|
||||||
|
paths.lessDir = 'less/';
|
||||||
|
paths.cssDir = paths.webroot + 'css/';
|
||||||
|
paths.jsDir = paths.webroot + 'js/';
|
||||||
|
|
||||||
|
gulp.task('lint', function () {
|
||||||
|
return gulp.src(paths.webroot + 'app/**/*.js')
|
||||||
|
.pipe(jshint())
|
||||||
|
.pipe(jshint.reporter('default'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('build', function (cb) {
|
||||||
|
return runSequence(
|
||||||
|
'clean',
|
||||||
|
['lib', 'less', 'settings', 'lint'],
|
||||||
|
cb);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('clean:js', function (cb) {
|
||||||
|
return rimraf(paths.concatJsDest, cb);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('clean:css', function (cb) {
|
||||||
|
return rimraf(paths.cssDir, cb);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('clean:lib', function (cb) {
|
||||||
|
return rimraf(paths.libDir, cb);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('clean', ['clean:js', 'clean:css', 'clean:lib', 'dist:clean']);
|
||||||
|
|
||||||
|
gulp.task('min:js', ['clean:js'], function () {
|
||||||
|
return gulp.src([paths.js, '!' + paths.minJs], { base: '.' })
|
||||||
|
.pipe(concat(paths.concatJsDest))
|
||||||
|
.pipe(uglify())
|
||||||
|
.pipe(gulp.dest('.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('min:css', [], function () {
|
||||||
|
return gulp.src([paths.cssDir + '**/*.css', '!' + paths.cssDir + '**/*.min.css'], { base: '.' })
|
||||||
|
.pipe(cssmin())
|
||||||
|
.pipe(rename({ suffix: '.min' }))
|
||||||
|
.pipe(gulp.dest('.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('min', ['min:js', 'min:css']);
|
||||||
|
|
||||||
|
gulp.task('lib', ['clean:lib'], function () {
|
||||||
|
var libs = [
|
||||||
|
{
|
||||||
|
src: [
|
||||||
|
paths.npmDir + 'bootstrap/dist/**/*',
|
||||||
|
'!' + paths.npmDir + 'bootstrap/dist/**/npm.js',
|
||||||
|
'!' + paths.npmDir + 'bootstrap/dist/**/css/*theme*'
|
||||||
|
],
|
||||||
|
dest: paths.libDir + 'bootstrap'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'font-awesome/css/*',
|
||||||
|
dest: paths.libDir + 'font-awesome/css'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'font-awesome/fonts/*',
|
||||||
|
dest: paths.libDir + 'font-awesome/fonts'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'jquery/dist/*.js',
|
||||||
|
dest: paths.libDir + 'jquery'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'admin-lte/dist/js/app*.js',
|
||||||
|
dest: paths.libDir + 'admin-lte/js'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'angular/angular*.js',
|
||||||
|
dest: paths.libDir + 'angular'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'angular-bootstrap-npm/dist/*tpls*.js',
|
||||||
|
dest: paths.libDir + 'angular-bootstrap'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'angular-bootstrap-show-errors/src/*.js',
|
||||||
|
dest: paths.libDir + 'angular-bootstrap-show-errors'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'angular-cookies/*cookies*.js',
|
||||||
|
dest: paths.libDir + 'angular-cookies'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'angular-jwt/dist/*.js',
|
||||||
|
dest: paths.libDir + 'angular-jwt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'angular-md5/angular-md5*.js',
|
||||||
|
dest: paths.libDir + 'angular-md5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'angular-resource/*resource*.js',
|
||||||
|
dest: paths.libDir + 'angular-resource'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: [paths.npmDir + 'angular-toastr/dist/**/*.css', paths.npmDir + 'angular-toastr/dist/**/*.js'],
|
||||||
|
dest: paths.libDir + 'angular-toastr'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'angular-ui-router/release/*.js',
|
||||||
|
dest: paths.libDir + 'angular-ui-router'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'angular-messages/*messages*.js',
|
||||||
|
dest: paths.libDir + 'angular-messages'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: [paths.npmDir + 'sjcl/core/cbc.js', paths.npmDir + 'sjcl/core/bitArray.js', paths.npmDir + 'sjcl/sjcl.js'],
|
||||||
|
dest: paths.libDir + 'sjcl'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'ngstorage/*.js',
|
||||||
|
dest: paths.libDir + 'ngstorage'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'papaparse/papaparse*.js',
|
||||||
|
dest: paths.libDir + 'papaparse'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'ngclipboard/dist/ngclipboard*.js',
|
||||||
|
dest: paths.libDir + 'ngclipboard'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'clipboard/dist/clipboard*.js',
|
||||||
|
dest: paths.libDir + 'clipboard'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
var tasks = libs.map(function (lib) {
|
||||||
|
return gulp.src(lib.src).pipe(gulp.dest(lib.dest));
|
||||||
|
});
|
||||||
|
|
||||||
|
return merge(tasks);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('settings', function () {
|
||||||
|
return config()
|
||||||
|
.pipe(gulp.dest(paths.webroot + 'app'));
|
||||||
|
});
|
||||||
|
|
||||||
|
function config() {
|
||||||
|
return gulp.src('./settings.json')
|
||||||
|
.pipe(ngConfig('bit', {
|
||||||
|
createModule: false,
|
||||||
|
constants: _.merge({}, {
|
||||||
|
appSettings: {
|
||||||
|
version: project.version,
|
||||||
|
environment: project.environment
|
||||||
|
}
|
||||||
|
}, require('./settings.' + project.environment + '.json') || {})
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
gulp.task('less', function () {
|
||||||
|
return gulp.src(paths.lessDir + 'vault.less')
|
||||||
|
.pipe(less())
|
||||||
|
.pipe(gulp.dest(paths.cssDir));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('watch', function () {
|
||||||
|
gulp.watch(paths.lessDir + '*.less', ['less']);
|
||||||
|
gulp.watch('./settings*.json', ['settings']);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('dist:clean', function (cb) {
|
||||||
|
return rimraf(paths.dist, cb);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('dist:move', function () {
|
||||||
|
var moves = [
|
||||||
|
{
|
||||||
|
src: [
|
||||||
|
paths.npmDir + 'bootstrap/dist/**/bootstrap.min.js',
|
||||||
|
paths.npmDir + 'bootstrap/dist/**/bootstrap.min.css',
|
||||||
|
paths.npmDir + 'bootstrap/dist/**/fonts/**/*',
|
||||||
|
],
|
||||||
|
dest: paths.dist + 'lib/bootstrap'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: [
|
||||||
|
paths.npmDir + 'font-awesome/**/font-awesome.min.css',
|
||||||
|
paths.npmDir + 'font-awesome/**/fonts/**/*'
|
||||||
|
],
|
||||||
|
dest: paths.dist + 'lib/font-awesome'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'jquery/dist/jquery.min.js',
|
||||||
|
dest: paths.dist + 'lib/jquery'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'angular/angular.min.js',
|
||||||
|
dest: paths.dist + 'lib/angular'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: [
|
||||||
|
paths.webroot + '**/app/**/*.html',
|
||||||
|
paths.webroot + '**/images/**/*',
|
||||||
|
paths.webroot + 'index.html',
|
||||||
|
paths.webroot + 'favicon.ico'
|
||||||
|
],
|
||||||
|
dest: paths.dist
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
var tasks = moves.map(function (move) {
|
||||||
|
return gulp.src(move.src).pipe(gulp.dest(move.dest));
|
||||||
|
});
|
||||||
|
|
||||||
|
return merge(tasks);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('dist:css', function () {
|
||||||
|
return gulp
|
||||||
|
.src([
|
||||||
|
paths.cssDir + '**/*.css',
|
||||||
|
'!' + paths.cssDir + '**/*.min.css'
|
||||||
|
])
|
||||||
|
.pipe(preprocess({ context: settings }))
|
||||||
|
.pipe(cssmin())
|
||||||
|
.pipe(rename({ suffix: '.min' }))
|
||||||
|
.pipe(gulp.dest(paths.dist + 'css'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('dist:js:app', function () {
|
||||||
|
var mainStream = gulp
|
||||||
|
.src([
|
||||||
|
paths.webroot + 'app/app.js',
|
||||||
|
'!' + paths.webroot + 'app/settings.js',
|
||||||
|
paths.webroot + 'app/**/*Module.js',
|
||||||
|
paths.webroot + 'app/**/*.js'
|
||||||
|
]);
|
||||||
|
|
||||||
|
merge(mainStream, config())
|
||||||
|
.pipe(preprocess({ context: settings }))
|
||||||
|
.pipe(concat(paths.dist + '/js/app.min.js'))
|
||||||
|
.pipe(ngAnnotate())
|
||||||
|
.pipe(uglify())
|
||||||
|
.pipe(gulp.dest('.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('dist:js:lib', function () {
|
||||||
|
return gulp
|
||||||
|
.src([
|
||||||
|
paths.libDir + 'sjcl/sjcl.js',
|
||||||
|
paths.libDir + 'sjcl/*.js',
|
||||||
|
paths.libDir + '**/*.js',
|
||||||
|
'!' + paths.libDir + '**/*.min.js',
|
||||||
|
'!' + paths.libDir + 'angular/**/*',
|
||||||
|
'!' + paths.libDir + 'bootstrap/**/*',
|
||||||
|
'!' + paths.libDir + 'jquery/**/*'
|
||||||
|
])
|
||||||
|
.pipe(concat(paths.dist + '/js/lib.min.js'))
|
||||||
|
.pipe(uglify())
|
||||||
|
.pipe(gulp.dest('.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('dist:preprocess', function () {
|
||||||
|
return gulp
|
||||||
|
.src([
|
||||||
|
paths.dist + '/**/*.html'
|
||||||
|
], { base: '.' })
|
||||||
|
.pipe(preprocess({ context: settings }))
|
||||||
|
.pipe(gulp.dest('.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('dist', ['build'], function (cb) {
|
||||||
|
return runSequence(
|
||||||
|
'dist:clean',
|
||||||
|
['dist:move', 'dist:css', 'dist:js:app', 'dist:js:lib'],
|
||||||
|
'dist:preprocess',
|
||||||
|
cb);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('deploy', ['dist'], function () {
|
||||||
|
return gulp.src(paths.dist + '**/*')
|
||||||
|
.pipe(ghPages({ cacheDir: paths.dist + '.publish' }));
|
||||||
|
});
|
47
src/Vault/less/theme.less
Normal file
47
src/Vault/less/theme.less
Normal 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";
|
170
src/Vault/less/vault.less
Normal file
170
src/Vault/less/vault.less
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
@import "theme.less";
|
||||||
|
|
||||||
|
/* Theme Adjustments */
|
||||||
|
|
||||||
|
@boxed-layout-bg-image-path: "../images/boxed-bg.png";
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: @gray;
|
||||||
|
.img-retina(@boxed-layout-bg-image-path, "../images/boxed-bg-2x.png", auto, auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
.main-header .logo,
|
||||||
|
h1, h2, h3, h4, h5, h6,
|
||||||
|
.h1, .h2, .h3, .h4, .h5, .h6 {
|
||||||
|
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-body > .table-responsive {
|
||||||
|
> .table {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: @screen-xs-max) {
|
||||||
|
border: none;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-form .form-group {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
input[type="text"], .form-control-feedback {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form div.validation-errors ul {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-page,
|
||||||
|
.register-page {
|
||||||
|
background-color: @gray;
|
||||||
|
background-repeat: repeat;
|
||||||
|
background-attachment: fixed;
|
||||||
|
.img-retina(@boxed-layout-bg-image-path, "../images/boxed-bg-2x.png", auto, auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-box-body,
|
||||||
|
.register-box-body {
|
||||||
|
.boxShadow(0 0 8px rgba(0, 0, 0, 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-box, .register-box {
|
||||||
|
.checkbox {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-options {
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin: 0 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
|
||||||
|
.btn-table {
|
||||||
|
padding: 1px 5px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-box-tool {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .btn .loading-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modals */
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toastr */
|
||||||
|
|
||||||
|
#toast-container {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
&.toast-top-right {
|
||||||
|
top: 65px;
|
||||||
|
right: 15px;
|
||||||
|
|
||||||
|
@media (max-width: @screen-xs-max) {
|
||||||
|
top: initial;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .toast {
|
||||||
|
background-image: none !important;
|
||||||
|
.border-radius(0);
|
||||||
|
.boxShadow(0 0 8px rgba(0, 0, 0, 0.5));
|
||||||
|
|
||||||
|
@media (max-width: @screen-xs-max) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.toast-danger, &.toast-error {
|
||||||
|
&:extend(.bg-red);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "\f0e7";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.toast-warning {
|
||||||
|
&:extend(.bg-yellow);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "\f071";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.toast-info {
|
||||||
|
&:extend(.bg-aqua);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "\f005";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.toast-success {
|
||||||
|
&:extend(.bg-green);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "\f00C";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: fixed;
|
||||||
|
font-family: FontAwesome;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
float: left;
|
||||||
|
color: #ffffff;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
margin: auto 0.5em auto -1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/Vault/package.json
Normal file
43
src/Vault/package.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "bitwarden",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"devDependencies": {
|
||||||
|
"connect": "^3.4.0",
|
||||||
|
"lodash": "3.10.1",
|
||||||
|
"gulp": "3.9.0",
|
||||||
|
"gulp-concat": "2.6.0",
|
||||||
|
"gulp-cssmin": "0.1.7",
|
||||||
|
"gulp-less": "3.0.3",
|
||||||
|
"gulp-rename": "1.2.2",
|
||||||
|
"gulp-uglify": "1.4.0",
|
||||||
|
"gulp-gh-pages": "0.5.4",
|
||||||
|
"gulp-preprocess": "1.2.0",
|
||||||
|
"gulp-ng-annotate": "1.1.0",
|
||||||
|
"gulp-ng-config": "1.2.1",
|
||||||
|
"jshint": "2.9.1-rc1",
|
||||||
|
"gulp-jshint": "2.0.0",
|
||||||
|
"rimraf": "2.4.3",
|
||||||
|
"run-sequence": "1.1.3",
|
||||||
|
"merge-stream": "1.0.0",
|
||||||
|
"jquery": "2.1.4",
|
||||||
|
"font-awesome": "4.4.0",
|
||||||
|
"bootstrap": "3.3.5",
|
||||||
|
"sjcl": "1.0.3",
|
||||||
|
"angular": "1.4.7",
|
||||||
|
"angular-resource": "1.4.7",
|
||||||
|
"angular-bootstrap-npm": "0.14.3",
|
||||||
|
"angular-ui-router": "0.2.15",
|
||||||
|
"angular-jwt": "0.0.9",
|
||||||
|
"angular-cookies": "1.4.7",
|
||||||
|
"admin-lte": "2.3.2",
|
||||||
|
"angular-md5": "0.1.8",
|
||||||
|
"angular-toastr": "1.5.0",
|
||||||
|
"angular-bootstrap-show-errors": "2.3.0",
|
||||||
|
"angular-messages": "1.4.7",
|
||||||
|
"ngstorage": "0.3.10",
|
||||||
|
"papaparse": "4.1.2",
|
||||||
|
"toastr": "2.1.2",
|
||||||
|
"clipboard": "1.5.5",
|
||||||
|
"ngclipboard": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
27
src/Vault/project.json
Normal file
27
src/Vault/project.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"version": "0.0.1",
|
||||||
|
"environment": "Development",
|
||||||
|
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
|
||||||
|
"Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
|
||||||
|
"Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final"
|
||||||
|
},
|
||||||
|
|
||||||
|
"commands": {
|
||||||
|
"web": "Microsoft.AspNet.Server.Kestrel"
|
||||||
|
},
|
||||||
|
|
||||||
|
"frameworks": {
|
||||||
|
"dnx451": { }
|
||||||
|
},
|
||||||
|
|
||||||
|
"exclude": [
|
||||||
|
"wwwroot",
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
|
"publishExclude": [
|
||||||
|
"**.user",
|
||||||
|
"**.vspscc"
|
||||||
|
]
|
||||||
|
}
|
5
src/Vault/settings.Development.json
Normal file
5
src/Vault/settings.Development.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"appSettings": {
|
||||||
|
"apiUri": "http://localhost:4000"
|
||||||
|
}
|
||||||
|
}
|
5
src/Vault/settings.Production.json
Normal file
5
src/Vault/settings.Production.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"appSettings": {
|
||||||
|
"apiUri": "https://api.bitwarden.com"
|
||||||
|
}
|
||||||
|
}
|
5
src/Vault/settings.Staging.json
Normal file
5
src/Vault/settings.Staging.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"appSettings": {
|
||||||
|
"apiUri": "https://api.bitwarden.com"
|
||||||
|
}
|
||||||
|
}
|
5
src/Vault/settings.json
Normal file
5
src/Vault/settings.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"appSettings": {
|
||||||
|
"rememberdEmailCookieName": "bit.rememberedEmail"
|
||||||
|
}
|
||||||
|
}
|
48
src/Vault/wwwroot/_references.js
Normal file
48
src/Vault/wwwroot/_references.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/// <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/accounts/accountsRegisterFinalizeController.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/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/settingsmodule.js" />
|
||||||
|
/// <reference path="app/settings/settingssessioncontroller.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" />
|
48
src/Vault/wwwroot/app/accounts/accountsLoginController.js
Normal file
48
src/Vault/wwwroot/app/accounts/accountsLoginController.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsLoginController', function ($scope, $rootScope, $cookies, apiService, cryptoService, authService, $state, appSettings) {
|
||||||
|
var rememberedEmail = $cookies.get(appSettings.rememberdEmailCookieName);
|
||||||
|
if (rememberedEmail) {
|
||||||
|
$scope.model = {
|
||||||
|
email: rememberedEmail,
|
||||||
|
rememberEmail: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.login = function (model) {
|
||||||
|
$scope.loginPromise = authService.logIn(model.email, model.masterPassword);
|
||||||
|
|
||||||
|
$scope.loginPromise.then(function () {
|
||||||
|
if (model.rememberEmail) {
|
||||||
|
var cookieExpiration = new Date();
|
||||||
|
cookieExpiration.setFullYear(cookieExpiration.getFullYear() + 10);
|
||||||
|
|
||||||
|
$cookies.put(
|
||||||
|
appSettings.rememberdEmailCookieName,
|
||||||
|
model.email,
|
||||||
|
{ expires: cookieExpiration });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$cookies.remove(appSettings.rememberdEmailCookieName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile = authService.getUserProfile();
|
||||||
|
if (profile.twoFactor) {
|
||||||
|
$state.go('frontend.login.twoFactor');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$state.go('backend.vault');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.twoFactor = function (model) {
|
||||||
|
// Only supporting Authenticator provider for now
|
||||||
|
$scope.twoFactorPromise = authService.logInTwoFactor(model.code, "Authenticator");
|
||||||
|
|
||||||
|
$scope.twoFactorPromise.then(function () {
|
||||||
|
$state.go('backend.vault');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,7 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsLogoutController', function ($scope, authService, $state) {
|
||||||
|
authService.logOut();
|
||||||
|
$state.go('frontend.login.info');
|
||||||
|
});
|
2
src/Vault/wwwroot/app/accounts/accountsModule.js
Normal file
2
src/Vault/wwwroot/app/accounts/accountsModule.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts', ['ui.bootstrap', 'ngCookies']);
|
@ -0,0 +1,12 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsPasswordHintController', function ($scope, $rootScope, apiService) {
|
||||||
|
$scope.success = false;
|
||||||
|
|
||||||
|
$scope.submit = function (model) {
|
||||||
|
$scope.submitPromise = apiService.accounts.postPasswordHint({ email: model.email }, function () {
|
||||||
|
$scope.success = true;
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
});
|
13
src/Vault/wwwroot/app/accounts/accountsRegisterController.js
Normal file
13
src/Vault/wwwroot/app/accounts/accountsRegisterController.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsRegisterController', function ($scope, $rootScope, apiService) {
|
||||||
|
$scope.success = false;
|
||||||
|
|
||||||
|
$scope.registerPromise = null;
|
||||||
|
$scope.register = function (model) {
|
||||||
|
$scope.registerPromise = apiService.accounts.registerToken({ email: model.email }, function () {
|
||||||
|
$scope.success = true;
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,48 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.accounts')
|
||||||
|
|
||||||
|
.controller('accountsRegisterFinalizeController', function ($scope, $rootScope, $location, $state, apiService, cryptoService, validationService) {
|
||||||
|
var params = $location.search();
|
||||||
|
|
||||||
|
if (!params.token || !params.email) {
|
||||||
|
$state.go('frontend.login.info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.success = false;
|
||||||
|
$scope.model = {
|
||||||
|
email: params.email,
|
||||||
|
token: params.token
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.info = function () {
|
||||||
|
$scope.model.confirmMasterPassword = null;
|
||||||
|
$state.go('frontend.registerFinalize.confirm');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.confirmPromise = null;
|
||||||
|
$scope.confirm = function (form) {
|
||||||
|
if ($scope.model.masterPassword != $scope.model.confirmMasterPassword) {
|
||||||
|
validationService.addError(form, 'ConfirmMasterPassword', 'Master password confirmation does not match.', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = cryptoService.makeKey($scope.model.masterPassword, $scope.model.email);
|
||||||
|
var request = {
|
||||||
|
token: $scope.model.token,
|
||||||
|
name: $scope.model.name,
|
||||||
|
email: $scope.model.email,
|
||||||
|
masterPasswordHash: cryptoService.hashPassword($scope.model.masterPassword, key),
|
||||||
|
masterPasswordHint: $scope.model.masterPasswordHint
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.confirmPromise = apiService.accounts.register(request, function () {
|
||||||
|
$scope.success = true;
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.loadInfo = function () {
|
||||||
|
$scope.model.masterPassword = null;
|
||||||
|
window.history.back();
|
||||||
|
};
|
||||||
|
});
|
7
src/Vault/wwwroot/app/accounts/views/accountsLogin.html
Normal file
7
src/Vault/wwwroot/app/accounts/views/accountsLogin.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<div class="login-box">
|
||||||
|
<div class="login-logo">
|
||||||
|
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||||
|
</div>
|
||||||
|
<div class="login-box-body" ui-view>
|
||||||
|
</div>
|
||||||
|
</div>
|
40
src/Vault/wwwroot/app/accounts/views/accountsLoginInfo.html
Normal file
40
src/Vault/wwwroot/app/accounts/views/accountsLoginInfo.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<p class="login-box-msg">Log in to access your vault.</p>
|
||||||
|
<form name="loginForm" ng-submit="loginForm.$valid && login(model)" api-form="loginPromise">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="loginForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in loginForm.$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="row">
|
||||||
|
<div class="col-xs-7">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="rememberEmail" ng-model="model.rememberEmail" /> Remember Email
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="loginForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="loginForm.$loading"></i>Log In
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<ul>
|
||||||
|
<li><a ui-sref="frontend.register">Register a new account</a></li>
|
||||||
|
<li><a ui-sref="frontend.passwordHint">Get master password hint</a></li>
|
||||||
|
</ul>
|
||||||
|
</form>
|
@ -0,0 +1,22 @@
|
|||||||
|
<p class="login-box-msg">Enter your two-step verification code.</p>
|
||||||
|
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(model)" api-form="twoFactorPromise">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in twoFactorForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-feedback" show-errors>
|
||||||
|
<label for="code" class="sr-only">Code</label>
|
||||||
|
<input type="number" id="code" name="Code" class="form-control" placeholder="Verification code" ng-model="model.code" required api-field
|
||||||
|
/>
|
||||||
|
<span class="fa fa-lock form-control-feedback"></span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-offset-7 col-xs-5">
|
||||||
|
<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
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
@ -0,0 +1,37 @@
|
|||||||
|
<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">Get your master password hint.</p>
|
||||||
|
<div class="text-center" ng-show="success">
|
||||||
|
<div class="callout callout-success">
|
||||||
|
If your account exists ({{model.email}}) we've sent you an email with your master password hint.
|
||||||
|
</div>
|
||||||
|
<a ui-sref="frontend.login.info">Ready to log in?</a>
|
||||||
|
</div>
|
||||||
|
<form name="passwordHintForm" ng-submit="passwordHintForm.$valid && submit(model)" ng-show="!success" api-form="submitPromise">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="passwordHintForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in passwordHintForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-feedback" show-errors>
|
||||||
|
<label for="email" class="sr-only">Your account email address</label>
|
||||||
|
<input type="email" id="email" name="Email" class="form-control" placeholder="Your account email address" ng-model="model.email" required api-field />
|
||||||
|
<span class="fa fa-envelope 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="passwordHintForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="passwordHintForm.$loading"></i>Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
37
src/Vault/wwwroot/app/accounts/views/accountsRegister.html
Normal file
37
src/Vault/wwwroot/app/accounts/views/accountsRegister.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<div class="register-box">
|
||||||
|
<div class="register-logo">
|
||||||
|
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||||
|
</div>
|
||||||
|
<div class="register-box-body">
|
||||||
|
<p class="login-box-msg">Register for a new account.</p>
|
||||||
|
<div class="text-center" ng-show="success">
|
||||||
|
<div class="callout callout-success">
|
||||||
|
<h4>Almost done!</h4> Check your email ({{model.email}}) to complete your registration.
|
||||||
|
</div>
|
||||||
|
<a ui-sref="frontend.login.info">Ready to log in?</a>
|
||||||
|
</div>
|
||||||
|
<form name="registerForm" ng-submit="registerForm.$valid && register(model)" ng-show="!success" api-form="registerPromise">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="registerForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in registerForm.$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="row">
|
||||||
|
<div class="col-xs-7">
|
||||||
|
<a ui-sref="frontend.login.info">Already have an account?</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="registerForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="registerForm.$loading"></i>Register
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,7 @@
|
|||||||
|
<div class="register-box">
|
||||||
|
<div class="register-logo">
|
||||||
|
<i class="fa fa-shield"></i> <b>bit</b>warden
|
||||||
|
</div>
|
||||||
|
<div class="register-box-body" ui-view>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,35 @@
|
|||||||
|
<p class="login-box-msg">Confirm your master password.</p>
|
||||||
|
<div class="text-center" ng-show="success">
|
||||||
|
<div class="callout callout-success">
|
||||||
|
<h4>You're Registered!</h4>
|
||||||
|
<p>You may now log in to your new account.</p>
|
||||||
|
</div>
|
||||||
|
<a ui-sref="frontend.login.info">Ready to log in?</a>
|
||||||
|
</div>
|
||||||
|
<form name="finalizeConfirmForm" ng-submit="finalizeConfirmForm.$valid && confirm(finalizeConfirmForm)" ng-show="!success"
|
||||||
|
api-form="confirmPromise">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="finalizeConfirmForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in finalizeConfirmForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-feedback" show-errors>
|
||||||
|
<label form="confirmMasterPassword" class="sr-only">Re-type Master Password</label>
|
||||||
|
<input type="password" id="confirmMasterPassword" name="ConfirmMasterPassword" class="form-control" placeholder="Re-type Master Password"
|
||||||
|
ng-model="model.confirmMasterPassword" api-field>
|
||||||
|
<span class="fa fa-lock form-control-feedback"></span>
|
||||||
|
<p class="help-block">It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it.</p>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-7">
|
||||||
|
<a href="#" ng-click="loadInfo()"><i class="fa fa-arrow-left"></i> Go Back</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="finalizeConfirmForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="finalizeConfirmForm.$loading"></i>Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
@ -0,0 +1,35 @@
|
|||||||
|
<p class="login-box-msg">Finalize the creation of your new account.</p>
|
||||||
|
<form name="finalizeInfoForm" ng-submit="finalizeInfoForm.$valid && info()">
|
||||||
|
<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" ng-model="model.email" placeholder="Email" readonly required>
|
||||||
|
<span class="fa fa-envelope form-control-feedback"></span>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-feedback" show-errors>
|
||||||
|
<label for="name" class="sr-only">Your Name</label>
|
||||||
|
<input type="text" id="name" name="Name" class="form-control" ng-model="model.name" placeholder="Your Name" required>
|
||||||
|
<span class="fa fa-user form-control-feedback"></span>
|
||||||
|
<p class="help-block">What should we call you?</p>
|
||||||
|
</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" ng-model="model.masterPassword" placeholder="Master Password" required>
|
||||||
|
<span class="fa fa-lock form-control-feedback"></span>
|
||||||
|
<p class="help-block">The master password is the password you use to access your vault.</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-feedback" show-errors>
|
||||||
|
<label for="hint" class="sr-only">Master Password Hint</label>
|
||||||
|
<input type="text" id="hint" name="MasterPasswordHint" class="form-control" ng-model="model.masterPasswordHint" placeholder="Master Password Hint">
|
||||||
|
<span class="fa fa-lightbulb-o form-control-feedback"></span>
|
||||||
|
<p class="help-block">A master password hint can help you remember your password if you forget it.</p>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-7">
|
||||||
|
<a ui-sref="frontend.login.info">Cancel registration</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block btn-flat">Continue</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
29
src/Vault/wwwroot/app/apiInterceptor.js
Normal file
29
src/Vault/wwwroot/app/apiInterceptor.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
angular
|
||||||
|
.module('bit')
|
||||||
|
|
||||||
|
.factory('apiInterceptor', function ($injector, $q, toastr) {
|
||||||
|
return {
|
||||||
|
request: function (config) {
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
response: function (response) {
|
||||||
|
if (response.status === 401 || response.status == 403) {
|
||||||
|
$injector.get('authService').logOut();
|
||||||
|
$injector.get('$state').go('frontend.login.info').then(function () {
|
||||||
|
toastr.warning('Your login session has expired.', 'Logged out');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return response || $q.when(response);
|
||||||
|
},
|
||||||
|
responseError: function (rejection) {
|
||||||
|
if (rejection.status === 401 || rejection.status == 403) {
|
||||||
|
$injector.get('authService').logOut();
|
||||||
|
$injector.get('$state').go('frontend.login.info').then(function () {
|
||||||
|
toastr.warning('Your login session has expired.', 'Logged out');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return $q.reject(rejection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
18
src/Vault/wwwroot/app/app.js
Normal file
18
src/Vault/wwwroot/app/app.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
angular
|
||||||
|
.module('bit', [
|
||||||
|
'ui.router',
|
||||||
|
'ngMessages',
|
||||||
|
'angular-jwt',
|
||||||
|
'angular-md5',
|
||||||
|
'ui.bootstrap.showErrors',
|
||||||
|
'toastr',
|
||||||
|
|
||||||
|
'bit.directives',
|
||||||
|
'bit.services',
|
||||||
|
|
||||||
|
'bit.global',
|
||||||
|
'bit.accounts',
|
||||||
|
'bit.vault',
|
||||||
|
'bit.settings',
|
||||||
|
'bit.tools'
|
||||||
|
]);
|
157
src/Vault/wwwroot/app/config.js
Normal file
157
src/Vault/wwwroot/app/config.js
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
angular
|
||||||
|
.module('bit')
|
||||||
|
|
||||||
|
.config(function ($stateProvider, $urlRouterProvider, $httpProvider, jwtInterceptorProvider, $uibTooltipProvider, toastrConfig) {
|
||||||
|
jwtInterceptorProvider.tokenGetter = /*@ngInject*/ function (config, appSettings, tokenService) {
|
||||||
|
if (config.url.indexOf(appSettings.apiUri) === 0) {
|
||||||
|
return tokenService.getToken();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
angular.extend(toastrConfig, {
|
||||||
|
closeButton: true,
|
||||||
|
progressBar: true,
|
||||||
|
showMethod: 'slideDown',
|
||||||
|
target: '.toast-target'
|
||||||
|
});
|
||||||
|
|
||||||
|
$uibTooltipProvider.options({
|
||||||
|
popupDelay: 600
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!$httpProvider.defaults.headers.get) {
|
||||||
|
$httpProvider.defaults.headers.get = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpProvider.defaults.headers.get['If-Modified-Since'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
|
||||||
|
$httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
|
||||||
|
$httpProvider.defaults.headers.get.Pragma = 'no-cache';
|
||||||
|
|
||||||
|
$httpProvider.interceptors.push('apiInterceptor');
|
||||||
|
$httpProvider.interceptors.push('jwtInterceptor');
|
||||||
|
|
||||||
|
$urlRouterProvider.otherwise('/');
|
||||||
|
|
||||||
|
$stateProvider
|
||||||
|
// Backend
|
||||||
|
.state('backend', {
|
||||||
|
templateUrl: 'app/views/backendLayout.html',
|
||||||
|
abstract: true,
|
||||||
|
data: {
|
||||||
|
authorize: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('backend.vault', {
|
||||||
|
url: '^/',
|
||||||
|
templateUrl: 'app/vault/views/vault.html',
|
||||||
|
controller: 'vaultController',
|
||||||
|
data: { pageTitle: 'My Vault' }
|
||||||
|
})
|
||||||
|
.state('backend.settings', {
|
||||||
|
url: '^/settings',
|
||||||
|
templateUrl: 'app/settings/views/settings.html',
|
||||||
|
controller: 'settingsController',
|
||||||
|
data: { pageTitle: 'Settings' }
|
||||||
|
})
|
||||||
|
.state('backend.tools', {
|
||||||
|
url: '^/tools',
|
||||||
|
templateUrl: 'app/tools/views/tools.html',
|
||||||
|
controller: 'toolsController',
|
||||||
|
data: { pageTitle: 'Tools' }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Frontend
|
||||||
|
.state('frontend', {
|
||||||
|
templateUrl: 'app/views/frontendLayout.html',
|
||||||
|
abstract: true,
|
||||||
|
data: {
|
||||||
|
authorize: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.login', {
|
||||||
|
templateUrl: 'app/accounts/views/accountsLogin.html',
|
||||||
|
controller: 'accountsLoginController',
|
||||||
|
data: {
|
||||||
|
bodyClass: 'login-page'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.login.info', {
|
||||||
|
url: '^/login',
|
||||||
|
templateUrl: 'app/accounts/views/accountsLoginInfo.html',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Log In'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.login.twoFactor', {
|
||||||
|
url: '^/login/two-factor',
|
||||||
|
templateUrl: 'app/accounts/views/accountsLoginTwoFactor.html',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Log In (Two Factor)',
|
||||||
|
authorizeTwoFactor: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.logout', {
|
||||||
|
url: '^/logout',
|
||||||
|
controller: 'accountsLogoutController',
|
||||||
|
data: {
|
||||||
|
authorize: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.passwordHint', {
|
||||||
|
url: '^/password-hint',
|
||||||
|
templateUrl: 'app/accounts/views/accountsPasswordHint.html',
|
||||||
|
controller: 'accountsPasswordHintController',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Master Password Hint',
|
||||||
|
bodyClass: 'login-page'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.register', {
|
||||||
|
url: '^/register',
|
||||||
|
templateUrl: 'app/accounts/views/accountsRegister.html',
|
||||||
|
controller: 'accountsRegisterController',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Register',
|
||||||
|
bodyClass: 'register-page'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.registerFinalize', {
|
||||||
|
controller: 'accountsRegisterFinalizeController',
|
||||||
|
templateUrl: 'app/accounts/views/accountsRegisterFinalize.html',
|
||||||
|
data: {
|
||||||
|
bodyClass: 'register-page'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.registerFinalize.info', {
|
||||||
|
url: '^/register/finalize',
|
||||||
|
templateUrl: 'app/accounts/views/accountsRegisterFinalizeInfo.html',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Finalize Registration'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.state('frontend.registerFinalize.confirm', {
|
||||||
|
url: '^/register/finalize/confirm',
|
||||||
|
templateUrl: 'app/accounts/views/accountsRegisterFinalizeConfirm.html',
|
||||||
|
data: {
|
||||||
|
pageTitle: 'Finalize Registration (Confirm)'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.run(function ($rootScope, authService, jwtHelper, tokenService, $state) {
|
||||||
|
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
|
||||||
|
if (!toState.data || !toState.data.authorize) {
|
||||||
|
if (authService.isAuthenticated() && !jwtHelper.isTokenExpired(tokenService.getToken())) {
|
||||||
|
event.preventDefault();
|
||||||
|
$state.go('backend.vault');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authService.isAuthenticated() || jwtHelper.isTokenExpired(tokenService.getToken())) {
|
||||||
|
event.preventDefault();
|
||||||
|
authService.logOut();
|
||||||
|
$state.go('frontend.login.info');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
30
src/Vault/wwwroot/app/directives/apiFieldDirective.js
Normal file
30
src/Vault/wwwroot/app/directives/apiFieldDirective.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.directives')
|
||||||
|
|
||||||
|
.directive('apiField', function () {
|
||||||
|
var linkFn = function (scope, element, attrs, ngModel) {
|
||||||
|
ngModel.$registerApiError = registerApiError;
|
||||||
|
ngModel.$validators.apiValidate = apiValidator;
|
||||||
|
|
||||||
|
function apiValidator() {
|
||||||
|
ngModel.$setValidity('api', true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerApiError() {
|
||||||
|
ngModel.$setValidity('api', false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
require: 'ngModel',
|
||||||
|
restrict: 'A',
|
||||||
|
compile: function (elem, attrs) {
|
||||||
|
if (!attrs.name || attrs.name === '') {
|
||||||
|
throw 'api-field element does not have a valid name attribute';
|
||||||
|
}
|
||||||
|
|
||||||
|
return linkFn;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
35
src/Vault/wwwroot/app/directives/apiFormDirective.js
Normal file
35
src/Vault/wwwroot/app/directives/apiFormDirective.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.directives')
|
||||||
|
|
||||||
|
.directive('apiForm', function ($rootScope, validationService) {
|
||||||
|
return {
|
||||||
|
require: 'form',
|
||||||
|
restrict: 'A',
|
||||||
|
link: function (scope, element, attrs, formCtrl) {
|
||||||
|
var watchPromise = attrs.apiForm || null;
|
||||||
|
if (watchPromise !== void 0) {
|
||||||
|
scope.$watch(watchPromise, formSubmitted.bind(null, formCtrl, scope));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function formSubmitted(form, scope, promise) {
|
||||||
|
if (!promise || !promise.then) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset errors
|
||||||
|
form.$errors = null;
|
||||||
|
|
||||||
|
// start loading
|
||||||
|
form.$loading = true;
|
||||||
|
|
||||||
|
promise.then(function success(response) {
|
||||||
|
form.$loading = false;
|
||||||
|
}, function failure(reason) {
|
||||||
|
form.$loading = false;
|
||||||
|
validationService.addErrors(form, reason);
|
||||||
|
scope.$broadcast('show-errors-check-validity');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
2
src/Vault/wwwroot/app/directives/directivesModule.js
Normal file
2
src/Vault/wwwroot/app/directives/directivesModule.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.directives', []);
|
40
src/Vault/wwwroot/app/directives/masterPasswordDirective.js
Normal file
40
src/Vault/wwwroot/app/directives/masterPasswordDirective.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.directives')
|
||||||
|
|
||||||
|
.directive('masterPassword', function (cryptoService, authService) {
|
||||||
|
return {
|
||||||
|
require: 'ngModel',
|
||||||
|
restrict: 'A',
|
||||||
|
link: function (scope, elem, attr, ngModel) {
|
||||||
|
var profile = authService.getUserProfile();
|
||||||
|
if (!profile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For DOM -> model validation
|
||||||
|
ngModel.$parsers.unshift(function (value) {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = cryptoService.makeKey(value, profile.email, true);
|
||||||
|
var valid = key == cryptoService.getKey(true);
|
||||||
|
ngModel.$setValidity('masterPassword', valid);
|
||||||
|
return valid ? value : undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
// For model -> DOM validation
|
||||||
|
ngModel.$formatters.unshift(function (value) {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = cryptoService.makeKey(value, profile.email, true);
|
||||||
|
var valid = key == cryptoService.getKey(true);
|
||||||
|
|
||||||
|
ngModel.$setValidity('masterPassword', valid);
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
22
src/Vault/wwwroot/app/directives/pageTitleDirective.js
Normal file
22
src/Vault/wwwroot/app/directives/pageTitleDirective.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.directives')
|
||||||
|
|
||||||
|
.directive('pageTitle', function ($rootScope, $timeout, appSettings) {
|
||||||
|
return {
|
||||||
|
link: function (scope, element) {
|
||||||
|
var listener = function (event, toState, toParams, fromState, fromParams) {
|
||||||
|
// Default title
|
||||||
|
var title = 'bitwarden Password Manager';
|
||||||
|
if (toState.data && toState.data.pageTitle) {
|
||||||
|
title = toState.data.pageTitle + ' - bitwarden Password Manager';
|
||||||
|
}
|
||||||
|
|
||||||
|
$timeout(function () {
|
||||||
|
element.text(title);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$rootScope.$on('$stateChangeStart', listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
73
src/Vault/wwwroot/app/directives/passwordMeterDirective.js
Normal file
73
src/Vault/wwwroot/app/directives/passwordMeterDirective.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.directives')
|
||||||
|
|
||||||
|
.directive('passwordMeter', function () {
|
||||||
|
return {
|
||||||
|
template: '<div class="progress {{outerClass}}"><div class="progress-bar progress-bar-{{valueClass}}" role="progressbar" aria-valuenow="{{value}}" aria-valuemin="0" aria-valuemax="100" ng-style="{width : ( value + \'%\' ) }"><span class="sr-only">{{value}}%</span></div></div>',
|
||||||
|
restrict: 'A',
|
||||||
|
scope: {
|
||||||
|
password: '=passwordMeter',
|
||||||
|
username: '=passwordMeterUsername',
|
||||||
|
value: '=passwordMeterStrength',
|
||||||
|
outerClass: '@?'
|
||||||
|
},
|
||||||
|
link: function (scope) {
|
||||||
|
var measureStrength = function (username, password) {
|
||||||
|
if (!password || password == username) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var strength = password.length;
|
||||||
|
|
||||||
|
if (username && username !== '') {
|
||||||
|
if (username.indexOf(password) != -1) strength -= 15;
|
||||||
|
if (password.indexOf(username) != -1) strength -= username.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length > 0 && password.length <= 4) strength += password.length;
|
||||||
|
else if (password.length >= 5 && password.length <= 7) strength += 6;
|
||||||
|
else if (password.length >= 8 && password.length <= 15) strength += 12;
|
||||||
|
else if (password.length >= 16) strength += 18;
|
||||||
|
|
||||||
|
if (password.match(/[a-z]/)) strength += 1;
|
||||||
|
if (password.match(/[A-Z]/)) strength += 5;
|
||||||
|
if (password.match(/\d/)) strength += 5;
|
||||||
|
if (password.match(/.*\d.*\d.*\d/)) strength += 5;
|
||||||
|
if (password.match(/[!,@,#,$,%,^,&,*,?,_,~]/)) strength += 5;
|
||||||
|
if (password.match(/.*[!,@,#,$,%,^,&,*,?,_,~].*[!,@,#,$,%,^,&,*,?,_,~]/)) strength += 5;
|
||||||
|
if (password.match(/(?=.*[a-z])(?=.*[A-Z])/)) strength += 2;
|
||||||
|
if (password.match(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/)) strength += 2;
|
||||||
|
if (password.match(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!,@,#,$,%,^,&,*,?,_,~])/)) strength += 2;
|
||||||
|
|
||||||
|
strength = Math.round(strength * 2);
|
||||||
|
return Math.max(0, Math.min(100, strength));
|
||||||
|
};
|
||||||
|
|
||||||
|
var getClass = function (strength) {
|
||||||
|
switch (Math.round(strength / 33)) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
return 'danger';
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
return 'warning';
|
||||||
|
case 3:
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var updateMeter = function (scope) {
|
||||||
|
scope.value = measureStrength(scope.username, scope.password);
|
||||||
|
scope.valueClass = getClass(scope.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
scope.$watch('password', function () {
|
||||||
|
updateMeter(scope);
|
||||||
|
});
|
||||||
|
|
||||||
|
scope.$watch('username', function () {
|
||||||
|
updateMeter(scope);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
27
src/Vault/wwwroot/app/directives/passwordViewerDirective.js
Normal file
27
src/Vault/wwwroot/app/directives/passwordViewerDirective.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.directives')
|
||||||
|
|
||||||
|
.directive('passwordViewer', function () {
|
||||||
|
return {
|
||||||
|
restrict: 'A',
|
||||||
|
link: function (scope, element, attr) {
|
||||||
|
var passwordViewer = attr.passwordViewer;
|
||||||
|
if (!passwordViewer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.onclick = function (event) { };
|
||||||
|
element.on('click', function (event) {
|
||||||
|
var passwordElement = $(passwordViewer);
|
||||||
|
if (passwordElement && passwordElement.attr('type') == 'password') {
|
||||||
|
element.removeClass('fa-eye').addClass('fa-eye-slash');
|
||||||
|
passwordElement.attr('type', 'text');
|
||||||
|
}
|
||||||
|
else if (passwordElement && passwordElement.attr('type') == 'text') {
|
||||||
|
element.removeClass('fa-eye-slash').addClass('fa-eye');
|
||||||
|
passwordElement.attr('type', 'password');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
2
src/Vault/wwwroot/app/global/globalModule.js
Normal file
2
src/Vault/wwwroot/app/global/globalModule.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.global', []);
|
78
src/Vault/wwwroot/app/global/mainController.js
Normal file
78
src/Vault/wwwroot/app/global/mainController.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.global')
|
||||||
|
|
||||||
|
.controller('mainController', function ($scope, $state, authService, appSettings, toastr) {
|
||||||
|
var vm = this;
|
||||||
|
vm.bodyClass = '';
|
||||||
|
vm.userProfile = null;
|
||||||
|
vm.searchVaultText = null;
|
||||||
|
vm.version = appSettings.version;
|
||||||
|
|
||||||
|
$scope.currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
|
$scope.$on('$viewContentLoaded', function () {
|
||||||
|
if ($.AdminLTE) {
|
||||||
|
if ($.AdminLTE.layout) {
|
||||||
|
$.AdminLTE.layout.fix();
|
||||||
|
$.AdminLTE.layout.fixSidebar();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($.AdminLTE.pushMenu) {
|
||||||
|
$.AdminLTE.pushMenu.expandOnHover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
|
||||||
|
vm.searchVaultText = null;
|
||||||
|
vm.userProfile = authService.getUserProfile();
|
||||||
|
|
||||||
|
if (toState.data.bodyClass) {
|
||||||
|
vm.bodyClass = toState.data.bodyClass;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vm.bodyClass = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.searchVault = function () {
|
||||||
|
$state.go('backend.vault');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addSite = function () {
|
||||||
|
$scope.$broadcast('vaultAddSite');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addFolder = function () {
|
||||||
|
$scope.$broadcast('vaultAddFolder');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.changeEmail = function () {
|
||||||
|
$scope.$broadcast('settingsChangeEmail');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.changePassword = function () {
|
||||||
|
$scope.$broadcast('settingsChangePassword');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.sessions = function () {
|
||||||
|
$scope.$broadcast('settingsSessions');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.twoFactor = function () {
|
||||||
|
$scope.$broadcast('settingsTwoFactor');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.import = function () {
|
||||||
|
$scope.$broadcast('toolsImport');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.export = function () {
|
||||||
|
$scope.$broadcast('toolsExport');
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.audits = function () {
|
||||||
|
$scope.$broadcast('toolsAudits');
|
||||||
|
};
|
||||||
|
});
|
6
src/Vault/wwwroot/app/global/sideNavController.js
Normal file
6
src/Vault/wwwroot/app/global/sideNavController.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.global')
|
||||||
|
|
||||||
|
.controller('sideNavController', function ($scope, $state) {
|
||||||
|
$scope.$state = $state;
|
||||||
|
});
|
6
src/Vault/wwwroot/app/global/topNavController.js
Normal file
6
src/Vault/wwwroot/app/global/topNavController.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.global')
|
||||||
|
|
||||||
|
.controller('topNavController', function ($scope) {
|
||||||
|
|
||||||
|
});
|
48
src/Vault/wwwroot/app/services/apiService.js
Normal file
48
src/Vault/wwwroot/app/services/apiService.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.services')
|
||||||
|
|
||||||
|
.factory('apiService', function ($resource, tokenService, appSettings) {
|
||||||
|
var _service = {},
|
||||||
|
_apiUri = appSettings.apiUri;
|
||||||
|
|
||||||
|
_service.sites = $resource(_apiUri + '/sites/:id', {}, {
|
||||||
|
get: { method: 'GET', params: { id: '@id' } },
|
||||||
|
list: { method: 'GET', params: {} },
|
||||||
|
post: { method: 'POST', params: {} },
|
||||||
|
put: { method: 'PUT', params: { id: '@id' } },
|
||||||
|
del: { method: 'DELETE', params: { id: '@id' } }
|
||||||
|
});
|
||||||
|
|
||||||
|
_service.folders = $resource(_apiUri + '/folders/:id', {}, {
|
||||||
|
get: { method: 'GET', params: { id: '@id' } },
|
||||||
|
list: { method: 'GET', params: {} },
|
||||||
|
post: { method: 'POST', params: {} },
|
||||||
|
put: { method: 'PUT', params: { id: '@id' } },
|
||||||
|
del: { method: 'DELETE', params: { id: '@id' } }
|
||||||
|
});
|
||||||
|
|
||||||
|
_service.ciphers = $resource(_apiUri + '/ciphers/:id', {}, {
|
||||||
|
putMany: { url: _apiUri + '/ciphers/many', method: 'PUT', params: {} }
|
||||||
|
});
|
||||||
|
|
||||||
|
_service.accounts = $resource(_apiUri + '/accounts', {}, {
|
||||||
|
registerToken: { url: _apiUri + '/accounts/register-token', method: 'POST', params: {} },
|
||||||
|
register: { url: _apiUri + '/accounts/register', method: 'POST', params: {} },
|
||||||
|
emailToken: { url: _apiUri + '/accounts/email-token', method: 'POST', params: {} },
|
||||||
|
email: { url: _apiUri + '/accounts/email', method: 'PUT', params: {} },
|
||||||
|
putPassword: { url: _apiUri + '/accounts/password', method: 'PUT', params: {} },
|
||||||
|
getProfile: { url: _apiUri + '/accounts/profile', method: 'GET', params: {} },
|
||||||
|
putProfile: { url: _apiUri + '/accounts/profile', method: 'PUT', params: {} },
|
||||||
|
getTwoFactor: { url: _apiUri + '/accounts/two-factor', method: 'GET', params: {} },
|
||||||
|
putTwoFactor: { url: _apiUri + '/accounts/two-factor', method: 'PUT', params: {} },
|
||||||
|
postPasswordHint: { url: _apiUri + '/accounts/password-hint', method: 'POST', params: {} },
|
||||||
|
putSecurityStamp: { url: _apiUri + '/accounts/security-stamp', method: 'PUT', params: {} }
|
||||||
|
});
|
||||||
|
|
||||||
|
_service.auth = $resource(_apiUri + '/auth', {}, {
|
||||||
|
token: { url: _apiUri + '/auth/token', method: 'POST', params: {} },
|
||||||
|
tokenTwoFactor: { url: _apiUri + '/auth/token/two-factor', method: 'POST', params: {} }
|
||||||
|
});
|
||||||
|
|
||||||
|
return _service;
|
||||||
|
});
|
111
src/Vault/wwwroot/app/services/authService.js
Normal file
111
src/Vault/wwwroot/app/services/authService.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.services')
|
||||||
|
|
||||||
|
.factory('authService', function (cryptoService, apiService, tokenService, $q, jwtHelper) {
|
||||||
|
var _service = {},
|
||||||
|
_userProfile = null;
|
||||||
|
|
||||||
|
_service.logIn = function (email, masterPassword) {
|
||||||
|
var key = cryptoService.makeKey(masterPassword, email);
|
||||||
|
|
||||||
|
var request = {
|
||||||
|
email: email,
|
||||||
|
masterPasswordHash: cryptoService.hashPassword(masterPassword, key)
|
||||||
|
};
|
||||||
|
|
||||||
|
var deferred = $q.defer();
|
||||||
|
apiService.auth.token(request, function (response) {
|
||||||
|
if (!response || !response.Token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenService.setToken(response.Token);
|
||||||
|
cryptoService.setKey(key);
|
||||||
|
_service.setUserProfile(response.Profile);
|
||||||
|
|
||||||
|
deferred.resolve(response);
|
||||||
|
}, function (error) {
|
||||||
|
deferred.reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.logInTwoFactor = function (code, provider) {
|
||||||
|
var request = {
|
||||||
|
code: code,
|
||||||
|
provider: provider
|
||||||
|
};
|
||||||
|
|
||||||
|
var deferred = $q.defer();
|
||||||
|
apiService.auth.tokenTwoFactor(request, function (response) {
|
||||||
|
if (!response || !response.Token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenService.setToken(response.Token);
|
||||||
|
_service.setUserProfile(response.Profile);
|
||||||
|
|
||||||
|
deferred.resolve(response);
|
||||||
|
}, function (error) {
|
||||||
|
deferred.reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.logOut = function () {
|
||||||
|
tokenService.clearToken();
|
||||||
|
cryptoService.clearKey();
|
||||||
|
_userProfile = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.getUserProfile = function () {
|
||||||
|
if (!_userProfile) {
|
||||||
|
_service.setUserProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _userProfile;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.setUserProfile = function (profile) {
|
||||||
|
var token = tokenService.getToken();
|
||||||
|
if (!token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var decodedToken = jwtHelper.decodeToken(token);
|
||||||
|
var twoFactor = decodedToken.authmethod == "TwoFactor";
|
||||||
|
|
||||||
|
_userProfile = {
|
||||||
|
id: decodedToken.nameid,
|
||||||
|
email: decodedToken.email,
|
||||||
|
twoFactor: twoFactor
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!twoFactor && profile) {
|
||||||
|
loadProfile(profile);
|
||||||
|
}
|
||||||
|
else if (!twoFactor && !profile) {
|
||||||
|
apiService.accounts.getProfile({}, loadProfile);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadProfile(profile) {
|
||||||
|
_userProfile.extended = {
|
||||||
|
name: profile.Name,
|
||||||
|
twoFactorEnabled: profile.TwoFactorEnabled,
|
||||||
|
culture: profile.Culture
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_service.isAuthenticated = function () {
|
||||||
|
return _service.getUserProfile() !== null && !_service.getUserProfile().twoFactor;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.isTwoFactorAuthenticated = function () {
|
||||||
|
return _service.getUserProfile() !== null && _service.getUserProfile().twoFactor;
|
||||||
|
};
|
||||||
|
|
||||||
|
return _service;
|
||||||
|
});
|
110
src/Vault/wwwroot/app/services/cipherService.js
Normal file
110
src/Vault/wwwroot/app/services/cipherService.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.services')
|
||||||
|
|
||||||
|
.factory('cipherService', function (cryptoService, apiService) {
|
||||||
|
var _service = {};
|
||||||
|
|
||||||
|
_service.decryptSites = function (encryptedSites) {
|
||||||
|
if (!encryptedSites) throw "encryptedSites is undefined or null";
|
||||||
|
|
||||||
|
var unencryptedSites = [];
|
||||||
|
for (var i = 0; i < encryptedSites.length; i++) {
|
||||||
|
unencryptedSites.push(_service.decryptSite(encryptedSites[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return unencryptedSites;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.decryptSite = function (encryptedSite) {
|
||||||
|
if (!encryptedSite) throw "encryptedSite is undefined or null";
|
||||||
|
|
||||||
|
var site = {
|
||||||
|
id: encryptedSite.Id,
|
||||||
|
'type': 1,
|
||||||
|
folderId: encryptedSite.FolderId,
|
||||||
|
name: cryptoService.decrypt(encryptedSite.Name),
|
||||||
|
uri: cryptoService.decrypt(encryptedSite.Uri),
|
||||||
|
username: cryptoService.decrypt(encryptedSite.Username),
|
||||||
|
password: cryptoService.decrypt(encryptedSite.Password),
|
||||||
|
notes: encryptedSite.Notes && encryptedSite.Notes !== '' ? cryptoService.decrypt(encryptedSite.Notes) : null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (encryptedSite.Folder) {
|
||||||
|
site.folder = {
|
||||||
|
name: cryptoService.decrypt(encryptedSite.Folder.Name)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return site;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.decryptFolders = function (encryptedFolders) {
|
||||||
|
if (!encryptedFolders) throw "encryptedFolders is undefined or null";
|
||||||
|
|
||||||
|
var unencryptedFolders = [];
|
||||||
|
for (var i = 0; i < encryptedFolders.length; i++) {
|
||||||
|
unencryptedFolders.push(_service.decryptFolder(encryptedFolders[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return unencryptedFolders;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.decryptFolder = function (encryptedFolder) {
|
||||||
|
if (!encryptedFolder) throw "encryptedFolder is undefined or null";
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: encryptedFolder.Id,
|
||||||
|
'type': 0,
|
||||||
|
name: cryptoService.decrypt(encryptedFolder.Name)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.encryptSites = function (unencryptedSites, key) {
|
||||||
|
if (!unencryptedSites) throw "unencryptedSites is undefined or null";
|
||||||
|
|
||||||
|
var encryptedSites = [];
|
||||||
|
for (var i = 0; i < unencryptedSites.length; i++) {
|
||||||
|
encryptedSites.push(_service.encryptSite(unencryptedSites[i], key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return encryptedSites;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.encryptSite = function (unencryptedSite, key) {
|
||||||
|
if (!unencryptedSite) throw "unencryptedSite is undefined or null";
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: unencryptedSite.id,
|
||||||
|
'type': 1,
|
||||||
|
folderId: unencryptedSite.folderId === '' ? null : unencryptedSite.folderId,
|
||||||
|
uri: cryptoService.encrypt(unencryptedSite.uri, key),
|
||||||
|
name: cryptoService.encrypt(unencryptedSite.name, key),
|
||||||
|
username: cryptoService.encrypt(unencryptedSite.username, key),
|
||||||
|
password: cryptoService.encrypt(unencryptedSite.password, key),
|
||||||
|
notes: !unencryptedSite.notes || unencryptedSite.notes === '' ? null : cryptoService.encrypt(unencryptedSite.notes, key)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.encryptFolders = function (unencryptedFolders, key) {
|
||||||
|
if (!unencryptedFolders) throw "unencryptedFolders is undefined or null";
|
||||||
|
|
||||||
|
var encryptedFolders = [];
|
||||||
|
for (var i = 0; i < unencryptedFolders.length; i++) {
|
||||||
|
encryptedFolders.push(_service.encryptFolder(unencryptedFolders[i], key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return encryptedFolders;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.encryptFolder = function (unencryptedFolder, key) {
|
||||||
|
if (!unencryptedFolder) throw "unencryptedFolder is undefined or null";
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: unencryptedFolder.id,
|
||||||
|
'type': 0,
|
||||||
|
name: cryptoService.encrypt(unencryptedFolder.name, key)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return _service;
|
||||||
|
});
|
114
src/Vault/wwwroot/app/services/cryptoService.js
Normal file
114
src/Vault/wwwroot/app/services/cryptoService.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.services')
|
||||||
|
|
||||||
|
.factory('cryptoService', function ($sessionStorage) {
|
||||||
|
var _service = {},
|
||||||
|
_key,
|
||||||
|
_b64Key,
|
||||||
|
_aes;
|
||||||
|
|
||||||
|
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
|
||||||
|
|
||||||
|
_service.setKey = function (key) {
|
||||||
|
_key = key;
|
||||||
|
$sessionStorage.key = sjcl.codec.base64.fromBits(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.getKey = function (b64) {
|
||||||
|
if (b64 && b64 === true && _b64Key) {
|
||||||
|
return _b64Key;
|
||||||
|
}
|
||||||
|
else if (!b64 && _key) {
|
||||||
|
return _key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sessionStorage.key) {
|
||||||
|
_key = sjcl.codec.base64.toBits($sessionStorage.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b64 && b64 === true) {
|
||||||
|
_b64Key = sjcl.codec.base64.fromBits(_key);
|
||||||
|
return _b64Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _key;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.clearKey = function () {
|
||||||
|
_key = _b64Key = _aes = null;
|
||||||
|
delete $sessionStorage.key;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.makeKey = function (password, salt, b64) {
|
||||||
|
var key = sjcl.misc.pbkdf2(password, salt, 5000, 256, null);
|
||||||
|
|
||||||
|
if (b64 && b64 === true) {
|
||||||
|
return sjcl.codec.base64.fromBits(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.hashPassword = function (password, key) {
|
||||||
|
if (!key) {
|
||||||
|
key = _service.getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password || !key) {
|
||||||
|
throw 'Invalid parameters.';
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashBits = sjcl.misc.pbkdf2(key, password, 1, 256, null);
|
||||||
|
return sjcl.codec.base64.fromBits(hashBits);
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.getAes = function () {
|
||||||
|
if (!_aes && _service.getKey()) {
|
||||||
|
_aes = new sjcl.cipher.aes(_service.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
return _aes;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.encrypt = function (plaintextValue, key) {
|
||||||
|
if (!_service.getKey() && !key) {
|
||||||
|
throw 'Encryption key unavailable.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
key = _service.getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = {};
|
||||||
|
var params = {
|
||||||
|
mode: "cbc",
|
||||||
|
iv: sjcl.random.randomWords(4, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
var ctJson = sjcl.encrypt(key, plaintextValue, params, response);
|
||||||
|
|
||||||
|
var ct = ctJson.match(/"ct":"([^"]*)"/)[1];
|
||||||
|
var iv = sjcl.codec.base64.fromBits(response.iv);
|
||||||
|
|
||||||
|
return iv + "|" + ct;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.decrypt = function (encValue) {
|
||||||
|
if (!_service.getAes()) {
|
||||||
|
throw 'AES encryption unavailable.';
|
||||||
|
}
|
||||||
|
|
||||||
|
var encPieces = encValue.split('|');
|
||||||
|
if (encPieces.length != 2) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
var ivBits = sjcl.codec.base64.toBits(encPieces[0]);
|
||||||
|
var ctBits = sjcl.codec.base64.toBits(encPieces[1]);
|
||||||
|
|
||||||
|
var decBits = sjcl.mode.cbc.decrypt(_service.getAes(), ctBits, ivBits, null);
|
||||||
|
return sjcl.codec.utf8String.fromBits(decBits);
|
||||||
|
};
|
||||||
|
|
||||||
|
return _service;
|
||||||
|
});
|
105
src/Vault/wwwroot/app/services/passwordService.js
Normal file
105
src/Vault/wwwroot/app/services/passwordService.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.services')
|
||||||
|
|
||||||
|
.factory('passwordService', function () {
|
||||||
|
var _service = {};
|
||||||
|
|
||||||
|
_service.generatePassword = function (options) {
|
||||||
|
var defaults = {
|
||||||
|
length: 10,
|
||||||
|
ambiguous: false,
|
||||||
|
number: true,
|
||||||
|
minNumber: 1,
|
||||||
|
uppercase: true,
|
||||||
|
minUppercase: 1,
|
||||||
|
lowercase: true,
|
||||||
|
minLowercase: 1,
|
||||||
|
special: false,
|
||||||
|
minSpecial: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// overload defaults with given options
|
||||||
|
var o = angular.extend({}, defaults, options);
|
||||||
|
|
||||||
|
// sanitize
|
||||||
|
if (o.uppercase && o.minUppercase < 0) o.minUppercase = 1;
|
||||||
|
if (o.lowercase && o.minLowercase < 0) o.minLowercase = 1;
|
||||||
|
if (o.number && o.minNumber < 0) o.minNumber = 1;
|
||||||
|
if (o.special && o.minSpecial < 0) o.minSpecial = 1;
|
||||||
|
|
||||||
|
if (!o.length || o.length < 1) o.length = 10;
|
||||||
|
var minLength = o.minUppercase + o.minLowercase + o.minNumber + o.minSpecial;
|
||||||
|
if (o.length < minLength) o.length = minLength;
|
||||||
|
|
||||||
|
var positions = [];
|
||||||
|
if (o.lowercase && o.minLowercase > 0) {
|
||||||
|
for (var i = 0; i < o.minLowercase; i++) {
|
||||||
|
positions.push('l');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o.uppercase && o.minUppercase > 0) {
|
||||||
|
for (var j = 0; j < o.minUppercase; j++) {
|
||||||
|
positions.push('u');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o.number && o.minNumber > 0) {
|
||||||
|
for (var k = 0; k < o.minNumber; k++) {
|
||||||
|
positions.push('n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o.special && o.minSpecial > 0) {
|
||||||
|
for (var l = 0; l < o.minSpecial; l++) {
|
||||||
|
positions.push('s');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (positions.length < o.length) {
|
||||||
|
positions.push('a');
|
||||||
|
}
|
||||||
|
|
||||||
|
// shuffle
|
||||||
|
positions.sort(function () {
|
||||||
|
return randomInt(0, 1) * 2 - 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// build out the char sets
|
||||||
|
var allCharSet = '';
|
||||||
|
|
||||||
|
var lowercaseCharSet = 'abcdefghijkmnopqrstuvwxyz';
|
||||||
|
if (o.ambiguous) lowercaseCharSet += 'l';
|
||||||
|
if (o.lowercase) allCharSet += lowercaseCharSet;
|
||||||
|
|
||||||
|
var uppercaseCharSet = 'ABCDEFGHIJKLMNPQRSTUVWXYZ';
|
||||||
|
if (o.ambiguous) uppercaseCharSet += 'O';
|
||||||
|
if (o.uppercase) allCharSet += uppercaseCharSet;
|
||||||
|
|
||||||
|
var numberCharSet = '23456789';
|
||||||
|
if (o.ambiguous) numberCharSet += '01';
|
||||||
|
if (o.number) allCharSet += numberCharSet;
|
||||||
|
|
||||||
|
var specialCharSet = '!@#$%^&*';
|
||||||
|
if (o.special) allCharSet += specialCharSet;
|
||||||
|
|
||||||
|
var password = '';
|
||||||
|
for (var m = 0; m < o.length; m++) {
|
||||||
|
var positionChars;
|
||||||
|
switch (positions[m]) {
|
||||||
|
case 'l': positionChars = lowercaseCharSet; break;
|
||||||
|
case 'u': positionChars = uppercaseCharSet; break;
|
||||||
|
case 'n': positionChars = numberCharSet; break;
|
||||||
|
case 's': positionChars = specialCharSet; break;
|
||||||
|
case 'a': positionChars = allCharSet; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var randomCharIndex = randomInt(0, positionChars.length - 1);
|
||||||
|
password += positionChars.charAt(randomCharIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return password;
|
||||||
|
};
|
||||||
|
|
||||||
|
function randomInt(min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _service;
|
||||||
|
});
|
2
src/Vault/wwwroot/app/services/servicesModule.js
Normal file
2
src/Vault/wwwroot/app/services/servicesModule.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.services', ['ngResource', 'ngStorage', 'angular-jwt']);
|
27
src/Vault/wwwroot/app/services/tokenService.js
Normal file
27
src/Vault/wwwroot/app/services/tokenService.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.services')
|
||||||
|
|
||||||
|
.factory('tokenService', function ($sessionStorage) {
|
||||||
|
var _service = {},
|
||||||
|
_token;
|
||||||
|
|
||||||
|
_service.setToken = function (token) {
|
||||||
|
$sessionStorage.authBearer = token;
|
||||||
|
_token = token;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.getToken = function () {
|
||||||
|
if (!_token) {
|
||||||
|
_token = $sessionStorage.authBearer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _token;
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.clearToken = function () {
|
||||||
|
_token = null;
|
||||||
|
delete $sessionStorage.authBearer;
|
||||||
|
};
|
||||||
|
|
||||||
|
return _service;
|
||||||
|
});
|
52
src/Vault/wwwroot/app/services/validationService.js
Normal file
52
src/Vault/wwwroot/app/services/validationService.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.services')
|
||||||
|
|
||||||
|
.factory('validationService', function () {
|
||||||
|
var _service = {};
|
||||||
|
|
||||||
|
_service.addErrors = function (form, reason) {
|
||||||
|
var data = reason.data;
|
||||||
|
var defaultErrorMessage = 'An unexpected error has occured.';
|
||||||
|
form.$errors = [];
|
||||||
|
|
||||||
|
if (!data || !angular.isObject(data)) {
|
||||||
|
form.$errors.push(defaultErrorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.ValidationErrors) {
|
||||||
|
if (data.Message) {
|
||||||
|
form.$errors.push(data.Message);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
form.$errors.push(defaultErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var key in data.ValidationErrors) {
|
||||||
|
if (!data.ValidationErrors.hasOwnProperty(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < data.ValidationErrors[key].length; i++) {
|
||||||
|
_service.addError(form, key, data.ValidationErrors[key][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.addError = function (form, key, errorMessage, clearExistingErrors) {
|
||||||
|
if (clearExistingErrors || !form.$errors) {
|
||||||
|
form.$errors = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
form.$errors.push(errorMessage);
|
||||||
|
if (key && key !== '' && form[key] && form[key].$registerApiError) {
|
||||||
|
form[key].$registerApiError();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return _service;
|
||||||
|
});
|
2
src/Vault/wwwroot/app/settings.js
Normal file
2
src/Vault/wwwroot/app/settings.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
angular.module('bit')
|
||||||
|
.constant('appSettings', {"rememberdEmailCookieName":"bit.rememberedEmail","version":"0.0.1","environment":"Development","apiUri":"http://localhost:4000"});
|
@ -0,0 +1,66 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.settings')
|
||||||
|
|
||||||
|
.controller('settingsChangeEmailController', function ($scope, $state, apiService, $uibModalInstance, cryptoService, cipherService, authService, $q, toastr) {
|
||||||
|
var _masterPasswordHash,
|
||||||
|
_newMasterPasswordHash,
|
||||||
|
_newKey;
|
||||||
|
|
||||||
|
$scope.token = function (model) {
|
||||||
|
_masterPasswordHash = cryptoService.hashPassword(model.masterPassword);
|
||||||
|
|
||||||
|
var request = {
|
||||||
|
newEmail: model.newEmail,
|
||||||
|
masterPasswordHash: _masterPasswordHash
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.tokenPromise = apiService.accounts.emailToken(request, function () {
|
||||||
|
_newKey = cryptoService.makeKey(model.masterPassword, model.newEmail);
|
||||||
|
_newMasterPasswordHash = cryptoService.hashPassword(model.masterPassword, _newKey);
|
||||||
|
|
||||||
|
$scope.tokenSent = true;
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.confirm = function (model) {
|
||||||
|
$scope.processing = true;
|
||||||
|
|
||||||
|
var reencryptedSites = [];
|
||||||
|
var sitesPromise = apiService.sites.list({ dirty: false }, function (encryptedSites) {
|
||||||
|
var unencryptedSites = cipherService.decryptSites(encryptedSites.Data);
|
||||||
|
reencryptedSites = cipherService.encryptSites(unencryptedSites, _newKey);
|
||||||
|
}).$promise;
|
||||||
|
|
||||||
|
var reencryptedFolders = [];
|
||||||
|
var foldersPromise = apiService.folders.list({ dirty: false }, function (encryptedFolders) {
|
||||||
|
var unencryptedFolders = cipherService.decryptFolders(encryptedFolders.Data);
|
||||||
|
reencryptedFolders = cipherService.encryptFolders(unencryptedFolders, _newKey);
|
||||||
|
}).$promise;
|
||||||
|
|
||||||
|
$q.all([sitesPromise, foldersPromise]).then(function () {
|
||||||
|
var request = {
|
||||||
|
token: model.token,
|
||||||
|
newEmail: model.newEmail,
|
||||||
|
masterPasswordHash: _masterPasswordHash,
|
||||||
|
newMasterPasswordHash: _newMasterPasswordHash,
|
||||||
|
ciphers: reencryptedSites.concat(reencryptedFolders)
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.confirmPromise = apiService.accounts.email(request, function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
authService.logOut();
|
||||||
|
$state.go('frontend.login.info').then(function () {
|
||||||
|
toastr.success('Please log back in.', 'Email Changed')
|
||||||
|
});
|
||||||
|
}, function () {
|
||||||
|
// TODO: recovery mode
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
toastr.error('Something went wrong.', 'Oh No!');
|
||||||
|
}).$promise;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,53 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.settings')
|
||||||
|
|
||||||
|
.controller('settingsChangePasswordController', function ($scope, $state, apiService, $uibModalInstance,
|
||||||
|
cryptoService, authService, cipherService, validationService, $q, toastr) {
|
||||||
|
$scope.save = function (model, form) {
|
||||||
|
if ($scope.model.newMasterPassword != $scope.model.confirmNewMasterPassword) {
|
||||||
|
validationService.addError(form, 'ConfirmNewMasterPassword', 'New master password confirmation does not match.', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.processing = true;
|
||||||
|
|
||||||
|
var profile = authService.getUserProfile();
|
||||||
|
var newKey = cryptoService.makeKey(model.newMasterPassword, profile.email);
|
||||||
|
|
||||||
|
var reencryptedSites = [];
|
||||||
|
var sitesPromise = apiService.sites.list({ dirty: false }, function (encryptedSites) {
|
||||||
|
var unencryptedSites = cipherService.decryptSites(encryptedSites.Data);
|
||||||
|
reencryptedSites = cipherService.encryptSites(unencryptedSites, newKey);
|
||||||
|
}).$promise;
|
||||||
|
|
||||||
|
var reencryptedFolders = [];
|
||||||
|
var foldersPromise = apiService.folders.list({ dirty: false }, function (encryptedFolders) {
|
||||||
|
var unencryptedFolders = cipherService.decryptFolders(encryptedFolders.Data);
|
||||||
|
reencryptedFolders = cipherService.encryptFolders(unencryptedFolders, newKey);
|
||||||
|
}).$promise;
|
||||||
|
|
||||||
|
$q.all([sitesPromise, foldersPromise]).then(function () {
|
||||||
|
var request = {
|
||||||
|
masterPasswordHash: cryptoService.hashPassword(model.masterPassword),
|
||||||
|
newMasterPasswordHash: cryptoService.hashPassword(model.newMasterPassword, newKey),
|
||||||
|
ciphers: reencryptedSites.concat(reencryptedFolders)
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.savePromise = apiService.accounts.putPassword(request, function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
authService.logOut();
|
||||||
|
$state.go('frontend.login.info').then(function () {
|
||||||
|
toastr.success('Please log back in.', 'Master Password Changed')
|
||||||
|
});
|
||||||
|
}, function () {
|
||||||
|
// TODO: recovery mode
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
toastr.error('Something went wrong.', 'Oh No!');
|
||||||
|
}).$promise;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
});
|
72
src/Vault/wwwroot/app/settings/settingsController.js
Normal file
72
src/Vault/wwwroot/app/settings/settingsController.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.settings')
|
||||||
|
|
||||||
|
.controller('settingsController', function ($scope, $uibModal, apiService, toastr, authService) {
|
||||||
|
$scope.model = {};
|
||||||
|
|
||||||
|
apiService.accounts.getProfile({}, function (user) {
|
||||||
|
$scope.model = {
|
||||||
|
name: user.Name,
|
||||||
|
email: user.Email,
|
||||||
|
masterPasswordHint: user.MasterPasswordHint,
|
||||||
|
culture: user.Culture,
|
||||||
|
twoFactorEnabled: user.TwoFactorEnabled
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.save = function (model) {
|
||||||
|
$scope.savePromise = apiService.accounts.putProfile({}, model, function (profile) {
|
||||||
|
authService.setUserProfile(profile);
|
||||||
|
toastr.success('Account has been updated.', 'Success!');
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.changePassword = function () {
|
||||||
|
$uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/settings/views/settingsChangePassword.html',
|
||||||
|
controller: 'settingsChangePasswordController'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$on('settingsChangePassword', function (event, args) {
|
||||||
|
$scope.changePassword();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.changeEmail = function () {
|
||||||
|
$uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/settings/views/settingsChangeEmail.html',
|
||||||
|
controller: 'settingsChangeEmailController',
|
||||||
|
size: 'sm'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$on('settingsChangeEmail', function (event, args) {
|
||||||
|
$scope.changeEmail();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.twoFactor = function () {
|
||||||
|
$uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/settings/views/settingsTwoFactor.html',
|
||||||
|
controller: 'settingsTwoFactorController'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$on('settingsTwoFactor', function (event, args) {
|
||||||
|
$scope.twoFactor();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.sessions = function () {
|
||||||
|
$uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/settings/views/settingsSessions.html',
|
||||||
|
controller: 'settingsSessionsController'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$on('settingsSessions', function (event, args) {
|
||||||
|
$scope.sessions();
|
||||||
|
});
|
||||||
|
});
|
2
src/Vault/wwwroot/app/settings/settingsModule.js
Normal file
2
src/Vault/wwwroot/app/settings/settingsModule.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.settings', ['ui.bootstrap', 'toastr']);
|
22
src/Vault/wwwroot/app/settings/settingsSessionsController.js
Normal file
22
src/Vault/wwwroot/app/settings/settingsSessionsController.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.settings')
|
||||||
|
|
||||||
|
.controller('settingsSessionsController', function ($scope, $state, apiService, $uibModalInstance, cryptoService, authService, toastr) {
|
||||||
|
$scope.submit = function (model) {
|
||||||
|
var request = {
|
||||||
|
masterPasswordHash: cryptoService.hashPassword(model.masterPassword)
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.submitPromise = apiService.accounts.putSecurityStamp(request, function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
authService.logOut();
|
||||||
|
$state.go('frontend.login.info').then(function () {
|
||||||
|
toastr.success('Please log back in.', 'All Sessions Deauthorized')
|
||||||
|
});
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,62 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.settings')
|
||||||
|
|
||||||
|
.controller('settingsTwoFactorController', function ($scope, apiService, $uibModalInstance, cryptoService, authService, $q, toastr) {
|
||||||
|
var _issuer = 'bitwarden',
|
||||||
|
_profile = authService.getUserProfile(),
|
||||||
|
_masterPasswordHash;
|
||||||
|
|
||||||
|
$scope.account = _profile.email;
|
||||||
|
$scope.enabled = function () {
|
||||||
|
return _profile.extended && _profile.extended.twoFactorEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.auth = function (model) {
|
||||||
|
_masterPasswordHash = cryptoService.hashPassword(model.masterPassword);
|
||||||
|
|
||||||
|
$scope.authPromise = apiService.accounts.getTwoFactor({
|
||||||
|
masterPasswordHash: _masterPasswordHash,
|
||||||
|
provider: 0 /* Only authenticator provider for now. */
|
||||||
|
}, function (response) {
|
||||||
|
var key = response.AuthenticatorKey;
|
||||||
|
$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;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.update = function (model) {
|
||||||
|
var currentlyEnabled = $scope.twoFactorModel.enabled;
|
||||||
|
if (currentlyEnabled && !confirm('Are you sure you want to disable two-step login?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = {
|
||||||
|
enabled: !currentlyEnabled,
|
||||||
|
token: model ? model.token : null,
|
||||||
|
masterPasswordHash: _masterPasswordHash,
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.updatePromise = apiService.accounts.putTwoFactor({}, request, function (response) {
|
||||||
|
if (response.TwoFactorEnabled) {
|
||||||
|
toastr.success('Two-step login has been enabled.');
|
||||||
|
if (_profile.extended) _profile.extended.twoFactorEnabled = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toastr.success('Two-step login has been disabled.');
|
||||||
|
if (_profile.extended) _profile.extended.twoFactorEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.close();
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
});
|
46
src/Vault/wwwroot/app/settings/views/settings.html
Normal file
46
src/Vault/wwwroot/app/settings/views/settings.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<section class="content-header">
|
||||||
|
<h1>
|
||||||
|
Settings
|
||||||
|
<small>manage your account</small>
|
||||||
|
</h1>
|
||||||
|
</section>
|
||||||
|
<section class="content">
|
||||||
|
<div class="box box-default">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">General</h3>
|
||||||
|
</div>
|
||||||
|
<form role="form" name="profileForm" ng-submit="profileForm.$valid && save(model)" api-form="savePromise">
|
||||||
|
<div class="box-body">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="profileForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in profileForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" id="name" name="Name" ng-model="model.name" class="form-control" required api-field />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email - <a href="javascript:void(0)" ng-click="changeEmail()">change</a></label>
|
||||||
|
<input type="text" id="email" ng-model="model.email" class="form-control" readonly />
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="hint">Master Password Hint</label>
|
||||||
|
<input type="text" id="hint" name="MasterPasswordHint" ng-model="model.masterPasswordHint" class="form-control" api-field />
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="culture">Language/Culture</label>
|
||||||
|
<select id="culture" name="Culture" ng-model="model.culture" class="form-control" api-field>
|
||||||
|
<option value="en-US">English (US)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="profileForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="profileForm.$loading"></i>Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
@ -0,0 +1,54 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="changeEmailModelLabel"><i class="fa fa-at"></i> Change Email</h4>
|
||||||
|
</div>
|
||||||
|
<form name="changeEmailForm" ng-submit="changeEmailForm.$valid && token(model)" api-form="tokenPromise" ng-show="!tokenSent && !processing">
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Below you can change your account's email address.</p>
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="changeEmailForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in changeEmailForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="masterPassword">Master Password</label>
|
||||||
|
<input type="password" id="masterPassword" name="MasterPasswordHash" ng-model="model.masterPassword" class="form-control"
|
||||||
|
required api-field />
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="newEmail">New Email</label>
|
||||||
|
<input type="email" id="newEmail" name="NewEmail" ng-model="model.newEmail" class="form-control" required api-field />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="changeEmailForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="changeEmailForm.$loading"></i>Submit
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form name="changeEmailConfirmForm" ng-submit="changeEmailConfirmForm.$valid && confirm(model)" api-form="confirmPromise"
|
||||||
|
ng-show="tokenSent && !processing">
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>We have emailed a verification code to <b>{{model.newEmail}}</b>. Please check your email for this code and enter it below to finalize your the email address change.</p>
|
||||||
|
<div class="callout callout-warning">
|
||||||
|
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||||
|
Proceeding will log you out of your current session, requiring you to log back in.
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="token">Code</label>
|
||||||
|
<input type="number" id="token" name="Token" ng-model="model.token" class="form-control" required api-field />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat">
|
||||||
|
Change Email
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div ng-show="processing" class="modal-body text-center">
|
||||||
|
<p><i class="fa fa-cog fa-spin fa-3x"></i></p>
|
||||||
|
<p>Please wait. We are now changing your email and reencrypting all of your data. Do not close this window. You will be automatically logged out when this process has finished.</p>
|
||||||
|
</div>
|
@ -0,0 +1,46 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="changePasswrdModelLabel"><i class="fa fa-key"></i> Change Password</h4>
|
||||||
|
</div>
|
||||||
|
<form name="changePasswordForm" ng-submit="changePasswordForm.$valid && save(model, changePasswordForm)" api-form="savePromise" ng-show="!processing">
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Below you can change your account's master password.</p>
|
||||||
|
<p>We recommend that you change your master password immediately if you believe that your credentials have been compromised.</p>
|
||||||
|
<div class="callout callout-warning">
|
||||||
|
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||||
|
Proceeding will log you out of your current session, requiring you to log back in.
|
||||||
|
</div>
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="changePasswordForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in changePasswordForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="masterPassword">Current Master Password</label>
|
||||||
|
<input type="password" id="masterPassword" name="MasterPasswordHash" ng-model="model.masterPassword" class="form-control"
|
||||||
|
required api-field />
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="newMasterPassword">New Master Password</label>
|
||||||
|
<input type="password" id="newMasterPassword" name="NewMasterPasswordHash" ng-model="model.newMasterPassword" class="form-control"
|
||||||
|
required api-field />
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="confirmNewMasterPassword">Confirm New Master Password</label>
|
||||||
|
<input type="password" id="confirmNewMasterPassword" name="ConfirmNewMasterPasswordHash" ng-model="model.confirmNewMasterPassword"
|
||||||
|
class="form-control" required api-field />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat">
|
||||||
|
Change Password
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div ng-show="processing" class="modal-body text-center">
|
||||||
|
<p><i class="fa fa-cog fa-spin fa-3x"></i></p>
|
||||||
|
<p>Please wait. We are now changing your password and reencrypting all of your data. Do not close this window. You will be automatically logged out when this process has finished.</p>
|
||||||
|
</div>
|
32
src/Vault/wwwroot/app/settings/views/settingsSessions.html
Normal file
32
src/Vault/wwwroot/app/settings/views/settingsSessions.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="logoutSessionsModelLabel"><i class="fa fa-ban"></i> Deauthorize Sessions</h4>
|
||||||
|
</div>
|
||||||
|
<form name="logoutSessionsForm" ng-submit="logoutSessionsForm.$valid && submit(model)" api-form="submitPromise">
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Concerned your account is logged in on another device?</p>
|
||||||
|
<p>Proceed below to deauthorize all computers or devices that you have previously used.</p>
|
||||||
|
<p>This security step is recommended if you previously used a public PC or accidentally saved your password on a device that isn't yours.</p>
|
||||||
|
<div class="callout callout-warning">
|
||||||
|
<h4><i class="fa fa-warning"></i> Warning</h4>
|
||||||
|
Proceeding will log you out of your current session as well, requiring you to log back in.
|
||||||
|
</div>
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="logoutSessionsForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in logoutSessionsForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="masterPassword">Master Password</label>
|
||||||
|
<input type="password" id="masterPassword" name="MasterPasswordHash" ng-model="model.masterPassword" class="form-control"
|
||||||
|
required api-field />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="logoutSessionsForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="logoutSessionsForm.$loading"></i>Deauthorize
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
82
src/Vault/wwwroot/app/settings/views/settingsTwoFactor.html
Normal file
82
src/Vault/wwwroot/app/settings/views/settingsTwoFactor.html
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="twoFactorModelLabel"><i class="fa fa-key"></i> Two-step Login</h4>
|
||||||
|
</div>
|
||||||
|
<form name="authTwoStepForm" ng-submit="authTwoStepForm.$valid && auth(authModel)" api-form="authPromise" ng-if="!twoFactorModel">
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Current Status: <span class="label bg-green" ng-show="enabled()">ENABLED</span><span class="label bg-gray" ng-show="!enabled()">DISABLED</span></p>
|
||||||
|
<p>Two-step login helps keep your account more secure by requiring a code provided by an app on your mobile device while logging in (in addition to your master password).</p>
|
||||||
|
<p ng-show="enabled()">Two-step login is already enabled on your account. To access your two-step settings enter your master password below.</p>
|
||||||
|
<p ng-show="!enabled()">To get started with two-step login enter your master password below.</p>
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="authTwoStepForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in authTwoStepForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="masterPassword">Master Password</label>
|
||||||
|
<input type="password" id="masterPassword" name="MasterPasswordHash" ng-model="authModel.masterPassword" class="form-control" required api-field />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="authTwoStepForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="authTwoStepForm.$loading"></i>Continue
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form name="updateTwoStepForm" ng-submit="updateTwoStepForm.$valid && update(updateModel)" api-form="updatePromise" ng-if="twoFactorModel">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div ng-show="enabled()">
|
||||||
|
<p>Two-step login is enabled 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>
|
||||||
|
</div>
|
||||||
|
<div ng-show="!enabled()">
|
||||||
|
<p>Setting up two-step verification is easy, just follow these steps:</p>
|
||||||
|
<h4>1. Download a two-step verification app</h4>
|
||||||
|
</div>
|
||||||
|
<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-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>
|
||||||
|
</ul>
|
||||||
|
<hr ng-show="enabled()" />
|
||||||
|
<h4 ng-show="!enabled()" style="margin-top: 30px;">2. Scan this QR code with your verification app</h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 text-center">
|
||||||
|
<p><img ng-src="{{twoFactorModel.qr}}" alt="QR" class="img-thumbnail" /></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<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">
|
||||||
|
<li><strong>Key:</strong> <samp>{{twoFactorModel.key}}</samp></li>
|
||||||
|
<li><strong>Account:</strong> {{account}}</li>
|
||||||
|
<li><strong>Time based:</strong> Yes</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ng-show="!enabled()">
|
||||||
|
<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>
|
||||||
|
<h4 style="margin-top: 30px;">3. Enter the resulting verification code from the app</h4>
|
||||||
|
<div class="form-group" show-errors ng-show="!twoFactorModel.enabled">
|
||||||
|
<label for="token" class="sr-only">Verification Code</label>
|
||||||
|
<input type="number" id="token" name="Token" placeholder="Verification Code" ng-model="updateModel.token" class="form-control" ng-required="!twoFactorModel.enabled" api-field />
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<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>
|
||||||
|
<span ng-show="twoFactorModel.enabled">Disable Two-step</span>
|
||||||
|
<span ng-show="!twoFactorModel.enabled">Enable Two-step</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
8
src/Vault/wwwroot/app/tools/toolsAuditsController.js
Normal file
8
src/Vault/wwwroot/app/tools/toolsAuditsController.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.tools')
|
||||||
|
|
||||||
|
.controller('toolsAuditsController', function ($scope, apiService, $uibModalInstance, toastr) {
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
});
|
42
src/Vault/wwwroot/app/tools/toolsController.js
Normal file
42
src/Vault/wwwroot/app/tools/toolsController.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.tools')
|
||||||
|
|
||||||
|
.controller('toolsController', function ($scope, $uibModal, apiService, toastr, authService) {
|
||||||
|
$scope.import = function () {
|
||||||
|
$uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/tools/views/toolsImport.html',
|
||||||
|
controller: 'toolsImportController',
|
||||||
|
size: 'sm'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$on('toolsImport', function (event, args) {
|
||||||
|
$scope.import();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.export = function () {
|
||||||
|
$uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/tools/views/toolsExport.html',
|
||||||
|
controller: 'toolsExportController',
|
||||||
|
size: 'sm'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$on('toolsExport', function (event, args) {
|
||||||
|
$scope.export();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.audits = function () {
|
||||||
|
$uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/tools/views/toolsAudits.html',
|
||||||
|
controller: 'toolsAuditsController'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$on('toolsAudits', function (event, args) {
|
||||||
|
$scope.audits();
|
||||||
|
});
|
||||||
|
});
|
71
src/Vault/wwwroot/app/tools/toolsExportController.js
Normal file
71
src/Vault/wwwroot/app/tools/toolsExportController.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.tools')
|
||||||
|
|
||||||
|
.controller('toolsExportController', function ($scope, apiService, authService, $uibModalInstance, cryptoService, cipherService, $q, toastr) {
|
||||||
|
$scope.export = function (model) {
|
||||||
|
$scope.startedExport = true;
|
||||||
|
apiService.sites.list({ expand: ['folder'] }, function (sites) {
|
||||||
|
try {
|
||||||
|
var decSites = cipherService.decryptSites(sites.Data);
|
||||||
|
|
||||||
|
var exportSites = [];
|
||||||
|
for (var i = 0; i < decSites.length; i++) {
|
||||||
|
var site = {
|
||||||
|
name: decSites[i].name,
|
||||||
|
uri: decSites[i].uri,
|
||||||
|
username: decSites[i].username,
|
||||||
|
password: decSites[i].password,
|
||||||
|
notes: decSites[i].notes,
|
||||||
|
folder: decSites[i].folder ? decSites[i].folder.name : null
|
||||||
|
};
|
||||||
|
|
||||||
|
exportSites.push(site);
|
||||||
|
}
|
||||||
|
|
||||||
|
var csvString = Papa.unparse(exportSites);
|
||||||
|
var csvBlob = new Blob([csvString]);
|
||||||
|
if (window.navigator.msSaveOrOpenBlob) { // IE hack. ref http://msdn.microsoft.com/en-us/library/ie/hh779016.aspx
|
||||||
|
window.navigator.msSaveBlob(csvBlob, makeFileName());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var a = window.document.createElement('a');
|
||||||
|
a.href = window.URL.createObjectURL(csvBlob, { type: 'text/plain' });
|
||||||
|
a.download = makeFileName();
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click(); // IE: "Access is denied". ref: https://connect.microsoft.com/IE/feedback/details/797361/ie-10-treats-blob-url-as-cross-origin-and-denies-access
|
||||||
|
document.body.removeChild(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
toastr.success('Your data has been exported. Check your browser\'s downloads folder.', 'Success!');
|
||||||
|
$scope.close();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
toastr.error('Something went wrong. Please try again.', 'Error!');
|
||||||
|
$scope.close();
|
||||||
|
}
|
||||||
|
}, function () {
|
||||||
|
toastr.error('Something went wrong. Please try again.', 'Error!');
|
||||||
|
$scope.close();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
function makeFileName() {
|
||||||
|
var now = new Date();
|
||||||
|
var dateString =
|
||||||
|
now.getFullYear() + '' + padNumber((now.getMonth() + 1), 2) + '' + padNumber(now.getDate(), 2) +
|
||||||
|
padNumber(now.getHours(), 2) + '' + padNumber(now.getMinutes(), 2) +
|
||||||
|
padNumber(now.getSeconds(), 2);
|
||||||
|
|
||||||
|
return 'bitwarden_export_' + dateString + '.csv';
|
||||||
|
}
|
||||||
|
|
||||||
|
function padNumber(number, width, paddingCharacter) {
|
||||||
|
paddingCharacter = paddingCharacter || '0';
|
||||||
|
number = number + '';
|
||||||
|
return number.length >= width ? number : new Array(width - number.length + 1).join(paddingCharacter) + number;
|
||||||
|
}
|
||||||
|
});
|
8
src/Vault/wwwroot/app/tools/toolsImportController.js
Normal file
8
src/Vault/wwwroot/app/tools/toolsImportController.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.tools')
|
||||||
|
|
||||||
|
.controller('toolsImportController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, $q, toastr) {
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
});
|
2
src/Vault/wwwroot/app/tools/toolsModule.js
Normal file
2
src/Vault/wwwroot/app/tools/toolsModule.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.tools', ['ui.bootstrap', 'toastr']);
|
9
src/Vault/wwwroot/app/tools/views/tools.html
Normal file
9
src/Vault/wwwroot/app/tools/views/tools.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<section class="content-header">
|
||||||
|
<h1>
|
||||||
|
Tools
|
||||||
|
<small>helpful utilities</small>
|
||||||
|
</h1>
|
||||||
|
</section>
|
||||||
|
<section class="content">
|
||||||
|
Content
|
||||||
|
</section>
|
10
src/Vault/wwwroot/app/tools/views/toolsAudits.html
Normal file
10
src/Vault/wwwroot/app/tools/views/toolsAudits.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="auditsModelLabel"><i class="fa fa-search"></i> Audits</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
Coming soon...
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
28
src/Vault/wwwroot/app/tools/views/toolsExport.html
Normal file
28
src/Vault/wwwroot/app/tools/views/toolsExport.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="exportModelLabel"><i class="fa fa-cloud-download"></i> Export</h4>
|
||||||
|
</div>
|
||||||
|
<form name="exportForm" ng-submit="exportForm.$valid && export(model)" api-form="exportPromise" ng-show="!startedExport">
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Export all of your vault data in <code>.csv</code> format. Enter your master password to continue.</p>
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="exportForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in exportForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="masterPassword">Master Password</label>
|
||||||
|
<input type="password" id="masterPassword" name="MasterPasswordHash" ng-model="model.masterPassword" class="form-control"
|
||||||
|
master-password required api-field ng-model-options="{ 'updateOn': 'blur'}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat">Export</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="modal-body text-center" ng-show="startedExport">
|
||||||
|
<p><i class="fa fa-cog fa-spin fa-3x"></i></p>
|
||||||
|
<p>Please wait. We are now exporting all of your data to a <code>.csv</code> file.</p>
|
||||||
|
</div>
|
21
src/Vault/wwwroot/app/tools/views/toolsImport.html
Normal file
21
src/Vault/wwwroot/app/tools/views/toolsImport.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="importModelLabel"><i class="fa fa-cloud-upload"></i> Import</h4>
|
||||||
|
</div>
|
||||||
|
<form name="importForm" ng-submit="importForm.$valid && import(model)" api-form="importPromise">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="importForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in importForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
Coming soon...
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="importForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="importForm.$loading"></i> Import
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
17
src/Vault/wwwroot/app/vault/vaultAddFolderController.js
Normal file
17
src/Vault/wwwroot/app/vault/vaultAddFolderController.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.vault')
|
||||||
|
|
||||||
|
.controller('vaultAddFolderController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService) {
|
||||||
|
$scope.savePromise = null;
|
||||||
|
$scope.save = function (model) {
|
||||||
|
var folder = cipherService.encryptFolder(model);
|
||||||
|
$scope.savePromise = apiService.folders.post(folder, function (response) {
|
||||||
|
var decFolder = cipherService.decryptFolder(response);
|
||||||
|
$uibModalInstance.close(decFolder);
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('close');
|
||||||
|
};
|
||||||
|
});
|
47
src/Vault/wwwroot/app/vault/vaultAddSiteController.js
Normal file
47
src/Vault/wwwroot/app/vault/vaultAddSiteController.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.vault')
|
||||||
|
|
||||||
|
.controller('vaultAddSiteController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, passwordService, folders, selectedFolder) {
|
||||||
|
$scope.folders = folders;
|
||||||
|
$scope.site = {
|
||||||
|
folderId: selectedFolder ? selectedFolder.id : null
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.savePromise = null;
|
||||||
|
$scope.save = function (model) {
|
||||||
|
var site = cipherService.encryptSite(model);
|
||||||
|
$scope.savePromise = apiService.sites.post(site, function (siteResponse) {
|
||||||
|
var decSite = cipherService.decryptSite(siteResponse);
|
||||||
|
$uibModalInstance.close(decSite);
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.generatePassword = function () {
|
||||||
|
if (!$scope.site.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||||
|
$scope.site.password = passwordService.generatePassword({ length: 10, special: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.clipboardSuccess = function (e) {
|
||||||
|
e.clearSelection();
|
||||||
|
selectPassword(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.clipboardError = function (e, password) {
|
||||||
|
if (password) {
|
||||||
|
selectPassword(e);
|
||||||
|
}
|
||||||
|
alert('Your web browser does not support easy clipboard copying. Copy it manually instead.');
|
||||||
|
};
|
||||||
|
|
||||||
|
function selectPassword(e) {
|
||||||
|
var target = $(e.trigger).parent().prev();
|
||||||
|
if (target.attr('type') == 'text') {
|
||||||
|
target.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('close');
|
||||||
|
};
|
||||||
|
});
|
155
src/Vault/wwwroot/app/vault/vaultController.js
Normal file
155
src/Vault/wwwroot/app/vault/vaultController.js
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.vault')
|
||||||
|
|
||||||
|
.controller('vaultController', function ($scope, $uibModal, apiService, $filter, cryptoService, authService, toastr, cipherService) {
|
||||||
|
$scope.sites = [];
|
||||||
|
$scope.folders = [];
|
||||||
|
|
||||||
|
apiService.sites.list({}, function (sites) {
|
||||||
|
var decSites = [];
|
||||||
|
for (var i = 0; i < sites.Data.length; i++) {
|
||||||
|
var decSite = {
|
||||||
|
id: sites.Data[i].Id,
|
||||||
|
folderId: sites.Data[i].FolderId
|
||||||
|
};
|
||||||
|
|
||||||
|
try { decSite.name = cryptoService.decrypt(sites.Data[i].Name); }
|
||||||
|
catch (err) { decSite.name = "[error: cannot decrypt]"; }
|
||||||
|
|
||||||
|
try { decSite.username = cryptoService.decrypt(sites.Data[i].Username); }
|
||||||
|
catch (err) { decSite.username = "[error: cannot decrypt]"; }
|
||||||
|
|
||||||
|
decSites.push(decSite);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.sites = decSites;
|
||||||
|
});
|
||||||
|
|
||||||
|
apiService.folders.list({}, function (folders) {
|
||||||
|
var decFolders = [{
|
||||||
|
id: null,
|
||||||
|
name: '(none)'
|
||||||
|
}];
|
||||||
|
|
||||||
|
for (var i = 0; i < folders.Data.length; i++) {
|
||||||
|
var decFolder = {
|
||||||
|
id: folders.Data[i].Id
|
||||||
|
};
|
||||||
|
|
||||||
|
try { decFolder.name = cryptoService.decrypt(folders.Data[i].Name); }
|
||||||
|
catch (err) { decFolder.name = "[error: cannot decrypt]"; }
|
||||||
|
|
||||||
|
decFolders.push(decFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.folders = decFolders;
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.editSite = function (site) {
|
||||||
|
var editModel = $uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/vault/views/vaultEditSite.html',
|
||||||
|
controller: 'vaultEditSiteController',
|
||||||
|
resolve: {
|
||||||
|
siteId: function () { return site.id; },
|
||||||
|
folders: function () { return $scope.folders; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
editModel.result.then(function (editedSite) {
|
||||||
|
var site = $filter('filter')($scope.sites, { id: editedSite.id }, true);
|
||||||
|
if (site && site.length > 0) {
|
||||||
|
site[0].folderId = editedSite.folderId;
|
||||||
|
site[0].name = editedSite.name;
|
||||||
|
site[0].username = editedSite.username;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$on('vaultAddSite', function (event, args) {
|
||||||
|
$scope.addSite();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.addSite = function (folder) {
|
||||||
|
var addModel = $uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/vault/views/vaultAddSite.html',
|
||||||
|
controller: 'vaultAddSiteController',
|
||||||
|
resolve: {
|
||||||
|
folders: function () { return $scope.folders; },
|
||||||
|
selectedFolder: function () { return folder; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addModel.result.then(function (addedSite) {
|
||||||
|
$scope.sites.push(addedSite);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.deleteSite = function (site) {
|
||||||
|
if (!confirm("Are you sure you want to delete this site (" + site.name + ")?")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
apiService.sites.del({ id: site.id }, function () {
|
||||||
|
var index = $scope.sites.indexOf(site);
|
||||||
|
$scope.sites.splice(index, 1);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.editFolder = function (folder) {
|
||||||
|
var editModel = $uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/vault/views/vaultEditFolder.html',
|
||||||
|
controller: 'vaultEditFolderController',
|
||||||
|
size: 'sm',
|
||||||
|
resolve: {
|
||||||
|
folderId: function () { return folder.id; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
editModel.result.then(function (editedFolder) {
|
||||||
|
var folder = $filter('filter')($scope.folders, { id: editedFolder.id }, true);
|
||||||
|
if (folder && folder.length > 0) {
|
||||||
|
folder[0].name = editedFolder.name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$on('vaultAddFolder', function (event, args) {
|
||||||
|
$scope.addFolder();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.addFolder = function () {
|
||||||
|
var addModel = $uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/vault/views/vaultAddFolder.html',
|
||||||
|
controller: 'vaultAddFolderController',
|
||||||
|
size: 'sm'
|
||||||
|
});
|
||||||
|
|
||||||
|
addModel.result.then(function (addedFolder) {
|
||||||
|
$scope.folders.push(addedFolder);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.deleteFolder = function (folder) {
|
||||||
|
if (!confirm("Are you sure you want to delete this folder (" + folder.name + ")?")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
apiService.folders.del({ id: folder.id }, function () {
|
||||||
|
var index = $scope.folders.indexOf(folder);
|
||||||
|
$scope.folders.splice(index, 1);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.canDeleteFolder = function (folder) {
|
||||||
|
if (!folder || !folder.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sites = $filter('filter')($scope.sites, { folderId: folder.id });
|
||||||
|
return sites.length === 0;
|
||||||
|
};
|
||||||
|
});
|
23
src/Vault/wwwroot/app/vault/vaultEditFolderController.js
Normal file
23
src/Vault/wwwroot/app/vault/vaultEditFolderController.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.vault')
|
||||||
|
|
||||||
|
.controller('vaultEditFolderController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, folderId) {
|
||||||
|
$scope.folder = {};
|
||||||
|
|
||||||
|
apiService.folders.get({ id: folderId }, function (folder) {
|
||||||
|
$scope.folder = cipherService.decryptFolder(folder);
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.savePromise = null;
|
||||||
|
$scope.save = function (model) {
|
||||||
|
var folder = cipherService.encryptFolder(model);
|
||||||
|
$scope.savePromise = apiService.folders.put({ id: folderId }, folder, function (response) {
|
||||||
|
var decFolder = cipherService.decryptFolder(response);
|
||||||
|
$uibModalInstance.close(decFolder);
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
});
|
48
src/Vault/wwwroot/app/vault/vaultEditSiteController.js
Normal file
48
src/Vault/wwwroot/app/vault/vaultEditSiteController.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.vault')
|
||||||
|
|
||||||
|
.controller('vaultEditSiteController', function ($scope, apiService, $uibModalInstance, cryptoService, cipherService, passwordService, siteId, folders) {
|
||||||
|
$scope.folders = folders;
|
||||||
|
$scope.site = {};
|
||||||
|
|
||||||
|
apiService.sites.get({ id: siteId }, function (site) {
|
||||||
|
$scope.site = cipherService.decryptSite(site);
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.save = function (model) {
|
||||||
|
var site = cipherService.encryptSite(model);
|
||||||
|
$scope.savePromise = apiService.sites.put({ id: siteId }, site, function (siteResponse) {
|
||||||
|
var decSite = cipherService.decryptSite(siteResponse);
|
||||||
|
$uibModalInstance.close(decSite);
|
||||||
|
}).$promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.generatePassword = function () {
|
||||||
|
if (!$scope.site.password || confirm('Are you sure you want to overwrite the current password?')) {
|
||||||
|
$scope.site.password = passwordService.generatePassword({ length: 10, special: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.clipboardSuccess = function (e) {
|
||||||
|
e.clearSelection();
|
||||||
|
selectPassword(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.clipboardError = function (e, password) {
|
||||||
|
if (password) {
|
||||||
|
selectPassword(e);
|
||||||
|
}
|
||||||
|
alert('Your web browser does not support easy clipboard copying. Copy it manually instead.');
|
||||||
|
};
|
||||||
|
|
||||||
|
function selectPassword(e) {
|
||||||
|
var target = $(e.trigger).parent().prev();
|
||||||
|
if (target.attr('type') == 'text') {
|
||||||
|
target.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
});
|
2
src/Vault/wwwroot/app/vault/vaultModule.js
Normal file
2
src/Vault/wwwroot/app/vault/vaultModule.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.vault', ['ui.bootstrap', 'ngclipboard']);
|
51
src/Vault/wwwroot/app/vault/views/vault.html
Normal file
51
src/Vault/wwwroot/app/vault/views/vault.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<section class="content-header">
|
||||||
|
<h1>
|
||||||
|
My Vault
|
||||||
|
<small>safe and secure</small>
|
||||||
|
</h1>
|
||||||
|
</section>
|
||||||
|
<section class="content">
|
||||||
|
<div class="box" ng-repeat="folder in folders | orderBy: 'name'">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title"><i class="fa fa-folder-open"></i> {{folder.name}}</h3>
|
||||||
|
<div class="box-tools pull-right">
|
||||||
|
<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>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-box-tool" ng-click="editFolder(folder)" ng-show="folder.id" uib-tooltip="Edit">
|
||||||
|
<i class="fa fa-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-box-tool" data-widget="collapse" uib-tooltip="Collapse">
|
||||||
|
<i class="fa fa-minus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-body" ng-class="{'no-padding': folderSites.length}">
|
||||||
|
<div ng-show="!folderSites.length">
|
||||||
|
<p>No sites in this folder.</p>
|
||||||
|
<button type="button" ng-click="addSite(folder)" class="btn btn-default btn-flat">Add a Site</button>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover" ng-show="folderSites.length">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="max-width: 200px;">Site</th>
|
||||||
|
<th>Username</th>
|
||||||
|
<th style="min-width: 150px;"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="site in folderSites = (sites | filter: { folderId: folder.id } | filter: (main.searchVaultText || '') | orderBy: ['name', 'username'])">
|
||||||
|
<td>{{site.name}}</td>
|
||||||
|
<td>{{site.username}}</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<button type="button" ng-click="deleteSite(site)" class="btn btn-link btn-table" uib-tooltip="Delete"><i class="fa fa-lg fa-trash"></i></button>
|
||||||
|
<button type="button" ng-click="editSite(site)" class="btn btn-link btn-table" uib-tooltip="View/Edit"><i class="fa fa-lg fa-pencil"></i></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
24
src/Vault/wwwroot/app/vault/views/vaultAddFolder.html
Normal file
24
src/Vault/wwwroot/app/vault/views/vaultAddFolder.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="addFolderModelLabel"><i class="fa fa-folder"></i> Add New Folder</h4>
|
||||||
|
</div>
|
||||||
|
<form name="addFolderForm" ng-submit="addFolderForm.$valid && save(folder)" api-form="savePromise">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="addFolderForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in addFolderForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" id="name" name="Name" ng-model="folder.name" class="form-control" required api-field />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="addFolderForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="addFolderForm.$loading"></i>Submit
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
92
src/Vault/wwwroot/app/vault/views/vaultAddSite.html
Normal file
92
src/Vault/wwwroot/app/vault/views/vaultAddSite.html
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="addSiteModelLabel"><i class="fa fa-globe"></i> Add New Site</h4>
|
||||||
|
</div>
|
||||||
|
<form name="addSiteForm" ng-submit="addSiteForm.$valid && save(site)" api-form="savePromise">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="addSiteForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in addSiteForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="uri">URI</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="url" id="uri" ng-model="site.uri" name="Uri" class="form-control" placeholder="http://..." required api-field />
|
||||||
|
<span class="input-group-btn" uib-tooltip="Copy URI" tooltip-placement="left">
|
||||||
|
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
||||||
|
ngclipboard-error="clipboardError(e)"
|
||||||
|
data-clipboard-target="#uri">
|
||||||
|
<i class="fa fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" id="name" name="Name" ng-model="site.name" class="form-control" required api-field />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="folder">Folder</label>
|
||||||
|
<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>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="username" name="Username" ng-model="site.username" class="form-control" required api-field />
|
||||||
|
<span class="input-group-btn" uib-tooltip="Copy Username" tooltip-placement="left">
|
||||||
|
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
||||||
|
ngclipboard-error="clipboardError(e)"
|
||||||
|
data-clipboard-target="#username">
|
||||||
|
<i class="fa fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<div class="pull-right password-options">
|
||||||
|
<i class="fa fa-lg fa-refresh" uib-tooltip="Generate Password" tooltip-placement="left" ng-click="generatePassword()"></i>
|
||||||
|
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Password" tooltip-placement="left" password-viewer="#password"></i>
|
||||||
|
</div>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<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 />
|
||||||
|
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
||||||
|
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
||||||
|
ngclipboard-success="clipboardSuccess(e)"
|
||||||
|
ngclipboard-error="clipboardError(e, true)"
|
||||||
|
data-clipboard-target="#password-text">
|
||||||
|
<i class="fa fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin: -10px 0 15px 0;" password-meter="site.password" password-meter-username="site.username" outer-class="xs"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="notes">Notes</label>
|
||||||
|
<textarea id="notes" name="Notes" class="form-control" ng-model="site.notes" api-field></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="addSiteForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="addSiteForm.$loading"></i>Submit
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
24
src/Vault/wwwroot/app/vault/views/vaultEditFolder.html
Normal file
24
src/Vault/wwwroot/app/vault/views/vaultEditFolder.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="editFolderModelLabel"><i class="fa fa-folder"></i> Edit Folder <small>{{folder.name}}</small></h4>
|
||||||
|
</div>
|
||||||
|
<form name="editFolderForm" ng-submit="editFolderForm.$valid && save(folder)" api-form="savePromise">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="editFolderForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in editFolderForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" id="name" name="Name" ng-model="folder.name" class="form-control" required api-field />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="editFolderForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="editFolderForm.$loading"></i>Save
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
95
src/Vault/wwwroot/app/vault/views/vaultEditSite.html
Normal file
95
src/Vault/wwwroot/app/vault/views/vaultEditSite.html
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="editSiteModelLabel"><i class="fa fa-globe"></i> Site Information <small>{{site.name}}</small></h4>
|
||||||
|
</div>
|
||||||
|
<form name="editSiteForm" ng-submit="editSiteForm.$valid && save(site)" api-form="savePromise">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="editSiteForm.$errors">
|
||||||
|
<h4>Errors have occured</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in editSiteForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="uri">URI</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="url" id="uri" name="Uri" ng-model="site.uri" class="form-control" placeholder="http://..." required api-field />
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button tabindex="-1" class="btn btn-default btn-flat" type="button" uib-tooltip="Copy URI" tooltip-placement="left" ngclipboard
|
||||||
|
ngclipboard-error="clipboardError(e)"
|
||||||
|
data-clipboard-target="#uri">
|
||||||
|
<i class="fa fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
|
<a href="{{site.uri}}" target="_blank" class="btn btn-default btn-flat" uib-tooltip="Go To Site" tooltip-placement="left">
|
||||||
|
<i class="fa fa-share"></i>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" id="name" name="Name" ng-model="site.name" class="form-control" required api-field />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="folder">Folder</label>
|
||||||
|
<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>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="username" name="Username" ng-model="site.username" class="form-control" required api-field />
|
||||||
|
<span class="input-group-btn" uib-tooltip="Copy Username" tooltip-placement="left">
|
||||||
|
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
||||||
|
ngclipboard-error="clipboardError(e)"
|
||||||
|
data-clipboard-target="#username">
|
||||||
|
<i class="fa fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<div class="pull-right password-options">
|
||||||
|
<i class="fa fa-lg fa-refresh" uib-tooltip="Generate Password" tooltip-placement="left" ng-click="generatePassword()"></i>
|
||||||
|
<i class="fa fa-lg fa-eye" uib-tooltip="Toggle Password" tooltip-placement="left" password-viewer="#password"></i>
|
||||||
|
</div>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<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 />
|
||||||
|
<span class="input-group-btn" uib-tooltip="Copy Password" tooltip-placement="left">
|
||||||
|
<button tabindex="-1" class="btn btn-default btn-flat" type="button" ngclipboard
|
||||||
|
ngclipboard-success="clipboardSuccess(e)"
|
||||||
|
ngclipboard-error="clipboardError(e, true)"
|
||||||
|
data-clipboard-target="#password-text">
|
||||||
|
<i class="fa fa-clipboard"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin: -10px 0 15px 0;" password-meter="site.password" password-meter-username="site.username" outer-class="xs"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="notes">Notes</label>
|
||||||
|
<textarea id="notes" name="Notes" class="form-control" ng-model="site.notes" api-field></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="editSiteForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="editSiteForm.$loading"></i>Save
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
78
src/Vault/wwwroot/app/views/backendLayout.html
Normal file
78
src/Vault/wwwroot/app/views/backendLayout.html
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<div class="wrapper toast-target">
|
||||||
|
<header class="main-header" ng-controller="topNavController as topNav">
|
||||||
|
<a ui-sref="backend.vault" class="logo">
|
||||||
|
<span class="logo-mini"><i class="fa fa-shield"></i></span>
|
||||||
|
<span class="logo-lg"><i class="fa fa-shield"></i> <b>bit</b>warden</span>
|
||||||
|
</a>
|
||||||
|
<nav class="navbar navbar-static-top" role="navigation">
|
||||||
|
<a class="sidebar-toggle" data-toggle="offcanvas" role="button">
|
||||||
|
<span class="sr-only">Toggle navigation</span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</a>
|
||||||
|
<div class="navbar-custom-menu">
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li><a ui-sref="frontend.logout">Log Out</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<aside class="main-sidebar" ng-controller="sideNavController as sideNav">
|
||||||
|
<section class="sidebar">
|
||||||
|
<div class="user-panel">
|
||||||
|
<div class="pull-left image">
|
||||||
|
<img src="http://www.gravatar.com/avatar/{{ main.userProfile.email | gravatar }}.jpg?s=45&d=mm" class="img-circle" alt="User Image">
|
||||||
|
</div>
|
||||||
|
<div class="pull-left info">
|
||||||
|
<p>{{main.userProfile.extended && main.userProfile.extended.name ? main.userProfile.extended.name : main.userProfile.email}}</p>
|
||||||
|
<a ui-sref="frontend.logout">Log Out</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form class="sidebar-form">
|
||||||
|
<label for="search" class="sr-only">Search</label>
|
||||||
|
<div class="form-group has-feedback">
|
||||||
|
<input type="text" id="search" class="form-control" placeholder="Search vault..." ng-focus="searchVault()" ng-model="main.searchVaultText" />
|
||||||
|
<span class="fa fa-search form-control-feedback" aria-hidden="true"></span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<ul class="sidebar-menu">
|
||||||
|
<li class="treeview" ng-class="{active: $state.includes('backend.vault')}">
|
||||||
|
<a ui-sref="backend.vault"><i class="fa fa-lock"></i> <span>My Vault</span></a>
|
||||||
|
<ul class="treeview-menu menu-open">
|
||||||
|
<li><a href="javascript:void(0)" ng-click="addSite()"><i class="fa fa-plus"></i> New Site</a></li>
|
||||||
|
<li><a href="javascript:void(0)" ng-click="addFolder()"><i class="fa fa-folder"></i> New Folder</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="treeview" ng-class="{active: $state.includes('backend.settings')}">
|
||||||
|
<a ui-sref="backend.settings"><i class="fa fa-cogs"></i> <span>Settings</span></a>
|
||||||
|
<ul class="treeview-menu">
|
||||||
|
<li><a href="javascript:void(0)" ng-click="changePassword()"><i class="fa fa-circle-o"></i> Change Password</a></li>
|
||||||
|
<li><a href="javascript:void(0)" ng-click="changeEmail()"><i class="fa fa-circle-o"></i> Change Email</a></li>
|
||||||
|
<li><a href="javascript:void(0)" ng-click="sessions()"><i class="fa fa-circle-o"></i> Deauthorize Sessions</a></li>
|
||||||
|
<li><a href="javascript:void(0)" ng-click="twoFactor()"><i class="fa fa-circle-o"></i> Two-step Login</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="treeview" ng-class="{active: $state.includes('backend.tools')}">
|
||||||
|
<a ui-sref="backend.tools"><i class="fa fa-wrench"></i> <span>Tools</span></a>
|
||||||
|
<ul class="treeview-menu">
|
||||||
|
<li><a href="javascript:void(0)" ng-click="import()"><i class="fa fa-circle-o"></i> Import</a></li>
|
||||||
|
<li><a href="javascript:void(0)" ng-click="export()"><i class="fa fa-circle-o"></i> Export</a></li>
|
||||||
|
<li><a href="javascript:void(0)" ng-click="audits()"><i class="fa fa-circle-o"></i> Audits</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div class="content-wrapper" ui-view>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="main-footer">
|
||||||
|
<div class="pull-right hidden-xs">
|
||||||
|
<b>Version</b> {{main.version}}
|
||||||
|
</div>
|
||||||
|
<strong>Copyright © <span ng-bind="currentYear"></span></strong>, bitwarden.com
|
||||||
|
</footer>
|
||||||
|
</div>
|
2
src/Vault/wwwroot/app/views/frontendLayout.html
Normal file
2
src/Vault/wwwroot/app/views/frontendLayout.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<div class="toast-target" ui-view>
|
||||||
|
</div>
|
BIN
src/Vault/wwwroot/favicon.ico
Normal file
BIN
src/Vault/wwwroot/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
src/Vault/wwwroot/images/boxed-bg-2x.png
Normal file
BIN
src/Vault/wwwroot/images/boxed-bg-2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
BIN
src/Vault/wwwroot/images/boxed-bg.png
Normal file
BIN
src/Vault/wwwroot/images/boxed-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
124
src/Vault/wwwroot/index.html
Normal file
124
src/Vault/wwwroot/index.html
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html ng-app="bit">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<base href="/" />
|
||||||
|
|
||||||
|
<title page-title>bitwarden.com Password Manager</title>
|
||||||
|
|
||||||
|
<!-- @if true !>
|
||||||
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" />
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/vault.min.css" />
|
||||||
|
<!-- @endif -->
|
||||||
|
<!-- @exclude -->
|
||||||
|
<link rel="stylesheet" href="lib/bootstrap/css/bootstrap.css" />
|
||||||
|
<link rel="stylesheet" href="lib/font-awesome/css/font-awesome.css" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/vault.css" />
|
||||||
|
<!-- @endexclude -->
|
||||||
|
</head>
|
||||||
|
<body ng-controller="mainController as main" class="layout-boxed skin-blue sidebar-mini {{main.bodyClass}}">
|
||||||
|
<div ui-view></div>
|
||||||
|
|
||||||
|
<!-- @if true !>
|
||||||
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
|
||||||
|
<script>(window.jQuery||document.write('<script src="lib\/jquery\/jquery.min.js"><\/script>'));</script>
|
||||||
|
|
||||||
|
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/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 src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
|
||||||
|
<script>(window.angular||document.write('<script src="lib\/angular\/angular.min.js"><\/script>'));</script>
|
||||||
|
|
||||||
|
<script src="js/lib.min.js"></script>
|
||||||
|
<script src="js/app.min.js"></script>
|
||||||
|
<!-- @endif -->
|
||||||
|
<!-- @exclude -->
|
||||||
|
<script src="lib/jquery/jquery.js"></script>
|
||||||
|
<script src="lib/bootstrap/js/bootstrap.js"></script>
|
||||||
|
<script src="lib/admin-lte/js/app.js"></script>
|
||||||
|
|
||||||
|
<script src="lib/sjcl/sjcl.js"></script>
|
||||||
|
<script src="lib/sjcl/cbc.js"></script>
|
||||||
|
<script src="lib/sjcl/bitArray.js"></script>
|
||||||
|
|
||||||
|
<script src="lib/papaparse/papaparse.js"></script>
|
||||||
|
<script src="lib/clipboard/clipboard.js"></script>
|
||||||
|
|
||||||
|
<script src="lib/angular/angular.js"></script>
|
||||||
|
<script src="lib/angular-cookies/angular-cookies.js"></script>
|
||||||
|
<script src="lib/angular-messages/angular-messages.js"></script>
|
||||||
|
<script src="lib/angular-jwt/angular-jwt.js"></script>
|
||||||
|
<script src="lib/angular-resource/angular-resource.js"></script>
|
||||||
|
<script src="lib/angular-ui-router/angular-ui-router.js"></script>
|
||||||
|
<script src="lib/angular-bootstrap/angular-bootstrap-tpls.js"></script>
|
||||||
|
<script src="lib/angular-md5/angular-md5.js"></script>
|
||||||
|
<script src="lib/angular-toastr/angular-toastr.tpls.js"></script>
|
||||||
|
<script src="lib/angular-bootstrap-show-errors/showErrors.js"></script>
|
||||||
|
<script src="lib/ngstorage/ngStorage.js"></script>
|
||||||
|
<script src="lib/ngclipboard/ngclipboard.js"></script>
|
||||||
|
|
||||||
|
<script src="app/app.js"></script>
|
||||||
|
<script src="app/settings.js"></script>
|
||||||
|
<script src="app/config.js"></script>
|
||||||
|
<script src="app/apiInterceptor.js"></script>
|
||||||
|
|
||||||
|
<script src="app/directives/directivesModule.js"></script>
|
||||||
|
<script src="app/directives/pageTitleDirective.js"></script>
|
||||||
|
<script src="app/directives/apiFormDirective.js"></script>
|
||||||
|
<script src="app/directives/apiFieldDirective.js"></script>
|
||||||
|
<script src="app/directives/masterPasswordDirective.js"></script>
|
||||||
|
<script src="app/directives/passwordMeterDirective.js"></script>
|
||||||
|
<script src="app/directives/passwordViewerDirective.js"></script>
|
||||||
|
|
||||||
|
<script src="app/services/servicesModule.js"></script>
|
||||||
|
<script src="app/services/tokenService.js"></script>
|
||||||
|
<script src="app/services/apiService.js"></script>
|
||||||
|
<script src="app/services/authService.js"></script>
|
||||||
|
<script src="app/services/cryptoService.js"></script>
|
||||||
|
<script src="app/services/cipherService.js"></script>
|
||||||
|
<script src="app/services/validationService.js"></script>
|
||||||
|
<script src="app/services/passwordService.js"></script>
|
||||||
|
|
||||||
|
<script src="app/global/globalModule.js"></script>
|
||||||
|
<script src="app/global/mainController.js"></script>
|
||||||
|
<script src="app/global/topNavController.js"></script>
|
||||||
|
<script src="app/global/sideNavController.js"></script>
|
||||||
|
|
||||||
|
<script src="app/accounts/accountsModule.js"></script>
|
||||||
|
<script src="app/accounts/accountsLoginController.js"></script>
|
||||||
|
<script src="app/accounts/accountsLogoutController.js"></script>
|
||||||
|
<script src="app/accounts/accountsRegisterController.js"></script>
|
||||||
|
<script src="app/accounts/accountsRegisterFinalizeController.js"></script>
|
||||||
|
<script src="app/accounts/accountsPasswordHintController.js"></script>
|
||||||
|
|
||||||
|
<script src="app/vault/vaultModule.js"></script>
|
||||||
|
<script src="app/vault/vaultController.js"></script>
|
||||||
|
<script src="app/vault/vaultEditSiteController.js"></script>
|
||||||
|
<script src="app/vault/vaultAddSiteController.js"></script>
|
||||||
|
<script src="app/vault/vaultEditFolderController.js"></script>
|
||||||
|
<script src="app/vault/vaultAddFolderController.js"></script>
|
||||||
|
|
||||||
|
<script src="app/settings/settingsModule.js"></script>
|
||||||
|
<script src="app/settings/settingsController.js"></script>
|
||||||
|
<script src="app/settings/settingsChangePasswordController.js"></script>
|
||||||
|
<script src="app/settings/settingsChangeEmailController.js"></script>
|
||||||
|
<script src="app/settings/settingsTwoFactorController.js"></script>
|
||||||
|
<script src="app/settings/settingsSessionsController.js"></script>
|
||||||
|
|
||||||
|
<script src="app/tools/toolsModule.js"></script>
|
||||||
|
<script src="app/tools/toolsController.js"></script>
|
||||||
|
<script src="app/tools/toolsImportController.js"></script>
|
||||||
|
<script src="app/tools/toolsExportController.js"></script>
|
||||||
|
<script src="app/tools/toolsAuditsController.js"></script>
|
||||||
|
<!-- @endexclude -->
|
||||||
|
</body>
|
||||||
|
</html>
|
9
src/Vault/wwwroot/web.config
Normal file
9
src/Vault/wwwroot/web.config
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<system.webServer>
|
||||||
|
<handlers>
|
||||||
|
<add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/>
|
||||||
|
</handlers>
|
||||||
|
<httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" stdoutLogEnabled="false" startupTimeLimit="3600"/>
|
||||||
|
</system.webServer>
|
||||||
|
</configuration>
|
Loading…
Reference in New Issue
Block a user