In the previous posts, the PasswordGenerator always returned ‘Password’. Instead each date should corresponds to a new unique password. We’ll make use of the Crypto-js node package and we’ll see that the AWS lambda copes just fine.
Installing a node package
Pull in the crypto-js package into the serverless component.
$ cd nodejscomponent/
$ npm install crypto-js --save
crypto-js@3.1.6 node_modules/crypto-js
Now we need the typescript definitions. Watch out there are two different TypeScript typings called cryptojs and crypto-js. The first one is more complete.
$ typings install cryptojs --ambient --save
? Found cryptojs typings for DefinitelyTyped. Continue? Yes
Installing cryptojs@~3.1.2 (DefinitelyTyped)...
cryptojs
└── (No dependencies)
I’m not sure why, but there’s no export in the typings file for cryptojs. Add the following to the bottom of the cryptojs.d.ts file.
/// <reference path="../typings/main.d.ts" />importCryptoJS=require("crypto-js");exportfunctioncheckPotd(password:string):boolean{returnnewPasswordGenerator().check(password);}exportclassPasswordGenerator{generate(date:Date):string{// Get the current date as a YYYYMMDD stringvaryyyy=date.getFullYear().toString();varmm=(date.getMonth()+1).toString();// getMonth() is zero-basedvardd=date.getDate().toString();varplain=`${yyyy}${mm}${dd}`;// Using AES CTR with 32 byte key and iv ensures the encrypted string is not too long// See http://stackoverflow.com/a/13298019/1077279varkey=CryptoJS.enc.Hex.parse('108c786594543687891374723e809ec5e475a8361f7ad82df04e91ba2c139321');// Use a different initialization vector each time by using the date as part of the vectorvariv=CryptoJS.enc.Hex.parse(plain+'3a8fe4440be1e113a271574f379d70a76c3477aaff036d1e83fcd4b9');varoptions={mode:CryptoJS.mode.CTR,padding:CryptoJS.pad.NoPadding,iv:iv};varencrypted=CryptoJS.AES.encrypt(plain,key,options);returnencrypted.ciphertext.toString();}check(password:string):boolean{// check the value matches today's password of the dayreturnpassword==this.generate(newDate());}}
Run the tests
We expect the tests to fail now since we are no longer returning the same password.
123456789101112131415161718192021222324252627
$ npm test> @0.0.1 pretest /Users/ra/Projects/Coprocess/serverlessPotd/nodejscomponent
> tsc
> @0.0.1 test /Users/ra/Projects/Coprocess/serverlessPotd/nodejscomponent
> mocha ./lib/test
Generator
#generate 1) should generate the password
#check ✓ should return false when the password is incorrect
#check 2) should return true when the password is correct
1 passing (16ms) 2 failing
1) Generator #generate should generate the password: Error: Expected 'Password' but was bb4bde4d76b055
at Context.<anonymous> (lib/test/passwordOfTheDayTest.js:12:23) 2) Generator #check should return true when the password is correct: Error: Expected 'true' but was falseat Context.<anonymous> (lib/test/passwordOfTheDayTest.js:28:23)npm ERR! Test failed. See above for more details.
Update and improve the tests
It’s getting a little more complicated so let’s pull in chai which is a pretty assertions library.
/// <reference path="../../typings/main.d.ts" />importPasswordOfTheDay=require("../passwordOfTheDay");importChai=require("chai");// Tell chai that we'll be using the "should" style assertions.Chai.should();describe("Generator",()=>{varsubject:PasswordOfTheDay.PasswordGenerator;beforeEach(function(){subject=newPasswordOfTheDay.PasswordGenerator();});describe("#generate",()=>{it("should generate the password when the date is 24th July 2010",()=>{vardate:Date=newDate(2010,6,24);varpassword:string=subject.generate(date);password.should.equal("92ab1ff89bf9af");});});describe("#generate",()=>{it("should generate a different password when the date is 25th July 2010",()=>{vardate:Date=newDate(2010,6,25);varpassword:string=subject.generate(date);password.should.equal("26a394b21800f1");});});describe("#check",()=>{it("should return false when the password is incorrect",()=>{varpassword:string="garbage";varresult:boolean=subject.check(password);result.should.be.false;});});describe("#check",()=>{it("should return false when the password is null",()=>{varpassword:string=null;varresult:boolean=subject.check(password);result.should.be.false;});});describe("#check",()=>{it("should return true when the password is correct",()=>{varpassword:string=subject.generate(newDate());varresult:boolean=subject.check(password);result.should.be.true;});});});
Run our tests:
123456789101112131415161718192021
$ npm test> @0.0.1 pretest /Users/ra/Projects/Coprocess/serverlessPotd/nodejscomponent
> tsc
> @0.0.1 test /Users/ra/Projects/Coprocess/serverlessPotd/nodejscomponent
> mocha ./lib/test
Generator
#generate ✓ should generate the password when the date is 24th July 2010
#generate ✓ should generate a different password when the date is 25th July 2010
#check ✓ should return false when the password is incorrect
#check ✓ should return false when the password is null
#check ✓ should return true when the password is correct
5 passing (20ms)
Deploy
$ serverless dash deploy
Now when we visit the endpoint with the correct password for today’s date which happens to be 89366e6199f3.
Mission accomplished! Notice that using a node package did not require any special steps on the AWS side. In fact we have not had to login to AWS since the very beginning when we created an IAM user for the project. And yet we’ve managed to build and deploy a cheap and scalable cloud-based service.
The source code is on GitHub. Note the default .gitignore file skips the admin.env file which contains the (sensitive) AWS keys in it so don’t forget to add your own.
That wraps up my series on building a small, but real-world Serverless application. In a future post, I’d like to look at providing a secured ‘generate’ service to allow authorized users to get today’s password. Stay tuned.