Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3354165
D9500.id33744.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
18 KB
Referenced Files
None
Subscribers
None
D9500.id33744.diff
View Options
diff --git a/keyserver/src/session/cookies.js b/keyserver/src/session/cookies.js
--- a/keyserver/src/session/cookies.js
+++ b/keyserver/src/session/cookies.js
@@ -14,8 +14,6 @@
import {
type ServerSessionChange,
cookieLifetime,
- cookieSources,
- type CookieSource,
cookieTypes,
sessionIdentifierTypes,
type SessionIdentifierType,
@@ -25,7 +23,6 @@
import type { UserInfo } from 'lib/types/user-types.js';
import { isDev } from 'lib/utils/dev-utils.js';
import { values } from 'lib/utils/objects.js';
-import { promiseAll } from 'lib/utils/promises.js';
import {
isBcryptHash,
@@ -59,29 +56,26 @@
};
type FetchViewerResult =
- | { type: 'valid', viewer: Viewer }
+ | { +type: 'valid', +viewer: Viewer }
| InvalidFetchViewerResult;
type InvalidFetchViewerResult =
| {
- type: 'nonexistant',
- cookieName: ?string,
- cookieSource: ?CookieSource,
- sessionParameterInfo: SessionParameterInfo,
+ +type: 'nonexistant',
+ +cookieName: ?string,
+ +sessionParameterInfo: SessionParameterInfo,
}
| {
- type: 'invalidated',
- cookieName: string,
- cookieID: string,
- cookieSource: CookieSource,
- sessionParameterInfo: SessionParameterInfo,
- platformDetails: ?PlatformDetails,
- deviceToken: ?string,
+ +type: 'invalidated',
+ +cookieName: string,
+ +cookieID: string,
+ +sessionParameterInfo: SessionParameterInfo,
+ +platformDetails: ?PlatformDetails,
+ +deviceToken: ?string,
};
async function fetchUserViewer(
cookie: string,
- cookieSource: CookieSource,
sessionParameterInfo: SessionParameterInfo,
): Promise<FetchViewerResult> {
const [cookieID, cookiePassword] = cookie.split(':');
@@ -89,7 +83,6 @@
return {
type: 'nonexistant',
cookieName: cookieTypes.USER,
- cookieSource,
sessionParameterInfo,
};
}
@@ -107,7 +100,6 @@
return {
type: 'nonexistant',
cookieName: cookieTypes.USER,
- cookieSource,
sessionParameterInfo,
};
}
@@ -141,7 +133,6 @@
type: 'invalidated',
cookieName: cookieTypes.USER,
cookieID,
- cookieSource,
sessionParameterInfo,
platformDetails,
deviceToken,
@@ -155,7 +146,6 @@
platformDetails,
deviceToken,
userID,
- cookieSource,
cookieID,
cookiePassword,
cookieHash,
@@ -171,7 +161,6 @@
async function fetchAnonymousViewer(
cookie: string,
- cookieSource: CookieSource,
sessionParameterInfo: SessionParameterInfo,
): Promise<FetchViewerResult> {
const [cookieID, cookiePassword] = cookie.split(':');
@@ -179,7 +168,6 @@
return {
type: 'nonexistant',
cookieName: cookieTypes.ANONYMOUS,
- cookieSource,
sessionParameterInfo,
};
}
@@ -197,7 +185,6 @@
return {
type: 'nonexistant',
cookieName: cookieTypes.ANONYMOUS,
- cookieSource,
sessionParameterInfo,
};
}
@@ -231,7 +218,6 @@
type: 'invalidated',
cookieName: cookieTypes.ANONYMOUS,
cookieID,
- cookieSource,
sessionParameterInfo,
platformDetails,
deviceToken,
@@ -243,7 +229,6 @@
id: cookieID,
platformDetails,
deviceToken,
- cookieSource,
cookieID,
cookiePassword,
cookieHash,
@@ -297,7 +282,6 @@
return {
type: 'nonexistant',
cookieName: null,
- cookieSource: null,
sessionParameterInfo,
};
}
@@ -306,7 +290,6 @@
return {
type: 'nonexistant',
cookieName: null,
- cookieSource: cookieSources.BODY,
sessionParameterInfo,
};
}
@@ -314,28 +297,18 @@
return {
type: 'nonexistant',
cookieName: null,
- cookieSource: null,
sessionParameterInfo,
};
}
const [type, cookie] = cookiePair.split('=');
if (type === cookieTypes.USER && cookie) {
- return await fetchUserViewer(
- cookie,
- cookieSources.BODY,
- sessionParameterInfo,
- );
+ return await fetchUserViewer(cookie, sessionParameterInfo);
} else if (type === cookieTypes.ANONYMOUS && cookie) {
- return await fetchAnonymousViewer(
- cookie,
- cookieSources.BODY,
- sessionParameterInfo,
- );
+ return await fetchAnonymousViewer(cookie, sessionParameterInfo);
}
return {
type: 'nonexistant',
cookieName: null,
- cookieSource: null,
sessionParameterInfo,
};
}
@@ -389,7 +362,7 @@
async function fetchViewerForSocket(
req: $Request,
clientMessage: InitialClientSocketMessage,
-): Promise<?Viewer> {
+): Promise<Viewer> {
assertSecureRequest(req);
const { sessionIdentification } = clientMessage.payload;
const { sessionID } = sessionIdentification;
@@ -412,30 +385,26 @@
return result.viewer;
}
- const promises = {};
- if (result.cookieSource === cookieSources.BODY) {
- // We initialize a socket's Viewer after the WebSocket handshake, since to
- // properly initialize the Viewer we need a bunch of data, but that data
- // can't be sent until after the handshake. Consequently, by the time we
- // know that a cookie may be invalid, we are no longer communicating via
- // HTTP, and have no way to set a new cookie for HEADER (web) clients.
- const platformDetails =
- result.type === 'invalidated' ? result.platformDetails : null;
- const deviceToken =
- result.type === 'invalidated' ? result.deviceToken : null;
- promises.anonymousViewerData = createNewAnonymousCookie({
- platformDetails,
- deviceToken,
- });
- }
- if (result.type === 'invalidated') {
- promises.deleteCookie = deleteCookie(result.cookieID);
- }
- const { anonymousViewerData } = await promiseAll(promises);
-
- if (!anonymousViewerData) {
- return null;
- }
+ const anonymousViewerDataPromise: Promise<AnonymousViewerData> =
+ (async () => {
+ const platformDetails =
+ result.type === 'invalidated' ? result.platformDetails : null;
+ const deviceToken =
+ result.type === 'invalidated' ? result.deviceToken : null;
+ return await createNewAnonymousCookie({
+ platformDetails,
+ deviceToken,
+ });
+ })();
+ const deleteCookiePromise = (async () => {
+ if (result.type === 'invalidated') {
+ await deleteCookie(result.cookieID);
+ }
+ })();
+ const [anonymousViewerData] = await Promise.all([
+ anonymousViewerDataPromise,
+ deleteCookiePromise,
+ ]);
return createViewerForInvalidFetchViewerResult(result, anonymousViewerData);
}
@@ -466,17 +435,8 @@
result: InvalidFetchViewerResult,
anonymousViewerData: AnonymousViewerData,
): Viewer {
- // If a null cookie was specified in the request body, result.cookieSource
- // will still be BODY here. The only way it would be null or undefined here
- // is if there was no cookie specified in either the body or the header, in
- // which case we default to returning the new cookie in the response header.
- const cookieSource =
- result.cookieSource !== null && result.cookieSource !== undefined
- ? result.cookieSource
- : cookieSources.HEADER;
const viewer = new Viewer({
...anonymousViewerData,
- cookieSource,
sessionIdentifierType: result.sessionParameterInfo.sessionIdentifierType,
isSocket: result.sessionParameterInfo.isSocket,
ipAddress: result.sessionParameterInfo.ipAddress,
@@ -521,9 +481,7 @@
userInfos: (values(userInfos).map(a => a): UserInfo[]),
}: ServerSessionChange);
}
- if (viewer.cookieSource === cookieSources.BODY) {
- sessionChange.cookie = viewer.cookiePairString;
- }
+ sessionChange.cookie = viewer.cookiePairString;
if (viewer.sessionIdentifierType === sessionIdentifierTypes.BODY_SESSION_ID) {
sessionChange.sessionID = viewer.sessionID ? viewer.sessionID : null;
}
@@ -539,10 +497,10 @@
// The result of this function should not be passed directly to the Viewer
// constructor. Instead, it should be passed to viewer.setNewCookie. There are
// several fields on AnonymousViewerData that are not set by this function:
-// sessionIdentifierType, cookieSource, ipAddress, and userAgent. These
-// parameters all depend on the initial request. If the result of this function
-// is passed to the Viewer constructor directly, the resultant Viewer object
-// will throw whenever anybody attempts to access the relevant properties.
+// sessionIdentifierType, ipAddress, and userAgent. These parameters all depend
+// on the initial request. If the result of this function is passed to the
+// Viewer constructor directly, the resultant Viewer object will throw whenever
+// anybody attempts to access the relevant properties.
async function createNewAnonymousCookie(
params: AnonymousCookieCreationParams,
): Promise<AnonymousViewerData> {
@@ -600,10 +558,10 @@
// The result of this function should never be passed directly to the Viewer
// constructor. Instead, it should be passed to viewer.setNewCookie. There are
// several fields on UserViewerData that are not set by this function:
-// sessionID, sessionIdentifierType, cookieSource, and ipAddress. These
-// parameters all depend on the initial request. If the result of this function
-// is passed to the Viewer constructor directly, the resultant Viewer object
-// will throw whenever anybody attempts to access the relevant properties.
+// sessionID, sessionIdentifierType, and ipAddress. These parameters all depend
+// on the initial request. If the result of this function is passed to the
+// Viewer constructor directly, the resultant Viewer object will throw whenever
+// anybody attempts to access the relevant properties.
async function createNewUserCookie(
userID: string,
params: UserCookieCreationParams,
diff --git a/keyserver/src/session/viewer.js b/keyserver/src/session/viewer.js
--- a/keyserver/src/session/viewer.js
+++ b/keyserver/src/session/viewer.js
@@ -6,7 +6,6 @@
import type { Platform, PlatformDetails } from 'lib/types/device-types.js';
import type { CalendarQuery } from 'lib/types/entry-types.js';
import {
- type CookieSource,
type SessionIdentifierType,
cookieTypes,
type CookieType,
@@ -21,7 +20,6 @@
+deviceToken: ?string,
+userID: string,
+cookieID: ?string,
- +cookieSource?: CookieSource,
+cookiePassword: ?string,
+cookieHash: ?string,
+cookieInsertedThisRequest?: boolean,
@@ -39,7 +37,6 @@
+id: string,
+platformDetails: ?PlatformDetails,
+deviceToken: ?string,
- +cookieSource?: CookieSource,
+cookieID: string,
+cookiePassword: ?string,
+cookieHash: ?string,
@@ -82,14 +79,6 @@
}
setNewCookie(data: ViewerData) {
- if (data.cookieSource === null || data.cookieSource === undefined) {
- if (data.loggedIn) {
- data = { ...data, cookieSource: this.cookieSource };
- } else {
- // This is a separate condition because of Flow
- data = { ...data, cookieSource: this.cookieSource };
- }
- }
if (
data.sessionIdentifierType === null ||
data.sessionIdentifierType === undefined
@@ -184,15 +173,6 @@
return this.data.loggedIn;
}
- get cookieSource(): CookieSource {
- const { cookieSource } = this.data;
- invariant(
- cookieSource !== null && cookieSource !== undefined,
- 'Viewer.cookieSource should be set',
- );
- return cookieSource;
- }
-
get cookieID(): string {
const { cookieID } = this.data;
invariant(
diff --git a/keyserver/src/socket/socket.js b/keyserver/src/socket/socket.js
--- a/keyserver/src/socket/socket.js
+++ b/keyserver/src/socket/socket.js
@@ -22,7 +22,6 @@
import { redisMessageTypes, type RedisMessage } from 'lib/types/redis-types.js';
import { serverRequestTypes } from 'lib/types/request-types.js';
import {
- cookieSources,
sessionCheckFrequency,
stateCheckInactivityActivationInterval,
} from 'lib/types/session-types.js';
@@ -74,11 +73,12 @@
import {
fetchViewerForSocket,
updateCookie,
- createNewAnonymousCookie,
isCookieMissingSignedIdentityKeysBlob,
isCookieMissingOlmNotificationsSession,
+ createNewAnonymousCookie,
} from '../session/cookies.js';
import { Viewer } from '../session/viewer.js';
+import type { AnonymousViewerData } from '../session/viewer.js';
import { serverStateSyncSpecs } from '../shared/state-sync/state-sync-specs.js';
import { commitSessionUpdate } from '../updaters/session-updaters.js';
import { compressMessage } from '../utils/compress.js';
@@ -209,12 +209,6 @@
this.httpRequest,
clientSocketMessage,
);
- if (!this.viewer) {
- // This indicates that the cookie was invalid, but the client is using
- // cookieSources.HEADER and thus can't accept a new cookie over
- // WebSockets. See comment under catch block for socket_deauthorized.
- throw new ServerError('socket_deauthorized');
- }
}
const { viewer } = this;
if (!viewer) {
@@ -284,59 +278,56 @@
invariant(clientSocketMessage, 'should be set');
const responseTo = clientSocketMessage.id;
if (error.message === 'socket_deauthorized') {
+ invariant(this.viewer, 'should be set');
const authErrorMessage: AuthErrorServerSocketMessage = {
type: serverSocketMessageTypes.AUTH_ERROR,
responseTo,
message: error.message,
- };
- if (this.viewer) {
- // viewer should only be falsey for cookieSources.HEADER (web)
- // clients. Usually if the cookie is invalid we construct a new
- // anonymous Viewer with a new cookie, and then pass the cookie down
- // in the error. But we can't pass HTTP cookies in WebSocket messages.
- authErrorMessage.sessionChange = {
+ sessionChange: {
cookie: this.viewer.cookiePairString,
currentUserInfo: {
anonymous: true,
},
- };
- }
+ },
+ };
+
await this.sendMessage(authErrorMessage);
this.ws.close(4100, error.message);
return;
} else if (error.message === 'client_version_unsupported') {
const { viewer } = this;
invariant(viewer, 'should be set');
- const promises = {};
- promises.deleteCookie = deleteCookie(viewer.cookieID);
- if (viewer.cookieSource !== cookieSources.BODY) {
- promises.anonymousViewerData = createNewAnonymousCookie({
+
+ const anonymousViewerDataPromise: Promise<AnonymousViewerData> =
+ createNewAnonymousCookie({
platformDetails: error.platformDetails,
deviceToken: viewer.deviceToken,
});
- }
- const { anonymousViewerData } = await promiseAll(promises);
+ const deleteCookiePromise = deleteCookie(viewer.cookieID);
+ const [anonymousViewerData] = await Promise.all([
+ anonymousViewerDataPromise,
+ deleteCookiePromise,
+ ]);
+
+ // It is normally not safe to pass the result of
+ // createNewAnonymousCookie to the Viewer constructor. That is because
+ // createNewAnonymousCookie leaves several fields of
+ // AnonymousViewerData unset, and consequently Viewer will throw when
+ // access is attempted. It is only safe here because we can guarantee
+ // that only cookiePairString and cookieID are accessed on anonViewer
+ // below.
+ const anonViewer = new Viewer(anonymousViewerData);
const authErrorMessage: AuthErrorServerSocketMessage = {
type: serverSocketMessageTypes.AUTH_ERROR,
responseTo,
message: error.message,
- };
- if (anonymousViewerData) {
- // It is normally not safe to pass the result of
- // createNewAnonymousCookie to the Viewer constructor. That is because
- // createNewAnonymousCookie leaves several fields of
- // AnonymousViewerData unset, and consequently Viewer will throw when
- // access is attempted. It is only safe here because we can guarantee
- // that only cookiePairString and cookieID are accessed on anonViewer
- // below.
- const anonViewer = new Viewer(anonymousViewerData);
- authErrorMessage.sessionChange = {
+ sessionChange: {
cookie: anonViewer.cookiePairString,
currentUserInfo: {
anonymous: true,
},
- };
- }
+ },
+ };
await this.sendMessage(authErrorMessage);
this.ws.close(4101, error.message);
return;
diff --git a/lib/socket/socket.react.js b/lib/socket/socket.react.js
--- a/lib/socket/socket.react.js
+++ b/lib/socket/socket.react.js
@@ -493,8 +493,6 @@
);
if (!recoverySessionChange && sessionChange) {
- // This should only happen in the cookieSources.BODY (native) case when
- // the resolution attempt failed
const { cookie: newerCookie, currentUserInfo } = sessionChange;
this.props.dispatch({
type: setNewSessionActionType,
diff --git a/lib/types/session-types.js b/lib/types/session-types.js
--- a/lib/types/session-types.js
+++ b/lib/types/session-types.js
@@ -19,21 +19,6 @@
// How long the server debounces after activity before initiating a state check
export const stateCheckInactivityActivationInterval = 3 * 1000; // in milliseconds
-// On native, we specify the cookie directly in the request and response body.
-// We do this because:
-// (1) We don't have the same XSS risks as we do on web, so there is no need to
-// prevent JavaScript from knowing the cookie password.
-// (2) In the past the internal cookie logic on Android has been buggy.
-// https://github.com/facebook/react-native/issues/12956 is an example
-// issue. By specifying the cookie in the body we retain full control of how
-// that data is passed, without necessitating any native modules like
-// react-native-cookies.
-export const cookieSources = Object.freeze({
- BODY: 0,
- HEADER: 1,
-});
-export type CookieSource = $Values<typeof cookieSources>;
-
// On native, we use the cookieID as a unique session identifier. This is
// because there is no way to have two instances of an app running. On the other
// hand, on web it is possible to have two sessions open using the same cookie,
diff --git a/lib/types/socket-types.js b/lib/types/socket-types.js
--- a/lib/types/socket-types.js
+++ b/lib/types/socket-types.js
@@ -335,14 +335,12 @@
});
export type AuthErrorServerSocketMessage = {
- type: 3,
- responseTo: number,
- message: string,
- // If unspecified, it is because the client is using cookieSources.HEADER,
- // which means the server can't update the cookie from a socket message.
- sessionChange?: {
- cookie: string,
- currentUserInfo: LoggedOutUserInfo,
+ +type: 3,
+ +responseTo: number,
+ +message: string,
+ +sessionChange: {
+ +cookie: string,
+ +currentUserInfo: LoggedOutUserInfo,
},
};
export const authErrorServerSocketMessageValidator: TInterface<AuthErrorServerSocketMessage> =
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Nov 24, 12:08 PM (21 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2574754
Default Alt Text
D9500.id33744.diff (18 KB)
Attached To
Mode
D9500: [keyserver] Remove cookieSources
Attached
Detach File
Event Timeline
Log In to Comment