Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@oclif/core": "^3.26.6",
"@salesforce/core": "^7.3.9",
"@salesforce/kit": "^3.1.2",
"@salesforce/lwc-dev-mobile-core": "4.0.0-alpha.3",
"@salesforce/lwc-dev-mobile-core": "4.0.0-alpha.4",
"@salesforce/sf-plugins-core": "^9.1.1",
"@inquirer/select": "^2.3.5",
"chalk": "^5.3.0",
Expand Down Expand Up @@ -58,6 +58,7 @@
"oclif": {
"commands": "./lib/commands",
"bin": "sf",
"configMeta": "./lib/configMeta",
"topicSeparator": " ",
"devPlugins": [
"@oclif/plugin-help",
Expand Down
25 changes: 25 additions & 0 deletions src/configMeta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import type { ConfigPropertyMeta } from '@salesforce/core';

export const enum ConfigVars {
/**
* The Base64-encoded identity token of the local web server, used to
* validate the web server's identity to the hmr-client.
*/
LOCAL_WEB_SERVER_IDENTITY_TOKEN = 'local-web-server-identity-token',
}

export default [
{
key: ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN,
description: 'The Base64-encoded identity token of the local web server',
hidden: true,
encrypted: true,
},
] as ConfigPropertyMeta[];
37 changes: 37 additions & 0 deletions src/shared/identityUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { CryptoUtils } from '@salesforce/lwc-dev-mobile-core';
import { Config, ConfigAggregator } from '@salesforce/core';
import configMeta, { ConfigVars } from './../configMeta.js';

export class IdentityUtils {
public static async getOrCreateIdentityToken(): Promise<string> {
let token = await this.getIdentityToken();
if (!token) {
token = CryptoUtils.generateIdentityToken();
await this.writeIdentityToken(token);
}
return token;
}

public static async getIdentityToken(): Promise<string | undefined> {
const config = await ConfigAggregator.create({ customConfigMeta: configMeta });
// Need to reload to make sure the values read are decrypted
await config.reload();
const identityToken = config.getPropertyValue(ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN);

return identityToken as string;
}

public static async writeIdentityToken(token: string): Promise<void> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method could be private but when I do that I won't be able to stub it in the unit test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is ok to leave it public.... it is a function in a utility library that could be on use in the future

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would however update the method signature to take an optional config param in addition to the token.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maliroteh-sf I didn't add optional config in "write" because now we need to deal separate "configs" depending on "writing" to or "reading" from sf's configuration. Making config optional and delegate that responsibility to the called is certainly an approach that could have been taken. But with given inflexibility with Config's APIs and their shortcomings I think the safest way for now for us is to encapsulate the different configs(Config and ConfigAggregator) in respective "write" and "read" methods.

const config = await Config.create({ isGlobal: false });
Config.addAllowedProperties(configMeta);
config.set(ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN, token);
await config.write();
}
}
76 changes: 76 additions & 0 deletions test/shared/identityUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */

import { expect } from 'chai';
import { Config, ConfigAggregator } from '@salesforce/core';
import { TestContext } from '@salesforce/core/testSetup';
import { CryptoUtils } from '@salesforce/lwc-dev-mobile-core';
import { IdentityUtils } from '../../src/shared/identityUtils.js';
import { ConfigVars } from '../../src/configMeta.js';

describe('identityUtils', () => {
const $$ = new TestContext();

afterEach(() => {
$$.restore();
});

it('getOrCreateIdentityToken resolves if token is found', async () => {
const fakeIdentityToken = 'fake identity token';
$$.SANDBOX.stub(IdentityUtils, 'getIdentityToken').resolves(fakeIdentityToken);

const resolved = await IdentityUtils.getOrCreateIdentityToken();
expect(resolved).to.equal(fakeIdentityToken);
});

it('getOrCreateIdentityToken resolves and writeIdentityToken is called when there is no token', async () => {
const fakeIdentityToken = 'fake identity token';
$$.SANDBOX.stub(IdentityUtils, 'getIdentityToken').resolves(undefined);
$$.SANDBOX.stub(CryptoUtils, 'generateIdentityToken').resolves(fakeIdentityToken);
const writeIdentityTokenStub = $$.SANDBOX.stub(IdentityUtils, 'writeIdentityToken').resolves();

const resolved = await IdentityUtils.getOrCreateIdentityToken();
expect(resolved).to.equal(fakeIdentityToken);
expect(writeIdentityTokenStub.calledOnce).to.be.true;
});

it('getIdentityToken resolves to undefined when identity token is not available', async () => {
$$.SANDBOX.stub(ConfigAggregator, 'create').resolves(ConfigAggregator.prototype);
$$.SANDBOX.stub(ConfigAggregator.prototype, 'reload').resolves();
$$.SANDBOX.stub(ConfigAggregator.prototype, 'getPropertyValue').returns(undefined);
const resolved = await IdentityUtils.getIdentityToken();

expect(resolved).to.equal(undefined);
});

it('getIdentityToken resolves to a string when identity token is available', async () => {
const fakeIdentityToken = 'fake identity token';
$$.SANDBOX.stub(ConfigAggregator, 'create').resolves(ConfigAggregator.prototype);
$$.SANDBOX.stub(ConfigAggregator.prototype, 'reload').resolves();
$$.SANDBOX.stub(ConfigAggregator.prototype, 'getPropertyValue').returns(fakeIdentityToken);

const resolved = await IdentityUtils.getIdentityToken();
expect(resolved).to.equal(fakeIdentityToken);
});

it('writeIdentityToken resolves', async () => {
const fakeIdentityToken = 'fake identity token';
$$.SANDBOX.stub(Config, 'create').withArgs($$.SANDBOX.match.any).resolves(Config.prototype);
$$.SANDBOX.stub(Config, 'addAllowedProperties').withArgs($$.SANDBOX.match.any);
$$.SANDBOX.stub(Config.prototype, 'set').withArgs(
ConfigVars.LOCAL_WEB_SERVER_IDENTITY_TOKEN,
$$.SANDBOX.match.string
);
$$.SANDBOX.stub(Config.prototype, 'write').resolves();

const resolved = await IdentityUtils.writeIdentityToken(fakeIdentityToken);
expect(resolved).to.equal(undefined);
});
});
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4304,10 +4304,10 @@
"@salesforce/ts-types" "^2.0.9"
tslib "^2.6.3"

"@salesforce/lwc-dev-mobile-core@4.0.0-alpha.3":
version "4.0.0-alpha.3"
resolved "https://registry.yarnpkg.com/@salesforce/lwc-dev-mobile-core/-/lwc-dev-mobile-core-4.0.0-alpha.3.tgz#7485689c78e97b53ba88ce714db11bbeacfde192"
integrity sha512-7o7n6tuTshqwmfym2vy89IvMB8rKUcqDL7mg/nQ77wP4OxAjtj+mQzamcLz1nQU7QI4529RbkBvXe/2R0zbXBA==
"@salesforce/lwc-dev-mobile-core@4.0.0-alpha.4":
version "4.0.0-alpha.4"
resolved "https://registry.yarnpkg.com/@salesforce/lwc-dev-mobile-core/-/lwc-dev-mobile-core-4.0.0-alpha.4.tgz#5e020cb3222f2603a01248359d43b3d6ca4062e3"
integrity sha512-gNEQQr4QIIdXgGmSr6820Y2/HR7YqwxBjf0Z/PKr/iNoQ24Agc1QGMfUysc5H7/Bmx69GPF9wq7ahbNjSvB+Kg==
dependencies:
"@oclif/core" "^3.26.6"
"@salesforce/core" "^7.3.6"
Expand Down