There are many password managers on the market right now for Android and iOS. I personally use a mobile application called 1Password, but what if you’re the type of person who doesn’t trust these companies with your sensitive passwords? The solution to this would be to build your own password management application, one where you know the algorithms and the logic. I mean, what if the available password managers are using DES encryption when they should be using AES? Best thing to do would be to do the job yourself.
We’re going to see how to develop a password manager for iOS and Android using the NativeScript framework by Progress Software. The application that we build will be completely functional, have a polished UI, and use all the best practices for cipher text and mobile development.
Before we get ahead of ourselves, let’s see what we’re going to build. In the following screenshots, you’ll see that our password manager will have five simple screens. We’ll have a screen for unlocking the application, a screen for resetting the master password, a screen for listing out all saved passwords, a screen for creating new passwords, and a screen for viewing saved password details.
In this particular example we won’t worry about editing passwords because it wouldn’t be difficult to add later and it doesn’t take away from this being a completely functional mobile application that can be used every day.
There are a few requirements that must happen first in order to make this project and tutorial a success.
Let me explain each of the requirements. We need Node.js installed because we will be actively using the Node Package Manager (NPM). For example, we’ll be using NPM to downloaded various cipher libraries for use in our project. We need NativeScript 2 because this project will be developed using Angular and TypeScript, something only available in the second version of NativeScript. You are more than welcome to create a vanilla project, but this tutorial will use Angular. You need at least one build platform installed, whether that be the Android SDK, Xcode, or both. This tutorial works for both Android and iOS, so it doesn’t matter what you choose.
Before we can start developing our application we need to create a new project. From the Command Prompt (Windows) or Terminal (Mac and Linux), execute the following:
tns create password-manager --ng
cd password-manager
tns platform add ios
tns platform add android
A few things that are worth noting in the above set of commands. You’ll notice the --ng
tag in the first command. This means that our project will use the Angular template that includes TypeScript. You’ll also notice that I’m including the iOS platform. I am using a Mac computer with Xcode installed and configured, so it is fair game. If you’re using a Linux or Windows computer you can only build for Android.
Now we can worry about the various project dependencies that are necessary to make it a success.
For our database we will be using Couchbase. It is a NoSQL database and I’m choosing it because much of our data will be complex. SQLite is great, but when you start working with complex data, things can become difficult. This is why I’m opting to use NoSQL. Don’t worry, Couchbase is open source so you won’t need to pay for it. To install the NativeScript Couchbase plugin, execute the following from your Terminal or Command Prompt:
tns plugin add nativescript-couchbase
While the above command will install the plugin, it will not link the TypeScript type definitions necessary for our project. To link the TypeScript definitions, open the project’s references.d.ts file and add the following line:
/// <reference path="./node_modules/nativescript-couchbase/couchbase.d.ts" />
With our data layer out of the way, we need to worry about the libraries necessary for encrypting and decrypting text. We’re actually going to use two different cipher libraries. We’re going to use a library called BcryptJS and it will handle the hashing of our master password. We’re also going to be using a library called Forge and it will handle the encryption and decryption of our application passwords.
Why are we using two different libraries?
We don’t ever want our master password to be decrypted. This is only possible through a hashing algorithm like Bcrypt. This means that there is no way to recover a lost or forgotten master password, which in my opinion is great. However, we will need to decrypt passwords that we’ve added to the mobile application. This means we will use Forge and a strong cipher like AES to get the job done.
To install both these cipher and hashing libraries, execute the following from your Command Prompt or Terminal:
npm install bcryptjs node-forge --save
npm install @types/bcryptjs --save
Notice that we’ve also chosen to download the type definitions for one of our libraries. This is because we’re developing with TypeScript and it makes life a little easier. Unfortunately, the other library doesn’t have any available type definitions at this time, but that won’t stop us.
There is something strange about BcryptJS though. It includes a file that is incompatible with Android only. To fix it, just remove the node_modules/bcryptjs/dist/bcrypt.min.js.gz file.
At this point all plugins and dependencies should be installed and ready for use.
Couchbase is a NoSQL Document database. The data we store in it will be JSON rather than the tables and rows that we’d find in SQLite. We will have two different types of documents in our database, a master password document type, and a password document type. Let’s take a second to see what each of these would look like when saved.
The master password document essentially just contains a single JSON property with a hashed string. Something like the following:
{
"password": "alskdjflk3j24k32j4lkj"
}
Of course in the above example I typed in randomness as the hash. There are more specific rules when it comes to hashing, but our library will take care of that.
Our password documents will be significantly more complex than that of our master password. We only have one master password document, but we’ll have any number of password documents. They will look something like this after being saved:
{
"title": "Twitter",
"username": {
"cipherText": "aslkdfjlksdjflaskdjf",
"salt": "sadkjh23h4rjkh",
"iv": "asdkj3k2j4"
},
"password": {
"cipherText": "l234kljkjsdf",
"salt": "sdkjhf3kj534",
"iv": "saldkfjl23k4j"
}
}
In the above example I’ve chosen not to encrypt the password title
. I figured so what if people figure out what the title is. Feel free to think differently than me. However, I’ve chosen to encrypt both the username
and password
properties of each of the password documents. The AES cipher that we use requires a salt
and an iv
for encryption and decryption. For maximum security it is best to use different values for each piece of cipher text.
This isn’t SQL, so data access is a bit different. We’ll get into the specifics later in the tutorial, but to query multiple documents you’ll need to use MapReduce views. They are not as scary as they sound. They are essentially functions that contain a bunch of logic about the documents you want to return. When the logic is satisfied, you return the results. Take the following pseudocode for our password documents:
function(document, emit) {
if(document.title) {
emit(document.title, document);
}
}
Notice in the above pseudocode we check to see if the document has a title
property. If it does, return a key-value pair where they key is the title
and the value is the document itself. Our master password does not have a title
property so it will not be included in the query results.
Since we only have one master password document, it can be obtained directly based on its key value. We’ll explore this more when the time comes.
The application we build will be a multi-page application. Like mentioned earlier, there will be five pages or components. We need to create the appropriate files for each of these components. I’m using a Mac so I’ll be using the mkdir
and touch
commands from my Terminal. Feel free to create these files and directories however you want.
mkdir app/components
mkdir app/components/login
mkdir app/components/reset
mkdir app/components/list
mkdir app/components/create
mkdir app/components/view
touch app/components/login/login.component.ts
touch app/components/login/login.component.html
touch app/components/login/login.component.css
touch app/components/reset/reset.component.ts
touch app/components/reset/reset.component.html
touch app/components/reset/reset.component.css
touch app/components/list/list.component.ts
touch app/components/list/list.component.html
touch app/components/list/list.component.css
touch app/components/create/create.component.ts
touch app/components/create/create.component.html
touch app/components/create/create.component.css
touch app/components/view/view.component.ts
touch app/components/view/view.component.html
touch app/components/view/view.component.css
In the above we are creating the five components and each component has a TypeScript, XML, and CSS file local to it. This helps us keep clean code and everything very separated from each other.
We’re not quite done though. There are a few shared instances, also known as shared services, that we will be using throughout our application. We’ll be using a shared Couchbase instance, Bcrypt instance, and Forge instance. Create the following files:
touch app/couchbase.ts
touch app/bcrypt.ts
touch app/forge.ts
These shared services will help us reduce a lot of duplicate code. It also allows us to treat them as singleton classes.
The Couchbase shared instance will give us a singleton connection to our database. This means instead of opening a new connection every time we want to use it, we will be using the same connection for as long as our application is open. It is far more efficient to do it this way.
Open your app/couchbase.ts file and include the following TypeScript code. Don’t worry, we’ll break it down after.
import {Couchbase} from 'nativescript-couchbase';
export class CouchbaseInstance {
private database: any;
public constructor() {
if(!this.database) {
this.database = new Couchbase("password-database");
this.database.createView("passwords", "1", (document, emitter) => {
if(document.hasOwnProperty("title")) {
emitter.emit(document.title, document);
}
});
}
}
public getDatabase() {
return this.database;
}
}
The first line is where we import the Couchbase plugin that we downloaded. Then we can worry about creating our CouchbaseInstance
class.
The database variable will be our single entry point to the database. It gets opened in the constructor
method only if it hasn’t already been opened. We are also creating that Couchbase MapReduce view that I mentioned earlier in the constructor
method. It nearly matches the pseudocode that I gave. The view name will be passwords
and it will be the first version. Should you need to change the view after you publish your application, you’ll need to increase the version number.
The final function we have here, getDatabase
will return the database variable.
The next shared service we want to build will handle our Bcrypt interactions. This is for hashing and comparing the user entered master password with the saved master password. Open the project’s app/bcrypt.ts file and include the following code:
import * as BcryptJS from "bcryptjs";
export class BcryptInstance {
public constructor() {
BcryptJS.setRandomFallback((random) => {
for(var a=[], i=0; i<random; ++i) {
a[i] = ((0.5 + Math.random() * 2.3283064365386963e-10) * 256) | 0;
}
return a;
});
}
public create(value: string) {
return BcryptJS.hashSync(value, 8);
}
public compare(value: string, hash: string) {
return BcryptJS.compareSync(value, hash);
}
}
This is a little more complex than our last service. Again we are importing the dependencies that we downloaded and doing most of our work in the constructor
method.
We must use the setRandomFallback
function because NativeScript doesn’t ship with the necessary crypto libraries required. This is predicted in the BcryptJS documentation. The logic that I included in this function was actually taken directly from one of the source code files of the BcryptJS library.
In the create
method we take a value, in this case our master password string, and hash it for safekeeping. In the compare
method we take our string password and compare it against the hashed version of it to see if it matches. This is because we cannot decrypt the hash, only compare against it.
The final shared service we have is the Forge service. It is responsible for encrypting and decrypting strings of data. Open the project’s app/forge.ts file and include the following code:
var forge = require("node-forge");
export class ForgeInstance {
public constructor() { }
public encrypt(message: string, password: string) {
let salt = forge.random.getBytesSync(128);
let key = forge.pkcs5.pbkdf2(password, salt, 4, 16);
let iv = forge.random.getBytesSync(16);
let cipher = forge.cipher.createCipher('AES-CBC', key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(message));
cipher.finish();
return {
cipherText: forge.util.encode64(cipher.output.getBytes()),
salt: forge.util.encode64(salt),
iv: forge.util.encode64(iv)
};
}
public decrypt(cipherText: string, password: string, salt: string, iv: string) {
let key = forge.pkcs5.pbkdf2(password, forge.util.decode64(salt), 4, 16);
let decipher = forge.cipher.createDecipher('AES-CBC', key);
decipher.start({iv: forge.util.decode64(iv)});
decipher.update(forge.util.createBuffer(forge.util.decode64(cipherText)));
decipher.finish();
return decipher.output.toString();
}
}
This is a tricky library to use. I’ll explain it from a high level, but for deeper information, visit a post I wrote previously on the subject of encryption in JavaScript.
In the encrypt
method we are essentially taking in a string message and a password to encrypt it with. Every time we call the encrypt
method we create a new salt and a new initialization vector. It helps keep our passwords unique and more difficult to decipher for malicious users. Once the message has been encrypted, all three values are returned as an object for storing our our database.
The decrypt
method takes those three stored values to work backwards and decipher our encrypted password. The result being a regular string value.
Before we can use the services globally in our application, they must first be bootstrapped. Open the project’s app/app.module.ts file and include the following code:
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { AppComponent } from "./app.component";
import { CouchbaseInstance } from "./couchbase";
import { ForgeInstance } from "./forge";
import { BcryptInstance } from "./bcrypt";
@NgModule({
declarations: [AppComponent],
bootstrap: [AppComponent],
imports: [
NativeScriptModule
],
providers: [
CouchbaseInstance,
ForgeInstance,
BcryptInstance
],
schemas: [NO_ERRORS_SCHEMA]
})
export class AppModule { }
Notice how we’ve imported each of the services and added them to a providers
array in the @NgModule
block? Now we can proceed to building each of our application components. We’re going to build the components in the order that we see them in the application lifecycle.
The login component, like the other components is composed of three parts. There is the TypeScript logic file, the HTML UI file, and the CSS styling file. We’re going to start with the TypeScript file since it will contain the bulk of our work. Open the project’s app/component/login/login.component.ts file and include the following code:
import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { Location } from "@angular/common";
import { CouchbaseInstance } from "../../couchbase";
import { BcryptInstance } from "../../bcrypt";
@Component({
selector: "login",
templateUrl: "./components/login/login.component.html",
styleUrls: ["./components/login/login.component.css"]
})
export class LoginComponent implements OnInit {
private database: any;
public password: string;
public constructor(private router: Router, private location: Location, private couchbaseInstance: CouchbaseInstance, private bcrypt: BcryptInstance) {
this.database = this.couchbaseInstance.getDatabase();
this.password = "";
}
public ngOnInit() {
this.location.subscribe((path) => {
this.password = "";
});
}
public validate() {
if(this.password) {
let masterPassword = this.database.getDocument("master-password");
if(masterPassword) {
if(this.bcrypt.compare(this.password, masterPassword.password)) {
this.router.navigate(["list", this.password]);
}
}
}
}
public reset() {
this.router.navigate(["reset"]);
}
}
So what is happening in the above file exactly?
First off, we’re importing all the necessary Angular classes and components followed by the Couchbase and Bcrypt shared instances. In the @Component
block we’re defining the HTML file and CSS file to be paired with this particular component. Now for the code that matters the most.
In the LoginComponent
we have a private database variable for keeping track of our opened database instance and a public password variable that will be bound to the UI to accept user input. In the constructor
method we are injecting a few components including our two shared services. Anything listed as private
will be accessible outside the constructor
method. Inside this method we grab the open database and blank out the password variable so our form is empty.
Within the ngOnInit
method which is triggered after the constructor
method, we start a subscription on the Location
component. We are essentially subscribing for back or pop events in the navigation stack. We want to do this so when the back button is pressed, our form becomes empty again. This will prevent the user from signing back in without first entering the password.
public validate() {
if(this.password) {
let masterPassword = this.database.getDocument("master-password");
if(masterPassword) {
if(this.bcrypt.compare(this.password, masterPassword.password)) {
this.router.navigate(["list", this.password]);
}
}
}
}
In the above snippet we validate the master password. The validate
method is called when the user decides to try to unlock the application. If the password is not empty we will get our applications only master password. It has a key that we’ve defined. Remember, the master password currently saved will be hashed. Once we obtain it we must compare our plain text password against it. If successful, we can navigate to the ListComponent
and pass along the master password. The master password will be used to decrypt our passwords as necessary.
Should the user decide to hit the reset button, the reset
method will be called. This will navigate the user to the ResetComponent
screen.
Now we can worry about our UI, but before we do, let’s create our stylesheet to be used. Open your project’s app/components/login/login.component.css file and include the following:
GridLayout {
background-color: #CCCCCC;
padding-left: 10;
padding-right: 10;
}
StackLayout {
background-color: #EEEEEE;
padding-left: 20;
padding-right: 20;
}
.login-container Label {
margin-top: 20;
margin-bottom: 20;
font-weight: bold;
}
.login-container TextField {
margin-bottom: 20;
}
.btn-unlock {
background-color: #000000;
color: #FFFFFF;
font-weight: bold;
margin-bottom: 20;
}
.btn-reset {
width: auto;
horizontal-align: right;
font-weight: bold;
margin-bottom: 20;
}
Nothing more than some basic CSS in the above file. It will give us a nice vertically centered login form for our application.
Now we can jump into the HMTL markup. Open the project’s app/components/login/login.component.html and include the following markup:
<ActionBar title="Password Manager"></ActionBar>
<GridLayout>
<GridLayout
rows="*">
<StackLayout class="login-container" verticalAlignment="center" row="0">
<Label text="Master Passcode"></Label>
<TextField
hint="Password"
secure="true"
returnKeyType="done"
autocorrect="false"
autocapitalizationType="none"
(returnPress)="validate()"
[(ngModel)]="password">
</TextField>
<Button class="btn-unlock" text="Unlock" (tap)="validate()"></Button>
<Button class="btn-reset" text="Reset Passcode" (tap)="reset()"></Button>
</StackLayout>
</GridLayout>
</GridLayout>
You’ll notice the above is not truly HTML. NativeScript has its own markup, but since that and HTML are both flavors of XML, it shouldn’t be too difficult to put together.
We are adding an action bar to make it look slick on iOS and Android. By using a GridLayout
we can define how much of the screen to use. It is like a table and in this case it allows us to do a vertical alignment. More on GridLayout’s can be seen in another tutorial I wrote. Inside the centered GridLayout
we have a StackLayout
with a Label
, TextField
, and two Button
elements. In the TextField
you’ll notice we are using [(ngModel)]
which allows us to bind the input to the public password variable from the TypeScript file.
When the unlock button or return key is pressed, the validate
method is called, otherwise the reset
method is called when the reset button is pressed.
Now let’s worry about resetting our master password. The first time we start the application we need to reset the password and if the password is ever lost we need to reset it. This will result in the entire Couchbase database from being wiped for security.
Like with the previous component, let’s start with the TypeScript logic file. Open the project’s app/components/reset/reset.component.ts and include the following code:
import { Component } from "@angular/core";
import { Location } from "@angular/common";
import { Router } from "@angular/router";
import { CouchbaseInstance } from "../../couchbase";
import { BcryptInstance } from "../../bcrypt";
@Component({
selector: "reset",
templateUrl: "./components/reset/reset.component.html",
styleUrls: ["./components/reset/reset.component.css"]
})
export class ResetComponent {
private database: any;
public password: string;
public confirmation: string;
public constructor(private location: Location, private couchbaseInstance: CouchbaseInstance, private bcrypt: BcryptInstance) {
this.database = this.couchbaseInstance.getDatabase();
this.password = "";
this.confirmation = "";
}
public reset() {
if(this.password && this.confirmation) {
if(this.password == this.confirmation) {
let rows = this.database.executeQuery("passwords");
for(let i = 0; i < rows.length; i++) {
this.database.deleteDocument(rows[i]._id);
}
this.database.deleteDocument("master-password");
this.database.createDocument({
"password": this.bcrypt.create(this.password)
}, "master-password");
this.location.back();
}
}
}
}
In the above TypeScript file we are doing a lot of what we saw in the previous component. We are importing the various Angular components, and the shared services. In the @Component
section we are defining which HTML UI and CSS file to pair with before defining the ResetComponent
class code.
In our class we have the private database variable that will contain our open database instance, and two public password variables that will be bound to our UI.
In the constructor
method we are grabbing the open database, and blanking out the bound form variables. Most of our magic happens in the reset
method. When the method is called we first make sure the form fields are not empty. If they are not empty we make sure that they equal each other. We wouldn’t want a non-matching password. That would be chaos in a production application. Provided the passwords match, we do a query for all passwords that currently exist in the database and delete them one by one. After all the passwords have been removed we wipe out the current master password before setting it again to a hash of whatever our new password is. When the new password has been saved, we pop back in the navigation stack.
Now let’s take a look at the stylesheet for this particular component. Open the project’s app/components/reset/reset.component.css file and include the following:
GridLayout {
background-color: #CCCCCC;
padding-left: 10;
padding-right: 10;
}
StackLayout {
background-color: #EEEEEE;
padding-left: 20;
padding-right: 20;
}
.form-label {
margin-top: 20;
margin-bottom: 20;
font-weight: bold;
}
.login-container TextField {
margin-bottom: 20;
}
.btn-unlock {
background-color: #000000;
color: #FFFFFF;
font-weight: bold;
margin-bottom: 20;
}
You’ll notice the above stylesheet really isn’t much different than the stylesheet for the login component. For us it doesn’t matter because we want to keep each component segregated from the rest.
With the TypeScript and CSS stuff out of the way, let’s look at the HTML portion to this component. Open the project’s app/components/reset/reset.component.html file and include the following markup:
<ActionBar title="Reset Master Password">
<NavigationButton text="Back"></NavigationButton>
</ActionBar>
<GridLayout>
<GridLayout
rows="*">
<StackLayout class="login-container" verticalAlignment="center" row="0">
<Label class="form-label" text="New Master Password"></Label>
<TextField
hint="Password"
secure="true"
returnKeyType="next"
autocorrect="false"
autocapitalizationType="none"
[(ngModel)]="password">
</TextField>
<Label class="form-label" text="Confirm Master Password"></Label>
<TextField
hint="Confirm Password"
secure="true"
returnKeyType="done"
autocorrect="false"
autocapitalizationType="none"
(returnPress)="reset()"
[(ngModel)]="confirmation">
</TextField>
<Button class="btn-unlock" text="Reset" (tap)="reset()"></Button>
</StackLayout>
</GridLayout>
</GridLayout>
The above HTML isn’t much different from the login component. We have a centered login form, but this time with two input fields and only one button. When the return key is pressed or the reset button is pressed, the reset
method is called. Each form input has an [(ngModel)]
for binding the data to a public TypeScript variable to be used in the reset process.
At this point the component for resetting passwords is now complete.
Pressing along in our adventure of building a password manager, we are faced with building the component for listing out all the saved password or protected documents. Like with the other components we have a TypeScript, HTML, and CSS file to work with.
We’re going to start with the TypeScript file again. Open the project’s app/components/list/list.component.ts file and include the following code:
import { Component, OnInit } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { Location } from "@angular/common";
import { CouchbaseInstance } from "../../couchbase";
@Component({
selector: "list",
templateUrl: "./components/list/list.component.html",
styleUrls: ["./components/list/list.component.css"]
})
export class ListComponent implements OnInit {
private database: any;
private masterPassword: string;
public passwordList: Array<Object>;
public constructor(private router: Router, private location: Location, private route: ActivatedRoute, private couchbaseInstance: CouchbaseInstance) {
this.masterPassword = "";
this.passwordList = [];
this.database = this.couchbaseInstance.getDatabase();
}
public ngOnInit() {
this.route.params.subscribe((params) => {
this.masterPassword = params["password"];
});
this.location.subscribe((path) => {
this.load();
});
this.load();
}
private load() {
let rows = this.database.executeQuery("passwords", {descending: false});
this.passwordList = rows;
}
public viewDetails(item: any) {
this.router.navigate(["view", this.masterPassword, item._id]);
}
public create() {
this.router.navigate(["create", this.masterPassword]);
}
}
We are looking at the same old story here. Include the various components and services, then define which HTML and CSS file get paired with the component. The differences here are in the class itself.
A little Angular, 101, tells us that all major initializations should happen in the ngOnInit
, not the constructor
method. In the constructor
we still grab our database and initialize our variables, but set a subscription on the Location
and load our list in the ngOnInit
method.
To populate our list we are calling the load
method which executes a query on the passwords
view. We are also defining a sort order. Remember the view key is the document title, so the sort will happen based on the title. Anything returned from the query will be added to the passwordList
variable which is public and bound to the UI.
The viewDetails
and create
methods are pretty much just navigation functions. When a particular password is selected from the list view, the password will be passed as a parameter where we can grab the document id. This along with the master password is passed to the next component being the ViewComponent
. The create
method just passes the master password.
This brings us to the stylesheet for this particular component. Open the project’s app/components/list/list.component.css file and include the following:
Label {
padding: 10;
}
Not much is being done for styling in the above. We just want to set some padding on the list elements and that is it.
Now we can worry about the UI that goes with the ListComponent
. Open the project’s app/components/list/list.component.html file and include the following markup:
<ActionBar title="Passwords">
<NavigationButton text="Back"></NavigationButton>
<ActionItem text="New" (tap)="create()" ios.position="right"></ActionItem>
</ActionBar>
<GridLayout>
<ListView [items]="passwordList">
<template let-item="item">
<Label [text]="item.title" (tap)="viewDetails(item)"></Label>
</template>
</ListView>
</GridLayout>
In the above markup, you see we have an action button in the action bar. This will call the create
method that will take us to the CreateComponent
. We also have a ListView
that will loop through and print every item that exists in our publicly declared passwordList
variable. Each item of that list is defined as item
where we can access certain properties and pass them to the viewDetails
method or display them on the screen.
That covers everything found in the component for listing passwords.
We’ve listed passwords, but how about creating passwords? This time we’re going to look at creating a password, encrypting it, and then saving it to Couchbase.
Starting with the TypeScript portion, open the project’s app/components/create/create.component.ts file and include the following code:
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { Location } from "@angular/common";
import { CouchbaseInstance } from "../../couchbase";
import { ForgeInstance } from "../../forge";
@Component({
selector: "create",
templateUrl: "./components/create/create.component.html",
styleUrls: ["./components/create/create.component.css"]
})
export class CreateComponent implements OnInit {
private database: any;
private masterPassword: string;
public title: string;
public username: string;
public password: string;
public constructor(private location: Location, private route: ActivatedRoute, private forgeInstance: ForgeInstance, private couchbaseInstance: CouchbaseInstance) {
this.database = this.couchbaseInstance.getDatabase();
this.masterPassword = "";
this.title = "";
this.username = "";
this.password = "";
}
public ngOnInit() {
this.route.params.subscribe((params) => {
this.masterPassword = params["password"];
});
}
public save() {
if(this.title && this.username && this.password) {
this.database.createDocument({
"title": this.title,
"username": this.forgeInstance.encrypt(this.username, this.masterPassword),
"password": this.forgeInstance.encrypt(this.password, this.masterPassword)
});
this.location.back();
}
}
}
Skipping over the same old code that have been in the previous component files, we’re going to look at strictly the CreateComponent
class.
The form that we’ve yet to create has three input fields. These input fields are the public class variables. In the constructor
method we blank them out and grab the open database. We’re not loading any data, but we do need to subscribe to our router in the ngOnInit
to get any parameters passed from the previous screen.
The save
method is where the magic happens. We first make sure each of the form variables is not blank. If the condition is met, then we can create a new Couchbase document. The title
property will be plain text, but the other two properties will be encrypted like shown at the beginning of this tutorial. This is where we make use of our Forge shared service to encrypt the data based on the master password being passed around in the router. No need to pick and pull, we are just storing the full object returned from the encryption process.
When successful we navigate backwards in the stack.
Let’s look at the stylesheet that goes with this particular component. Open the project’s app/components/create/create.component.css and include the following:
.item-group {
padding: 10;
}
.item-label {
font-weight: bold;
}
Nothing particularly fancy happening in the above stylesheet. Just trying to make our form look a little nicer.
Time to take a look at the HTML UI that goes with our CSS and TypeScript file. Open the project’s app/components/create/create.component.html file and include the following markup:
<ActionBar title="Create Password">
<NavigationButton text="Back"></NavigationButton>
<ActionItem text="Save" (tap)="save()" ios.position="right"></ActionItem>
</ActionBar>
<StackLayout>
<StackLayout class="item-group">
<Label class="item-label" text="Title"></Label>
<TextField hint="Password Title" [(ngModel)]="title"></TextField>
</StackLayout>
<StackLayout class="item-group">
<Label class="item-label" text="Username"></Label>
<TextField hint="Password Username" [(ngModel)]="username"></TextField>
</StackLayout>
<StackLayout class="item-group">
<Label class="item-label" text="Password"></Label>
<TextField hint="Actual Password" [(ngModel)]="password"></TextField>
</StackLayout>
</StackLayout>
Just like with the previous component, this one has an action bar with an action button for saving the documents. As far as the form goes, we have a vertically stacked StackLayout
with three form elements, each bound to the TypeScript file with the [(ngModel)]
property. Nothing really fancy happening here.
This completes the component for creating new password data.
We have only one component remaining to complete this application. We need to create a component that will allow us to decrypt and view any passwords that have been added. Like with the other four components, let’s start by looking at the TypeScript logic file.
Open the project’s app/components/view/view.component.ts file and include the following code:
import { Component, OnInit } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { CouchbaseInstance } from "../../couchbase";
import { ForgeInstance } from "../../forge";
@Component({
selector: "view",
templateUrl: "./components/view/view.component.html",
styleUrls: ["./components/view/view.component.css"]
})
export class ViewComponent implements OnInit {
private database: any;
public passwordItem: any;
public constructor(private router: Router, private route: ActivatedRoute, private couchbaseInstance: CouchbaseInstance, private forgeInstance: ForgeInstance) {
this.passwordItem = {};
this.database = this.couchbaseInstance.getDatabase();
}
public ngOnInit() {
this.route.params.subscribe((params) => {
this.passwordItem = this.database.getDocument(params["id"]);
this.passwordItem.username = this.forgeInstance.decrypt(this.passwordItem.username.cipherText, params["password"], this.passwordItem.username.salt, this.passwordItem.username.iv);
this.passwordItem.password = this.forgeInstance.decrypt(this.passwordItem.password.cipherText, params["password"], this.passwordItem.password.salt, this.passwordItem.password.iv);
});
}
}
There isn’t a whole lot happening in the above TypeScript code. Like with each of the other components, we obtain the open database and initialize our public variables in the constructor
method. We actually do our data load in the ngOnInit
method instead. Remember, on each navigation in the router we’re passing around the master password and sometimes a particular document id. We’re going to make use of both in this area.
First we do a document lookup based on the particular document id. This password document is stored in our public password variable. However, the document is still encrypted. We need to make use of the Forge service to decrypt our cipher text using the salt, initialization vector, and master password. When this is done, we will have something pleasant to show on the screen.
The stylesheet that goes with this component is as follows. Open the project’s app/components/view/view.component.css file and include:
.item-group {
padding: 10;
}
.item-label {
font-weight: bold;
}
It isn’t any different than the last CSS file that we saw.
Finally, let’s look at the HTML UI that goes with this component. Open the project’s app/components/view/view.component.html file and include the following markup:
<ActionBar title="Password Details">
<NavigationButton text="Back"></NavigationButton>
</ActionBar>
<StackLayout>
<StackLayout class="item-group">
<Label class="item-label" text="Title"></Label>
<Label [text]="passwordItem.title"></Label>
</StackLayout>
<StackLayout class="item-group">
<Label class="item-label" text="Username"></Label>
<Label [text]="passwordItem.username"></Label>
</StackLayout>
<StackLayout class="item-group">
<Label class="item-label" text="Password"></Label>
<Label [text]="passwordItem.password"></Label>
</StackLayout>
</StackLayout>
The above is the same as the CreateComponent
, with the exception that the input fields have been replaced with labels. We’ve also removed the action bar button since this is a read only screen.
The final component is now complete, but our application is not quite ready to be tested.
We have all these components, but they aren’t connect via the core application. We need to define each of the routes so they can be used. Create and open an app/app.routing.ts file in your project and include the following code:
import { ViewComponent } from "./components/view/view.component";
import { CreateComponent } from "./components/create/create.component";
import { LoginComponent } from "./components/login/login.component";
import { ResetComponent } from "./components/reset/reset.component";
import { ListComponent } from "./components/list/list.component";
export const AppRoutes: any = [
{ path: "", component: LoginComponent },
{ path: "list/:password", component: ListComponent },
{ path: "create/:password", component: CreateComponent },
{ path: "view/:password/:id", component: ViewComponent },
{ path: "reset", component: ResetComponent },
];
export const AppComponents: any = [
ViewComponent,
CreateComponent,
LoginComponent,
ResetComponent,
ListComponent
];
Let’s break down what is happening in the above code.
First we are importing every component that will be displayed on the screen. Knowing the component isn’t good enough, so we need to define a list of routes first. The route with an empty path is known as the default route and will be the first to be displayed on the screen. Notice that some of the paths include parameters like :password
. These are the parameters that we pass around each of the components and can be accessed from the TypeScript file.
As a convenience for the next step, we create an array of every possible component.
With the routing defined, we need to add it to the @NgModule
block of the project’s app/app.module.ts file. Open this file and include the following:
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { NativeScriptFormsModule } from "nativescript-angular/forms";
import { AppRoutes, AppComponents } from "./app.routing";
import { AppComponent } from "./app.component";
import { CouchbaseInstance } from "./couchbase";
import { ForgeInstance } from "./forge";
import { BcryptInstance } from "./bcrypt";
@NgModule({
declarations: [AppComponent, ...AppComponents],
bootstrap: [AppComponent],
imports: [
NativeScriptModule,
NativeScriptFormsModule,
NativeScriptRouterModule,
NativeScriptRouterModule.forRoot(AppRoutes)
],
providers: [
CouchbaseInstance,
ForgeInstance,
BcryptInstance
],
schemas: [NO_ERRORS_SCHEMA]
})
export class AppModule { }
In the above, we’ve imported the routing file, but also the NativeScriptRouterModule
as well. The array of available components from the routing file are merged with the declarations
array in the @NgModule
block and the router module is added and configured in the imports
array.
With everything brought together, we need a way to display things on the screen. Within the project’s app/app.component.ts file add the following:
import { Component } from "@angular/core";
@Component({
selector: "my-app",
template: "<page-router-outlet></page-router-outlet>",
})
export class AppComponent { }
The line of interest is the addition of the following:
<page-router-outlet></page-router-outlet>
All routes will pass through the above outlet. Because we are no longer using a template URL, we don’t need the app/app.component.html file any longer.
Congratulations, the application is now complete!
It was a lot of work, but we’re finally ready to test the application. If you’ve done everything mentioned, execute the following from your Command Prompt or Terminal:
tns emulate ios
This will run the application in the iOS emulator for Mac. If you’re wanting to test it in Android, execute the following:
tns emulate android
Let’s say you didn’t want to run through this long tutorial, but you still wanted to test it out. You can download the full project source code here. To run the packaged source code, extract it and navigate into the directory using your Command Prompt or Terminal. Before trying to run with the above commands you need to download all the dependencies. This can be done by executing the following:
tns install
Now you can try to emulate the project. Just remember to remove the node_modules/bcryptjs/dist/bcrypt.min.js.gz file before trying to build for Android.
We just create an epic NativeScript password manager project. This tutorial contained everything from using Couchbase NoSQL, to encrypting and decrypting strings. Not only did this make you a better NativeScript developer, but most of the code that we used is completely compatible in an Angular web application (minus the Couchbase plugin).
If you’d like to download the source code to this project, it can be found here. I spent a lot of time writing this tutorial and coming up with the source code. I’d like to request that you do not share it.