Skip to content

Commit a349887

Browse files
authoredJul 21, 2019
Licensing strategy (#126)
* Add decryptor and start licensing verification * Add marketplace type definitions * Add method to update marketplace info of apps
1 parent 30ad8ac commit a349887

28 files changed

+426
-8
lines changed
 

‎src/definition/AppStatus.ts

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export enum AppStatus {
1414
* An attempt to enable it again will fail, as it needs to be updated.
1515
*/
1616
COMPILER_ERROR_DISABLED = 'compiler_error_disabled',
17+
/**
18+
* The App was disable due to its license being invalid
19+
*/
20+
INVALID_LICENSE_DISABLED = 'invalid_license_disabled',
1721
/** The App was disabled due to an unrecoverable error being thrown. */
1822
ERROR_DISABLED = 'error_disabled',
1923
/** The App was manually disabled by a user. */
@@ -40,6 +44,7 @@ export class AppStatusUtilsDef {
4044
case AppStatus.ERROR_DISABLED:
4145
case AppStatus.MANUALLY_DISABLED:
4246
case AppStatus.INVALID_SETTINGS_DISABLED:
47+
case AppStatus.INVALID_LICENSE_DISABLED:
4348
case AppStatus.DISABLED:
4449
return true;
4550
default:

‎src/definition/metadata/index.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ import { IAppAuthorInfo } from './IAppAuthorInfo';
33
import { IAppInfo } from './IAppInfo';
44
import { RocketChatAssociationModel, RocketChatAssociationRecord } from './RocketChatAssociations';
55

6-
export { AppMethod, IAppAuthorInfo, IAppInfo, RocketChatAssociationModel, RocketChatAssociationRecord };
6+
export {
7+
AppMethod,
8+
IAppAuthorInfo,
9+
IAppInfo,
10+
RocketChatAssociationModel,
11+
RocketChatAssociationRecord,
12+
};

‎src/server/AppManager.ts

+82-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { IGetAppsFilter } from './IGetAppsFilter';
44
import {
55
AppAccessorManager,
66
AppApiManager,
7+
AppLicenseManager,
78
AppListenerManager,
89
AppSettingsManager,
910
AppSlashCommandManager,
@@ -14,6 +15,8 @@ import { AppLogStorage, AppStorage, IAppStorageItem } from './storage';
1415

1516
import { AppStatus, AppStatusUtils } from '../definition/AppStatus';
1617
import { AppMethod } from '../definition/metadata';
18+
import { InvalidLicenseError } from './errors';
19+
import { IMarketplaceInfo } from './marketplace';
1720

1821
export class AppManager {
1922
public static Instance: AppManager;
@@ -31,6 +34,7 @@ export class AppManager {
3134
private readonly commandManager: AppSlashCommandManager;
3235
private readonly apiManager: AppApiManager;
3336
private readonly settingsManager: AppSettingsManager;
37+
private readonly licenseManager: AppLicenseManager;
3438

3539
private isLoaded: boolean;
3640

@@ -67,6 +71,7 @@ export class AppManager {
6771
this.commandManager = new AppSlashCommandManager(this);
6872
this.apiManager = new AppApiManager(this);
6973
this.settingsManager = new AppSettingsManager(this);
74+
this.licenseManager = new AppLicenseManager(this);
7075

7176
this.isLoaded = false;
7277
AppManager.Instance = this;
@@ -112,6 +117,10 @@ export class AppManager {
112117
return this.commandManager;
113118
}
114119

120+
public getLicenseManager(): AppLicenseManager {
121+
return this.licenseManager;
122+
}
123+
115124
/** Gets the api manager's instance. */
116125
public getApiManager(): AppApiManager {
117126
return this.apiManager;
@@ -222,7 +231,9 @@ export class AppManager {
222231
for (const rl of this.apps.values()) {
223232
if (AppStatusUtils.isDisabled(rl.getStatus())) {
224233
continue;
225-
} else if (rl.getStatus() === AppStatus.INITIALIZED) {
234+
}
235+
236+
if (rl.getStatus() === AppStatus.INITIALIZED) {
226237
this.listenerManager.unregisterListeners(rl);
227238
this.commandManager.unregisterCommands(rl.getID());
228239
this.apiManager.unregisterApis(rl.getID());
@@ -361,7 +372,7 @@ export class AppManager {
361372
return true;
362373
}
363374

364-
public async add(zipContentsBase64d: string, enable = true): Promise<AppFabricationFulfillment> {
375+
public async add(zipContentsBase64d: string, enable = true, marketplaceInfo?: IMarketplaceInfo): Promise<AppFabricationFulfillment> {
365376
const aff = new AppFabricationFulfillment();
366377
const result = await this.getParser().parseZip(this.getCompiler(), zipContentsBase64d);
367378

@@ -382,10 +393,13 @@ export class AppManager {
382393
languageContent: result.languageContent,
383394
settings: {},
384395
implemented: result.implemented.getValues(),
396+
marketplaceInfo,
385397
});
386398

387399
if (!created) {
388-
throw new Error('Failed to create the App, the storage did not return it.');
400+
aff.setStorageError('Failed to create the App, the storage did not return it.');
401+
402+
return aff;
389403
}
390404

391405
// Now that is has all been compiled, let's get the
@@ -548,6 +562,51 @@ export class AppManager {
548562
return rl;
549563
}
550564

565+
public async updateAppsMarketplaceInfo(appsOverview: Array<{ latest: IMarketplaceInfo }>): Promise<void> {
566+
try {
567+
appsOverview.forEach(({ latest: appInfo }) => {
568+
if (!appInfo.subscriptionInfo) {
569+
return;
570+
}
571+
572+
const app = this.apps.get(appInfo.id);
573+
574+
if (!app) {
575+
return;
576+
}
577+
578+
const appStorageItem = app.getStorageItem();
579+
const subscriptionInfo = appStorageItem.marketplaceInfo && appStorageItem.marketplaceInfo.subscriptionInfo;
580+
581+
if (subscriptionInfo && subscriptionInfo.startDate === appInfo.subscriptionInfo.startDate) {
582+
return;
583+
}
584+
585+
appStorageItem.marketplaceInfo.subscriptionInfo = appInfo.subscriptionInfo;
586+
587+
this.storage.update(appStorageItem).catch(console.error); // TODO: Figure out something better
588+
});
589+
} catch (err) {
590+
// Errors here are not important
591+
}
592+
593+
const queue = [] as Array<Promise<void>>;
594+
595+
this.apps.forEach((app) => queue.push(app.validateLicense().catch((error) => {
596+
if (!(error instanceof InvalidLicenseError)) {
597+
console.error(error);
598+
return;
599+
}
600+
601+
this.commandManager.unregisterCommands(app.getID());
602+
this.apiManager.unregisterApis(app.getID());
603+
604+
return app.setStatus(AppStatus.INVALID_LICENSE_DISABLED);
605+
})));
606+
607+
await Promise.all(queue);
608+
}
609+
551610
/**
552611
* Goes through the entire loading up process. WARNING: Do not use. ;)
553612
*
@@ -603,20 +662,29 @@ export class AppManager {
603662
const envRead = this.getAccessorManager().getEnvironmentRead(storageItem.id);
604663

605664
try {
665+
await app.validateLicense();
666+
606667
await app.call(AppMethod.INITIALIZE, configExtend, envRead);
607-
result = true;
608668
await app.setStatus(AppStatus.INITIALIZED, silenceStatus);
669+
670+
result = true;
609671
} catch (e) {
672+
let status = AppStatus.ERROR_DISABLED;
673+
610674
if (e.name === 'NotEnoughMethodArgumentsError') {
611675
console.warn('Please report the following error:');
612676
}
613677

678+
if (e instanceof InvalidLicenseError) {
679+
status = AppStatus.INVALID_LICENSE_DISABLED;
680+
}
681+
614682
console.error(e);
615683
this.commandManager.unregisterCommands(storageItem.id);
616684
this.apiManager.unregisterApis(storageItem.id);
617685
result = false;
618686

619-
await app.setStatus(AppStatus.ERROR_DISABLED, silenceStatus);
687+
await app.setStatus(status, silenceStatus);
620688
}
621689

622690
if (saveToDb) {
@@ -657,19 +725,27 @@ export class AppManager {
657725
let enable: boolean;
658726

659727
try {
728+
await app.validateLicense();
729+
660730
enable = await app.call(AppMethod.ONENABLE,
661731
this.getAccessorManager().getEnvironmentRead(storageItem.id),
662732
this.getAccessorManager().getConfigurationModify(storageItem.id)) as boolean;
733+
663734
await app.setStatus(isManual ? AppStatus.MANUALLY_ENABLED : AppStatus.AUTO_ENABLED, silenceStatus);
664735
} catch (e) {
665736
enable = false;
737+
let status = AppStatus.ERROR_DISABLED;
666738

667739
if (e.name === 'NotEnoughMethodArgumentsError') {
668740
console.warn('Please report the following error:');
669741
}
670742

743+
if (e instanceof InvalidLicenseError) {
744+
status = AppStatus.INVALID_LICENSE_DISABLED;
745+
}
746+
671747
console.error(e);
672-
await app.setStatus(AppStatus.ERROR_DISABLED, silenceStatus);
748+
await app.setStatus(status, silenceStatus);
673749
}
674750

675751
if (enable) {

‎src/server/ProxiedApp.ts

+15
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import { AppMethod, IAppAuthorInfo, IAppInfo } from '../definition/metadata';
88
import { AppManager } from './AppManager';
99
import { NotEnoughMethodArgumentsError } from './errors';
1010
import { AppConsole } from './logging';
11+
import { AppLicenseValidationResult } from './marketplace/license';
1112
import { IAppStorageItem } from './storage';
1213

1314
export class ProxiedApp implements IApp {
1415
private previousStatus: AppStatus;
1516

17+
private latestLicenseValidationResult: AppLicenseValidationResult;
18+
1619
constructor(private readonly manager: AppManager,
1720
private storageItem: IAppStorageItem,
1821
private readonly app: App,
@@ -143,4 +146,16 @@ export class ProxiedApp implements IApp {
143146
public getAccessors(): IAppAccessors {
144147
return this.app.getAccessors();
145148
}
149+
150+
public getLatestLicenseValidationResult(): AppLicenseValidationResult {
151+
return this.latestLicenseValidationResult;
152+
}
153+
154+
public validateLicense(): Promise<void> {
155+
const { marketplaceInfo } = this.getStorageItem();
156+
157+
this.latestLicenseValidationResult = new AppLicenseValidationResult();
158+
159+
return this.manager.getLicenseManager().validate(this.latestLicenseValidationResult, marketplaceInfo);
160+
}
146161
}

‎src/server/bridges/IInternalBridge.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { ISetting } from '../../definition/settings';
2+
13
export interface IInternalBridge {
24
getUsernamesOfRoomById(roomId: string): Array<string>;
5+
getWorkspacePublicKey(): Promise<ISetting>;
36
}

‎src/server/bridges/IUserBridge.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ export interface IUserBridge {
44
getById(id: string, appId: string): Promise<IUser>;
55

66
getByUsername(username: string, appId: string): Promise<IUser>;
7+
8+
getActiveUserCount(): Promise<number>;
79
}

‎src/server/compiler/AppFabricationFulfillment.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { IAppInfo } from '../../definition/metadata';
2-
2+
import { AppLicenseValidationResult } from '../marketplace/license';
33
import { ProxiedApp } from '../ProxiedApp';
44
import { ICompilerError } from './ICompilerError';
55

@@ -8,13 +8,17 @@ export class AppFabricationFulfillment {
88
public app: ProxiedApp;
99
public implemented: { [int: string]: boolean };
1010
public compilerErrors: Array<ICompilerError>;
11+
public licenseValidationResult: AppLicenseValidationResult;
12+
public storageError: string;
1113

1214
constructor() {
1315
this.compilerErrors = new Array<ICompilerError>();
16+
this.licenseValidationResult = new AppLicenseValidationResult();
1417
}
1518

1619
public setAppInfo(information: IAppInfo): void {
1720
this.info = information;
21+
this.licenseValidationResult.setAppId(information.id);
1822
}
1923

2024
public getAppInfo(): IAppInfo {
@@ -44,4 +48,20 @@ export class AppFabricationFulfillment {
4448
public getCompilerErrors(): Array<ICompilerError> {
4549
return this.compilerErrors;
4650
}
51+
52+
public setStorageError(errorMessage: string): void {
53+
this.storageError = errorMessage;
54+
}
55+
56+
public getStorageError(): string {
57+
return this.storageError;
58+
}
59+
60+
public hasStorageError(): boolean {
61+
return !!this.storageError;
62+
}
63+
64+
public getLicenseValidationResult(): AppLicenseValidationResult {
65+
return this.licenseValidationResult;
66+
}
4767
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { AppLicenseValidationResult } from '../marketplace/license/AppLicenseValidationResult';
2+
3+
export class InvalidLicenseError extends Error {
4+
public constructor(public readonly validationResult: AppLicenseValidationResult) {
5+
super('Invalid app license');
6+
}
7+
}

‎src/server/errors/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CommandAlreadyExistsError } from './CommandAlreadyExistsError';
22
import { CommandHasAlreadyBeenTouchedError } from './CommandHasAlreadyBeenTouchedError';
33
import { CompilerError } from './CompilerError';
4+
import { InvalidLicenseError } from './InvalidLicenseError';
45
import { MustContainFunctionError } from './MustContainFunctionError';
56
import { MustExtendAppError } from './MustExtendAppError';
67
import { NotEnoughMethodArgumentsError } from './NotEnoughMethodArgumentsError';
@@ -16,4 +17,5 @@ export {
1617
MustExtendAppError,
1718
NotEnoughMethodArgumentsError,
1819
RequiredApiVersionError,
20+
InvalidLicenseError,
1921
};
+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { AppManager } from '../AppManager';
2+
import { IUserBridge } from '../bridges';
3+
import { InvalidLicenseError } from '../errors';
4+
import { IMarketplaceInfo } from '../marketplace';
5+
import { AppLicenseValidationResult } from '../marketplace/license';
6+
import { Crypto } from '../marketplace/license';
7+
8+
enum LicenseVersion {
9+
v1 = 1,
10+
}
11+
12+
export class AppLicenseManager {
13+
private readonly crypto: Crypto;
14+
private readonly userBridge: IUserBridge;
15+
constructor(private readonly manager: AppManager) {
16+
this.crypto = new Crypto(this.manager.getBridges().getInternalBridge());
17+
this.userBridge = this.manager.getBridges().getUserBridge();
18+
}
19+
20+
public async validate(validationResult: AppLicenseValidationResult, appMarketplaceInfo?: IMarketplaceInfo): Promise<void> {
21+
if (!appMarketplaceInfo || !appMarketplaceInfo.subscriptionInfo) {
22+
return;
23+
}
24+
25+
validationResult.setValidated(true);
26+
27+
const { id: appId, subscriptionInfo } = appMarketplaceInfo;
28+
29+
let license;
30+
try {
31+
license = await this.crypto.decryptLicense(subscriptionInfo.license.license) as any;
32+
} catch (err) {
33+
validationResult.addError('publicKey', err.message);
34+
35+
throw new InvalidLicenseError(validationResult);
36+
}
37+
38+
if (license.appId !== appId) {
39+
validationResult.addError('appId', `License hasn't been issued for this app`);
40+
}
41+
42+
switch (license.version) {
43+
case LicenseVersion.v1:
44+
await this.validateV1(appMarketplaceInfo, license, validationResult);
45+
break;
46+
}
47+
}
48+
49+
private async validateV1(appMarketplaceInfo: IMarketplaceInfo, license: any, validationResult: AppLicenseValidationResult): Promise<void> {
50+
const renewal = new Date(license.renewalDate);
51+
const expire = new Date(license.expireDate);
52+
const now = new Date();
53+
54+
if (expire < now) {
55+
validationResult.addError('expire', 'License is no longer valid');
56+
}
57+
58+
const currentActiveUsers = await this.userBridge.getActiveUserCount();
59+
60+
if (license.maxSeats < currentActiveUsers) {
61+
validationResult.addError('maxSeats', 'License does not accomodate the currently active users');
62+
}
63+
64+
if (validationResult.hasErrors) {
65+
throw new InvalidLicenseError(validationResult);
66+
}
67+
68+
if (renewal < now) {
69+
validationResult.addWarning('renewal', 'License has expired and needs to be renewed');
70+
}
71+
72+
if (license.seats < currentActiveUsers) {
73+
validationResult.addWarning(
74+
'seats',
75+
`The license for the app "${
76+
appMarketplaceInfo.name
77+
}" does not have enough seats to accommodate the current amount of active users. Please increase the number of seats`,
78+
);
79+
}
80+
}
81+
}

‎src/server/managers/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { AppAccessorManager} from './AppAccessorManager';
22
import { AppApiManager } from './AppApiManager';
3+
import { AppLicenseManager } from './AppLicenseManager';
34
import { AppListenerManager } from './AppListenerManager';
45
import { AppSettingsManager } from './AppSettingsManager';
56
import { AppSlashCommandManager } from './AppSlashCommandManager';
67

78
export {
89
AppAccessorManager,
10+
AppLicenseManager,
911
AppListenerManager,
1012
AppSettingsManager,
1113
AppSlashCommandManager,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface IAppLicenseMetadata {
2+
license: string;
3+
version: number;
4+
expireDate: Date;
5+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { IAppInfo } from '../../definition/metadata';
2+
import { IMarketplacePricingPlan } from './IMarketplacePricingPlan';
3+
import { IMarketplaceSimpleBundleInfo } from './IMarketplaceSimpleBundleInfo';
4+
import { IMarketplaceSubscriptionInfo } from './IMarketplaceSubscriptionInfo';
5+
import { MarketplacePurchaseType } from './MarketplacePurchaseType';
6+
7+
export interface IMarketplaceInfo extends IAppInfo {
8+
categories: Array<string>;
9+
status: string;
10+
reviewedNote?: string;
11+
rejectionNote?: string;
12+
isVisible: boolean;
13+
isPurchased: boolean;
14+
isSubscribed: boolean;
15+
isBundled: boolean;
16+
createdDate: string;
17+
modifiedDate: string;
18+
price: number;
19+
subscriptionInfo?: IMarketplaceSubscriptionInfo;
20+
puchaseType: MarketplacePurchaseType;
21+
pricingPlans?: Array<IMarketplacePricingPlan>;
22+
bundledIn?: Array<IMarketplaceSimpleBundleInfo>;
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { IMarketplacePricingTier } from './IMarketplacePricingTier';
2+
import { MarketplacePricingStrategy } from './MarketplacePricingStrategy';
3+
4+
export interface IMarketplacePricingPlan {
5+
id: string;
6+
enabled: boolean;
7+
price: number;
8+
isPerSeat: boolean;
9+
strategy: MarketplacePricingStrategy;
10+
tiers?: Array<IMarketplacePricingTier>;
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface IMarketplacePricingTier {
2+
perUnit: boolean;
3+
minimum: number;
4+
maximum: number;
5+
price: number;
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface IMarketplaceSimpleBundleInfo {
2+
bundleId: string;
3+
bundleName: string;
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { IAppLicenseMetadata } from './IAppLicenseMetadata';
2+
import { MarketplaceSubscriptionStatus } from './MarketplaceSubscriptionStatus';
3+
import { MarketplaceSubscriptionType } from './MarketplaceSubscriptionType';
4+
5+
export interface IMarketplaceSubscriptionInfo {
6+
seats: number;
7+
maxSeats: number;
8+
startDate: string;
9+
periodEnd: string;
10+
isSubscripbedViaBundle: boolean;
11+
endDate?: string;
12+
typeOf: MarketplaceSubscriptionType;
13+
status: MarketplaceSubscriptionStatus;
14+
license: IAppLicenseMetadata;
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export enum MarketplacePricingStrategy {
2+
PricingStrategyOnce = 'once',
3+
PricingStrategyMonthly = 'monthly',
4+
PricingStrategyYearly = 'yearly',
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum MarketplacePurchaseType {
2+
PurchaseTypeBuy = 'buy',
3+
PurchaseTypeSubscription = 'subscription',
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export enum MarketplaceSubscriptionStatus {
2+
// PurchaseSubscriptionStatusTrialing is when the subscription is in the trial phase
3+
PurchaseSubscriptionStatusTrialing = 'trialing',
4+
// PurchaseSubscriptionStatusActive is when the subscription is active and being billed for
5+
PurchaseSubscriptionStatusActive = 'active',
6+
// PurchaseSubscriptionStatusCanceled is when the subscription is inactive due to being canceled
7+
PurchaseSubscriptionStatusCanceled = 'canceled',
8+
// PurchaseSubscriptionStatusPastDue is when the subscription was active but is now past due as a result of incorrect billing information
9+
PurchaseSubscriptionStatusPastDue = 'pastDue',
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum MarketplaceSubscriptionType {
2+
SubscriptionTypeApp = 'app',
3+
SubscriptionTypeService = 'service',
4+
}

‎src/server/marketplace/index.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { IAppLicenseMetadata } from './IAppLicenseMetadata';
2+
import { IMarketplaceInfo } from './IMarketplaceInfo';
3+
import { IMarketplacePricingPlan } from './IMarketplacePricingPlan';
4+
import { IMarketplacePricingTier } from './IMarketplacePricingTier';
5+
import { IMarketplaceSimpleBundleInfo } from './IMarketplaceSimpleBundleInfo';
6+
import { IMarketplaceSubscriptionInfo } from './IMarketplaceSubscriptionInfo';
7+
8+
export {
9+
IAppLicenseMetadata,
10+
IMarketplaceInfo,
11+
IMarketplacePricingPlan,
12+
IMarketplacePricingTier,
13+
IMarketplaceSimpleBundleInfo,
14+
IMarketplaceSubscriptionInfo,
15+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
2+
export class AppLicenseValidationResult {
3+
private errors: {[key: string]: string} = {};
4+
private warnings: {[key: string]: string} = {};
5+
private validated: boolean = false;
6+
private appId: string;
7+
8+
public addError(field: string, message: string): void {
9+
this.errors[field] = message;
10+
}
11+
12+
public addWarning(field: string, message: string): void {
13+
this.warnings[field] = message;
14+
}
15+
16+
public get hasErrors(): boolean {
17+
return !!Object.keys(this.errors).length;
18+
}
19+
20+
public get hasWarnings(): boolean {
21+
return !!Object.keys(this.warnings).length;
22+
}
23+
24+
public get hasBeenValidated(): boolean {
25+
return this.validated;
26+
}
27+
28+
public setValidated(validated: boolean): void {
29+
this.validated = validated;
30+
}
31+
32+
public setAppId(appId: string): void {
33+
this.appId = appId;
34+
}
35+
36+
public getAppId(): string {
37+
return this.appId;
38+
}
39+
40+
public getErrors(): object {
41+
return this.errors;
42+
}
43+
44+
public getWarnings(): object {
45+
return this.warnings;
46+
}
47+
48+
public toJSON(): object {
49+
return {
50+
errors: this.errors,
51+
warnings: this.warnings,
52+
};
53+
}
54+
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { publicDecrypt } from 'crypto';
2+
import { IInternalBridge } from '../../bridges';
3+
4+
export class Crypto {
5+
constructor(private readonly internalBridge: IInternalBridge) {}
6+
7+
public async decryptLicense(content: string): Promise<object> {
8+
const publicKeySetting = await this.internalBridge.getWorkspacePublicKey();
9+
10+
if (!publicKeySetting || !publicKeySetting.value) {
11+
throw new Error('Public key not available, cannot decrypt'); // TODO: add custom error?
12+
}
13+
14+
const decoded = publicDecrypt(publicKeySetting.value, Buffer.from(content, 'base64'));
15+
16+
let license;
17+
try {
18+
license = JSON.parse(decoded.toString());
19+
} catch (error) {
20+
throw new Error('Invalid license provided');
21+
}
22+
23+
return license;
24+
}
25+
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { AppLicenseValidationResult } from './AppLicenseValidationResult';
2+
import { Crypto } from './Crypto';
3+
4+
export {
5+
AppLicenseValidationResult,
6+
Crypto,
7+
};

‎src/server/storage/IAppStorageItem.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { AppStatus } from '../../definition/AppStatus';
22
import { IAppInfo } from '../../definition/metadata';
33
import { ISetting } from '../../definition/settings';
4+
import { IMarketplaceInfo } from '../marketplace';
45

56
export interface IAppStorageItem {
67
_id?: string;
@@ -14,4 +15,5 @@ export interface IAppStorageItem {
1415
languageContent: { [key: string]: object };
1516
settings: { [id: string]: ISetting };
1617
implemented: { [int: string]: boolean };
18+
marketplaceInfo?: IMarketplaceInfo;
1719
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import { ISetting } from '../../../src/definition/settings';
12
import { IInternalBridge } from '../../../src/server/bridges';
23

34
export class TestsInternalBridge implements IInternalBridge {
45
public getUsernamesOfRoomById(roomId: string): Array<string> {
56
throw new Error('Method not implemented.');
67
}
8+
9+
public getWorkspacePublicKey(): Promise<ISetting> {
10+
throw new Error('Method not implemented.');
11+
}
712
}

‎tests/test-data/bridges/userBridge.ts

+4
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@ export class TestsUserBridge implements IUserBridge {
1010
public getByUsername(username: string, appId: string): Promise<IUser> {
1111
throw new Error('Method not implemented.');
1212
}
13+
14+
public getActiveUserCount(): Promise<number> {
15+
throw new Error('Method not implemented.');
16+
}
1317
}

0 commit comments

Comments
 (0)
Please sign in to comment.