From 8b7964a140bf6f74b1c34f0f9cac4ac36f2d42eb Mon Sep 17 00:00:00 2001 From: dillon Date: Fri, 2 Oct 2015 15:52:31 -0700 Subject: [PATCH] add annotated source for phishing detection --- _examples/phishing-detection.html | 268 +++++++++++++++++------------- 1 file changed, 149 insertions(+), 119 deletions(-) diff --git a/_examples/phishing-detection.html b/_examples/phishing-detection.html index e98c513d5..1c6b520fb 100644 --- a/_examples/phishing-detection.html +++ b/_examples/phishing-detection.html @@ -2,13 +2,13 @@ title: "phishing-detection" description: "Finding and showing you a warning if an email is a possible phishing scam." assumed_experience: "No experience with React, Flux, Electron, or N1." -github: "todo-fix-this" +github: "https://github.com/nylas/N1/tree/master/examples/N1-Phishing-Detection" --- - Translation Plugin + Phishing Detection @@ -27,40 +27,12 @@ github: "todo-fix-this"
-

Translation Plugin

-

Last Revised: April 23, 2015 by Ben Gotow

-

TranslateButton is a simple React component that allows you to select -a language from a popup menu and translates draft text into that language.

+

Phishing Detection

+

This is a simple package to notify N1 users if an email is a potential +phishing scam.

-
-request = require 'request'
-
-{Utils,
- React,
- ComponentRegistry,
- DraftStore} = require 'nylas-exports'
-{Menu,
- RetinaImg,
- Popover} = require 'nylas-component-kit'
-
-YandexTranslationURL = 'https://translate.yandex.net/api/v1.5/tr.json/translate'
-YandexTranslationKey = 'trnsl.1.1.20150415T044616Z.24814c314120d022.0a339e2bc2d2337461a98d5ec9863fc46e42735e'
-YandexLanguages =
-  'English': 'en'
-  'Spanish': 'es'
-  'Russian': 'ru'
-  'Chinese': 'zh'
-  'French': 'fr'
-  'German': 'de'
-  'Italian': 'it'
-  'Japanese': 'ja'
-  'Portuguese': 'pt'
-  'Korean': 'ko'
-
-class TranslateButton extends React.Component
- @@ -70,11 +42,11 @@ YandexLanguages =
-

Adding a displayName makes debugging React easier

+

You can access N1 dependencies by requiring ‘nylas-exports’

-
  @displayName: 'TranslateButton'
+
{React,
@@ -85,14 +57,11 @@ YandexLanguages =
-

Since our button is being injected into the Composer Footer, -we receive the local id of the current draft as a prop (a read-only -property). Since our code depends on this prop, we mark it as a requirement.

+

The ComponentRegistry manages all React components in N1.

-
  @propTypes:
-    draftLocalId: React.PropTypes.string.isRequired
+
 ComponentRegistry,
@@ -103,29 +72,12 @@ property). Since our code depends on this prop, we mark it as a requirement.

-

The render method returns a React Virtual DOM element. This code looks -like HTML, but don’t be fooled. The CJSX preprocessor converts

-

<a href="http://facebook.github.io/react/">Hello!</a>

-

into Javascript objects which describe the HTML you want:

-

React.createElement('a', {href: 'http://facebook.github.io/react/'}, 'Hello!')

-

We’re rendering a Popover with a Menu inside. These components are part -of Edgehill’s standard nylas-component-kit library, and make it easy to build -interfaces that match the rest of Edgehill’s UI.

+

A Store is a Flux component which contains all business logic and data +models to be consumed by React components to render markup.

-
  render: =>
-    React.createElement(Popover, {"ref": "popover",  \
-             "className": "translate-language-picker pull-right",  \
-             "buttonComponent": (@_renderButton())},
-      React.createElement(Menu, {"items": ( Object.keys(YandexLanguages) ),  \
-            "itemKey": ( (item) -> item ),  \
-            "itemContent": ( (item) -> item ),  \
-            "onSelect": (@_onTranslate)
-            })
-    )
-
-
+
 MessageStore} = require 'nylas-exports'
@@ -136,20 +88,13 @@ interfaces that match the rest of Edgehill’s UI.

-

Helper method to render the button that will activate the popover. Using the -RetinaImg component makes it easy to display an image from our package. -RetinaImg will automatically chose the best image format for our display.

+

Notice that this file is main.cjsx rather than main.coffee. We use the +.cjsx filetype because we use the CJSX DSL to describe markup for React to +render. Without the CJSX, we could just name this file main.coffee instead.

-
  _renderButton: =>
-    React.createElement("button", {"className": "btn btn-toolbar"}, """
-      Translate
-""", React.createElement(RetinaImg, {"name": "toolbar-chevron.png"})
-    )
-
-  _onTranslate: (lang) =>
-    @refs.popover.close()
+
class PhishingIndicator extends React.Component
@@ -160,16 +105,11 @@ interfaces that match the rest of Edgehill’s UI.

-

Obtain the session for the current draft. The draft session provides us -the draft object and also manages saving changes to the local cache and -Nilas API as multiple parts of the application touch the draft.

+

Adding a @displayName to a React component helps for debugging.

