Skip to content

fewlinesco/connect-account

Repository files navigation

Connect.Account

Flows

Connect authentication flow

OAuth2 flow diagram

For more information, check the Authorization code flow documentation.

Identity commands and queries

Identity action flow

Set or Update user password

Set or Update user password

NPM scripts

Setup

Before being able to start the project locally, you should have a way to run Node.js applications.

We recommend you to install the latest version of asdf to do so.

You will need nodejs and yarn. These can be installed via the provided .tool-versions using asdf install.

Get required environment variables by copying .env.sample and if necessary tuning it to values relevant to your dev environment:

cp .env_vars.sample .env_vars
ln -s Procfile.dev Procfile

Source the file to get variables defined your environnement:

source .env_vars

The application also needs couple external tools to work properly: - hivemind - docker - docker-compose - nginx - mkcert

To install them on MacOS, you can run the following:

brew bundle install --file Brewfile

Then, install dependencies:

yarn install

HTTPS certificates

To allow TLS locally, we are using a nginx proxy. The proxy listens port 29704, and redirects — with the certificates — on port 29703.

You might need the HTTPS certificates on your localhost to allow cookies writing.

If you are using Firefox as your web browser, you’ll need nss alongside mkcert.

brew install nss

To generate certificates:

mkdir certs
(cd certs; mkcert connect-account.local)

Then, add the local FQDN in /etc/hosts:

echo "127.0.0.1 connect-account.local" | sudo tee -a /etc/hosts
echo "::1 connect-account.local" | sudo tee -a /etc/hosts

Development Process

hivemind # or overmind s

# In an other terminal tab
docker-compose up

Access your application on URL: https://connect-account.local:29704.

If you want to build and start the application, you should run the following:

yarn build && yarn start [-p 29703]

Database

We made the choice to use DynamoDB.

The users DynamoDB table is created automatically during the docker compose launch.

If you want to interact with the database, you can use the AWS CLI, of use one of the following npm scripts that use the SDK:

Name Description

db:create-users-table

Create the users table.

db:reset-users-table

Reset the users table.

db:delete-users-table

Delete the users table.

db:put-user

Put a user entry in the users table.

db:get-user-from-sub sub

Get the user entry the users table using its sub.

Testing

To run integration and unit tests :

yarn test

To run the e2e tests suite on local environment :

yarn test:e2e:local

Linting

We use a set of strict linting rules through TypeScript and ESLint. While TypeScript config is pretty standard, the ESLint one is mostly set with our own custom package, called @fewlines/eslint-config. You should read the documentation if you want the full power of the config while using VSCode.

Note that, contrary to errors, warnings do not break testing or app compilation.

You can manually lint, using:

yarn lint

or

yarn lint --fix

if you want to automatically fix linting issues.

Environment variables

Name Description

NEXT_PUBLIC_FEATURE_FLAG

Should be set to false. Used to access in development features.

CONNECT_ACCOUNT_PORT

Local port used to run the application.

CONNECT_ACCOUNT_SESSION_SALT

The password used to seal or access the cookie session. It needs to be at least 32 characters long.

CONNECT_ACCOUNT_HOSTNAME

Hostname of the account web application. This is not needed on Heroku for review environments.

DYNAMODB_REGION

Region of the AWS cluster.

DYNAMODB_ENDPOINT

URL of the AWS cluster where your DynamoDB instance run from.

DYNAMODB_ACCESS_KEY_ID

Access key ID used for production when your DB is hosted by AWS.

DYNAMODB_SECRET_ACCESS_KEY

Secret access key used for production when your DB is hosted by AWS.

DYNAMODB_TABLE_NAME

Name of the DynamoDB table. You can see this as the name of the DB, as tables are different from relational DB in the context of a DynamoDB.

CONNECT_MANAGEMENT_URL

URL used to fetch identities from the management GraphQL endpoint.

CONNECT_MANAGEMENT_API_KEY

API key used to access the management GraphQL endpoint.

CONNECT_PROVIDER_URL

URL used to start the connect oauth flow.

CONNECT_APPLICATION_CLIENT_ID

Client ID of the online service (e.g. internet website, application) that uses the Provider Authentication and Authorization service for its User.

