Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / iOS

Pwnage Checker - An iOS App Built Using Swift

5.00/5 (12 votes)
16 Jun 2016CPOL7 min read 25.9K   155  
Pwnage Checker is an iOS app that allows users to check whether an account has been compromised in a known data breach
For the latest version of the code, please check the project at Github.  
 
Image 1

Introduction

Adobe, Snapchat, Sony, these are just some of the many high profile data breaches in recent years. Have you ever wondered if your account has been compromised in one of these data breaches?

In this article, we will walk through the creation of Pwnage Checker. Pwnage checker is an iOS app built using Swift. It allows users to easily check whether an account has been compromised in a known data breach on any iOS device. It leverages the https://haveibeenpwned.com API created by Troy Hunt, which aggregates public leaked data from breaches and makes them readily searchable. 

Core Functionality

Search Function

Image 2

The main function of the app is to search whether an account is found in a known data breach, therefore the home screen presents this function. The search has the following work flow.

  1. User begins a search by specifying the account to search for in one of two ways:
    • User chooses "Enter an account" to begin a search by entering an account.
    • User chooses "Select from contact" to begin a search by selecting the email address of an existing contact. This is useful if you want to check for someone else, e.g.. a less technical friend or elders in your family.
  2. User is presented with the search result.
    • If no breach is found, a screen with green background and a reassuring smiley face will be presented.
    • If the account is found in one or more data breaches, a screen with red background and a sad face will be presented to draw attention. The screen will also list out the breaches the account is found in.
  3. If the account is found in any data breach, user can drill down to view the detail of the data breach.

Browse Function

Image 3

The app allows the users to browse all breaches that are loaded into the service. User can drill down to view the detail information of each breach, such as the number of accounts that were leaked and the types of data that were leaked.

Subscribe Function   

Image 4

The app allows users to subscribe to breach notifications on their account. The subscribe feature is not available via the https://haveibeenpwned.com API, but it is available on the website. This screen simply hosts a webview which loads the subscribe page from the site. This is provided as a convenience for the user.

Using the code

Listing 1: SearchViewController class
class SearchViewController: UIViewController, CNContactPickerDelegate {
The SearchViewController handles the interaction on the home screen. It implements the CNContactPickerDelegate protocol because it uses CNContactPickerViewController to let user select a contact. 

Image 5  

Listing 2: SearchViewController.enterAccountButtonTouch method

C#
@IBAction func enterAccountButtonTouch(sender: AnyObject) {
    var inputTextField: UITextField?
    let prompt = UIAlertController(title: "Enter an account", message: "", preferredStyle: UIAlertControllerStyle.Alert)

    prompt.addAction(UIAlertAction(title: "Check", style: UIAlertActionStyle.Default, handler: { (action) -> Void in
        if let account = inputTextField?.text {
            self.checkAccount(account)
        }
    }))
    prompt.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default, handler: nil))
    prompt.addTextFieldWithConfigurationHandler({(textField: UITextField!) in
        textField.placeholder = "Enter email address or username"
        inputTextField = textField
    })
    presentViewController(prompt, animated: true, completion: nil)
}

The "Enter an account" button's touch action is connected to the enterAccountButtonTouch method. When the button is touched, it creates an UIAlertController to prompt user for an account. The controls on the UIAlertController are dynamically added. The addAction method is used to add the check button and the cancel button with the appropriate handler code. The addTextFieldWithConfigurationHandler method is used to add and configure the text field. 

Image 6

Listing 3: SearchViewController.selectContactButtonTouch method

@IBAction func selectContactButtonTouch(sender: AnyObject) {
    // show contact picker and configure it with the following behaviors:
    // contacts with 0  email address:   disabled, cannot be selected.
    // contacts with 1  email address:   when selected, the contact's only email address will be used
    // contacts with 1+ email addresses: when selected, the detail of the contact will be presented,
    //                                   and user can select one of the email addresses
    
    let contactPickerViewController = CNContactPickerViewController()
    
    // disable contacts with no email addresses
    contactPickerViewController.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0")
    
    // when a contact is selected, show contact detail if there are more than 1 email addresses
    contactPickerViewController.predicateForSelectionOfContact = NSPredicate(format: "emailAddresses.@count == 1")
    
    // in contact detail card, only show the email address
    contactPickerViewController.displayedPropertyKeys = [CNContactEmailAddressesKey]
    
    contactPickerViewController.delegate = self
    
    presentViewController(contactPickerViewController, animated: true, completion: nil)
}

The "Select from contact" button's touch action is connected to the selectContactButtonTouch method. When the button is touched, it will create and present a CNContactPickerViewController to allow user to pick an email from a contact. 

predicateForEnablingContact property is used to set a condition for when a contact should be enabled. We only want a contact to be enabled if it has at least one email address, and we set it like so:

contactPickerViewController.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0")

