{
  "name": "Authentication",
  "description": "Let's users log in, log out, reset password, and kick people out of the app if not logged in",
  "workflow": "https://content.screencast.com/users/KyleGraham/folders/Jing/media/a092ff21-0757-43bb-86d3-d0626c1e4599/00000003.png",
  "user_stories": [
    "As a user, i want to be able to login to my app so that I can access my campaign",
    "As a user, I want to be able to log out when I'm done",
    "As a user, I want to be able to safely reset my password so that I still have access if I have forgotten my credentials",
    "As a user, if my account validation fails during log in, don't let me through",
    "As a user, I want to remain logged in indefinitely, until I log out to save me from logging in all the time",
    "As a stakeholder, I want to show users ads before they log in and after logging out",
    "As a developer, I want other developers to have access to the lifecycle of each component i make (methods, props, states, render response)",
    "As a user, when I log in, I want to be dynamically routed to the appropriate destination based on my account status"
  ],
  "test_plan": [
    {
      "scenario": "Submits correct credentials",
      "expected": "Successfully routed to dashboard",
      "automated": "Yes",
      "type": "End To End"
    },
    {
      "scenario": "Submits incorrect credentials",
      "expected": "Successfully routed to dashboard",
      "automated": "Yes",
      "type": "End To End"
    },
    {
      "scenario": "Forgets their password",
      "expected": "Error message is displayed",
      "automated": "Yes",
      "type": "End To End"
    },
    {
      "scenario": "Clicks (in email) to reset their password",
      "expected": "New page with email text field & reset button. If clicked it emails the user a secure reset link/token that expires in 30 minutes",
      "automated": "Yes",
      "type": "End To End"
    },
    {
      "scenario": "Changes their password",
      "expected": "Checks if > 30m.  If it is, show \"expired\" message.  If not, show password, confirm password, & submit form button",
      "automated": "Yes",
      "type": "End To End"
    },
    {
      "scenario": "Types in an invalid password",
      "expected": "Repeat 30m check.  If passes, update in db & route to login page with a success message",
      "automated": "Yes",
      "type": "End To End"
    },
    {
      "scenario": "Clicks [Log out] button",
      "expected": "If password entered doesn't meet criteria, show failed message and disable submit button until passes",
      "automated": "Yes",
      "type": "End To End"
    },
    {
      "scenario": "Revisits after Logging in",
      "expected": "Removes all user tokens/cookies & routes user to new pae with \"thank you for visiting\" message",
      "automated": "Yes",
      "type": "End To End"
    },
    {
      "scenario": "Clicks [back to login] link",
      "expected": "Allowed entry without forcing them to re-login",
      "automated": "Yes",
      "type": "End To End"
    },
    {
      "scenario": "Multiple failed log in attempts",
      "expected": "Routes user back to login page",
      "automated": "Yes",
      "type": "End To End"
    },
    {
      "scenario": "Unexpected country",
      "expected": "N/A",
      "automated": "Yes",
      "type": "End To End"
    },
    {
      "scenario": "Rapid attempts to log in",
      "expected": "N/A",
      "automated": "Yes",
      "type": "End To End"
    }
  ],
  "screens": "https://content.screencast.com/users/KyleGraham/folders/Jing/media/3b4ba161-5221-4cae-9129-1c3457984040/00000002.png",
  "components": [
    {
      "type": "Layouts",
      "name": "AuthLayout",
      "description": "Shows an ad on the left and auth stuff on the right",
      "behaviors": "Can show an ad and auth block on right",
      "exists": "No",
      "package": "No",
      "schema": [
        {
          "schema_type": "prop",
          "name": "children",
          "required": false,
          "public": true,
          "type": "elements",
          "description": "Show inner content"
        },
        {
          "schema_type": "prop",
          "name": "adComponent",
          "required": false,
          "public": true,
          "type": "element",
          "description": "Takes a component as an input that shows an ad (usually <Announcement />"
        },
        {
          "schema_type": "prop",
          "name": "authComponent",
          "required": true,
          "public": true,
          "type": "element",
          "description": "Takes a component as an input intended to show a authoriation component (like a registration form, login form, or reset form)"
        }
      ],
      "tests": [
        {
          "it": "Provided component is in the dom when provided",
          "type": "Integration"
        },
        {
          "it": "Provided component is in the dom when provided",
          "type": "Integration"
        }
      ]
    },
    {
      "type": "Layouts",
      "name": "MessageLayout",
      "description": "A big page with inner custom content",
      "behaviors": "Shows inner content inside",
      "exists": "No",
      "package": "No",
      "schema": [
        {
          "schema_type": "prop",
          "name": "children",
          "required": true,
          "public": true,
          "type": "elements",
          "description": "Shows custom inner content (e.g. logo & message) & shows children when availabl"
        }
      ],
      "tests": [
        {
          "it": "Fails with no children provided",
          "type": "Integration"
        }
      ]
    },
    {
      "type": "Containers",
      "name": "LoginContainer",
      "description": "Page using AuthLayout to show login features",
      "behaviors": "Uses AuthLayout to show an ad from the db & login features.  Also triggers a verification script on login",
      "exists": "No",
      "package": "No",
      "schema": [
        {
          "schema_type": "prop",
          "name": "children",
          "required": false,
          "public": true,
          "type": "elements",
          "description": "Show inner content"
        },
        {
          "schema_type": "method",
          "name": "handleVerify(email: string, password: string): Boolean",
          "required": true,
          "public": true,
          "type": "function",
          "description": "Calls the verification API using the provided credentials.  Responds with the user payload (if successful) and an error code + message explaining the failure if any"
        },
        {
          "schema_type": "method",
          "name": "handleForgotPassword()",
          "required": true,
          "public": true,
          "type": "function",
          "description": "Routes to the forgot password page"
        },
        {
          "schema_type": "method",
          "name": "handleValidate(email: string, password: string): Boolean",
          "required": true,
          "public": true,
          "type": "function",
          "description": "Calls the email & password validation API"
        }
      ],
      "tests": [
        {
          "it": "Should call the appropriate API when the handleVerify method is called",
          "type": "Unit"
        },
        {
          "it": "Should navigate to the 'forgot password' page when the handleForgotPassword method is called",
          "type": "Integration"
        },
        {
          "it": "Should call the appropriate API when the handleValidate method is called",
          "type": "Unit"
        }
      ]
    },
    {
      "type": "Containers",
      "name": "ForgotContainer",
      "description": "Page with MessageLayout & custom content",
      "behaviors": "Uses MessageLayout to show the logo & content.  Uses the message component to show reset message.  A button on the page triggers an email string validation and then sends a reset email with a link (that will expire) to the user.",
      "exists": "No",
      "package": "No",
      "schema": [
        {
          "schema_type": "prop",
          "name": "children",
          "required": false,
          "public": true,
          "type": "elements",
          "description": "Show inner content"
        },
        {
          "schema_type": "method",
          "name": "handleSendResetLink(email: string, tokenExpirationDate?: Date): Boolean ",
          "required": true,
          "public": true,
          "type": "function",
          "description": "Calls the reset password API using the provided email address. tokenExpiration tells the server how long the link should remain valid"
        },
        {
          "schema_type": "method",
          "name": "handleEmailValidation(email: string): Boolean",
          "required": true,
          "public": true,
          "type": "function",
          "description": "Calls the email & password validation API"
        }
      ],
      "tests": [
        {
          "it": "Should call the verification API when the handleVerify method is called",
          "type": "Unit"
        },
        {
          "it": "Should call the email & passowrd validation API when the handleEmailValidation method is called",
          "type": "Unit"
        }
      ]
    },
    {
      "type": "Containers",
      "name": "ResetContainer",
      "description": "Page using AuthLayout & reset password content",
      "behaviors": "Uses AuthLayout to show reset form content, which triggers the reset user api when button on page is clicked",
      "exists": "No",
      "package": "No",
      "schema": [
        {
          "schema_type": "prop",
          "name": "children",
          "required": false,
          "public": true,
          "type": "elements",
          "description": "Show inner content"
        },
        {
          "schema_type": "method",
          "name": "handlePasswordReset(email: string, password: string): Boolean",
          "required": true,
          "public": true,
          "type": "function",
          "description": "Calls the password reset API"
        },
        {
          "schema_type": "method",
          "name": "handleValidate(email: string, password: string): Boolean",
          "required": true,
          "public": true,
          "type": "function",
          "description": "Calls the email & password validation API"
        },
        {
          "schema_type": "method",
          "name": "handlePasswordConfirmation(password1:string, password2: string): Boolean",
          "required": true,
          "public": true,
          "type": "function",
          "description": "Checks if the passwords match (calls the api)"
        }
      ],
      "tests": [
        {
          "it": "Calls the password reset API when the handlePasswordReset method is called",
          "type": "Unit"
        },
        {
          "it": "Calls the email & password validation API when the handleValidate method is called",
          "type": "Unit"
        },
        {
          "it": "Checks if the passwords match (calls the api) when the handlePasswordConfirmation method is called",
          "type": "Unit"
        }
      ]
    },
    {
      "type": "Containers",
      "name": "ExpiredContainer",
      "description": "Page that uses MessageLayout to show an expired message & default logo",
      "behaviors": "Uses MessageLayout & shows an expired message & default logo",
      "exists": "No",
      "package": "No",
      "schema": [
        {
          "schema_type": "prop",
          "name": "children",
          "required": true,
          "public": true,
          "type": "elements",
          "description": "Children (<MessageLayout />)"
        }
      ],
      "tests": []
    },
    {
      "type": "Containers",
      "name": "LogoutContainer",
      "description": "Page that uses the MessageLayout template to show a custom log out message & logo",
      "behaviors": "Uses MessageLayout & shows logout confirmation message & default logo",
      "exists": "No",
      "package": "No",
      "schema": [
        {
          "schema_type": "prop",
          "name": "children",
          "required": true,
          "public": true,
          "type": "elements",
          "description": "Description for children"
        },
        {
          "schema_type": "prop",
          "name": "logo",
          "required": true,
          "public": true,
          "type": "enum[element, string]",
          "description": "Shows the logo (<Logo />)"
        }
      ],
      "tests": []
    },
    {
      "type": "Components",
      "name": "Announcement",
      "description": "Shows an ad from the database",
      "behaviors": "Shows an image, or custom component",
      "exists": "No",
      "package": "Yes",
      "schema": [
        {
          "schema_type": "prop",
          "name": "children",
          "required": true,
          "public": true,
          "type": "elements",
          "description": "Description for children"
        },
        {
          "schema_type": "prop",
          "name": "image",
          "required": true,
          "public": true,
          "type": "string",
          "description": "The url of the image to display"
        },
        {
          "schema_type": "prop",
          "name": "componentOverride",
          "required": false,
          "public": true,
          "type": "string",
          "description": "A custom component to display for the announcment; Overrides the displaying of the image url, the message, & the link"
        },
        {
          "schema_type": "prop",
          "name": "message",
          "required": true,
          "public": true,
          "type": "string",
          "description": "The message of the announcement"
        },
        {
          "schema_type": "prop",
          "name": "type",
          "required": true,
          "public": true,
          "type": "enum [ BANNER ]",
          "description": "The type of message"
        },
        {
          "schema_type": "prop",
          "name": "size",
          "required": true,
          "public": true,
          "type": "enum [ SMALL, MEDIUM, LARGE ]",
          "description": "The size of the message.  Takes common ad size formats"
        },
        {
          "schema_type": "prop",
          "name": "href",
          "required": true,
          "public": true,
          "type": "string",
          "description": "The link to send people to if the message is clicked"
        },
        {
          "schema_type": "prop",
          "name": "onClick",
          "required": true,
          "public": true,
          "type": "function",
          "description": "Triggered when the message is clicked"
        }
      ],
      "tests": [
        {
          "it": "Shows the image when rendered",
          "type": "Integration"
        },
        {
          "it": "Shows the provided message when available (and no override component",
          "type": "Integration"
        },
        {
          "it": "Does not let the link work when a component override is present",
          "type": "Integration"
        },
        {
          "it": "Allows for the announcement to be a clickable link when one is present (and no override",
          "type": "Integration"
        },
        {
          "it": "Takes the user to the provided href destination when clicked",
          "type": "Integration"
        },
        {
          "it": "Does not show the image when a component override is present",
          "type": "Integration"
        },
        {
          "it": "Does not show the message when a component override is present",
          "type": "Integration"
        },
        {
          "it": "Shows the icon chosen",
          "type": "Integration"
        },
        {
          "it": "Shows a default icon when provided icon type does not match one of the available types",
          "type": "Integration"
        },
        {
          "it": "Shows the announcement based on the size requested",
          "type": "Integration"
        }
      ]
    },
    {
      "type": "Components",
      "name": "Message",
      "description": "Shows a custom error, warning, info message",
      "behaviors": "Displays a message at the top, inline, bottom, with a provided animation style, icon, message, & default number of seconds to expire.",
      "exists": "No",
      "package": "Yes",
      "schema": [
        {
          "schema_type": "prop",
          "name": "children",
          "required": false,
          "public": true,
          "type": "elements",
          "description": "Show inner content"
        },
        {
          "schema_type": "prop",
          "name": "message",
          "required": true,
          "public": true,
          "type": "string",
          "description": "The actual message string to display"
        },
        {
          "schema_type": "prop",
          "name": "type",
          "required": true,
          "public": true,
          "type": "enum [ERROR, EXPIRED, SUCCESS, FAILED]",
          "description": "Indicates the type of message it is, displaying an associated icon"
        },
        {
          "schema_type": "prop",
          "name": "hideAfterNSeconds",
          "required": true,
          "public": true,
          "type": "number",
          "description": "Number of seconds before triggering the hideMessage() method."
        },
        {
          "schema_type": "prop",
          "name": "animationStyle",
          "required": true,
          "public": true,
          "type": "enum []",
          "description": "Options for presenting the message in a variety of entrance animations"
        },
        {
          "schema_type": "prop",
          "name": "position",
          "required": true,
          "public": true,
          "type": "enum []",
          "description": "Options for presenting the message in a variety of positions"
        },
        {
          "schema_type": "method",
          "name": "showMessage()",
          "required": true,
          "public": true,
          "type": "function",
          "description": "Displays the message"
        },
        {
          "schema_type": "method",
          "name": "hideMessage()",
          "required": true,
          "public": true,
          "type": "function",
          "description": "Hides the message"
        }
      ],
      "tests": [
        {
          "it": "Should display a message when one is provided",
          "type": "Unit"
        },
        {
          "it": "Should display nothing if no message is provided",
          "type": "Unit"
        },
        {
          "it": "Should show the appropriate message icon",
          "type": "Unit"
        },
        {
          "it": "Should allow the message to disappear after N provided seconds",
          "type": "Unit"
        },
        {
          "it": "Should make sure the Message does NOT disappear at all if -1 seconds is provided",
          "type": "Unit"
        },
        {
          "it": "Should display the message as an animation if an animation style is provi",
          "type": "Unit"
        },
        {
          "it": "Should display the message in the specified posit",
          "type": "Unit"
        },
        {
          "it": "Should display the message in a default position (inline) if no position is specified",
          "type": "Unit"
        },
        {
          "it": "Should show the message when showMessage() is triggered",
          "type": "Unit"
        },
        {
          "it": "Hide the message when hideMessage() is triggered",
          "type": "Unit"
        }
      ]
    },
    {
      "type": "Components",
      "name": "Typography",
      "description": "-",
      "behaviors": "",
      "exists": "Yes",
      "package": "Yes"
    },
    {
      "type": "Components",
      "name": "Link",
      "description": "-",
      "behaviors": "",
      "exists": "Yes",
      "package": "Yes"
    },
    {
      "type": "Components",
      "name": "Logo",
      "description": "Shows a custom logo",
      "behaviors": "Will show the default logo provided by the theme.  Logo displayed also be overridden.  Can also show logos in 3 different sizes",
      "exists": "No",
      "package": "Yes",
      "schema": [
        {
          "schema_type": "prop",
          "name": "children",
          "required": false,
          "public": true,
          "type": "elements",
          "description": "Show inner content"
        },
        {
          "schema_type": "prop",
          "name": "size",
          "required": false,
          "public": true,
          "type": "enum [SMALL, MEDIUM, LARGE]",
          "description": "Shows the logo in one of the three formats"
        },
        {
          "schema_type": "prop",
          "name": "imageOverride",
          "required": false,
          "public": true,
          "type": "element",
          "description": "Overrides the logo that the parent theme provides"
        }
      ],
      "tests": [
        {
          "it": "Shows the logo from the theme if the imageOverride option is not provided",
          "type": "UNIT"
        },
        {
          "it": "Overrides the theme logo if an imageOverride prop is provided",
          "type": "UNIT"
        },
        {
          "it": "Shows a small logo when one is provided",
          "type": "UNIT"
        },
        {
          "it": "Shows a medium-sized logo when one is provided",
          "type": "UNIT"
        },
        {
          "it": "Shows a large-sized logo when one is provided",
          "type": "UNIT"
        },
        {
          "it": "Shows a medium-sized logo if no size is specified",
          "type": "UNIT"
        },
        {
          "it": "Shows a medium-sized logo if an invalid size is specified",
          "type": "UNIT"
        }
      ]
    },
    {
      "type": "Components",
      "name": "Button",
      "description": "-",
      "behaviors": "",
      "exists": "Yes",
      "package": "Yes"
    },
    {
      "type": "Components",
      "name": "FormInput",
      "description": "-",
      "behaviors": "",
      "exists": "Yes",
      "package": "Yes"
    }
  ],
  "database": [
    {
      "name": "User",
      "schema": [
        {
          "name": "id",
          "type": "ID!"
        },
        {
          "name": "password",
          "type": "string"
        },
        {
          "name": "email",
          "type": "string"
        },
        {
          "name": "country",
          "type": "string"
        },
        {
          "name": "accountPlans",
          "type": "AccountPlans"
        }
      ]
    },
    {
      "name": "Account Plan",
      "schema": [
        {
          "name": "id",
          "type": "ID!"
        },
        {
          "name": "users",
          "type": "User"
        },
        {
          "name": "name",
          "type": "string"
        }
      ]
    },
    {
      "name": "Logs",
      "schema": [
        {
          "name": "id",
          "type": "ID!"
        },
        {
          "name": "userId",
          "type": "User"
        },
        {
          "name": "payload",
          "type": "JSON"
        },
        {
          "name": "dateCreated",
          "type": "Date"
        }
      ]
    },
    {
      "name": "Announcement",
      "schema": [
        {
          "name": "type",
          "type": "enum [LOGIN, DASHBOARD]"
        },
        {
          "name": "id",
          "type": "ID!"
        },
        {
          "name": "message",
          "type": "string"
        },
        {
          "name": "image",
          "type": "string"
        },
        {
          "name": "linksTo",
          "type": "string"
        },
        {
          "name": "priority",
          "type": "number"
        },
        {
          "name": "expires",
          "type": "Date"
        },
        {
          "name": "enabled",
          "type": "Boolean"
        },
        {
          "name": "tag",
          "type": "string"
        }
      ]
    }
  ],
  "api": [
    {
      "name": "Auth",
      "schema": [
        {
          "schema_type": "field",
          "name": "userEmail",
          "type": "string",
          "public": false
        },
        {
          "schema_type": "field",
          "name": "userId",
          "type": "string",
          "public": false
        },
        {
          "schema_type": "field",
          "name": "resetToken",
          "type": "string",
          "public": true
        },
        {
          "schema_type": "field",
          "name": "userPassword",
          "type": "string",
          "public": false
        },
        {
          "schema_type": "method",
          "name": "verifyByEmail(userEmail: string): Boolean",
          "type": "function",
          "public": true
        },
        {
          "schema_type": "method",
          "name": "verifyByPassword(userEmail: string, userPassword: string): Boolean",
          "type": "function",
          "public": true
        },
        {
          "schema_type": "method",
          "name": "verifyBYUserId(userId: string): Boolean",
          "type": "function",
          "public": true
        },
        {
          "schema_type": "method",
          "name": "resetCredentials(userId: string, userPassword: string): Object",
          "type": "function",
          "public": true
        },
        {
          "schema_type": "method",
          "name": "sendResetLink(userEmail: string, tokenExpirationDate: Date): Object",
          "type": "function",
          "public": true
        },
        {
          "schema_type": "method",
          "name": "getLoginAttempts(userId: ID!, dateStart: Date, dateEnd: Date): Object",
          "type": "function",
          "public": true
        },
        {
          "schema_type": "method",
          "name": "checkResetLinkExpiration(token: string): Boolean",
          "type": "function",
          "public": true
        }
      ],
      "tests": [
        {
          "it": "Should disable button if invalid fields (email)",
          "type": "Integration"
        },
        {
          "it": "Should disable button if invalid fields (passowrd)",
          "type": "Integration"
        },
        {
          "it": "Should enable button if valid fields",
          "type": "Integration"
        },
        {
          "it": "Should return true if email exists (verifiy email)",
          "type": "Unit"
        },
        {
          "it": "Should return false if email not exists (verify email)",
          "type": "Unit"
        },
        {
          "it": "Should return true if email/pass match (verifyByPass)",
          "type": "Unit"
        },
        {
          "it": "Should return false if email/pass match not found (verifyByPass)",
          "type": "Unit"
        },
        {
          "it": "Should return true if userId found",
          "type": "Unit"
        },
        {
          "it": "Should return false if userId not fond",
          "type": "Unit"
        },
        {
          "it": "Should call db update when rest credentials",
          "type": "Unit"
        },
        {
          "it": "Should call validateEmail & confirmPass when reset credentials",
          "type": "Unit"
        },
        {
          "it": "Should receive true response from db mock",
          "type": "Unit"
        },
        {
          "it": "loginCount should increment on successful og in",
          "type": "Unit"
        },
        {
          "it": "getLoginAttempts should be called when successful login",
          "type": "Unit"
        },
        {
          "it": "getLoginAttempts should not be called when login is NOT successful",
          "type": "Unit"
        },
        {
          "it": "getLoginAttempts response should be of specific shape",
          "type": "Unit"
        },
        {
          "it": "getLoginAttempts should respond with values within date filter range",
          "type": "Unit"
        },
        {
          "it": "emailMock is called once when setResetLink is called",
          "type": "Unit"
        },
        {
          "it": "Should return false if expiration date on token has expired",
          "type": "Unit"
        },
        {
          "it": "Should still set token if no expiration date is given",
          "type": "Unit"
        },
        {
          "it": "Should fail if date of expiration is invalid format",
          "type": "Unit"
        }
      ]
    },
    {
      "name": "Session",
      "schema": [
        {
          "schema_type": "field",
          "name": "token",
          "type": "string",
          "public": false
        },
        {
          "schema_type": "field",
          "name": "removetoken(token): Boolean",
          "type": "function",
          "public": true
        },
        {
          "schema_type": "field",
          "name": "getToken(): string",
          "type": "function",
          "public": true
        },
        {
          "schema_type": "field",
          "name": "setToken(token:string): void",
          "type": "function",
          "public": true
        }
      ],
      "tests": [
        {
          "it": "Token does not exist",
          "type": "Unit"
        },
        {
          "it": "Token is retrieved from localStorage",
          "type": "Unit"
        },
        {
          "it": "Token retrieval failure when not logged in",
          "type": "Unit"
        },
        {
          "it": "Token does not exist, now it does",
          "type": "Unit"
        },
        {
          "it": "Operation (requiring token) should fail if it does not exist",
          "type": "Integration"
        },
        {
          "it": "Operation (requiring token) should succeed if it does",
          "type": "Integration"
        }
      ]
    },
    {
      "name": "Utils",
      "schema": [
        {
          "schema_type": "field",
          "name": "confirmPasswordsMatch(password1:string, password2:string): Boolean",
          "type": "function",
          "public": true
        },
        {
          "schema_type": "field",
          "name": "validateEmail(email: string): Boolean",
          "type": "function",
          "public": true
        },
        {
          "schema_type": "field",
          "name": "validatePassword(password: string, criteria?: object): Boolean",
          "type": "function",
          "public": true
        }
      ],
      "tests": [
        {
          "it": "Return true if password values match",
          "type": "Unit"
        },
        {
          "it": "Return false if they don't",
          "type": "Unit"
        },
        {
          "it": "Return false if password verification fails",
          "type": "Unit"
        },
        {
          "it": "Make sure password verification is called",
          "type": "Unit"
        },
        {
          "it": "Return true if email validation password",
          "type": "Unit"
        },
        {
          "it": "Return false if it doesn't",
          "type": "Unit"
        },
        {
          "it": "Return true if password validation passes (criteria 1)",
          "type": "Unit"
        },
        {
          "it": "Return true if password validation passes (criteria 2)",
          "type": "Unit"
        },
        {
          "it": "Return false if password validation fails",
          "type": "Unit"
        }
      ]
    }
  ]
}
