Skip to content

Commit

Permalink
Only request token endpoint initially, then use a cookie to determine…
Browse files Browse the repository at this point in the history
… if there is an authenticated user (#1740)

* Create readable client side cookie to determine login state

* Fix cookie being set when it shouldn't and fix reset cookie

* Rename has token key

* Update cookie name in toolbar

* Update variable names

* mergeCookies function and properly set expires and maxage

* Remove `removeCookie` in favor of `setCookie`

* Add `getHeader` to mocked test responses

* Add unit tests

* Add changeset

* Update .changeset/brave-cougars-lie.md

Co-authored-by: Matthew Wright <1815200+matthewguywright@users.noreply.github.com>

---------

Co-authored-by: Matthew Wright <1815200+matthewguywright@users.noreply.github.com>
  • Loading branch information
blakewilson and matthewguywright authored Feb 28, 2024
1 parent 2b3da86 commit 0759959
Show file tree
Hide file tree
Showing 9 changed files with 340 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/brave-cougars-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@faustwp/core': patch
---

Fixed the behavior of a request to the `api/faust/auth/token` endpoint on every page load when the toolbar is enabled. We now set a `WP_URL-has-rt` token with a `0` or `1` value that can be read client side (aka, not an `httpOnly` cookie) for determining if there is a logged in user or not.
76 changes: 76 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/faustwp-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@types/is-number": "^7.0.1",
"@types/isomorphic-fetch": "^0.0.35",
"@types/jest": "^27.0.2",
"@types/js-cookie": "^3.0.6",
"@types/lodash": "^4.14.176",
"@types/node": "^17.0.17",
"@types/testing-library__react": "10.2.0",
Expand All @@ -38,6 +39,7 @@
"deepmerge": "^4.2.2",
"fast-xml-parser": "^4.2.5",
"isomorphic-fetch": "^3.0.0",
"js-cookie": "^3.0.5",
"js-sha256": "^0.9.0",
"lodash": "^4.17.21"
},
Expand Down
15 changes: 13 additions & 2 deletions packages/faustwp-core/src/components/Toolbar/Toolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { gql, useQuery } from '@apollo/client';
import cookies from 'js-cookie';
import React, { useEffect, useMemo, useState } from 'react';
import { getApolloAuthClient } from '../../client.js';
import { useAuth } from '../../hooks/useAuth.js';
import { getWpUrl } from '../../lib/getWpUrl.js';
import { SeedNode } from '../../queries/seedQuery.js';
import { hooks } from '../../wpHooks/index.js';
import { ToolbarNode } from './ToolbarNode.js';
import { Edit } from './nodes/Edit.js';
import { GraphiQL } from './nodes/GraphiQL.js';
import { MyAccount } from './nodes/MyAccount.js';
import { SiteName } from './nodes/SiteName.js';
import { ToolbarNode } from './ToolbarNode.js';

