Repository: auth0/nextjs-auth0
Pull request #78 (2020-02-04 11:47:06)

Make options optional in handlers
### Description

In the file `instance.d.ts`, the handlers third arguments were marked as required, even though they are optional.

### Testing
–

- [ ] This change adds test coverage for new/changed/fixed functionality

### Checklist

- [ ] I have added documentation for new/changed functionality in this PR or in auth0.com/docs
- [x] All active GitHub checks for tests, formatting, and security are passing
- [x] The correct base branch is being used, if not `master`



Patch:
diff --git a/README.md b/README.md
index 3dd180b2..49face77 100644
--- a/README.md
+++ b/README.md
@@ -135,6 +135,7 @@ You can optionally send extra parameters to Auth0 to influence the transaction,
 - Filling in the user's email address
 - Exposing information to the custom login page (eg: to show the signup tab)
 - Using a custom `state`
+- Redirecting the user to a `redirectTo` url after the transaction is finished
 
 ```js
 import auth0 from '../../utils/auth0';
@@ -148,7 +149,8 @@ export default async function login(req, res) {
         scope: 'some other scope',
         state: 'a custom state',
         foo: 'bar'
-      }
+      },
+      redirectTo: '/custom-url'
     });
   } catch (error) {
     console.error(error);
diff --git a/package-lock.json b/package-lock.json
index d6d8284e..fdd0c9dc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1736,9 +1736,9 @@
       }
     },
     "acorn": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
-      "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
+      "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
       "dev": true
     },
     "acorn-globals": {
@@ -1752,9 +1752,9 @@
       },
       "dependencies": {
         "acorn": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
-          "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
+          "version": "6.4.1",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+          "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
           "dev": true
         }
       }
@@ -7074,9 +7074,9 @@
       },
       "dependencies": {
         "acorn": {
-          "version": "5.7.3",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
-          "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==",
+          "version": "5.7.4",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz",
+          "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==",
           "dev": true
         }
       }
@@ -11412,9 +11412,9 @@
       },
       "dependencies": {
         "acorn": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
-          "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
+          "version": "6.4.1",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+          "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
           "dev": true
         },
         "eslint-scope": {
diff --git a/src/handlers/callback.ts b/src/handlers/callback.ts
index de166b99..900ec156 100644
--- a/src/handlers/callback.ts
+++ b/src/handlers/callback.ts
@@ -46,8 +46,8 @@ export default function callbackHandler(
     // Create the session.
     await sessionStore.save(req, res, session);
 
-    // Redirect to the homepage.
-    const redirectTo = (options && options.redirectTo) || '/';
+    // Redirect to the homepage or custom url.
+    const redirectTo = (options && options.redirectTo) || cookies['a0:redirectTo'] || '/';
     res.writeHead(302, {
       Location: redirectTo
     });
diff --git a/src/handlers/login.ts b/src/handlers/login.ts
index ee89eae2..7cc0d096 100644
--- a/src/handlers/login.ts
+++ b/src/handlers/login.ts
@@ -37,6 +37,7 @@ export interface AuthorizationParameters {
 
 export interface LoginOptions {
   authParams?: AuthorizationParameters;
+  redirectTo?: string;
 }
 
 export default function loginHandler(settings: IAuth0Settings, clientProvider: IOidcClientFactory) {
@@ -51,6 +52,7 @@ export default function loginHandler(settings: IAuth0Settings, clientProvider: I
 
     const opt = options || {};
     const { state = base64url(randomBytes(48)), ...authParams } = (opt && opt.authParams) || {};
+    const { redirectTo } = opt;
 
     // Create the authorization url.
     const client = await clientProvider();
@@ -70,6 +72,11 @@ export default function loginHandler(settings: IAuth0Settings, clientProvider: I
         name: 'a0:state',
         value: state,
         maxAge: 60 * 60
+      },
+      {
+        name: 'a0:redirectTo',
+        value: redirectTo || '/',
+        maxAge: 60 * 60
       }
     ]);
 
diff --git a/src/handlers/logout.ts b/src/handlers/logout.ts
index 1a12a3b3..4e2c8c6c 100644
--- a/src/handlers/logout.ts
+++ b/src/handlers/logout.ts
@@ -33,7 +33,8 @@ export default function logoutHandler(settings: IAuth0Settings, sessionSettings:
         name: sessionSettings.cookieName,
         value: '',
         maxAge: -1,
-        path: sessionSettings.cookiePath
+        path: sessionSettings.cookiePath,
+        domain: sessionSettings.cookieDomain
       }
     ]);
 
diff --git a/src/instance.ts b/src/instance.ts
index 1c2af9be..2772a7b1 100644
--- a/src/instance.ts
+++ b/src/instance.ts
@@ -12,12 +12,12 @@ export interface ISignInWithAuth0 {
   /**
    * Login handler which will redirect the user to Auth0.
    */
-  handleLogin: (req: IncomingMessage, res: ServerResponse, options: LoginOptions) => Promise<void>;
+  handleLogin: (req: IncomingMessage, res: ServerResponse, options?: LoginOptions) => Promise<void>;
 
   /**
    * Callback handler which will complete the transaction and create a local session.
    */
-  handleCallback: (req: IncomingMessage, res: ServerResponse, options: CallbackOptions) => Promise<void>;
+  handleCallback: (req: IncomingMessage, res: ServerResponse, options?: CallbackOptions) => Promise<void>;
 
   /**
    * Logout handler which will clear the local session and the Auth0 session
@@ -27,7 +27,7 @@ export interface ISignInWithAuth0 {
   /**
    * Profile handler which return profile information about the user.
    */
-  handleProfile: (req: NextApiRequest, res: NextApiResponse, options: ProfileOptions) => Promise<void>;
+  handleProfile: (req: NextApiRequest, res: NextApiResponse, options?: ProfileOptions) => Promise<void>;
 
   /**
    * Session handler which returns the current session


Test patch:
diff --git a/tests/handlers/callback.test.ts b/tests/handlers/callback.test.ts
index 5acdc0f2..5b4f9d88 100644
--- a/tests/handlers/callback.test.ts
+++ b/tests/handlers/callback.test.ts
@@ -220,7 +220,7 @@ describe('callback handler', () => {
           url: `${httpServer.getUrl()}?state=foo&code=bar`,
           followRedirect: false,
           headers: {
-            cookie: 'a0:state=foo;'
+            cookie: 'a0:state=foo;a0:redirectTo=/custom-url;'
           }
         });
 
@@ -260,6 +260,11 @@ describe('callback handler', () => {
         expect(cookie['Max-Age']).toBe('3600');
         expect(cookie.Expires).toBe(new Date(time.getTime() + 3600 * 1000).toUTCString());
       });
+
+      test('should redirect to cookie url', async () => {
+        expect(responseStatus).toBe(302);
+        expect(responseHeaders.location).toBe('/custom-url');
+      });
     });
   });
 
diff --git a/tests/handlers/login.test.ts b/tests/handlers/login.test.ts
index 2515eb64..59229d39 100644
--- a/tests/handlers/login.test.ts
+++ b/tests/handlers/login.test.ts
@@ -17,7 +17,7 @@ describe('login handler', () => {
 
   beforeEach(done => {
     discovery(withoutApi);
-    loginOptions = null;
+    loginOptions = { redirectTo: '/custom-url' };
     loginHandler = login(withoutApi, getClient(withoutApi));
     httpServer = new HttpServer((req, res) => loginHandler(req, res, loginOptions));
     httpServer.start(done);
@@ -37,6 +37,18 @@ describe('login handler', () => {
     expect(state).toBeTruthy();
   });
 
+  test('should create a redirectTo cookie', async () => {
+    const { headers } = await getAsync({
+      url: httpServer.getUrl(),
+      followRedirect: false
+    });
+
+    const state = parse(headers['set-cookie'][0]);
+    const redirectTo = parse(headers['set-cookie'][1]);
+    expect(state).toBeTruthy();
+    expect(redirectTo['a0:redirectTo']).toEqual('/custom-url');
+  });
+
   test('should redirect to the identity provider', async () => {
     const { statusCode, headers } = await getAsync({
       url: httpServer.getUrl(),
diff --git a/tests/handlers/logout.test.ts b/tests/handlers/logout.test.ts
index c2ca8c2b..5852d082 100644
--- a/tests/handlers/logout.test.ts
+++ b/tests/handlers/logout.test.ts
@@ -53,3 +53,40 @@ describe('logout handler', () => {
     });
   });
 });
+
+describe('logout handler with cookieDomain', () => {
+  const cookieDomain = 'www.acme.com';
+  let httpServer: HttpServer;
+
+  beforeAll(done => {
+    httpServer = new HttpServer(
+      logout(withoutApi, new CookieSessionStoreSettings({ ...withoutApi.session, cookieDomain }))
+    );
+    httpServer.start(done);
+  });
+
+  afterAll(done => {
+    httpServer.stop(done);
+  });
+
+  test('should delete the state and session', async () => {
+    const { headers } = await getAsync({
+      url: httpServer.getUrl(),
+      headers: {
+        cookie: ['a0:state=foo', 'a0:session=bar'].join('; ')
+      },
+      followRedirect: false
+    });
+
+    const [stateCookie, sessionCookie] = headers['set-cookie'];
+    expect(parse(stateCookie)).toMatchObject({
+      'a0:state': '',
+      'Max-Age': '-1'
+    });
+    expect(parse(sessionCookie)).toMatchObject({
+      'a0:session': '',
+      'Max-Age': '-1',
+      Domain: cookieDomain
+    });
+  });
+});