Micro Services: Fix logout issue (#19423)

pull/19388/head
Rodrigo Nascimento 5 years ago committed by GitHub
parent aacbdd98ff
commit edd8259742
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      client/lib/meteorCallWrapper.ts
  2. 30
      ee/server/services/account/Account.ts
  3. 4
      ee/server/services/ddp-streamer/Client.ts
  4. 2
      ee/server/services/ddp-streamer/Publication.ts
  5. 34
      ee/server/services/ddp-streamer/configureServer.ts
  6. 2
      ee/server/services/ecosystem.config.js
  7. 20
      server/modules/notifications/notifications.module.ts
  8. 6
      server/modules/streamer/streamer.module.ts
  9. 2
      server/sdk/types/IAccount.ts
  10. 2
      server/sdk/types/IMeteor.ts

@ -6,6 +6,7 @@ import { APIClient } from '../../app/utils/client';
const bypassMethods: string[] = [ const bypassMethods: string[] = [
'setUserStatus', 'setUserStatus',
'logout',
]; ];
function shouldBypass({ method, params }: Meteor.IDDPMessage): boolean { function shouldBypass({ method, params }: Meteor.IDDPMessage): boolean {

@ -1,10 +1,10 @@
import { import {
IStampedToken,
_generateStampedLoginToken, _generateStampedLoginToken,
_hashStampedToken, _hashStampedToken,
_hashLoginToken, _hashLoginToken,
_tokenExpiration, _tokenExpiration,
validatePassword, validatePassword,
IHashedStampedToken,
} from './lib/utils'; } from './lib/utils';
import { getCollection, Collections } from '../mongo'; import { getCollection, Collections } from '../mongo';
import { IUser } from '../../../../definition/IUser'; import { IUser } from '../../../../definition/IUser';
@ -12,11 +12,25 @@ import { ServiceClass } from '../../../../server/sdk/types/ServiceClass';
import { IAccount, ILoginResult } from '../../../../server/sdk/types/IAccount'; import { IAccount, ILoginResult } from '../../../../server/sdk/types/IAccount';
const saveSession = async (uid: string, newToken: IStampedToken): Promise<void> => { const saveSession = async (uid: string, newToken: IHashedStampedToken): Promise<void> => {
const Users = await getCollection<IUser>(Collections.User); const Users = await getCollection<IUser>(Collections.User);
await Users.updateOne({ _id: uid }, { await Users.updateOne({ _id: uid }, {
$push: { $push: {
'services.resume.loginTokens': _hashStampedToken(newToken), 'services.resume.loginTokens': newToken.hashedToken,
},
});
};
const removeSession = async (uid: string, loginToken: string): Promise<void> => {
const Users = await getCollection<IUser>(Collections.User);
await Users.updateOne({ _id: uid }, {
$pull: {
'services.resume.loginTokens': {
$or: [
{ hashedToken: loginToken },
{ token: loginToken },
],
},
}, },
}); });
}; };
@ -43,6 +57,7 @@ const loginViaResume = async (resume: string): Promise<false | ILoginResult> =>
return { return {
uid: user._id, uid: user._id,
token: resume, token: resume,
hashedToken,
tokenExpires: when ? _tokenExpiration(when) : undefined, tokenExpires: when ? _tokenExpiration(when) : undefined,
type: 'resume', type: 'resume',
}; };
@ -62,11 +77,14 @@ const loginViaUsername = async ({ username }: {username: string}, password: stri
const newToken = _generateStampedLoginToken(); const newToken = _generateStampedLoginToken();
await saveSession(user._id, newToken); const hashedToken = _hashStampedToken(newToken);
await saveSession(user._id, hashedToken);
return { return {
uid: user._id, uid: user._id,
token: newToken.token, token: newToken.token,
hashedToken: hashedToken.hashedToken,
tokenExpires: _tokenExpiration(newToken.when), tokenExpires: _tokenExpiration(newToken.when),
type: 'password', type: 'password',
}; };
@ -86,4 +104,8 @@ export class Account extends ServiceClass implements IAccount {
return false; return false;
} }
async logout({ userId, token }: {userId: string; token: string}): Promise<void> {
return removeSession(userId, token);
}
} }

@ -26,9 +26,9 @@ export class Client extends EventEmitter {
public wait = false; public wait = false;
public userId: string; public userId?: string;
public userToken: string; public userToken?: string;
constructor( constructor(
public ws: WebSocket, public ws: WebSocket,

@ -55,7 +55,7 @@ export class Publication extends EventEmitter implements IPublication {
this.server.removed(this.client, collection, id); this.server.removed(this.client, collection, id);
} }
get userId(): string { get userId(): string | undefined {
return this.client.userId; return this.client.userId;
} }
} }

@ -1,6 +1,6 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { DDP_EVENTS } from './constants'; import { DDP_EVENTS, WS_ERRORS } from './constants';
import { Account, Presence, MeteorService } from '../../../../server/sdk'; import { Account, Presence, MeteorService } from '../../../../server/sdk';
import { USER_STATUS } from '../../../../definition/UserStatus'; import { USER_STATUS } from '../../../../definition/UserStatus';
import { Server } from './Server'; import { Server } from './Server';
@ -73,7 +73,7 @@ server.methods({
} }
this.userId = result.uid; this.userId = result.uid;
this.userToken = result.token; this.userToken = result.hashedToken;
this.emit(DDP_EVENTS.LOGGED); this.emit(DDP_EVENTS.LOGGED);
@ -86,20 +86,50 @@ server.methods({
type: result.type, type: result.type,
}; };
}, },
async logout() {
if (this.userToken && this.userId) {
await Account.logout({ userId: this.userId, token: this.userToken });
}
// TODO: run the handles on monolith to track SAU correctly
// accounts._successfulLogout(this.connection, this.userId);
this.userToken = undefined;
this.userId = undefined;
// Close connection after return success to the method call.
// This ensures all the subscriptions will be closed, meteor makes it manually
// here https://github.com/meteor/meteor/blob/2377ebe879d9b965d699f599392d4e8047eb7d78/packages/ddp-server/livedata_server.js#L781
// re doing the default subscriptions.
setTimeout(() => {
this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR);
}, 1);
},
'UserPresence:setDefaultStatus'(status) { 'UserPresence:setDefaultStatus'(status) {
const { userId } = this; const { userId } = this;
if (!userId) {
return;
}
return Presence.setStatus(userId, status); return Presence.setStatus(userId, status);
}, },
'UserPresence:online'() { 'UserPresence:online'() {
const { userId, session } = this; const { userId, session } = this;
if (!userId) {
return;
}
return Presence.setConnectionStatus(userId, USER_STATUS.ONLINE, session); return Presence.setConnectionStatus(userId, USER_STATUS.ONLINE, session);
}, },
'UserPresence:away'() { 'UserPresence:away'() {
const { userId, session } = this; const { userId, session } = this;
if (!userId) {
return;
}
return Presence.setConnectionStatus(userId, USER_STATUS.AWAY, session); return Presence.setConnectionStatus(userId, USER_STATUS.AWAY, session);
}, },
'setUserStatus'(status, statusText) { 'setUserStatus'(status, statusText) {
const { userId } = this; const { userId } = this;
if (!userId) {
return;
}
return Presence.setStatus(userId, status, statusText); return Presence.setStatus(userId, status, statusText);
}, },
// Copied from /app/livechat/server/methods/setUpConnection.js // Copied from /app/livechat/server/methods/setUpConnection.js

