diff --git a/.circleci/config.yml b/.circleci/config.yml index 203949e3..47059462 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,20 +3,23 @@ version: 2.1 jobs: build: docker: - - image: cypress/browsers:node12.13.0-chrome80-ff74 + - image: circleci/node:12.9.1-browsers steps: - checkout - restore_cache: - keys: - - dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }} + key: dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum "examples/cra-react-router/package.json" }}-{{ checksum "examples/users-api/package-lock.json" }} - run: npm ci + - run: npm run install:examples - save_cache: - key: dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }} + key: dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum "examples/cra-react-router/package.json" }}-{{ checksum "examples/users-api/package-lock.json" }} paths: - ~/.npm - ~/.cache - run: npm run build - run: npm test + # TODO: remove when facebook/create-react-app#8845 is shipped + - run: sed -i '/process.env.CI/ s/isInteractive [|]*//' examples/cra-react-router/node_modules/react-scripts/scripts/start.js + - run: npm run test:integration - run: npm run codecov - store_test_results: path: test-results diff --git a/.eslintrc.js b/.eslintrc.js index c370b53b..19dae4fe 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { rules: { '@typescript-eslint/camelcase': 'off', }, + ignorePatterns: ['examples/**'], overrides: [ { files: ['*.js'], diff --git a/.gitignore b/.gitignore index 26fb4d0e..930c9799 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,6 @@ dist .idea test-results + +cypress/screenshots +cypress/videos diff --git a/EXAMPLES.md b/EXAMPLES.md index 7b82e86f..0205f3e1 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -164,6 +164,7 @@ export default withAuthenticationRequired(Profile); ```js // use-api.js import { useEffect, useState } from 'react'; +import { useAuth0 } from '@auth0/auth0-react'; export const useApi = (url, options = {}) => { const { getAccessTokenSilently } = useAuth0(); diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..69cb7601 --- /dev/null +++ b/codecov.yml @@ -0,0 +1 @@ +comment: false diff --git a/cypress.json b/cypress.json new file mode 100644 index 00000000..e3cac04e --- /dev/null +++ b/cypress.json @@ -0,0 +1,9 @@ +{ + "baseUrl": "http://localhost:3000", + "chromeWebSecurity": false, + "viewportWidth": 1000, + "viewportHeight": 1000, + "fixturesFolder": false, + "pluginsFile": false, + "supportFile": false +} diff --git a/cypress/integration/smoke.test.ts b/cypress/integration/smoke.test.ts new file mode 100644 index 00000000..0cb36b21 --- /dev/null +++ b/cypress/integration/smoke.test.ts @@ -0,0 +1,39 @@ +const loginToAuth0 = (): void => { + cy.get('.auth0-lock-input-username .auth0-lock-input') + .clear() + .type('johnfoo+integration@gmail.com'); + cy.get('.auth0-lock-input-password .auth0-lock-input').clear().type('1234'); + cy.get('.auth0-lock-submit').click(); +}; + +describe('Smoke tests', () => { + it('do basic login and show user', () => { + cy.visit('/'); + cy.get('#login').should('exist'); + cy.get('#login').click(); + + loginToAuth0(); + + cy.get('#hello').contains(`Hello, ${Cypress.env('USER_EMAIL')}!`); + cy.get('#logout').click(); + cy.get('#login').should('exist'); + }); + + it('should protect a route and return to path after login', () => { + cy.visit('/users'); + + loginToAuth0(); + + cy.url().should('include', '/users'); + cy.get('#logout').click(); + }); + + it('should access an api', () => { + cy.visit('/users'); + + loginToAuth0(); + + cy.get('table').contains('bob@example.com'); + cy.get('#logout').click(); + }); +}); diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json new file mode 100644 index 00000000..0cbfffb0 --- /dev/null +++ b/cypress/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "strict": true, + "baseUrl": "../node_modules", + "target": "es5", + "lib": ["es5", "dom"], + "types": ["cypress"] + }, + "include": ["**/*.ts"] +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..c0ab4fa6 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,28 @@ +# @auth0/auth0-react Examples + +To run the examples: + +- Follow the steps to configure an Auth0 Single-Page Application (SPA) in https://auth0.com/docs/quickstart/spa/react/01-login#configure-auth0 +- Follow the steps to create an API in https://auth0.com/docs/quickstart/spa/react/02-calling-an-api#create-an-api +- Add a permission to your API of `read:users` following the steps in https://auth0.com/docs/dashboard/guides/apis/add-permissions-apis +- Add a `.env` file to `./examples/cra-react-router/.env` With the `domain` and `clientId` of the application and `audience` (your API identifier) + +```dotenv +REACT_APP_DOMAIN=your_domain +REACT_APP_CLIENT_ID=your_client_id +REACT_APP_AUDIENCE=your_audience +SKIP_PREFLIGHT_CHECK=true # To workaround issues with nesting create-react-app in another package +``` + +- Add a `.env` file to `./examples/users-api/.env` With the `domain` and `audience` (your API identifier) + +```dotenv +DOMAIN=your_domain +AUDIENCE=your_audience +``` + +- Start the api and the web application by running the 2 start commands (from the project root) + +```bash +$ npm run start:api && npm run start:cra +``` diff --git a/examples/cra-react-router/.env.sample b/examples/cra-react-router/.env.sample new file mode 100644 index 00000000..9814b8a3 --- /dev/null +++ b/examples/cra-react-router/.env.sample @@ -0,0 +1,5 @@ +SKIP_PREFLIGHT_CHECK=true +REACT_APP_DOMAIN=your-tenant.auth0.com +REACT_APP_CLIENT_ID=yourclientid +REACT_APP_AUDIENCE=https://api.example.com/users +REACT_APP_API_PORT=3001 diff --git a/examples/cra-react-router/.gitignore b/examples/cra-react-router/.gitignore new file mode 100644 index 00000000..5d6efa59 --- /dev/null +++ b/examples/cra-react-router/.gitignore @@ -0,0 +1,26 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Intentionally removing package lock because it has problems when using local file resolutions +package-lock.json diff --git a/examples/cra-react-router/README.md b/examples/cra-react-router/README.md new file mode 100644 index 00000000..414da02f --- /dev/null +++ b/examples/cra-react-router/README.md @@ -0,0 +1,29 @@ +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+You will also see any lint errors in the console. + +### `npm run build` + +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). diff --git a/examples/cra-react-router/package.json b/examples/cra-react-router/package.json new file mode 100644 index 00000000..b8f76a3d --- /dev/null +++ b/examples/cra-react-router/package.json @@ -0,0 +1,42 @@ +{ + "name": "cra-react-router", + "version": "0.1.0", + "private": true, + "dependencies": { + "@auth0/auth0-react": "file:../..", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.5.0", + "@testing-library/user-event": "^7.2.1", + "@types/history": "^4.7.6", + "@types/jest": "^24.9.1", + "@types/node": "^12.12.43", + "@types/react": "^16.9.35", + "@types/react-dom": "^16.9.8", + "@types/react-router-dom": "^5.1.5", + "history": "^4.10.1", + "react": "file:../../node_modules/react", + "react-dom": "file:../../node_modules/react-dom", + "react-router-dom": "^5.2.0", + "react-scripts": "^3.4.1", + "typescript": "^3.7.5" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/examples/cra-react-router/public/favicon.ico b/examples/cra-react-router/public/favicon.ico new file mode 100644 index 00000000..bcd5dfd6 Binary files /dev/null and b/examples/cra-react-router/public/favicon.ico differ diff --git a/examples/cra-react-router/public/index.html b/examples/cra-react-router/public/index.html new file mode 100644 index 00000000..0e7ba8bb --- /dev/null +++ b/examples/cra-react-router/public/index.html @@ -0,0 +1,24 @@ + + + + + + + + + + React App + + + +
+ + diff --git a/examples/cra-react-router/src/App.css b/examples/cra-react-router/src/App.css new file mode 100644 index 00000000..435bf140 --- /dev/null +++ b/examples/cra-react-router/src/App.css @@ -0,0 +1,5 @@ +.spinner-border { + top: 50%; + position: fixed; + margin-top: -1rem; +} diff --git a/examples/cra-react-router/src/App.tsx b/examples/cra-react-router/src/App.tsx new file mode 100644 index 00000000..2143c0a4 --- /dev/null +++ b/examples/cra-react-router/src/App.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { useAuth0 } from '@auth0/auth0-react'; +import { createBrowserHistory } from 'history'; +import { Route, Router, Switch } from 'react-router-dom'; +import './App.css'; +import { ProtectedRoute } from './ProtectedRoute'; +import { Nav } from './Nav'; +import { Error } from './Error'; +import { Loading } from './Loading'; +import { Users } from './Users'; + +export const history = createBrowserHistory(); + +function App() { + const { isLoading, error } = useAuth0(); + + if (isLoading) { + return ; + } + + return ( + +