Repository: auth0/nextjs-auth0
Pull request #855 (2022-10-10 14:41:33)

Change `updateUser` to `updateSession`
Initially I wanted to restrict updates to just the user. That way people are less likely to shoot themselves in the food by overwriting properties the SDK depends on. On reflection I think only allowing to update `session.user` will be jarring for people:

- `updateUser` is not consistent with the hooks (where  they can update the whole session)
- We allowed updating the whole session in v1 so there's probably existing code out there that relies on updating the whole session outside of hooks



Patch:
diff --git a/README.md b/README.md
index ce76c429..cff7cb75 100644
--- a/README.md
+++ b/README.md
@@ -185,7 +185,7 @@ For other comprehensive examples, see the [EXAMPLES.md](./EXAMPLES.md) document.
 - [withPageAuthRequired](https://auth0.github.io/nextjs-auth0/modules/helpers_with_page_auth_required.html#withpageauthrequired)
 - [withMiddlewareAuthRequired](https://auth0.github.io/nextjs-auth0/modules/helpers_with_middleware_auth_required.html)
 - [getSession](https://auth0.github.io/nextjs-auth0/modules/session_get_session.html)
-- [updateUser](https://auth0.github.io/nextjs-auth0/modules/session_update_user.html)
+- [updateSession](https://auth0.github.io/nextjs-auth0/modules/session_update_session.html)
 - [getAccessToken](https://auth0.github.io/nextjs-auth0/modules/session_get_access_token.html)
 - [initAuth0](https://auth0.github.io/nextjs-auth0/modules/instance.html)
 
diff --git a/V2_MIGRATION_GUIDE.md b/V2_MIGRATION_GUIDE.md
index 81290b54..bdcfdac7 100644
--- a/V2_MIGRATION_GUIDE.md
+++ b/V2_MIGRATION_GUIDE.md
@@ -3,7 +3,7 @@
 Guide to migrating from `1.x` to `2.x`
 
 - [`getSession` now returns a `Promise`](#getsession-now-returns-a-promise)
-- [`updateUser` has been added](#updateuser-has-been-added)
+- [`updateSession` has been added](#updatesession-has-been-added)
 - [`getServerSidePropsWrapper` has been removed](#getserversidepropswrapper-has-been-removed)
 - [Profile API route no longer returns a 401](#profile-api-route-no-longer-returns-a-401)
 - [The ID token is no longer stored by default](#the-id-token-is-no-longer-stored-by-default)
@@ -37,7 +37,7 @@ async function myApiRoute(req, res) {
 }
 ```
 
-## `updateUser` has been added
+## `updateSession` has been added
 
 ### Before
 
@@ -48,8 +48,8 @@ Previously your application could make modifications to the session during the l
 import { getSession } from '@auth0/nextjs-auth0';
 
 function myApiRoute(req, res) {
-  const { user } = getSession(req, res);
-  user.foo = 'bar';
+  const session = getSession(req, res);
+  session.foo = 'bar';
   res.json({ success: true });
 }
 // The updated session is serialized and the cookie is updated
@@ -58,19 +58,19 @@ function myApiRoute(req, res) {
 
 ### After
 
-We've introduced a new `updateUser` method which must be explicitly invoked in order to update the session's user.
+We've introduced a new `updateSession` method which must be explicitly invoked in order to update the session.
 
 This will immediately serialise the session, write it to the cookie and return a `Promise`.
 
 ```js
 // /pages/api/update-user
-import { getSession, updateUser } from '@auth0/nextjs-auth0';
+import { getSession, updateSession } from '@auth0/nextjs-auth0';
 
 async function myApiRoute(req, res) {
-  const { user } = await getSession(req, res);
+  const session = await getSession(req, res);
   // The session is updated, serialized and the cookie is updated
-  // everytime you call `updateUser`.
-  await updateUser(req, res, { ...user, foo: 'bar' });
+  // everytime you call `updateSession`.
+  await updateSession(req, res, { ...session, user: { ...session.user, foo: 'bar' } });
   res.json({ success: true });
 }
 ```
@@ -214,7 +214,7 @@ export default handleAuth({
   login: async (req, res) => {
     try {
       await handleLogin(req, res, {
-        authorizationParams: { connection: 'github' },
+        authorizationParams: { connection: 'github' }
       });
     } catch (error) {
       // ...
diff --git a/src/index.browser.ts b/src/index.browser.ts
index 15c5ad25..d550208a 100644
--- a/src/index.browser.ts
+++ b/src/index.browser.ts
@@ -19,8 +19,8 @@ const instance: SignInWithAuth0 = {
   getSession() {
     throw new Error(serverSideOnly('getSession'));
   },
-  updateUser() {
-    throw new Error(serverSideOnly('updateUser'));
+  updateSession() {
+    throw new Error(serverSideOnly('updateSession'));
   },
   getAccessToken() {
     throw new Error(serverSideOnly('getAccessToken'));
diff --git a/src/index.ts b/src/index.ts
index cb8c81fa..079d5156 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -38,8 +38,8 @@ import {
   AccessTokenRequest,
   GetAccessTokenResult,
   Claims,
-  updateUserFactory,
-  UpdateUser
+  updateSessionFactory,
+  UpdateSession
 } from './session/';
 import {
   withPageAuthRequiredFactory,
@@ -88,7 +88,7 @@ export const _initAuth = (params?: ConfigParameters): SignInWithAuth0 & { sessio
 
   // Init Next layer (with next config)
   const getSession = sessionFactory(sessionCache);
-  const updateUser = updateUserFactory(sessionCache);
+  const updateSession = updateSessionFactory(sessionCache);
   const getAccessToken = accessTokenFactory(nextConfig, getClient, sessionCache);
   const withApiAuthRequired = withApiAuthRequiredFactory(sessionCache);
   const withPageAuthRequired = withPageAuthRequiredFactory(nextConfig.routes.login, () => sessionCache);
@@ -101,7 +101,7 @@ export const _initAuth = (params?: ConfigParameters): SignInWithAuth0 & { sessio
   return {
     sessionCache,
     getSession,
-    updateUser,
+    updateSession,
     getAccessToken,
     withApiAuthRequired,
     withPageAuthRequired,
@@ -116,7 +116,7 @@ export const _initAuth = (params?: ConfigParameters): SignInWithAuth0 & { sessio
 /* c8 ignore start */
 const getSessionCache = () => getInstance().sessionCache;
 export const getSession: GetSession = (...args) => getInstance().getSession(...args);
-export const updateUser: UpdateUser = (...args) => getInstance().updateUser(...args);
+export const updateSession: UpdateSession = (...args) => getInstance().updateSession(...args);
 export const getAccessToken: GetAccessToken = (...args) => getInstance().getAccessToken(...args);
 export const withApiAuthRequired: WithApiAuthRequired = (...args) => getInstance().withApiAuthRequired(...args);
 export const withPageAuthRequired: WithPageAuthRequired = withPageAuthRequiredFactory(getLoginUrl(), getSessionCache);
@@ -174,7 +174,7 @@ export {
   WithPageAuthRequired,
   SessionCache,
   GetSession,
-  UpdateUser,
+  UpdateSession,
   GetAccessToken,
   Session,
   Claims,
diff --git a/src/instance.ts b/src/instance.ts
index e0f5ce80..5c3892ca 100644
--- a/src/instance.ts
+++ b/src/instance.ts
@@ -1,4 +1,4 @@
-import { GetSession, GetAccessToken, UpdateUser } from './session';
+import { GetSession, GetAccessToken, UpdateSession } from './session';
 import { WithApiAuthRequired, WithPageAuthRequired } from './helpers';
 import { HandleAuth, HandleCallback, HandleLogin, HandleLogout, HandleProfile } from './handlers';
 import { ConfigParameters } from './auth0-session';
@@ -21,7 +21,7 @@ export interface SignInWithAuth0 {
   /**
    * Append properties to the user.
    */
-  updateUser: UpdateUser;
+  updateSession: UpdateSession;
 
   /**
    * Access token getter.
diff --git a/src/session/index.ts b/src/session/index.ts
index 1fc810e0..7c8b2dc1 100644
--- a/src/session/index.ts
+++ b/src/session/index.ts
@@ -7,4 +7,4 @@ export {
   GetAccessTokenResult
 } from './get-access-token';
 export { default as SessionCache } from './cache';
-export { default as updateUserFactory, UpdateUser } from './update-user';
+export { default as updateSessionFactory, UpdateSession } from './update-session';
diff --git a/src/session/update-session.ts b/src/session/update-session.ts
new file mode 100644
index 00000000..1138a920
--- /dev/null
+++ b/src/session/update-session.ts
@@ -0,0 +1,45 @@
+import { IncomingMessage, ServerResponse } from 'http';
+import { NextApiRequest, NextApiResponse } from 'next';
+import { Session, SessionCache } from '../session';
+import { assertReqRes } from '../utils/assert';
+
+/**
+ * Update the session object. The provided `session` object will replace the existing session.
+ *
+ * **Note** you can't use this method to login or logout - you should use the login and logout handlers for this.
+ * If no session is provided, it doesn't contain a user or the user is not authenticated; this is a no-op.
+ *
+ * ```js
+ * // pages/api/update-user.js
+ * import { getSession, updateSession } from '@auth0/nextjs-auth0';
+ *
+ * export default async function updateSession(req, res) {
+ *   if (req.method === 'PUT') {
+ *     const session = getSession(req, res);
+ *     updateSession(req, res, { ...session, user: { ...user, foo: req.query.foo } });
+ *     res.json({ success: true });
+ *   }
+ * };
+ * ```
+ *
+ * @category Server
+ */
+export type UpdateSession = (
+  req: IncomingMessage | NextApiRequest,
+  res: ServerResponse | NextApiResponse,
+  user: Session
+) => Promise<void>;
+
+/**
+ * @ignore
+ */
+export default function updateSessionFactory(sessionCache: SessionCache): UpdateSession {
+  return async (req, res, newSession) => {
+    assertReqRes(req, res);
+    const session = await sessionCache.get(req, res);
+    if (!session || !newSession || !newSession.user) {
+      return;
+    }
+    await sessionCache.set(req, res, newSession);
+  };
+}
diff --git a/src/session/update-user.ts b/src/session/update-user.ts
deleted file mode 100644
index 37e3b44f..00000000
--- a/src/session/update-user.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { IncomingMessage, ServerResponse } from 'http';
-import { NextApiRequest, NextApiResponse } from 'next';
-import { Claims, SessionCache } from '../session';
-import { assertReqRes } from '../utils/assert';
-
-/**
- * Update the session's user object. The provided user object will replace `session.user`.
- *
- * If no user is provided, or the user is not authenticated, this is a no-op.
- *
- * ```js
- * // pages/api/update-user.js
- * import { getSession, updateUser } from '@auth0/nextjs-auth0';
- *
- * export default async function UpdateUser(req, res) {
- *   if (req.method === 'PUT') {
- *     const { user } = getSession(req, res);
- *     updateUser(req, res, { ...user, foo: req.query.foo });
- *     res.json({ success: true });
- *   }
- * };
- * ```
- *
- * @category Server
- */
-export type UpdateUser = (
-  req: IncomingMessage | NextApiRequest,
-  res: ServerResponse | NextApiResponse,
-  user: Claims
-) => Promise<void>;
-
-/**
- * @ignore
- */
-export default function updateUserFactory(sessionCache: SessionCache): UpdateUser {
-  return async (req, res, user) => {
-    assertReqRes(req, res);
-    const session = await sessionCache.get(req, res);
-    if (!session || !user) {
-      return;
-    }
-    await sessionCache.set(req, res, { ...session, user });
-  };
-}


Test patch:
diff --git a/tests/fixtures/global.d.ts b/tests/fixtures/global.d.ts
index e56d1d25..885406e5 100644
--- a/tests/fixtures/global.d.ts
+++ b/tests/fixtures/global.d.ts
@@ -2,7 +2,7 @@ declare global {
   namespace NodeJS {
     interface Global {
       getSession?: Function;
-      updateUser?: Function;
+      updateSession?: Function;
       handleAuth?: Function;
       withApiAuthRequired?: Function;
       withPageAuthRequired?: Function;
diff --git a/tests/fixtures/setup.ts b/tests/fixtures/setup.ts
index 50b59c11..e773b368 100644
--- a/tests/fixtures/setup.ts
+++ b/tests/fixtures/setup.ts
@@ -78,7 +78,7 @@ export const setup = async (
     handleLogout,
     handleProfile,
     getSession,
-    updateUser,
+    updateSession,
     getAccessToken,
     withApiAuthRequired,
     withPageAuthRequired
@@ -90,7 +90,7 @@ export const setup = async (
   const handlers: Handlers = { onError, callback, login, logout, profile };
   global.handleAuth = handleAuth.bind(null, handlers);
   global.getSession = getSession;
-  global.updateUser = updateUser;
+  global.updateSession = updateSession;
   global.withApiAuthRequired = withApiAuthRequired;
   global.withPageAuthRequired = (): any => withPageAuthRequired(withPageAuthRequiredOptions);
   global.withPageAuthRequiredCSR = withPageAuthRequired;
@@ -105,7 +105,7 @@ export const teardown = async (): Promise<void> => {
   nock.cleanAll();
   await stop();
   delete global.getSession;
-  delete global.updateUser;
+  delete global.updateSession;
   delete global.handleAuth;
   delete global.withApiAuthRequired;
   delete global.withPageAuthRequired;
diff --git a/tests/fixtures/test-app/pages/api/update-user.ts b/tests/fixtures/test-app/pages/api/update-session.ts
similarity index 70%
rename from tests/fixtures/test-app/pages/api/update-user.ts
rename to tests/fixtures/test-app/pages/api/update-session.ts
index d791ea4f..b44cdbf8 100644
--- a/tests/fixtures/test-app/pages/api/update-user.ts
+++ b/tests/fixtures/test-app/pages/api/update-session.ts
@@ -2,7 +2,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
 
 export default async function sessionHandler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
   const session = await global.getSession?.(req, res);
-  const updated = { ...session?.user, ...req.body?.user };
-  await global.updateUser?.(req, res, updated);
+  const updated = { ...session, ...req.body?.session };
+  await global.updateSession?.(req, res, updated);
   res.status(200).json(updated);
 }
diff --git a/tests/helpers/with-page-auth-required.test.ts b/tests/helpers/with-page-auth-required.test.ts
index c6f8c3a0..732eb42d 100644
--- a/tests/helpers/with-page-auth-required.test.ts
+++ b/tests/helpers/with-page-auth-required.test.ts
@@ -132,8 +132,8 @@ describe('with-page-auth-required ssr', () => {
       withPageAuthRequiredOptions: {
         async getServerSideProps(ctx) {
           await Promise.resolve();
-          const session = (global as any).getSession(ctx.req, ctx.res);
-          await (global as any).updateUser(ctx.req, ctx.res, { ...session.user, test: 'Hello World!' });
+          const session = await (global as any).getSession(ctx.req, ctx.res);
+          await (global as any).updateSession(ctx.req, ctx.res, { ...session, test: 'Hello World!' });
           return { props: {} };
         }
       }
@@ -145,6 +145,6 @@ describe('with-page-auth-required ssr', () => {
     } = await get(baseUrl, '/protected', { cookieJar, fullResponse: true });
     expect(statusCode).toBe(200);
     const session = await get(baseUrl, '/api/session', { cookieJar });
-    expect(session.user.test).toBe('Hello World!');
+    expect(session.test).toBe('Hello World!');
   });
 });
diff --git a/tests/session/update-user.test.ts b/tests/session/update-session.test.ts
similarity index 53%
rename from tests/session/update-user.test.ts
rename to tests/session/update-session.test.ts
index 1441129a..4d8de02d 100644
--- a/tests/session/update-user.test.ts
+++ b/tests/session/update-session.test.ts
@@ -6,22 +6,25 @@ import { CookieJar } from 'tough-cookie';
 describe('update-user', () => {
   afterEach(teardown);
 
-  test('should update user', async () => {
+  test('should update session', async () => {
     const baseUrl = await setup(withoutApi);
     const cookieJar = await login(baseUrl);
     const user = await get(baseUrl, '/api/auth/me', { cookieJar });
     expect(user).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' });
-    await post(baseUrl, '/api/update-user', { cookieJar, body: { user: { foo: 'bar' } } });
-    const updatedUser = await get(baseUrl, '/api/auth/me', { cookieJar });
-    expect(updatedUser).toMatchObject({ foo: 'bar' });
+    await post(baseUrl, '/api/update-session', { cookieJar, body: { session: { foo: 'bar' } } });
+    const updatedSession = await get(baseUrl, '/api/session', { cookieJar });
+    expect(updatedSession).toMatchObject({
+      foo: 'bar',
+      user: expect.objectContaining({ nickname: '__test_nickname__', sub: '__test_sub__' })
+    });
   });
 
-  test('should ignore updates if user is not defined', async () => {
+  test('should ignore updates if session is not defined', async () => {
     const baseUrl = await setup(withoutApi);
     const cookieJar = await login(baseUrl);
     const user = await get(baseUrl, '/api/auth/me', { cookieJar });
     expect(user).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' });
-    await post(baseUrl, '/api/update-user', { cookieJar, body: { user: undefined } });
+    await post(baseUrl, '/api/update-session', { cookieJar, body: { session: undefined } });
     const updatedUser = await get(baseUrl, '/api/auth/me', { cookieJar });
     expect(updatedUser).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' });
   });
@@ -30,7 +33,17 @@ describe('update-user', () => {
     const baseUrl = await setup(withoutApi);
     const cookieJar = new CookieJar();
     await expect(get(baseUrl, '/api/auth/me', { cookieJar })).resolves.toBe('');
-    await post(baseUrl, '/api/update-user', { body: { user: { sub: 'foo' } }, cookieJar });
+    await post(baseUrl, '/api/update-session', { body: { session: { sub: 'foo' } }, cookieJar });
     await expect(get(baseUrl, '/api/auth/me', { cookieJar })).resolves.toBe('');
   });
+
+  test('should ignore updates if user is not defined in update', async () => {
+    const baseUrl = await setup(withoutApi);
+    const cookieJar = await login(baseUrl);
+    const user = await get(baseUrl, '/api/auth/me', { cookieJar });
+    expect(user).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' });
+    await post(baseUrl, '/api/update-session', { cookieJar, body: { session: { user: undefined } } });
+    const updatedUser = await get(baseUrl, '/api/auth/me', { cookieJar });
+    expect(updatedUser).toEqual({ nickname: '__test_nickname__', sub: '__test_sub__' });
+  });
 });