CONNECT_APPLICATION_CLIENT_SECRET

Paired with the client ID, used to authenticate the Application from which the User intent to sign in.

CONNECT_APPLICATION_SCOPES

Represents the kind of user authorized information and actions that an Application is able to access on another Application.

CONNECT_OPEN_ID_CONFIGURATION_URL

URL used for the @fewlines/connect-client package to fetch the OpenID configuration.

CONNECT_REDIRECT_URI

URL used for the Connect authentication flow.

CONNECT_AUDIENCE

Name of the Application that identifies the recipients that the JWT is intended for.

CONNECT_JWT_ALGORITHM

Represents the kind of user authorized information and actions that an Application is able to access on another Application.

ACCOUNT_JWE_PRIVATE_KEY

The PEM formatted private key used to decrypt the JWE access token. (i.e. "-----BEGIN RSA PRIVATE KEY-----\nqewnjfb…​\n..")

IS_JWE_SIGNED

A boolean value that indicates if the JWE access token is signed or not.

SERVICE_NAME

Service name for Lightstep.

LIGHTSTEP_ACCESS_TOKEN

Your Lightstep access token.

FWL_TRACING_COLLECTORS

Config of the collectors used for tracing purposes.

SENTRY_AUTH_TOKEN

Auth token required to contact Sentry.

NEXT_PUBLIC_SENTRY_ENVIRONMENT

Should be set to development. Used to prevent sentry report when working with a built version, locally.

MEMCACHED_CLIENT_PASSWORD

Password used to initiate the Memcached Client for rate limiting.

MEMCACHED_CLIENT_USERNAME

Username used to initiate the Memcached Client for rate limiting.

MEMCACHED_CLIENT_SERVERS

Address of the Memcached Client servers.

CONNECT_TEST_ACCOUNT_EMAIL

Email of the Connect account that will be used for e2e tests.

CONNECT_TEST_ACCOUNT_PASSWORD

Password of the Connect account that will be used for e2e tests.

CONNECT_TEST_ACCOUNT_SUB

Sub of the Connect account that will be used for e2e tests.

CONNECT_TEST_ACCOUNT_URL

URL of the Connect.Account application that will be used for e2e tests.

CONNECT_ACCOUNT_TEST_NO_PROFILE

Set to true if you need to do a Lighthouse audit on /profile/user-profile/new.

CONNECT_PROFILE_URL

URL of Connect.Profile server.

Connect login implementation

To understand the flow of connect-account, you should read the connect documentation.

@fewlines/connect-client

To understand the abstraction added by the @fewlines/connect-client, please read the documentation.

Source Code Organization

We are using the NextJS folder architecture (i.e. /pages) to utilize its router, out of the box. For more information, please refer to the documentation.

We are also using the Command Query Responsibility Segregation(CQRS) pattern to separate queries from mutations. They are located in the queries/ and command/ folder.

.circleci/

  • config.yml: Config file for CircleCI.

.github/

  • workflows/: GitHub Actions used to run tests during CI/CD process flow.

  • /dependabot.yml: Config file for dependabot.

  • /PULL_REQUEST_TEMPLATE.md: Template used when opening a pull request on GitHub.

bin/

  • dynamodb/: Scripts to interact with your local DynamoDB instead of the AWS CLI, which requires sensitives admin credentials.

  • e2e/: Scripts related to e2e tests, used in our CI/CD pipeline or in local environment.

doc/

PlantUML diagrams and their respective built image.

public/

Favicons for various OS.