In the screen shot below, notice the David Taylor contact is disabled in the picker, it's because it has no email address.

Image 7

predicateForSelectionOfContact property is used to set the condition on whether a selected contact should be returned to our code, or to display the detail of the selected contact. We want the selected contact returned immediately if it has only one email address, otherwise we want to display the detail view so that the user can select one of the multiple email addresses. We setup the predicate like so:

contactPickerViewController.predicateForSelectionOfContact = NSPredicate(format: "emailAddresses.@count == 1")

displayedPropertyKeys property is used to limit what properties will be shown on the contact detail view. For our app, we only need the email address. We set it up like so:

 contactPickerViewController.displayedPropertyKeys = [CNContactEmailAddressesKey]

Below screen shot shows what happens when a contact with multiple email addresses is selected. The contact detail view is show. User can select from one of the email addresses. 

Image 8

Listing 4: SearchViewController.checkAccount method

C#
func checkAccount(account: String) {
    LoadingIndicatorView.show("checking")
    HaveIBeenPwnedClient.sharedInstance().getBreachesForAccount(account) {
        (hasBreaches, result, error) in
        dispatch_async(dispatch_get_main_queue()) {
            if (error != nil) {
                LoadingIndicatorView.hide()
                ViewHelper.showError("Unable to check account. ")
                print(error)
            }
            else {
                let controller = self.storyboard?.instantiateViewControllerWithIdentifier("SearchResultViewController") as! SearchResultsViewController
                controller.account = account
                controller.hasBreaches = hasBreaches
                controller.apiResult = result
                self.navigationController?.pushViewController(controller, animated: true)
                LoadingIndicatorView.hide()
            }
        }
    }
}
The checkAccount method will initiate the search using the HaveIBeenPwnedClient and presents the search result with SearchResultViewController. The method is called when user enters an account in account prompt, or when user selects an email from the contact picker.
 
Listing 5: SearchResultsViewController class
class SearchResultsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

SearchResultsViewController presents a search result. It uses UITableView to present a list breaches that the account is found in, and therefore implements the related UITableViewDataSource, UITableViewDelegate protocols.

Listing 6: SearchResultsViewController.showBreach method

func showBreach() {
    let breachArray = apiResult as! [[String:AnyObject]]
    for breachItem in breachArray {
        let breach = Breach(apiBreachResult: breachItem, context: tempContext)
        breaches.append(breach)
        print(breach.title)
    }
    print(breaches.count)

    view.backgroundColor = UIColor(red: 229/255, green: 000/255, blue: 0/255, alpha: 1)
    headerView.backgroundColor = UIColor(red: 229/255, green: 000/255, blue: 0/255, alpha: 1)
    iconLabel.text = "\u{e403}"
    titleLabel.text = "Oh no — pwned! Pwned on \(breaches.count) breached sites"
    subtitleLabel.text = "A \"breach\" is an incident where a site's data has been illegally accessed by hackers and then released publicly. Review the types of data that were compromised (email addresses, passwords, credit cards etc.) and take appropriate action, such as changing passwords."
    tableView.hidden = false
    tableView.reloadData()
}

showBreach method is called when an account is found in one or more data breach.  It updates the screen background and labels. It also sets up the breachArray and reloads the UITableView to show all the breaches the account are found in.  The following screen shot shows what it looks like.

Image 9

Listing 7: SearchResultsViewController.showNoBreach method

func showNoBreach() {
    view.backgroundColor = UIColor(red: 0/255, green: 100/255, blue: 0/255, alpha: 1)
    headerView.backgroundColor = UIColor(red: 0/255, green: 100/255, blue: 0/255, alpha: 1)
    iconLabel.text = "\u{e415}"
    titleLabel.text = "Good news — no pwnage found!"
    subtitleLabel.text = "No breached accounts"
    tableView.hidden = true
}

showNoBreach method is called when the account is not found in any of the data breach in the system. It updates the screen background and labels. The following screen shot shows what it looks like.

Image 10

Listing 8: BreachTableViewController class

class BreachTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate

The BreachTableViewController is responsible for showing all the breaches that are loaded into the service. It shows the data breaches in a UITableView and therefore implements the related UITableViewDataSource and UITableViewDelegate protocols. It also implements the NSFetchedResultsControllerDelegate protocol to manage the update of the UITableView when the underlying data changes. Following is a sample screen shot.

Image 11

Listing 9: BreachViewController class

