Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

regression: not allowed to install marketplace apps #33695

Merged
3 changes: 2 additions & 1 deletion apps/meteor/app/api/server/v1/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,8 @@ API.v1.addRoute(
const settingsIds: string[] = [];

if (this.bodyParams.setDeploymentAs === 'new-workspace') {
await WorkspaceCredentials.unsetCredentialByScope();
await WorkspaceCredentials.removeAllCredentials();

settingsIds.push(
'Cloud_Service_Agree_PrivacyTerms',
'Cloud_Workspace_Id',
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/apps/server/bridges/cloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class AppCloudBridge extends CloudWorkspaceBridge {
public async getWorkspaceToken(scope: string, appId: string): Promise<IWorkspaceToken> {
this.orch.debugLog(`App ${appId} is getting the workspace's token`);

const token = await getWorkspaceAccessTokenWithScope(scope);
const token = await getWorkspaceAccessTokenWithScope({ scope });

return token;
}
Expand Down
21 changes: 15 additions & 6 deletions apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { IWorkspaceCredentials } from '@rocket.chat/core-typings';
import { WorkspaceCredentials } from '@rocket.chat/models';

import { SystemLogger } from '../../../../server/lib/logger/system';
import { workspaceScopes } from '../oauthScopes';
import { getWorkspaceAccessTokenWithScope } from './getWorkspaceAccessTokenWithScope';
import { retrieveRegistrationStatus } from './retrieveRegistrationStatus';

Expand All @@ -24,20 +26,27 @@ export async function getWorkspaceAccessToken(forceNew = false, scope = '', save
return '';
}

const workspaceCredentials = await WorkspaceCredentials.getCredentialByScope(scope);
if (!workspaceCredentials) {
throw new CloudWorkspaceAccessTokenError();
// Note: If no scope is given, it means we should assume the default scope, we store the default scopes
// in the global variable workspaceScopes.
if (scope === '') {
scope = workspaceScopes.join(' ');
}

if (!hasWorkspaceAccessTokenExpired(workspaceCredentials) && !forceNew) {
const workspaceCredentials = await WorkspaceCredentials.getCredentialByScope(scope);
if (workspaceCredentials && !hasWorkspaceAccessTokenExpired(workspaceCredentials) && !forceNew) {
SystemLogger.debug(
`Workspace credentials cache hit using scope: ${scope}. Avoiding generating a new access token from cloud services.`,
);
return workspaceCredentials.accessToken;
}

const accessToken = await getWorkspaceAccessTokenWithScope(scope, throwOnError);
SystemLogger.debug(`Workspace credentials cache miss using scope: ${scope}, fetching new access token from cloud services.`);

const accessToken = await getWorkspaceAccessTokenWithScope({ scope, throwOnError });

if (save) {
await WorkspaceCredentials.updateCredentialByScope({
scope,
scope: accessToken.scope,
accessToken: accessToken.token,
expirationDate: accessToken.expiresAt,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,24 @@ import { CloudWorkspaceAccessTokenError } from './getWorkspaceAccessToken';
import { removeWorkspaceRegistrationInfo } from './removeWorkspaceRegistrationInfo';
import { retrieveRegistrationStatus } from './retrieveRegistrationStatus';

export async function getWorkspaceAccessTokenWithScope(scope = '', throwOnError = false) {
type WorkspaceAccessTokenWithScope = {
token: string;
expiresAt: Date;
scope: string;
};

type GetWorkspaceAccessTokenWithScopeParams = {
scope?: string;
throwOnError?: boolean;
};

export async function getWorkspaceAccessTokenWithScope({
scope = '',
throwOnError = false,
}: GetWorkspaceAccessTokenWithScopeParams): Promise<WorkspaceAccessTokenWithScope> {
const { workspaceRegistered } = await retrieveRegistrationStatus();

const tokenResponse = { token: '', expiresAt: new Date() };
const tokenResponse = { token: '', expiresAt: new Date(), scope: '' };

if (!workspaceRegistered) {
return tokenResponse;
Expand Down Expand Up @@ -62,6 +76,7 @@ export async function getWorkspaceAccessTokenWithScope(scope = '', throwOnError
return {
token: payload.access_token,
expiresAt,
scope: payload.scope,
};
} catch (err: any) {
if (err instanceof CloudWorkspaceAccessTokenError) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { applyLicense } from '@rocket.chat/license';
import { Settings, WorkspaceCredentials } from '@rocket.chat/models';
import { Settings } from '@rocket.chat/models';

import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener';
import { settings } from '../../../settings/server';
Expand Down Expand Up @@ -59,12 +59,6 @@ async function saveRegistrationDataBase({
{ _id: 'Cloud_Workspace_Registration_Client_Uri', value: registration_client_uri },
];

await WorkspaceCredentials.updateCredentialByScope({
scope: '',
accessToken: '',
expirationDate: new Date(0),
});

const promises = [...settingsData.map(({ _id, value }) => Settings.updateValueById(_id, value))];

(await Promise.all(promises)).forEach((value, index) => {
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/ee/server/apps/communication/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export class AppsRestApi {
return API.v1.failure({ error: 'Invalid purchase type' });
}

const response = await getWorkspaceAccessTokenWithScope('marketplace:purchase');
const response = await getWorkspaceAccessTokenWithScope({ scope: 'marketplace:purchase' });
if (!response.token) {
return API.v1.failure({ error: 'Unauthorized' });
}
Expand Down Expand Up @@ -289,7 +289,7 @@ export class AppsRestApi {
return API.v1.failure({ error: 'Invalid purchase type' });
}

const token = await getWorkspaceAccessTokenWithScope('marketplace:purchase');
const token = await getWorkspaceAccessTokenWithScope({ scope: 'marketplace:purchase' });
if (!token) {
return API.v1.failure({ error: 'Unauthorized' });
}
Expand Down
29 changes: 4 additions & 25 deletions apps/meteor/server/models/raw/WorkspaceCredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,15 @@ export class WorkspaceCredentialsRaw extends BaseRaw<IWorkspaceCredentials> impl
}

protected modelIndexes(): IndexDescription[] {
return [{ key: { scopes: 1, expirationDate: 1, accessToken: 1 }, unique: true }];
return [{ key: { scope: 1, expirationDate: 1, accessToken: 1 }, unique: true }];
}

getCredentialByScope(scope = ''): Promise<IWorkspaceCredentials | null> {
const query: Filter<IWorkspaceCredentials> = {
scopes: {
$all: [scope],
$size: 1,
},
};
const query: Filter<IWorkspaceCredentials> = { scope };

return this.findOne(query);
}

unsetCredentialByScope(scope = ''): Promise<DeleteResult> {
const query: Filter<IWorkspaceCredentials> = {
scopes: {
$all: [scope],
$size: 1,
},
};

return this.deleteOne(query);
}

updateCredentialByScope({
scope,
accessToken,
Expand All @@ -46,18 +30,13 @@ export class WorkspaceCredentialsRaw extends BaseRaw<IWorkspaceCredentials> impl
}): Promise<UpdateResult> {
const record = {
$set: {
scopes: [scope],
scope,
accessToken,
expirationDate,
},
};

const query: Filter<IWorkspaceCredentials> = {
scopes: {
$all: [scope],
$size: 1,
},
};
const query: Filter<IWorkspaceCredentials> = { scope };

return this.updateOne(query, record, { upsert: true });
}
Expand Down
15 changes: 2 additions & 13 deletions apps/meteor/server/startup/migrations/v316.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { Settings, WorkspaceCredentials } from '@rocket.chat/models';
import { Settings } from '@rocket.chat/models';

import { addMigration } from '../../lib/migrations';

addMigration({
version: 316,
name: 'Remove Cloud_Workspace_Access_Token and Cloud_Workspace_Access_Token_Expires_At from the settings collection and add to the WorkspaceCredentials collection',
name: 'Remove Cloud_Workspace_Access_Token and Cloud_Workspace_Access_Token_Expires_At from the settings collection',
async up() {
const workspaceCredentials = await WorkspaceCredentials.getCredentialByScope();
if (workspaceCredentials) {
return;
}

const accessToken = ((await Settings.getValueById('Cloud_Workspace_Access_Token')) as string) || '';
const expirationDate = ((await Settings.getValueById('Cloud_Workspace_Access_Token_Expires_At')) as Date) || new Date(0);

Expand All @@ -21,11 +16,5 @@ addMigration({
if (expirationDate) {
await Settings.removeById('Cloud_Workspace_Access_Token_Expires_At');
}

await WorkspaceCredentials.updateCredentialByScope({
scope: '',
accessToken,
expirationDate,
});
},
});
2 changes: 1 addition & 1 deletion packages/core-typings/src/ee/IWorkspaceCredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { IRocketChatRecord } from '../IRocketChatRecord';

export interface IWorkspaceCredentials extends IRocketChatRecord {
_id: string;
scopes: string[];
scope: string;
expirationDate: Date;
accessToken: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type { IBaseModel } from './IBaseModel';

export interface IWorkspaceCredentialsModel extends IBaseModel<IWorkspaceCredentials> {
getCredentialByScope(scope?: string): Promise<IWorkspaceCredentials | null>;
unsetCredentialByScope(scope?: string): Promise<DeleteResult>;
updateCredentialByScope(credentials: { scope: string; accessToken: string; expirationDate: Date }): Promise<UpdateResult>;
removeAllCredentials(): Promise<DeleteResult>;
}
Loading