src/

  • @types/: Type declaration used in multiple places.

  • commands/: Write (e.g. POST) database actions.

  • components/: React functional components used to render.

  • configs/:

    • config-variables.ts: Entry point used to verify env vars sourcing, and prevent the app to run if forgotten.

    • db-client.ts: Singleton of the DynamoDB client.

    • intl.ts: File regrouping all the intl object, which are instances to store the cache of all Intl.* APIs, configurations, compiled messages and such.

    • logger.ts: Singleton of the logger client provided by @fwl/web.

    • oauth2-client.ts: Singleton of the OAuth2 Client provided by @fewlines/connect-client.

    • profile-client.ts: Singleton of the Profile Client provided by Connect Profile.

    • rate-limiting-config.ts: Config used for the rate limiting middleware provided by @fwl/web.

    • tracer.ts: Singleton of the tracer client provided by @fwl/web.

  • design-system/:

    • global-style.css: Global style CSS rules and tailwindcss built-in rules.

  • errors/:

    • errors.ts: List of exceptions related to Connect.Account.

    • web-errors.ts: List of exceptions related to @fwl/web WebErrors.

  • middlewares/: Reusable wrappers to add various features to server side actions.

  • pages/: NextJS router.

  • queries/: Read (i.e. GET) database actions.

  • utils/: Small snippets/functions used multiple times throughout the application.

  • workflows/: workflows used in multiples places.

tests/

  • config/: Config files for the different libraries used to test, and import fix files.

  • e2e/: Centralized e2e test files.

  • mocks/: Centralized mocked data used in different test files.

  • pages/: Centralized Next.js pages integration tests.

  • unit/: Centralized components and functions unit tests.

config files

  • .dockerignore: Ignored files for the Docker image build process.

  • .env_vars.sample: Environment variables template file. You will need to copy this file, remove the .sample part, and add the correct values.

  • .gitignore: GitHub config file used to prevent the pushing of certain files.

  • .tool-version: asdf config file.

  • app.json: Building instructions for Heroku.

  • assets.d.ts: Type declaration allowing the import of assets in TypeScript files.

  • Brewfile: Tools needed that will be installed via Brew for MacOS users.

  • connect-profile-openapi.yml: OpenAPI file of Connect Profile.

  • docker-compose.yml: Instructions to launch DynamoDB and the observability tools.

  • Dockerfile: Instructions for Docker image build process.

  • lighthouserc.js: Instructions for Lighthouse audits.

  • next-env.d.ts: Adds NextJS types globally.

  • next.config.js: Extended webpack compiler config used by NextJS.

  • nginx.conf: Local proxy configuration.

  • otel-collector-config: Configuration of the OpenTelemetry collector, which is used to receive, process and export tracing data.

  • package.json: We use this file, as much as possible as a centralized config file for various packages, like ESLint, Jest or Babel.

  • Procfile.dev: Instructions for Hivemind/Overmind.

  • README.adoc: Connect.Account documentation, written in AsciiDoc.

  • sentry.client.config.js: Client side sentry config file.

  • sentry.properties: Variables used by Sentry to connect the app with its Sentry instance.

  • sentry.server.config.js: Server side sentry config file.

  • tsconfig.json: TypeScript compiler options.

  • yarn.lock: Package manager instructions.

TypeScript

Type declaration

The rule we follow is that, if a declared type is only used in one file, we locate it in said file. Otherwise, we move it in its own file, under @types/. The exceptions to this rule are next-env.d.ts and assets.d.ts as NextJS required them to be located at the root of the repository.

Typing React components

We chose to type React component like so:

import React from "react";

// Without props.
const Foo: React.FC = () => {
  return <React.Fragment />;
};

// With props.
const Bar: React.FC<{ foo: "bar" }> = ({ foo }) => {
  return <div>{foo}</div>;
};

If you are not familiar with TypeScript generic types, please take a look at the documentation.

Components

Next’s Link component requires its child to be an anchor tag. To lighten the JSX, we made a custom component called NeutralLink that provides the anchor tag.

Social Identities

When adding a new supported Social Identity to the application, remember to add the corresponding icon as SVG.

Dev buttons

To help with repetitive tasks during the development of a feature, or to help debugging, we have added a set dev buttons to trigger various action on press. You can find them inside src/components/dev-buttons. To enable them, you will have to render <DevButtons/> inside _app.tsx, like this:

import { DevButtons } from "@src/components/dev-buttons/dev-buttons";

const AccountApp: React.FC = ({ children }) => {
  return (
    <SSRProvider>
      <ThemeProvider theme={theme}>
        <Head>
          <meta
            name="viewport"
            content="initial-scale=1.0, width=device-width"
          />
          <title>Connect Account</title>
        </Head>
        <GlobalStyle />
        <AlertMessages />
        <SWRConfig
          value={{
            // ...
          }}
        >
          {children}
        </SWRConfig>
        <DevButtons/>
      </ThemeProvider>
    </SSRProvider>
  );
};