class BreachViewController: UIViewController {

var breach : Breach!

@IBOutlet weak var logoImageView: UIImageView!
@IBOutlet weak var descriptionLabel: UILabel!
@IBOutlet weak var compromisedDataLabel: UILabel!
@IBOutlet weak var pwnCountLabel: UILabel!
@IBOutlet weak var breachDateLabel: UILabel!
@IBOutlet weak var isSensitiveSwitch: UISwitch!

override func viewDidLoad() {
    super.viewDidLoad()
    
    navigationItem.title = breach.title
    compromisedDataLabel.text = breach.dataClasses ?? "N/A"
    pwnCountLabel.text = breach.pwnCount?.stringValue ?? "N/A"
    breachDateLabel.text = breach.breachDate ?? "N/A"
    isSensitiveSwitch.on = breach.isSensitive?.integerValue > 0
    
    var text = ""
    if let desc = breach.desc?.dataUsingEncoding(NSUTF8StringEncoding) {
        do {
            text = try NSAttributedString(data: desc,
                options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                    NSCharacterEncodingDocumentAttribute:NSUTF8StringEncoding],
                documentAttributes: nil).string
        } catch let error as NSError {
            print(error.localizedDescription)
        }
    }
    descriptionLabel.text = text
    
    if let image = ImageCache.sharedInstance().imageWithName(breach.domain!) {
        logoImageView.image = image
    }
}
}

The BreachViewController is responsible for showing the detail information of a data breach. To Following is a screen shot.

Image 12

Listing 10: NotifyViewController class

 class NotifyViewController: UIViewController {

    @IBOutlet weak var webView: UIWebView!
    
    override func viewDidLoad() {
        webView.loadRequest(NSURLRequest(URL: NSURL(string: "https://haveibeenpwned.com/NotifyMe")!))
        self.navigationController?.navigationBarHidden = true
    }
}

The NotifyViewController class is responsible for loading and showing the subscribe page from the haveibeenpwned.com website. It's 

Listing 11: HaveIBeenPwnedClient class

class HaveIBeenPwnedClient : NSObject {
   
    func getBreachesForAccount(emailOrUsername : String, completionHandler : (hasBreaches: Bool, result: AnyObject!, error: String?)->Void) {
        ....
    }
    func refreshBreachesInBackground(completionHandler: (error: String?)->Void)->Void {
        ...
    }
...

The HaveIBeenPwnedClient class is responsible for making api request to the haveibeenpwned.com api.

Listing 12: ClearbitClient class

class ClearbitClient : NSObject {
    // get image given a domain
    func getImage(domain: String, completionHandler: (imageData: NSData?, error: String?)->Void) -> Void {
        ...
    }
...

The ClearbitClient class is responsible for making api request to the Clearbit Logo api for retrieving logo images of the companies in a data breach.

Listing 13: HttpClient class

class HttpClient: NSObject {
    // Perform a HTTP GET operation
    func httpGet(baseUrl: String, method: String, urlParams: [String:AnyObject]?, headerParams: [String:AnyObject]?, completionHandler: (result: NSData?, code: Int?, error: String?) -> Void) {
        ....
    }
    // Perform a HTTP POST operation
    func httpPost(baseUrl: String, method: String, urlParams: [String:AnyObject]?, headerParams: [String:AnyObject]?, jsonBody: [String:AnyObject], completionHandler: (result: NSData?, code: Int?, error: String?) -> Void) {
        ....
    }       
    // Perform a HTTP PUT operation
    func httpPut(baseUrl: String, method: String, urlParams: [String:AnyObject]?, headerParams: [String:AnyObject]?, jsonBody: [String:AnyObject], completionHandler: (result: NSData?, code: Int?, error: String?) -> Void) {
        ....
    }   
...

The HttpClient class is a helper that that provides utility methods for making http requests easier. Both the HaveIBeenPwnedClient and ClearbitClient uses it for making api requests.

Points of Interest

The App's application logic is relatively simple. Most of the time was actually spent on making the app more polished. This involves trying out different UI layouts and finding image resources that the app can use. In my quest for making the App more attractive, I find the following useful:

Google Material Icons

Google Material icons are a set of beautifully crafted and easy to use icons licensed under creative common that you can use in your web, Android, and iOS projects. When you download the icon, you have several options. You can choose the density dependent pixels in 18dp, 24dp, 36dp, or 48dp. You can choose white or dark background. Moreover, you can choose the format as svg, pngs, or icon font. The icons used in the app are all from the Google Material Icons.

Image 13

Clearbit Logo API

The Clearbit Logo Api is allows you to quickly retrieve a company's logo image given its domain. In the breach data returned from haveibeenpwned.com api, each item contains the company domain, but no company image. With the clearbit api, the app is able to show the image along with the breach data. See the image below. The aesthetic difference in terms an all text list and a text + image list is huge.

Image 14

Using emojicon as image

Emojicons are widley supported on different platforms. It looks attractive and has a wide selection. To use them as 'fake" image, you simply have to create a label and set its text to the desired emojico's code. You adjust the size by changing the font size. In this app, the search result screen make use of two emojicons.

iconLabel.text = "\u{e403}"

When breach is found, it shows the sad face emojicon.  As in the following image

Image 15

iconLabel.text = "\u{e415}"

When no breach is found, it shows the smiley face emojicon.  As in the following image

Image 16

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)