@ -18,6 +18,8 @@ module.exports = {
instances: 1, instances: 1,
env: { env: {
MOLECULER_LOG_LEVEL: 'info', MOLECULER_LOG_LEVEL: 'info',
TRANSPORTER: 'nats://localhost:4222',
MONGO_URL: 'mongodb://localhost:3001/meteor',
}, },
})), })),
}; };

@ -95,6 +95,10 @@ export class NotificationsModule {
this.streamRoomMessage.allowWrite('none'); this.streamRoomMessage.allowWrite('none');
this.streamRoomMessage.allowRead(async function(eventName /* , args*/) { this.streamRoomMessage.allowRead(async function(eventName /* , args*/) {
if (!this.userId) {
return false;
}
const room = await Rooms.findOneById(eventName); const room = await Rooms.findOneById(eventName);
if (!room) { if (!room) {
return false; return false;
@ -114,6 +118,10 @@ export class NotificationsModule {
this.streamRoomMessage.allowRead('__my_messages__', 'all'); this.streamRoomMessage.allowRead('__my_messages__', 'all');
this.streamRoomMessage.allowEmit('__my_messages__', async function(_eventName, { rid }) { this.streamRoomMessage.allowEmit('__my_messages__', async function(_eventName, { rid }) {
if (!this.userId) {
return false;
}
try { try {
const room = await Rooms.findOneById(rid); const room = await Rooms.findOneById(rid);
if (!room) { if (!room) {
@ -180,6 +188,10 @@ export class NotificationsModule {
return false; return false;
} }
if (!this.userId) {
return false;
}
try { try {
// TODO consider using something to cache settings // TODO consider using something to cache settings
const key = await Settings.getValueById('UI_Use_Real_Name') ? 'name' : 'username'; const key = await Settings.getValueById('UI_Use_Real_Name') ? 'name' : 'username';
@ -209,6 +221,10 @@ export class NotificationsModule {
this.streamRoomUsers.allowRead('none'); this.streamRoomUsers.allowRead('none');
this.streamRoomUsers.allowWrite(async function(eventName, ...args) { this.streamRoomUsers.allowWrite(async function(eventName, ...args) {
if (!this.userId) {
return false;
}
const [roomId, e] = eventName.split('/'); const [roomId, e] = eventName.split('/');
if (await Subscriptions.countByRoomIdAndUserId(roomId, this.userId) > 0) { if (await Subscriptions.countByRoomIdAndUserId(roomId, this.userId) > 0) {
const subscriptions: ISubscription[] = await Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId, { projection: { 'u._id': 1, _id: 0 } }).toArray(); const subscriptions: ISubscription[] = await Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId, { projection: { 'u._id': 1, _id: 0 } }).toArray();
@ -282,6 +298,10 @@ export class NotificationsModule {
this.streamRoomData.allowWrite('none'); this.streamRoomData.allowWrite('none');
this.streamRoomData.allowRead(async function(rid) { this.streamRoomData.allowRead(async function(rid) {
if (!this.userId) {
return false;
}
try { try {
const room = await Rooms.findOneById(rid); const room = await Rooms.findOneById(rid);
if (!room) { if (!room) {

@ -13,7 +13,7 @@ export const StreamerCentral = new StreamerCentralClass();
export type Client = { export type Client = {
meteorClient: boolean; meteorClient: boolean;
ws: any; ws: any;
userId: string; userId?: string;
send: Function; send: Function;
} }
@ -23,13 +23,13 @@ export interface IPublication {
connection: Connection; connection: Connection;
_session: { _session: {
sendAdded(publicationName: string, id: string, fields: Record<string, any>): void; sendAdded(publicationName: string, id: string, fields: Record<string, any>): void;
userId: string; userId?: string;
socket?: { socket?: {
send: Function; send: Function;
}; };
}; };
ready: Function; ready: Function;
userId: string; userId: string | undefined;
client: Client; client: Client;
} }

@ -3,10 +3,12 @@ import { IServiceClass } from './ServiceClass';
export interface ILoginResult { export interface ILoginResult {
uid: string; uid: string;
token: string; token: string;
hashedToken: string;
tokenExpires?: Date; tokenExpires?: Date;
type: 'resume' | 'password'; type: 'resume' | 'password';
} }
export interface IAccount extends IServiceClass { export interface IAccount extends IServiceClass {
login({ resume, user, password }: {resume: string; user: {username: string}; password: string}): Promise<false | ILoginResult>; login({ resume, user, password }: {resume: string; user: {username: string}; password: string}): Promise<false | ILoginResult>;
logout({ userId, token }: {userId: string; token: string}): Promise<void>;
} }

@ -16,7 +16,7 @@ export interface IMeteor extends IServiceClass {
getLoginServiceConfiguration(): Promise<any[]>; getLoginServiceConfiguration(): Promise<any[]>;
callMethodWithToken(userId: string, token: string, method: string, args: any[]): Promise<void | any>; callMethodWithToken(userId: string | undefined, token: string | undefined, method: string, args: any[]): Promise<void | any>;
notifyGuestStatusChanged(token: string, status: string): Promise<void>; notifyGuestStatusChanged(token: string, status: string): Promise<void>;

Loading…
Cancel
Save