In this assignment, you will be creating a web service that serves as an online API to the credit card features you wrote earlier.
Create a new repo with the following structure, described further below:
├── Gemfile
├── Gemfile.lock
├── Procfile
├── README.md
├── app.rb
├── config.ru
├── lib/
├── spec/
lib/
: Put your earlier CreditCard and crypto code files in here -- do not change any files!spec/
: put your old specs in here, but they must now work with your code inlib/
Gemfile
: list the gems your new service will needGemfile.lock
: autocreated bybundle install
Procfile
: whatrackup
will launch with the configurations inconfig.ru
config.ru
: which file and class will the service run?README.md
: start a meaningful README describing service features, and your deployed Heroku applicationapp.rb
: your Sinatra service should be coded in here
Move your old project *.rb
files in to the lib/
folder of this repo, and your old specs into spec/
. You must make sure your specs still run from your app's root directory. Try to see if you can reuse your CreditCard
code without modifying it.
Create a class called CreditCardAPI
inherited from Sinatra::Base
. Your service should support two routes (URLs):
/
: the root route should return a message that your service is running./api/v1/credit_card/validate
: this route should validate credit card numbers using the Luhn algorithm. Simply create CreditCard object using your previous code (now inlib/
) and validate it. Examples of requests it must service include:http://127.0.0.1:9393/api/v1/credit_card/validate?card_number=4024097178888052
This number should not get validated, and your service should return the following json:{"card":"4024097178888052","validated":false}
http://127.0.0.1:9393/api/v1/credit_card/validate?card_number=4916603231464963
This number should get validated, and your route should return:{"card":"4916603231464963","validated":true}
We don't like it when our work isn't deployed. Once your two routes work, deploy it to Heroku. Name your deployed application appropriately (more on this later).
Submit your Github repo. But put a link in your README.md
to your Heroku application so we can find it and try it out!
Important: We will not be deploying this part of your project. So please work in a git branch called sqlite
and push that to github. Individual members should pull that branch down and branch off from there for their individual parts. We will not merge this with master for this part of the project. Instead, issue pull requests to sqlite
.
Follow the instructions in class to create a local sqlite database called db/dev.db
. Your table should should be called Card
and should have all the items found in your CreditCard class. Some special things to note:
- Be very careful about using appropriate singular and plural name for credit cards in each of the three steps we discussed in class:
- Step 1: n/a
- Step 2:
- the class name of your model should be
CreditCard
and the filename should becredit_card.rb
- the name of the
db:create_migration
create taskcreate_credit_cards
- the class name of your model should be
- Step 3:
- make sure the name of your migration file is
db/migrate/*_create_credit_cards.rb
but that the name of the migration class isCreateCreditCards
- the name of the
create_table
parameter in the#change
method should be:credit_cards
- make sure the name of your migration file is
- When making the CreditCard model (step 2 of class instructions): we will make our earlier CreditCard object into an ActiveRecord
- move
credit_card.rb
fromlib/
folder to a newmodel/
folder - comment out the attributes (
name
,owner
,expiration_date
, andcredit_network
) -- we will define these in the migration file instead - comment out the
initialize
method -- ActiveRecord has its own initialization process that we don't want to interfere with - make the
CreditCard
class inherit fromActiveRecord::Base
- note: the specs you wrote earlier will fail -- we will talk about fixing them later.
- move
####(1) Create a POST route that creates a new credit card on your database:
- The route should be:
POST /api/v1/credit_card
- e.g.,
curl -X POST -d "{\"number\":\"5192234226081802\",\"expiration_date\":\"2017-04-19\",\"owner\":\"Cheng-Yu Hsu\",\"credit_network\":\"Visa\"}" http://127.0.0.1:9292/api/v1/credit_card
- e.g.,
- It should take a json request body with attributes
number
,owner
,credit_network
andexpiration_date
- If the card number is not valid, halt with HTTP status code 400
- If succesful, it should return HTTP status
201
(you can simply usestatus 201
to set a return status) for resource created, or else halt with410
if there is a problem.
####(b) Create a GET index route that returns all the credit cards in the database
- Use your active record (CreditCard) to get
.all
the cards - Return a json array of all cards
- Return a status of
200
if all goes well, or else500
if there is any error
We are not proud of code that saves sensitive data in an unsecured database! Let's just make sure it works locally on your own machine. We will create a safer solution next week. Remember, keep your work in a feature branch called sqlite
but not in master.
Submit a link to the sqlite
branch of your Github repo.
Important: Please work in a git branch called hardened_db
and push that to github. Do not merge into master quite yet.
You currently have 3 GET/POST routes that take parameters. Make them resistant to SQL injection attacks using Query Parameterization
- Do not directly pass any variables to
ActiveRecord
methods if the data came directly from user paramaters (response.body
orparams[]
) - Instead, use the references from our class notes to pass in query parameters
- If you cannot find relevant query parameters for any
ActiveRecord
methods, please discuss a solution on our Canvas website!
Our credit card numbers are currently in raw form on the database. Let's encrypt them!
- Change your migration to only store
:encrypted_number
rather than:number
- also store a
:nonce
, which serves as a one-time initialization vector - delete
db/schema.rb
anddb/dev.db
- rerun
rake db:migrate
- also store a
- Add getter and setter methods for
number
to yourCreditCard
modeldef number=
should take a raw number, encrypt it with a key and nonce, and store it in#encrypted_number
def number
should decrypt the#encrypted_number
using a key and the record's appropriate nonce- you should get the key from
ENV
rather than hard-wiring it into your code
- Put development key to secret file
- Store your encryption key in a secret
config_env.rb
file as we showed in class - Use the
config_env
gem to load the secret key as an environment variable - Create a sample file
config_env.rb.example
as an empty template of what the file should look like
- Store your encryption key in a secret
We will discuss setting up a better production database in class this coming week. So don't ship your code to Heroku quite yet!
Submit a link to the hardened_db
branch of your Github repo.
Create tests and a production database for a real deployment!
- Write at least two Minitest specs for each route that takes input
- A happy path: test that valid input is take and proper output and status is returned
- A sad path: test that invalid input returns an appropriate error status
- Create a rake task (e.g.,
rake spec
) that can be run from the command line to execute all your tests
- Configure your API service for a Postgres database when deployed in production
- Add the Postgres addon to your Heroku service
- Add your database encryption key to Heroku's environment
- Make sure to put details of your API in your README.md
- Merge your work to
master
branch this time! - Make sure your tests work one more time (
rake spec
)! - Deploy your master branch to Heroku!
- Submit the URL of your Github account and the URL of your Heroku app