-
    session = DraftStore.sessionForLocalId(@props.draftLocalId)
-    session.prepare().then =>
-      body = session.draft().body
-      bodyQuoteStart = Utils.quotedTextIndex(body)
+
  @displayName: 'PhishingIndicator'
@@ -180,21 +120,13 @@ Nilas API as multiple parts of the application touch the draft.

-

Identify the text we want to translate. We need to make sure we -don’t translate quoted text.

+

@propTypes is an object which validates the datatypes of properties that +this React component can receive.

-
      if bodyQuoteStart > 0
-        text = body.substr(0, bodyQuoteStart)
-      else
-        text = body
-
-      query =
-        key: YandexTranslationKey
-        lang: YandexLanguages[lang]
-        text: text
-        format: 'html'
+
  @propTypes:
+    thread: React.PropTypes.object.isRequired
@@ -205,13 +137,14 @@ don’t translate quoted text.

-

Use Node’s request library to perform the translation using the Yandex API.

+

A React component’s render method returns a virtual DOM element described +in CJSX. render is deterministic: with the same input, it will always +render the same output. Here, the input is provided by @isPhishingAttempt. +@state and @props are popular inputs as well.

-
      request {url: YandexTranslationURL, qs: query}, (error, resp, data) =>
-        return @_onError(error) unless resp.statusCode is 200
-        json = JSON.parse(data)
+
  render: =>
@@ -222,13 +155,11 @@ don’t translate quoted text.

-

The new text of the draft is our translated response, plus any quoted text -that we didn’t process.

+

Our inputs for the virtual DOM to render come from @isPhishingAttempt.

-
        translated = json.text.join('')
-        translated += body.substr(bodyQuoteStart) if bodyQuoteStart > 0
+
    [from, reply_to] = @isPhishingAttempt()
@@ -239,22 +170,15 @@ that we didn’t process.

-

To update the draft, we add the new body to it’s session. The session object -automatically marshalls changes to the database and ensures that others accessing -the same draft are notified of changes.

+

We add some more application logic to decide how to render.

-
        session.changes.add(body: translated)
-        session.changes.commit()
-
-  _onError: (error) =>
-    @refs.popover.close()
-    dialog = require('remote').require('dialog')
-    dialog.showErrorBox('Geolocation Failed', error.toString())
-
-
-module.exports =
+
    if from isnt null and reply_to isnt null
+      React.createElement("div", {"className": "phishingIndicator"},
+        React.createElement("b", null, "This message looks suspicious!"),
+        React.createElement("p", null, "It originates from ", (from), " but replies will go to ", (reply_to), ".")
+      )
@@ -265,14 +189,15 @@ the same draft are notified of changes.

-

Activate is called when the package is loaded. If your package previously -saved state using serialize it is provided.

+

If you don’t want a React component to render anything at all, then your +render method should return null or undefined.

-
  activate: (@state) ->
-    ComponentRegistry.register TranslateButton,
-      role: 'Composer:ActionButton'
+
    else
+      null
+
+  isPhishingAttempt: =>
@@ -283,6 +208,111 @@ saved state using serialize it is provided.

+

In this package, the MessageStore is the source of our data which will be +the input for the render function. @isPhishingAttempt is performing some +domain-specific application logic to prepare the data for render.

+ + + +
    message = MessageStore.items()[0]
+ + + + +
  • +
    + +
    + +
    +

    This package’s strategy to ascertain whether or not the email is a +phishing attempt boils down to checking the replyTo attributes on +Message models from MessageStore.

    + +
    + +
        if message.replyTo? and message.replyTo.length != 0
    + +
  • + + +
  • +
    + +
    + +
    +

    The from and replyTo attributes on Message models both refer to +arrays of Contact models, which in turn have email attributes.

    + +
    + +
          from = message.from[0].email
    +      reply_to = message.replyTo[0].email
    + +
  • + + +
  • +
    + +
    + +
    +

    This is our core logic for our whole package! If the from and +replyTo emails are different, then we want to show a phishing warning.

    + +
    + +
          if reply_to isnt from
    +          return [from, reply_to]
    +
    +    return [null, null];
    +
    +module.exports =
    + +
  • + + +
  • +
    + +
    + +
    +

    Activate is called when the package is loaded. If your package previously +saved state using serialize it is provided.

    + +
    + +
      activate: (@state) ->
    + +
  • + + +
  • +
    + +
    + +
    +

    This is a good time to tell the ComponentRegistry to insert our +React component into the 'MessageListHeaders' part of the application.

    + +
    + +
        ComponentRegistry.register PhishingIndicator,
    +      role: 'MessageListHeaders'
    + +
  • + + +
  • +
    + +
    + +

    Serialize is called when your package is about to be unmounted. You can return a state object that will be passed back to your package when it is re-activated.

    @@ -294,11 +324,11 @@ when it is re-activated.

  • -
  • +
  • - +

    This optional method is called when the window is shutting down, or when your package is being updated or disabled. If your package is @@ -308,7 +338,7 @@ subscribing to events, release them here.

      deactivate: ->
    -    ComponentRegistry.unregister(TranslateButton)
    + ComponentRegistry.unregister(PhishingIndicator)