mirror of
https://github.com/wassname/talk.git
synced 2026-07-28 11:27:05 +08:00
[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:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user