diff --git a/components/header.js b/components/header.js
index 4687e994..b3e81a92 100644
--- a/components/header.js
+++ b/components/header.js
@@ -89,7 +89,7 @@ function NotificationBell () {
function NavProfileMenu ({ me, dropNavKey }) {
const { registration: swRegistration, togglePushSubscription } = useServiceWorker()
const showModal = useShowModal()
- const { resetMultiAuthPointer } = useAccounts()
+ const { multiAuthSignout } = useAccounts()
return (
@@ -131,6 +131,9 @@ function NavProfileMenu ({ me, dropNavKey }) {
showModal(onClose => )}>switch account
{
+ const status = await multiAuthSignout()
+ // only signout if multiAuth did not find a next available account
+ if (status === 201) return
try {
// order is important because we need to be logged in to delete push subscription on server
const pushSubscription = await swRegistration?.pushManager.getSubscription()
@@ -141,7 +144,6 @@ function NavProfileMenu ({ me, dropNavKey }) {
// don't prevent signout because of an unsubscription error
console.error(err)
}
- resetMultiAuthPointer()
await signOut({ callbackUrl: '/' })
}}
>logout
diff --git a/components/switch-account.js b/components/switch-account.js
index 989ae978..26e2d53d 100644
--- a/components/switch-account.js
+++ b/components/switch-account.js
@@ -18,16 +18,22 @@ export const AccountProvider = ({ children }) => {
const [accounts, setAccounts] = useState([])
const [isAnon, setIsAnon] = useState(true)
- useEffect(() => {
+ const updateAccountsFromCookie = useCallback(() => {
try {
const { multi_auth: multiAuthCookie } = cookie.parse(document.cookie)
const accounts = multiAuthCookie
? JSON.parse(b64Decode(multiAuthCookie))
: me ? [{ id: me.id, name: me.name, photoId: me.photoId }] : []
+ console.log(accounts)
+ if (multiAuthCookie) console.log(JSON.parse(b64Decode(multiAuthCookie)))
setAccounts(accounts)
} catch (err) {
console.error('error parsing cookies:', err)
}
+ }, [setAccounts])
+
+ useEffect(() => {
+ updateAccountsFromCookie()
}, [])
const addAccount = useCallback(user => {
@@ -38,9 +44,14 @@ export const AccountProvider = ({ children }) => {
setAccounts(accounts => accounts.filter(({ id }) => id !== userId))
}, [setAccounts])
- const resetMultiAuthPointer = useCallback(() => {
- document.cookie = 'multi_auth.user-id='
- }, [])
+ const multiAuthSignout = useCallback(async () => {
+ // document.cookie = 'multi_auth.user-id='
+ // switch to next available account
+ const { status } = await fetch('/api/signout', { credentials: 'include' })
+ console.log('multiAuthSignout rseponse', status)
+ if (status === 201) updateAccountsFromCookie()
+ return status
+ }, [updateAccountsFromCookie])
useEffect(() => {
// document not defined on server
@@ -49,7 +60,7 @@ export const AccountProvider = ({ children }) => {
setIsAnon(multiAuthUserIdCookie === 'anonymous')
}, [])
- return {children}
+ return {children}
}
export const useAccounts = () => useContext(AccountContext)
@@ -100,6 +111,7 @@ const Account = ({ account, className }) => {
>
{
+ console.log('switching to account', account.id)
document.cookie = `multi_auth.user-id=${account.id}; Path=/; Secure`
await refreshMe()
// order is important to prevent flashes of inconsistent data in switch account dialog
diff --git a/pages/api/signout.js b/pages/api/signout.js
new file mode 100644
index 00000000..10301c32
--- /dev/null
+++ b/pages/api/signout.js
@@ -0,0 +1,60 @@
+import cookie from 'cookie'
+import { datePivot } from '../../lib/time'
+
+/**
+ * @param {NextApiRequest} req
+ * @param {NextApiResponse} res
+ * @return {void}
+ */
+export default (req, res) => {
+ // is there a cookie pointer?
+ const cookiePointerName = 'multi_auth.user-id'
+ const userId = req.cookies[cookiePointerName]
+ // is there a session?
+ const sessionCookieName = '__Secure-next-auth.session-token'
+ const sessionJWT = req.cookies[sessionCookieName]
+
+ if (!userId || !sessionJWT) {
+ // no cookie pointer or no session cookie present. do nothing.
+ res.status(404).end()
+ return
+ }
+
+ const cookies = []
+
+ const cookieOptions = {
+ path: '/',
+ secure: true,
+ httpOnly: true,
+ sameSite: 'lax',
+ expires: datePivot(new Date(), { months: 1 })
+ }
+ // remove JWT pointed to by cookie pointer
+ cookies.push(cookie.serialize(`multi_auth.${userId}`, '', { ...cookieOptions, expires: 0, maxAge: 0 }))
+
+ // update multi_auth cookie
+ const oldMultiAuth = b64Decode(req.cookies.multi_auth)
+ const newMultiAuth = oldMultiAuth.filter(({ id }) => id !== Number(userId))
+ cookies.push(cookie.serialize('multi_auth', b64Encode(newMultiAuth), { ...cookieOptions, httpOnly: false }))
+
+ // switch to next available account
+ if (!newMultiAuth.length) {
+ // no next account available
+ res.setHeader('Set-Cookie', cookies)
+ res.status(204).end()
+ return
+ }
+
+ const newUserId = newMultiAuth[0].id
+ const newUserJWT = req.cookies[`multi_auth.${newUserId}`]
+ res.setHeader('Set-Cookie', [
+ ...cookies,
+ cookie.serialize(cookiePointerName, newUserId, { ...cookieOptions, httpOnly: false }),
+ cookie.serialize(sessionCookieName, newUserJWT, cookieOptions)
+ ])
+
+ res.status(201).end()
+}
+
+const b64Encode = obj => Buffer.from(JSON.stringify(obj)).toString('base64')
+const b64Decode = s => JSON.parse(Buffer.from(s, 'base64'))