We have also added a test to ensure that the component is not being rendered in review/production env, so don’t forget to remove <DevButtons/> when you are done. If you need to add new buttons, feel free to do so.

Style

Global style

The global-style CSS sheet, found in /src/design-system/global-style.css, is used to import built-in tailwincss rules and remove undesired style and behavior found in HTML.

Note that we chose to set the global font size to 62.5%. This font size means that '1rem' is exactly equal to '10px', which makes setting REM values very simple.

SVG

If you want to use SVGs in your application, we recommend to render them as a React component, instead of importing the file:

import React from 'react'

const SVGIcon: React.FC = () => {
  return (
    <svg>
      // ...
    </svg>
  )
};

export { SVGIcon };

When SVG are monochromatic (i.e. icons) and to improve components reusability, we replace the fill attribute value of tags composing SVG, such as path, with the CSS variable currentColor instead of hardcoded color codes. This way, the SVG will be rendered with the color inherited from its parent.

import React from 'react';

const CrossIcon: React.FC = () => {
  return (
    <svg>
      <path fill="currentColor">...</path>
      // ...
    </svg>
  )
};

When you use Figma to export SVG, be sure to export the upper component (i.e. name 40x40).

Also remember to add a <title/> JSX tag under the <svg/> tag for accessibility.

Database

We are using DynamoDB as our persistence layer. Its K/V structure allows fast performances, but you’ll need to be aware of some specificities, like being unable to update an existing value.

Cookies

We store the access_token and sub in what we call a UserCookie. It is a sealed object living in the user’s browser. We also store alert messages in the user cookies, with no sealing.

Accessibility

We want our application to be accessible. To do so, besides following good practices and standards, we chose to use @react-aria external library to ease the accessibility implementation.

i18n

We made the choice to use react-intl, and to localize our strings to English and French.

Localized string object

Localized strings are located inside en.ts and fr.ts. The strings are organized following the router path they will called on. We then give the string an ID to be able to call them.

Note that the ID naming is still WIP.

Client side

To localize client side generated strings, you can use the useIntl hook like this:

import React from "react";
import { useIntl } from "react-intl";

const LocalizedString: React.FC = () => {
  const { formatMessage } = useIntl();

  return (
    <p>{formatMessage({ id: "stringID" })}</p>
  )
};

This will call the key’s value inside the router path’s key. If no value are found, the ID will be used as fallback.

Server side

For server side generated strings, we generate an intl object, and pass the needed strings (see intl.ts).

import { createIntl, createIntlCache } from "@formatjs/intl";

import * as locales from "@content/locales";

const cache = createIntlCache();
const enIntl = createIntl(
  {
    locale: "en",
    messages: { ...locales["en"].alertMessages },
  },
  cache,
);

export "enIntl"

Testing

Unit testing

For unit testing, we are using Jest.

User interaction with components

We are using Testing Library to test components behavior regarding user interactions.

User flow

We are using Taiko to test our workflows.

CI/CD pipeline

To automate our test processes, we use CircleCI, which allow us to run our test suites on new commit pushed to each pull request.

Several flows (or jobs) are executed in this context and orchestrated in workflows inside the config.yml file:

  • lint-and-tests: runs the code linting and the integration/unit tests.

  • e2e-tests: runs the e2e test suite on a deployed Connect.Account version.

  • lighthouse: runs the Lighthouse audits on a deployed Connect.Account version.

  • promote-to-production: used to automate the production deployment from staging when the test suite is successful.

Monitoring

We are using Sentry to monitor production and review app’s exceptions raised.

We’re using Sentry Next.js SDK and have added a wrapper around the withSentry middleware to be able to use is it with SSR.

Documentation

Diagrams

We are using PlantUML to make the sequences diagram.

To compile your PlantUML code, you can run the following:

cat name-of-the-file.uml | docker run  --rm -i fewlines/developers-portal-diagram-generator plantuml -Djava.awt.headless=true -p -tjpg > name-of-the-file.jpg