[next] Cookie Support (#2339)

* feat: added cookie support to coral

* feat: adapt client to use cookies

* fix: safari input styles

* fix: lint

* fix: linting

* fix: support clearing cookies properly, oauth

* feat: support cookies for websocket upgrade requests

* fix: lint

* fix: tests
This commit is contained in:
Wyatt Johnson
2019-06-25 21:48:58 +02:00
committed by Vinh
parent 3576b6a943
commit e72b15c505
79 changed files with 375 additions and 278 deletions
@@ -2,7 +2,6 @@ type Local {
accessToken: String
accessTokenExp: Int
accessTokenJTI: String
loggedIn: Boolean!
}
extend type Query {
@@ -28,7 +28,6 @@ type Local {
accessToken: String
accessTokenExp: Int
accessTokenJTI: String
loggedIn: Boolean!
redirectPath: String
authView: View
authError: String
@@ -12,13 +12,7 @@ const SignInMutation = createMutation(
context: CoralContext
) => {
const result = await signIn(context.rest, input);
// Put the token on the hash and clean the session.
// It'll be picked up by initLocalState.
location.hash = `accessToken=${result.token}`;
await context.clearSession();
// TODO: (cvle) A better way would be if `context.clearSession` would return the new session and
// we set the accessToken directly in there.
await context.clearSession(result.token);
}
);
@@ -764,4 +764,4 @@ exports[`shows server error 1`] = `
</form>
`;
exports[`submits form successfully 1`] = `"http://localhost/admin/login#accessToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzMWIyNjU5MS00ZTlhLTQzODgtYTdmZi1lMWJkYzVkOTdjY2UifQ=="`;
exports[`submits form successfully 1`] = `"http://localhost/admin/login"`;
@@ -2,7 +2,6 @@ import { pureMerge } from "coral-common/utils";
import { GQLResolver } from "coral-framework/schema";
import {
act,
createAccessToken,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
@@ -40,8 +39,6 @@ async function createTestRenderer(
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue("SIGN_IN", "authView");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -1,7 +1,6 @@
import { pureMerge } from "coral-common/utils";
import { GQLResolver } from "coral-framework/schema";
import {
createAccessToken,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
@@ -37,8 +36,6 @@ async function createTestRenderer(
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue("ADD_EMAIL_ADDRESS", "authView");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -1,7 +1,6 @@
import { pureMerge } from "coral-common/utils";
import { GQLResolver } from "coral-framework/schema";
import {
createAccessToken,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
@@ -37,8 +36,6 @@ async function createTestRenderer(
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue("CREATE_PASSWORD", "authView");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -1,7 +1,6 @@
import { pureMerge } from "coral-common/utils";
import { GQLResolver } from "coral-framework/schema";
import {
createAccessToken,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
@@ -31,8 +30,6 @@ async function createTestRenderer(
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue("CREATE_USERNAME", "authView");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -4,7 +4,6 @@ import {
} from "coral-framework/schema";
import {
act,
createAccessToken,
createQueryResolverStub,
createResolversStub,
CreateTestRendererParams,
@@ -43,8 +42,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
localRecord.setValue("SIGN_IN", "authView");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
@@ -39,7 +39,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(false, "loggedIn");
localRecord.setValue("SIGN_IN", "authView");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
@@ -4,7 +4,6 @@ import { pureMerge } from "coral-common/utils";
import { LOCAL_ID, lookup } from "coral-framework/lib/relay";
import { GQLResolver, GQLUSER_ROLE } from "coral-framework/schema";
import {
createAccessToken,
createResolversStub,
CreateTestRendererParams,
replaceHistoryLocation,
@@ -41,8 +40,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAccessToken(), "accessToken");
localRecord.setValue("SIGN_IN", "authView");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
@@ -105,6 +102,5 @@ it("sign out when clicking on sign in as", async () => {
"/admin/moderate/reported"
);
});
expect(lookup(context.relayEnvironment, LOCAL_ID)!.loggedIn).toBeFalsy();
restMock.verify();
});
@@ -41,7 +41,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(false, "loggedIn");
localRecord.setValue("SIGN_IN", "authView");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
@@ -1,7 +1,6 @@
import sinon from "sinon";
import sinon, { SinonStub } from "sinon";
import { pureMerge } from "coral-common/utils";
import { LOCAL_ID, lookup } from "coral-framework/lib/relay";
import { GQLResolver } from "coral-framework/schema";
import {
createResolversStub,
@@ -40,7 +39,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -77,6 +75,8 @@ it("logs out", async () => {
signOutButton.props.onClick();
await wait(() => {
expect(lookup(context.relayEnvironment, LOCAL_ID)!.loggedIn).toBeFalsy();
restMock.verify();
// Wait for new session to start.
expect((context.clearSession as SinonStub).called).toBe(true);
});
});
@@ -52,7 +52,6 @@ const createTestRenderer = async (
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -33,7 +33,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -51,7 +51,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -35,7 +35,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -33,7 +33,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -33,7 +33,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -32,7 +32,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -33,7 +33,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -78,7 +78,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -53,7 +53,6 @@ async function createTestRenderer(
params.resolvers
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -1,12 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`get auth token from url 1`] = `
exports[`get access token from url 1`] = `
"{
\\"__id\\": \\"client:root.local\\",
\\"__typename\\": \\"Local\\",
\\"accessToken\\": \\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzMWIyNjU5MS00ZTlhLTQzODgtYTdmZi1lMWJkYzVkOTdjY2UifQ==\\",
\\"accessTokenJTI\\": \\"31b26591-4e9a-4388-a7ff-e1bdc5d97cce\\",
\\"loggedIn\\": true,
\\"view\\": \\"SIGN_IN\\",
\\"error\\": null
}"
@@ -27,7 +26,6 @@ exports[`init local state 1`] = `
\\"accessToken\\": \\"\\",
\\"accessTokenExp\\": null,
\\"accessTokenJTI\\": null,
\\"loggedIn\\": false,
\\"view\\": \\"SIGN_IN\\",
\\"error\\": null
}
@@ -39,7 +39,7 @@ it("set view from query", async () => {
restoreHistoryLocation();
});
it("get auth token from url", async () => {
it("get access token from url", async () => {
const restoreHistoryLocation = replaceHistoryLocation(
`http://localhost/#accessToken=${createAccessToken()}`
);
-1
View File
@@ -11,7 +11,6 @@ type Local {
accessToken: String
accessTokenExp: Int
accessTokenJTI: String
loggedIn: Boolean!
view: View!
error: String
}
@@ -340,7 +340,7 @@ exports[`auth configuration renders all auth enabled 1`] = `
>
<a
className="TextLink-root"
href="http://localhost/?view=FORGOT_PASSWORD#accessToken=access-token"
href="http://localhost/?view=FORGOT_PASSWORD"
onClick={[Function]}
>
Forgot your password?
@@ -122,7 +122,7 @@ it("do not render createPassword view when local auth is disabled", async () =>
await wait(() => expect(windowMock.closeStub.called).toBe(true));
});
it("send back auth token", async () => {
it("send back access token", async () => {
const { context } = await createTestRenderer({
Query: {
viewer: {
+10 -4
View File
@@ -1,8 +1,9 @@
import { get } from "lodash";
import sinon from "sinon";
import sinon, { SinonStub } from "sinon";
import { pureMerge } from "coral-common/utils";
import {
createAccessToken,
toJSON,
wait,
waitForElement,
@@ -165,7 +166,7 @@ it("shows server error", async () => {
it("submits form successfully", async () => {
const { form, context } = await createTestRenderer();
const { getByLabelText } = within(form!);
const accessToken = "access-token";
const accessToken = createAccessToken();
const emailAddressField = getByLabelText("Email Address");
const passwordField = getByLabelText("Password");
const submitButton = form!.find(
@@ -198,8 +199,13 @@ it("submits form successfully", async () => {
expect(toJSON(form!)).toMatchSnapshot();
// Wait for window hash to contain a token.
await wait(() => expect(location.hash).toBe(`#accessToken=${accessToken}`));
// Wait for new session to start.
await wait(() =>
expect((context.clearSession as SinonStub).calledWith(accessToken)).toBe(
true
)
);
restMock.verify();
});
+9 -4
View File
@@ -1,7 +1,8 @@
import { get, merge } from "lodash";
import sinon from "sinon";
import sinon, { SinonStub } from "sinon";
import {
createAccessToken,
toJSON,
wait,
waitForElement,
@@ -186,7 +187,7 @@ it("shows server error", async () => {
it("submits form successfully", async () => {
const { context, main, form } = await createTestRenderer();
const { getByLabelText } = within(form!);
const accessToken = "access-token";
const accessToken = createAccessToken();
const emailAddressField = getByLabelText("Email Address");
const usernameField = getByLabelText("Username");
const passwordField = getByLabelText("Password");
@@ -223,8 +224,12 @@ it("submits form successfully", async () => {
expect(toJSON(main)).toMatchSnapshot();
// Wait for window hash to contain a token.
await wait(() => expect(location.hash).toBe(`#accessToken=${accessToken}`));
// Wait for new session to start.
await wait(() =>
expect((context.clearSession as SinonStub).calledWith(accessToken)).toBe(
true
)
);
restMock.verify();
});
@@ -13,12 +13,7 @@ export async function commit(
{ rest, clearSession }: CoralContext
) {
const result = await signIn(rest, pick(input, ["email", "password"]));
// Put the token on the hash and clean the session.
// It'll be picked up by initLocalState.
location.hash = `accessToken=${result.token}`;
await clearSession();
// TODO: (cvle) A better way would be if `context.clearSession` would return the new session and
// we set the accessToken directly in there.
await clearSession(result.token);
}
export const withSignInMutation = createMutationContainer("signIn", commit);
@@ -16,10 +16,7 @@ export async function commit(
rest,
pick(input, ["email", "password", "username"])
);
// Put the token on the hash and clean the session.
// It'll be picked up by initLocalState.
location.hash = `accessToken=${result.token}`;
await clearSession();
await clearSession(result.token);
}
export const withSignUpMutation = createMutationContainer("signUp", commit);
@@ -62,7 +62,7 @@ export interface CoralContext {
eventEmitter: EventEmitter2;
/** Clear session data. */
clearSession: () => Promise<void>;
clearSession: (nextAccessToken?: string | null) => Promise<void>;
/** Controls router transitions (for tests) */
transitionControl?: TransitionControlData;
@@ -8,7 +8,11 @@ import { Environment, RecordSource, Store } from "relay-runtime";
import uuid from "uuid/v1";
import { getBrowserInfo } from "coral-framework/lib/browserInfo";
import { LOCAL_ID } from "coral-framework/lib/relay";
import {
commitLocalUpdatePromisified,
LOCAL_ID,
setAccessTokenInLocalState,
} from "coral-framework/lib/relay";
import {
createLocalStorage,
createPromisifiedStorage,
@@ -105,7 +109,7 @@ function createRelayEnvironment(
const tokenGetter: TokenGetter = () => {
const localState = source.get(LOCAL_ID);
if (localState) {
return (localState.loggedIn && localState.accessToken) || "";
return localState.accessToken || "";
}
return "";
};
@@ -145,7 +149,7 @@ function createMangedCoralContextProvider(
}
// This is called every time a user session starts or ends.
private clearSession = async () => {
private clearSession = async (nextAccessToken?: string | null) => {
// Clear session storage.
this.state.context.sessionStorage.clear();
@@ -173,6 +177,13 @@ function createMangedCoralContextProvider(
// a new token.
subscriptionClient.setAccessToken(newTokenGetter());
// Set next access token.
if (nextAccessToken) {
await commitLocalUpdatePromisified(newEnvironment, async store => {
setAccessTokenInLocalState(nextAccessToken, store);
});
}
// Propagate new context.
this.setState(
{
@@ -292,7 +303,7 @@ export default async function createManaged({
uuidGenerator: uuid,
// Noop, this is later replaced by the
// managed CoralContextProvider.
clearSession: () => Promise.resolve(),
clearSession: (nextAccessToken?: string | null) => Promise.resolve(),
};
// Initialize local state.
@@ -25,14 +25,13 @@ export function setAccessTokenInLocalState(
const localRecord = source.get(LOCAL_ID)!;
localRecord.setValue(accessToken || "", "accessToken");
if (accessToken) {
const { payload, expired } = parseJWT(accessToken);
const { payload } = parseJWT(accessToken);
localRecord.setValue(payload.exp, "accessTokenExp");
localRecord.setValue(payload.jti, "accessTokenJTI");
localRecord.setValue(!expired, "loggedIn");
// TODO: (cvle) maybe a timer to detect when accessToken has expired?
} else {
localRecord.setValue(null, "accessTokenExp");
localRecord.setValue(null, "accessTokenJTI");
localRecord.setValue(false, "loggedIn");
}
}
@@ -41,10 +40,6 @@ export async function initLocalBaseState(
{ localStorage }: CoralContext,
accessToken?: string | null
) {
if (accessToken === undefined) {
accessToken = await localStorage!.getItem("accessToken");
}
commitLocalUpdate(environment, s => {
const root = s.getRoot();
@@ -52,7 +47,7 @@ export async function initLocalBaseState(
const localRecord = createAndRetain(environment, s, LOCAL_ID, LOCAL_TYPE);
root.setLinkedRecord(localRecord, "local");
// Set auth token
// Set access token
setAccessTokenInLocalState(accessToken || null, s);
});
}
@@ -1,59 +0,0 @@
import { Environment, RecordSource } from "relay-runtime";
import sinon from "sinon";
import { CoralContext } from "coral-framework/lib/bootstrap";
import { LOCAL_ID } from "coral-framework/lib/relay";
import { createPromisifiedStorage } from "coral-framework/lib/storage";
import {
createAccessToken,
createRelayEnvironment,
} from "coral-framework/testHelpers";
import SetAccessTokenMutation from "./SetAccessTokenMutation";
let environment: Environment;
const source: RecordSource = new RecordSource();
beforeAll(() => {
environment = createRelayEnvironment({
source,
});
});
const accessToken = createAccessToken();
it("Sets auth token to localStorage", async () => {
const clearSessionStub = sinon.stub();
const context: Partial<CoralContext> = {
localStorage: createPromisifiedStorage(),
clearSession: clearSessionStub,
};
await SetAccessTokenMutation.commit(
environment,
{ accessToken },
context as any
);
expect(source.get(LOCAL_ID)!.accessToken).toEqual(accessToken);
await expect(context.localStorage!.getItem("accessToken")).resolves.toEqual(
accessToken
);
expect(clearSessionStub.calledOnce).toBe(true);
});
it("Removes auth token from localStorage", async () => {
const clearSessionStub = sinon.stub();
const context: Partial<CoralContext> = {
localStorage: createPromisifiedStorage(),
clearSession: clearSessionStub,
};
localStorage.setItem("accessToken", accessToken);
await SetAccessTokenMutation.commit(
environment,
{ accessToken: null },
context as any
);
await expect(
context.localStorage!.getItem("accessToken")
).resolves.toBeNull();
expect(clearSessionStub.calledOnce).toBe(true);
});
@@ -1,11 +1,7 @@
import { Environment } from "relay-runtime";
import { CoralContext } from "coral-framework/lib/bootstrap";
import {
commitLocalUpdatePromisified,
createMutation,
setAccessTokenInLocalState,
} from "coral-framework/lib/relay";
import { createMutation } from "coral-framework/lib/relay";
interface SetAccessTokenInput {
accessToken: string | null;
@@ -16,18 +12,10 @@ const SetAccessTokenMutation = createMutation(
async (
environment: Environment,
input: SetAccessTokenInput,
{ localStorage, clearSession }: CoralContext
{ clearSession }: CoralContext
) => {
await commitLocalUpdatePromisified(environment, async store => {
setAccessTokenInLocalState(input.accessToken, store);
if (input.accessToken) {
await localStorage.setItem("accessToken", input.accessToken);
} else {
await localStorage.removeItem("accessToken");
}
// Clear current session, as we are starting a new one.
await clearSession();
});
// Clear current session, as we are starting a new one.
await clearSession(input.accessToken);
}
);
@@ -16,6 +16,7 @@ import { RestClient } from "coral-framework/lib/rest";
import { createPromisifiedStorage } from "coral-framework/lib/storage";
import { createUUIDGenerator } from "coral-framework/testHelpers";
import sinon from "sinon";
import createFluentBundle from "./createFluentBundle";
import createRelayEnvironment from "./createRelayEnvironment";
import createSubscriptionHandler, {
@@ -102,13 +103,12 @@ export default function createTestRenderer<
browserInfo: params.browserInfo || { ios: false },
uuidGenerator: createUUIDGenerator(),
eventEmitter: new EventEmitter2({ wildcard: true, maxListeners: 20 }),
clearSession: () => Promise.resolve(),
clearSession: sinon.stub(),
transitionControl: {
allowTransition: true,
history: [],
},
};
let testRenderer: ReactTestRenderer;
TestRenderer.act(() => {
testRenderer = TestRenderer.create(
@@ -31,16 +31,16 @@ export class TabBarContainer extends Component<Props> {
public render() {
const {
local: { loggedIn, activeTab },
local: { activeTab },
viewer,
} = this.props;
return (
<TabBar
activeTab={activeTab}
showProfileTab={loggedIn}
showProfileTab={Boolean(viewer)}
showConfigureTab={
!!this.props.viewer &&
can(this.props.viewer, Ability.CHANGE_STORY_CONFIGURATION)
!!viewer && can(viewer, Ability.CHANGE_STORY_CONFIGURATION)
}
onTabClick={this.handleSetActiveTab}
/>
@@ -52,7 +52,6 @@ const enhanced = withSetActiveTabMutation(
withLocalStateContainer(
graphql`
fragment TabBarContainerLocal on Local {
loggedIn
activeTab
}
`
@@ -5,7 +5,7 @@ import { createSinonStub } from "coral-framework/testHelpers";
import { OnPostMessageSetAccessToken } from "./OnPostMessageSetAccessToken";
it("Listens to event and sets auth token", () => {
it("Listens to event and sets access token", () => {
const token = "access-token";
const postMessage: any = {
on: (name: string, cb: (token: string) => void) => {
@@ -17,6 +17,7 @@ it("renders fully", () => {
focus: false,
view: "SIGN_IN",
},
accessToken: "access-token",
accessTokenJTI: "JTI",
},
viewer: null,
@@ -74,6 +75,7 @@ it("renders without logout button", () => {
focus: false,
view: "SIGN_IN",
},
accessToken: "access-token",
accessTokenJTI: null,
},
viewer: null,
@@ -131,6 +133,7 @@ it("renders sso only", () => {
focus: false,
view: "SIGN_IN",
},
accessToken: "access-token",
accessTokenJTI: "JTI",
},
viewer: null,
@@ -188,6 +191,7 @@ it("renders sso only without logout button", () => {
focus: false,
view: "SIGN_IN",
},
accessToken: "access-token",
accessTokenJTI: "JTI",
},
viewer: null,
@@ -245,6 +249,7 @@ it("renders without register button", () => {
focus: false,
view: "SIGN_IN",
},
accessToken: "access-token",
accessTokenJTI: "JTI",
},
viewer: null,
@@ -45,7 +45,9 @@ export class UserBoxContainer extends Component<Props> {
private handleSignOut = () => this.props.signOut();
private get supportsLogout() {
return !!this.props.local.accessTokenJTI;
return Boolean(
!this.props.local.accessToken || this.props.local.accessTokenJTI
);
}
private get supportsRegister() {
@@ -125,6 +127,7 @@ const enhanced = withSignOutMutation(
focus
view
}
accessToken
accessTokenJTI
}
`
@@ -15,7 +15,6 @@ exports[`init local state 1`] = `
\\"accessToken\\": \\"\\",
\\"accessTokenExp\\": null,
\\"accessTokenJTI\\": null,
\\"loggedIn\\": false,
\\"commentsOrderBy\\": \\"CREATED_AT_DESC\\",
\\"authPopup\\": {
\\"__ref\\": \\"client:root.local.authPopup\\"
@@ -5,7 +5,6 @@ import { CoralContext } from "coral-framework/lib/bootstrap";
import { LOCAL_ID } from "coral-framework/lib/relay";
import { createPromisifiedStorage } from "coral-framework/lib/storage";
import {
createAccessToken,
createRelayEnvironment,
replaceHistoryLocation,
} from "coral-framework/testHelpers";
@@ -57,13 +56,3 @@ it("set commentID from query", async () => {
expect(source.get(LOCAL_ID)!.commentID).toBe(commentID);
restoreHistoryLocation();
});
it("set accessToken from localStorage", async () => {
const context: Partial<CoralContext> = {
localStorage: createPromisifiedStorage(),
};
const accessToken = createAccessToken();
context.localStorage!.setItem("accessToken", accessToken);
await initLocalState(environment, context as any);
expect(source.get(LOCAL_ID)!.accessToken).toBe(accessToken);
});
@@ -46,7 +46,6 @@ type Local {
accessToken: String
accessTokenExp: Int
accessTokenJTI: String
loggedIn: Boolean!
activeTab: TAB!
authPopup: AuthPopup!
storyID: String
@@ -23,7 +23,7 @@ class ReportButtonContainer extends React.Component<
ReportButtonContainerProps
> {
private get loggedIn() {
return this.props.viewer;
return Boolean(this.props.viewer);
}
private handleSignIn = () => this.props.showAuthPopup({ view: "SIGN_IN" });
@@ -19,9 +19,6 @@ type Props = PropTypesOf<typeof PostCommentFormContainerN>;
function createDefaultProps(add: DeepPartial<Props> = {}): Props {
return pureMerge(
{
local: {
loggedIn: true,
},
showAuthPopup: noop as any,
createComment: noop as any,
refreshSettings: noop as any,
@@ -49,6 +46,9 @@ function createDefaultProps(add: DeepPartial<Props> = {}): Props {
message: "",
},
},
viewer: {
id: "viewer-id",
},
},
add
);
@@ -11,13 +11,12 @@ import {
graphql,
withFetch,
withFragmentContainer,
withLocalStateContainer,
} from "coral-framework/lib/relay";
import { PromisifiedStorage } from "coral-framework/lib/storage";
import { PropTypesOf } from "coral-framework/types";
import { PostCommentFormContainer_settings as SettingsData } from "coral-stream/__generated__/PostCommentFormContainer_settings.graphql";
import { PostCommentFormContainer_story as StoryData } from "coral-stream/__generated__/PostCommentFormContainer_story.graphql";
import { PostCommentFormContainerLocal as Local } from "coral-stream/__generated__/PostCommentFormContainerLocal.graphql";
import { PostCommentFormContainer_settings } from "coral-stream/__generated__/PostCommentFormContainer_settings.graphql";
import { PostCommentFormContainer_story } from "coral-stream/__generated__/PostCommentFormContainer_story.graphql";
import { PostCommentFormContainer_viewer } from "coral-stream/__generated__/PostCommentFormContainer_viewer.graphql";
import {
ShowAuthPopupMutation,
withShowAuthPopupMutation,
@@ -42,9 +41,9 @@ interface Props {
createComment: CreateCommentMutation;
refreshSettings: FetchProp<typeof RefreshSettingsFetch>;
sessionStorage: PromisifiedStorage;
settings: SettingsData;
local: Local;
story: StoryData;
settings: PostCommentFormContainer_settings;
viewer: PostCommentFormContainer_viewer | null;
story: PostCommentFormContainer_story;
showAuthPopup: ShowAuthPopupMutation;
}
@@ -71,7 +70,7 @@ export class PostCommentFormContainer extends Component<Props, State> {
initialized: false,
nudge: true,
keepFormWhenClosed:
this.props.local.loggedIn &&
!!this.props.viewer &&
!this.props.story.isClosed &&
!this.props.settings.disableCommenting.enabled,
submitStatus: null,
@@ -194,7 +193,7 @@ export class PostCommentFormContainer extends Component<Props, State> {
);
}
}
if (!this.props.local.loggedIn) {
if (!this.props.viewer) {
return (
<PostCommentFormFake
draft={this.state.notLoggedInDraft}
@@ -243,44 +242,41 @@ const enhanced = withContext(({ sessionStorage }) => ({
withShowAuthPopupMutation(
withCreateCommentMutation(
withFetch(RefreshSettingsFetch)(
withLocalStateContainer(
graphql`
fragment PostCommentFormContainerLocal on Local {
loggedIn
withFragmentContainer<Props>({
settings: graphql`
fragment PostCommentFormContainer_settings on Settings {
charCount {
enabled
min
max
}
disableCommenting {
enabled
message
}
closeCommenting {
message
}
}
`
)(
withFragmentContainer<Props>({
settings: graphql`
fragment PostCommentFormContainer_settings on Settings {
charCount {
`,
story: graphql`
fragment PostCommentFormContainer_story on Story {
id
isClosed
...MessageBoxContainer_story
settings {
messageBox {
enabled
min
max
}
disableCommenting {
enabled
message
}
closeCommenting {
message
}
}
`,
story: graphql`
fragment PostCommentFormContainer_story on Story {
id
isClosed
...MessageBoxContainer_story
settings {
messageBox {
enabled
}
}
}
`,
})(PostCommentFormContainer)
)
}
`,
viewer: graphql`
fragment PostCommentFormContainer_viewer on User {
id
}
`,
})(PostCommentFormContainer)
)
)
)
@@ -85,6 +85,7 @@ export const StreamContainer: FunctionComponent<Props> = props => {
<PostCommentFormContainer
settings={props.settings}
story={props.story}
viewer={props.viewer}
/>
)}
{banned && <BannedInfo />}
@@ -170,6 +171,7 @@ const enhanced = withFragmentContainer<Props>({
...UserBoxContainer_viewer
...CreateCommentReplyMutation_viewer
...CreateCommentMutation_viewer
...PostCommentFormContainer_viewer
status {
current
}
@@ -8,7 +8,6 @@ export default function create(params: CreateTestRendererParams) {
initLocalState: (localRecord, source, environment) => {
localRecord.setValue("COMMENTS", "activeTab");
localRecord.setValue("ALL_COMMENTS", "commentsTab");
localRecord.setValue(false, "loggedIn");
localRecord.setValue("jti", "accessTokenJTI");
localRecord.setValue("CREATED_AT_DESC", "commentsOrderBy");
if (params.initLocalState) {
@@ -60,7 +60,6 @@ const createTestRenderer = async (
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(storyFixture.id, "storyID");
localRecord.setValue(commentFixture.id, "commentID");
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -51,7 +51,6 @@ async function createTestRenderer(
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(story.id, "storyID");
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -52,7 +52,6 @@ async function createTestRenderer(
resolvers,
initLocalState: localRecord => {
localRecord.setValue(stories[0].id, "storyID");
localRecord.setValue(true, "loggedIn");
},
});
@@ -44,7 +44,6 @@ async function createTestRenderer(
resolvers,
initLocalState: localRecord => {
localRecord.setValue(stories[0].id, "storyID");
localRecord.setValue(true, "loggedIn");
},
});
@@ -44,7 +44,6 @@ async function createTestRenderer(
resolvers,
initLocalState: localRecord => {
localRecord.setValue(stories[0].id, "storyID");
localRecord.setValue(true, "loggedIn");
},
});
@@ -69,7 +69,6 @@ function createTestRenderer(
resolvers,
initLocalState: localRecord => {
localRecord.setValue(storyWithReplies.id, "storyID");
localRecord.setValue(true, "loggedIn");
},
});
return testRenderer;
@@ -38,7 +38,6 @@ async function createTestRenderer(
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(story.id, "storyID");
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -31,7 +31,6 @@ async function createTestRenderer(
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(story.id, "storyID");
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -33,7 +33,6 @@ async function createTestRenderer(
),
initLocalState: (localRecord, source, environment) => {
localRecord.setValue(story.id, "storyID");
localRecord.setValue(true, "loggedIn");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
}
@@ -45,7 +45,6 @@ async function createTestRenderer(
resolvers,
initLocalState: localRecord => {
localRecord.setValue(stories[0].id, "storyID");
localRecord.setValue(true, "loggedIn");
},
});
@@ -60,7 +60,6 @@ beforeEach(() => {
resolvers,
initLocalState: localRecord => {
localRecord.setValue(storyWithDeepestReplies.id, "storyID");
localRecord.setValue(true, "loggedIn");
},
}));
});
@@ -41,7 +41,6 @@ async function createTestRenderer(
resolvers,
initLocalState: localRecord => {
localRecord.setValue(stories[0].id, "storyID");
localRecord.setValue(true, "loggedIn");
},
});
@@ -57,7 +57,6 @@ function createTestRenderer() {
resolvers,
initLocalState: localRecord => {
localRecord.setValue(stories[0].id, "storyID");
localRecord.setValue(true, "loggedIn");
},
});
return { testRenderer, resolvers };
@@ -32,7 +32,6 @@ async function createTestRenderer(
resolvers,
initLocalState: localRecord => {
localRecord.setValue(storyWithNoComments.id, "storyID");
localRecord.setValue(Boolean(data.loggedIn), "loggedIn");
},
});
@@ -66,7 +66,6 @@ function createTestRenderer(
resolvers,
initLocalState: localRecord => {
localRecord.setValue(stories[0].id, "storyID");
localRecord.setValue(true, "loggedIn");
},
});
return { testRenderer, resolvers };
@@ -11,7 +11,6 @@ export default function create(params: CreateTestRendererParams) {
initLocalState: (localRecord, source, environment) => {
localRecord.setValue("PROFILE", "activeTab");
localRecord.setValue("jti", "accessTokenJTI");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(story.id, "storyID");
if (params.initLocalState) {
params.initLocalState(localRecord, source, environment);
@@ -13,7 +13,6 @@
box-sizing: border-box;
border-radius: var(--round-corners);
width: 100%;
line-height: 36px;
align-self: stretch;
&:read-only {
+1 -1
View File
@@ -183,7 +183,7 @@
font-weight: var(--font-weight-regular);
font-family: var(--font-family-sans-serif);
font-size: calc(16rem / var(--rem-base));
line-height: calc(18em / 16);
line-height: 1;
letter-spacing: calc(0.2em / 16);
color: var(--palette-text-primary);
}