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
1 change: 1 addition & 0 deletions extension/chrome/settings/modules/debug_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ View.run(
Xss.sanitizeAppend('#content', `Unsupported which: ${Xss.escape(this.which)} (not implemented)`);
} else if (this.which === 'local_store') {
const storage = await AcctStore.get(this.acctEmail, [
'authentication',
'notification_setup_needed_dismissed',
'email_provider',
'hide_message_password',
Expand Down
3 changes: 3 additions & 0 deletions extension/js/common/api/account-servers/external-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Buf } from '../../core/buf.js';
import { ClientConfigurationError, ClientConfigurationJson } from '../../client-configuration.js';
import { InMemoryStore } from '../../platform/store/in-memory-store.js';
import { GoogleOAuth } from '../authentication/google/google-oauth.js';
import { AuthenticationConfiguration } from '../../authentication-configuration.js';

// todo - decide which tags to use
type EventTag = 'compose' | 'decrypt' | 'setup' | 'settings' | 'import-pub' | 'import-prv';
Expand Down Expand Up @@ -88,6 +89,8 @@ export class ExternalService extends Api {
};

public fetchAndSaveClientConfiguration = async (): Promise<ClientConfigurationJson> => {
const auth = await this.request<AuthenticationConfiguration>('GET', `/api/${this.apiVersion}/client-configuration/authentication?domain=${this.domain}`);
await AcctStore.set(this.acctEmail, { authentication: auth });
const r = await this.request<FesRes.ClientConfiguration>('GET', `/api/${this.apiVersion}/client-configuration?domain=${this.domain}`);
if (r.clientConfiguration && !r.clientConfiguration.flags) {
throw new ClientConfigurationError('missing_flags');
Expand Down
11 changes: 11 additions & 0 deletions extension/js/common/authentication-configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */

export type AuthenticationConfiguration = {
oauth: {
clientId: string;
clientSecret: string;
redirectUrl: string;
authCodeUrl: string;
tokensUrl: string;
};
};
6 changes: 6 additions & 0 deletions extension/js/common/domain-configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */

import { AuthenticationConfiguration } from './authentication-configuration';
import { ClientConfigurationJson } from './client-configuration';

export type DomainConfiguration = { authentication: AuthenticationConfiguration; clientConfigurationJson: ClientConfigurationJson };
3 changes: 2 additions & 1 deletion extension/js/common/platform/store/abstract-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { ClientConfigurationJson } from '../../client-configuration.js';
import { GmailRes } from '../../api/email-provider/gmail/gmail-parser.js';
import { AcctStoreDict, AccountIndex } from './acct-store.js';
import { UnreportableError, Catch } from '../catch.js';
import { AuthenticationConfiguration } from '../../authentication-configuration.js';

type SerializableTypes = FlatTypes | string[] | number[] | boolean[] | ClientConfigurationJson;
export type StorageType = 'session' | 'local';
export type FlatTypes = null | undefined | number | string | boolean;
type Storable = FlatTypes | string[] | StoredKeyInfo[] | KeyInfoWithIdentity[] | GmailRes.OpenId | ClientConfigurationJson;
type Storable = FlatTypes | string[] | StoredKeyInfo[] | KeyInfoWithIdentity[] | GmailRes.OpenId | ClientConfigurationJson | AuthenticationConfiguration;
export type Serializable = SerializableTypes | SerializableTypes[] | Dict<SerializableTypes> | Dict<SerializableTypes>[];

export interface RawStore {
Expand Down
5 changes: 4 additions & 1 deletion extension/js/common/platform/store/acct-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { GoogleOAuth } from '../../api/authentication/google/google-oauth.js';
import { ApiErr } from '../../api/shared/api-error.js';
import { AuthenticationConfiguration } from '../../authentication-configuration.js';
import { BrowserMsg } from '../../browser/browser-msg.js';
import { storageLocalGet, storageLocalRemove, storageLocalSet } from '../../browser/chrome.js';
import { ClientConfigurationJson } from '../../client-configuration.js';
Expand Down Expand Up @@ -44,7 +45,8 @@ export type AccountIndex =
| 'rules'
| 'fesUrl'
| 'failedPassphraseAttempts'
| 'lastUnsuccessfulPassphraseAttempt';
| 'lastUnsuccessfulPassphraseAttempt'
| 'authentication';

export type SendAsAlias = {
isPrimary: boolean;
Expand Down Expand Up @@ -75,6 +77,7 @@ export type AcctStoreDict = {
fesUrl?: string; // url where FlowCrypt External Service is deployed
failedPassphraseAttempts?: number;
lastUnsuccessfulPassphraseAttempt?: number;
authentication?: AuthenticationConfiguration;
};
/* eslint-enable @typescript-eslint/naming-convention */

Expand Down
9 changes: 9 additions & 0 deletions test/source/mock/fes/customer-url-fes-endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ export const getMockCustomerUrlFesEndpoints = (config: FesConfig | undefined): H
}
throw new HttpClientErr(`Unexpected FES domain "${req.headers.host}" and url "${req.url}"`);
},
'/api/v1/client-configuration/authentication': async ({}, req) => {
if (req.method !== 'GET') {
throw new HttpClientErr('Unsupported method');
}
if (config?.authenticationConfiguration) {
return config.authenticationConfiguration;
}
return {};
},
'/api/v1/message/new-reply-token': async ({}, req) => {
if (req.headers.host === standardFesUrl(parsePort(req)) && req.method === 'POST') {
authenticate(req, 'oidc');
Expand Down
20 changes: 20 additions & 0 deletions test/source/mock/fes/shared-tenant-fes-endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ export type FesClientConfiguration = {
};
/* eslint-enable @typescript-eslint/naming-convention */

export type FesAuthenticationConfiguration = {
oauth: {
clientId: string;
clientSecret: string;
redirectUrl: string;
authCodeUrl: string;
tokensUrl: string;
};
};

export interface FesMessageReturnType {
url: string;
externalId: string;
Expand All @@ -61,6 +71,7 @@ export interface FesConfig {
returnError?: HttpClientErr;
apiEndpointReturnError?: HttpClientErr;
clientConfiguration?: FesClientConfiguration;
authenticationConfiguration?: FesAuthenticationConfiguration;
messagePostValidator?: (body: string, fesUrl: string) => Promise<FesMessageReturnType>;
}

Expand Down Expand Up @@ -102,6 +113,15 @@ export const getMockSharedTenantFesEndpoints = (config: FesConfig | undefined):
},
};
},
'/shared-tenant-fes/api/v1/client-configuration/authentication': async ({}, req) => {
if (req.method !== 'GET') {
throw new HttpClientErr('Unsupported method');
}
if (config?.authenticationConfiguration) {
return config.authenticationConfiguration;
}
return {};
},
'/shared-tenant-fes/api/v1/log-collector/exception': async ({ body }) => {
reportedErrors.push(body as ReportedError);
return { saved: true };
Expand Down
46 changes: 45 additions & 1 deletion test/source/tests/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
import { testSksKey } from '../mock/sks/sks-constants';
import { flowcryptCompatibilityAliasList, multipleEmailAliasList } from '../mock/google/google-endpoints';
import { standardSubDomainFesClientConfiguration } from '../mock/fes/customer-url-fes-endpoints';
import { FesClientConfiguration } from '../mock/fes/shared-tenant-fes-endpoints';
import { FesAuthenticationConfiguration, FesClientConfiguration } from '../mock/fes/shared-tenant-fes-endpoints';
import { GoogleData } from '../mock/google/google-data';
import Parse from '../util/parse';
import { MsgUtil } from '../core/crypto/pgp/msg-util';
Expand Down Expand Up @@ -2479,9 +2479,53 @@ AN8G3r5Htj8olot+jm9mIa5XLXWzMNUZgg==
);
})
);

test(
'setup - check custom authentication config from the local store (customer url fes)',
testWithBrowser(async (t, browser) => {
const oauthConfig = {
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
redirectUrl: 'https://example.com/redirect',
authCodeUrl: 'https://example.com/auth',
tokensUrl: 'https://example.com/tokens',
};
t.context.mockApi!.configProvider = new ConfigurationProvider({
fes: {
authenticationConfiguration: {
oauth: oauthConfig,
},
},
});
const acctEmail = 'user@authentication-config-test.flowcrypt.test';
await BrowserRecipe.openSettingsLoginApprove(t, browser, acctEmail);
const settingsPage = await browser.newExtensionSettingsPage(t, acctEmail);
const debugFrame = await SettingsPageRecipe.awaitNewPageFrame(settingsPage, '@action-show-local-store-contents', ['debug_api.htm']);
await debugFrame.waitForContent('@container-pre', 'authentication');
const key = `cryptup_${emailKeyIndex(acctEmail, 'authentication')}`;
const auth = (await settingsPage.getFromLocalStorage([key]))[key];
const { oauth } = auth as FesAuthenticationConfiguration;
expect(oauth).to.deep.equal(oauthConfig);
})
);
}

if (testVariant === 'CONSUMER-MOCK') {
test(
'setup - check custom authentication config from the local store (shared tenant fes)',
testWithBrowser(async (t, browser) => {
const acctEmail = 'flowcrypt.compatibility@gmail.com';
await BrowserRecipe.setupCommonAcctWithAttester(t, browser, 'compatibility');
const settingsPage = await browser.newExtensionSettingsPage(t, acctEmail);
const debugFrame = await SettingsPageRecipe.awaitNewPageFrame(settingsPage, '@action-show-local-store-contents', ['debug_api.htm']);
await debugFrame.waitForContent('@container-pre', 'authentication');
const key = `cryptup_${emailKeyIndex(acctEmail, 'authentication')}`;
const auth = (await settingsPage.getFromLocalStorage([key]))[key];
const authenticationConfiguration = auth as FesAuthenticationConfiguration;
expect(authenticationConfiguration).to.be.empty;
})
);

test(
'setup - imported key with multiple alias should show checkbox per alias',
testWithBrowser(async (t, browser) => {
Expand Down