/**
* The available menu locations that nodes can be added to.
Expand Down Expand Up @@ -215,7 +217,16 @@ export function ToolbarAwaitUser({ seedNode }: ToolbarProps) {
* Renders a Toolbar that is based on WordPress' own toolbar.
*/
export function Toolbar({ seedNode }: ToolbarProps) {
const { isAuthenticated } = useAuth();
const hasAuthenticatedUser = cookies.get(`${getWpUrl()}-has-rt`);

const { isAuthenticated } = useAuth({
strategy: 'redirect',
/**
* If the hasAuthenticatedUser cookie exists and it's "0", skip
* running the useAuth hook.
*/
skip: hasAuthenticatedUser === '0',
});

if (isAuthenticated !== true) {
return null;
Expand Down
49 changes: 37 additions & 12 deletions packages/faustwp-core/src/server/auth/cookie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,38 @@ export interface CookieOptions {
isJson?: boolean;
}

/**
* Merge cookies from current Set-Cookie header with a new cookie string.
*
* @param setCookieHeader Current Set-Cookie header if exists.
* @param newCookie The new cookie string to be applied.
* @returns A cookie string or array of cookie strings.
*/
export function mergeCookies(
setCookieHeader: string | string[] | number | undefined,
newCookie: string,
) {
// If there is no setCookieHeader, return the newCookie early.
if (!setCookieHeader) {
return newCookie;
}

/**
* If there is already a Set-Cookie header, create an array and merge
* the existing ones with the new cookie.
*/
let newCookies: string[] = [];
if (Array.isArray(setCookieHeader)) {
newCookies = [...setCookieHeader];
} else {
newCookies = [setCookieHeader as string];
}

newCookies = [...newCookies, newCookie];

return newCookies;
}

export class Cookies {
private request: IncomingMessage;

Expand Down Expand Up @@ -58,20 +90,13 @@ export class Cookies {

this.cookies[key] = cookieValue;

this.response?.setHeader(
'Set-Cookie',
const existingCookieHeader = this.response?.getHeader('Set-Cookie');

const newCookies = mergeCookies(
existingCookieHeader,
cookie.serialize(key, cookieValue, serializeOptions),
);
}

public removeCookie(key: string): void {
delete this.cookies[key];

this.response?.setHeader(
'Set-Cookie',
cookie.serialize(key, '', {
expires: new Date(0),
}),
);
this.response?.setHeader('Set-Cookie', newCookies);
}
}
1 change: 1 addition & 0 deletions packages/faustwp-core/src/server/auth/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export async function authorizeHandler(

if (!refreshToken && !code) {
res.statusCode = 401;
oauth.setRefreshToken(undefined);
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ error: 'Unauthorized' }));

Expand Down
34 changes: 29 additions & 5 deletions packages/faustwp-core/src/server/auth/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,53 @@ export class OAuth {

private tokenKey: string;

private hasTokenKey: string;

constructor(cookies: Cookies) {
this.cookies = cookies;
this.tokenKey = `${getWpUrl()}-rt`;
this.hasTokenKey = `${getWpUrl()}-has-rt`;
}

public getRefreshToken(): string | undefined {
return this.cookies.getCookie(this.tokenKey);
}

public setRefreshToken(token?: string, expires?: number): void {
if (!isString(token) || token.length === 0) {
this.cookies.removeCookie(this.tokenKey);
}

let maxAge: number | undefined = 2592000;
let expiresIn: Date | undefined;

if (!isString(token) || token.length === 0) {
this.cookies.setCookie(this.tokenKey, '', {
path: '/',
expires: new Date(0),
secure: true,
httpOnly: true,
});

this.cookies.setCookie(this.hasTokenKey, '0', {
path: '/',
encoded: false,
maxAge,
expires: expiresIn,
});

return;
}

if (isNumber(expires)) {
expiresIn = new Date(expires * 1000);
maxAge = undefined;
}

this.cookies.setCookie(this.tokenKey, token as string, {
this.cookies.setCookie(this.hasTokenKey, '1', {
path: '/',
encoded: false,
maxAge,
expires: expiresIn,
});

this.cookies.setCookie(this.tokenKey, token, {
expires: expiresIn,
maxAge,
path: '/',
Expand Down
28 changes: 28 additions & 0 deletions packages/faustwp-core/tests/server/auth/cookie.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { mergeCookies } from '../../../src/server/auth/cookie';

describe('mergeCookies', () => {
it('merges cookies from an existing setCookie header and a new cookie', () => {
const existingSetCookieHeader = `http://headless.local-rt=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure`;
const newCookie = `http://headless.local-has-rt=0; Max-Age=2592000; Path=/`;
const result = mergeCookies(existingSetCookieHeader, newCookie);

expect(result).toStrictEqual([existingSetCookieHeader, newCookie]);
});

it('returns the cookie if existing set cookie header does not exist', () => {
const newCookie = `http://headless.local-has-rt=0; Max-Age=2592000; Path=/`;

expect(mergeCookies(undefined, newCookie)).toStrictEqual(newCookie);
});

it('merges cookies from an existing array of setCookies', () => {
const existingSetCookieHeader = [
`http://headless.local-rt=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure`,
`http://testing.local-rt=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure`,
];
const newCookie = `http://headless.local-has-rt=0; Max-Age=2592000; Path=/`;
const result = mergeCookies(existingSetCookieHeader, newCookie);

expect(result).toStrictEqual([...existingSetCookieHeader, newCookie]);
});
});
Loading

0 comments on commit 0759959

Please sign in to comment.