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: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# FlowCrypt: Encrypt Gmail with PGP


## Users

Get [FlowCrypt](https://flowcrypt.com/) browser extension at: https://flowcrypt.com/download
Expand Down
2 changes: 1 addition & 1 deletion extension/chrome/elements/composer/composer-atts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class ComposerAtts extends ComposerComponent {

private getMaxAttSizeAndOversizeNotice = async (): Promise<AttLimits> => {
const subscription = await Store.subscription(this.view.acctEmail);
if (!Rules.relaxSubscriptionRequirements(this.composer.sender.getSender()) && !subscription.active) {
if (!Rules.isPublicEmailProviderDomain(this.composer.sender.getSender()) && !subscription.active) {
return {
sizeMb: 5,
size: 5 * 1024 * 1024,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ declare const openpgp: typeof OpenPGP;
export class EncryptedMsgMailFormatter extends BaseMailFormatter implements MailFormatterInterface {

private armoredPubkeys: PubkeyResult[];
private FC_WEB_URL = 'https://flowcrypt.com'; // todo Send plain (not encrypted)uld use Api.url()
private pgpMimeRootType = `multipart/encrypted; protocol="application/pgp-encrypted";`;
private fcAdminCodes: string[] = [];

Expand Down Expand Up @@ -185,7 +184,7 @@ export class EncryptedMsgMailFormatter extends BaseMailFormatter implements Mail
const { short, admin_code } = await Backend.messageUpload(authInfo, encryptedBody['text/plain']!);
const storage = await Store.getAcct(this.acctEmail, ['outgoing_language']);
const lang = storage.outgoing_language || 'EN';
const msgUrl = `${this.FC_WEB_URL}/${short}`;
const msgUrl = Backend.url('decrypt', short);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This needs additional manual testing

const a = `<a href="${Xss.escape(msgUrl)}" style="padding: 2px 6px; background: #2199e8; color: #fff; display: inline-block; text-decoration: none;">
${Lang.compose.openMsg[lang]}
</a>`;
Expand Down
3 changes: 1 addition & 2 deletions extension/chrome/elements/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,7 @@ View.run(class SubscribeView extends View {
private renderSubscriptionDetails = async () => {
this.authInfo = await Store.authInfo(this.acctEmail);
try {
await Backend.accountGet(this.authInfo);
await Backend.getSubscriptionWithoutLogin(this.acctEmail);
await Backend.accountGetAndUpdateLocalStore(this.authInfo);
} catch (e) {
if (Api.err.isAuthErr(e)) {
Xss.sanitizeRender('#content', `Not logged in. ${Ui.retryLink()}`);
Expand Down
4 changes: 2 additions & 2 deletions extension/chrome/settings/index.htm
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ <h1 class="text-center">Set up FlowCrypt</h1>
</div>

<div class="col-sm-2 col-xs-6">
<a href="#" class="show_settings_page" page="modules/backup.htm">
<a href="#" class="show_settings_page" page="modules/backup.htm" data-test="action-open-backup-page">
<span class="box">
<img src="/img/svgs/keys-icon.svg" class="keys-icon" alt="FlowCrypt, set up and backup your Keys">
</span>
Expand All @@ -121,7 +121,7 @@ <h1 class="text-center">Set up FlowCrypt</h1>
</div>

<div class="col-sm-2 col-xs-6">
<a href="#" class="show_settings_page" page="modules/help.htm">
<a href="#" class="show_settings_page" page="modules/help.htm" data-test="action-open-help-page">
<span class="box">
<img src="/img/svgs/feedback-icon.svg" class="feedback-icon" alt="Leave Feedback on FlowCrypt">
</span>
Expand Down
14 changes: 7 additions & 7 deletions extension/chrome/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ View.run(class SettingsView extends View {

render = async () => {
$('#status-row #status_v').text(`v:${VERSION}`);
const rules = await Rules.newInstance(this.acctEmail);
if (!rules.canBackupKeys()) {
$('.show_settings_page[page="modules/backup.htm"]').parent().remove();
$('.settings-icons-rows').css({ position: 'relative', left: '64px' }); // lost a button - center it again
}
for (const webmailLName of await Env.webmails()) {
$('.signin_button.' + webmailLName).css('display', 'inline-block');
}
Expand Down Expand Up @@ -176,6 +171,11 @@ View.run(class SettingsView extends View {
const storage = await Store.getAcct(this.acctEmail, ['setup_done', 'email_provider', 'picture']);
const scopes = await Store.getScopes(this.acctEmail);
if (storage.setup_done) {
const rules = await Rules.newInstance(this.acctEmail);
if (!rules.canBackupKeys()) {
$('.show_settings_page[page="modules/backup.htm"]').parent().remove();
$('.settings-icons-rows').css({ position: 'relative', left: '64px' }); // lost a button - center it again
}
this.checkGoogleAcct().catch(Catch.reportErr);
this.checkFcAcctAndSubscriptionAndContactPage().catch(Catch.reportErr);
if (storage.picture) {
Expand Down Expand Up @@ -227,9 +227,9 @@ View.run(class SettingsView extends View {
const authInfo = await Store.authInfo(this.acctEmail!);
if (authInfo.uuid) { // have auth email set
try {
const response = await Backend.accountUpdate(authInfo);
const response = await Backend.accountGetAndUpdateLocalStore(authInfo);
$('#status-row #status_flowcrypt').text(`fc:ok`);
if (response?.result?.alias) {
if (response?.account?.alias) {
statusContainer.find('.status-indicator-text').css('display', 'none');
statusContainer.find('.status-indicator').addClass('active');
} else {
Expand Down
2 changes: 1 addition & 1 deletion extension/chrome/settings/modules/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ View.run(class AccountView extends View {
try {
const r = await Backend.getSubscriptionWithoutLogin(this.acctEmail);
subscription = new Subscription(r.subscription);
await Backend.accountGet(authInfo); // here to test auth
await Backend.accountGetAndUpdateLocalStore(authInfo); // here to test auth
} catch (e) {
if (Api.err.isAuthErr(e) && subscription.level) {
Settings.offerToLoginWithPopupShowModalOnErr(this.acctEmail, () => window.location.reload());
Expand Down
6 changes: 3 additions & 3 deletions extension/chrome/settings/modules/contact_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ View.run(class ContactPageView extends View {
render = async () => {
Xss.sanitizeRender(this.S.cached('status'), 'Loading..' + Ui.spinner('green'));
try {
const response = await Backend.accountUpdate(await this.authInfoPromise);
this.renderFields(response.result);
const response = await Backend.accountGetAndUpdateLocalStore(await this.authInfoPromise);
this.renderFields(response.account);
} catch (e) {
if (Api.err.isAuthErr(e)) {
Settings.offerToLoginWithPopupShowModalOnErr(this.acctEmail, () => window.location.reload());
Expand All @@ -67,7 +67,7 @@ View.run(class ContactPageView extends View {
this.S.cached('action_close').click(this.setHandler(() => this.onCloseHandler()));
}

private renderFields = (result: BackendRes.FcAccountUpdate$result) => {
private renderFields = (result: BackendRes.FcAccount$info) => {
if (result.alias) {
const me = Backend.url('me', result.alias);
const meEscaped = Xss.escape(me);
Expand Down
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 @@ -41,6 +41,7 @@ View.run(class DebugApiView extends View {
'notification_setup_needed_dismissed', 'email_provider', 'google_token_scopes', 'hide_message_password', 'sendAs', 'outgoing_language',
'full_name', 'cryptup_enabled', 'setup_done', 'setup_simple', 'is_newly_created_key', 'key_backup_method',
'key_backup_prompt', 'successfully_received_at_leat_one_message', 'notification_setup_done_seen', 'openid',
'rules', 'subscription', 'use_rich_text',
]);
this.renderCallRes('Local account storage', { acctEmail: this.acctEmail }, storage);
} else {
Expand Down
4 changes: 2 additions & 2 deletions extension/chrome/settings/modules/security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ View.run(class SecurityView extends View {
if (subscription.active) {
Xss.sanitizeRender('.select_loader_container', Ui.spinner('green'));
try {
const response = await Backend.accountUpdate(this.authInfo!);
const response = await Backend.accountGetAndUpdateLocalStore(this.authInfo!);
$('.select_loader_container').text('');
$('.default_message_expire').val(Number(response.result.default_message_expire).toString()).prop('disabled', false).css('display', 'inline-block');
$('.default_message_expire').val(Number(response.account.default_message_expire).toString()).prop('disabled', false).css('display', 'inline-block');
$('.default_message_expire').change(this.setHandler(() => this.onDefaultExpireUserChange()));
} catch (e) {
if (Api.err.isAuthErr(e)) {
Expand Down
2 changes: 1 addition & 1 deletion extension/chrome/settings/setup.htm
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@

<div id="content" class="setup">
<div class="wide line">
<img src="/img/svgs/chevron-left.svg" class="chevron-left back" alt="Go back">
<img src="/img/svgs/chevron-left.svg" class="chevron-left back" alt="Go back" data-test="action-setup-go-back">
<a href="#" class="action_show_help" page="modules/help.htm">
<img src="/img/svgs/question-icon.svg" alt="Help icon">
</a>
Expand Down
31 changes: 18 additions & 13 deletions extension/js/common/api/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { Catch } from '../platform/catch.js';
import { Att } from '../core/att.js';
import { Ui } from '../browser/ui.js';
import { Buf } from '../core/buf.js';
import { DomainRules } from '../rules.js';
import { BACKEND_API_HOST } from '../core/const.js';

type SubscriptionLevel = 'pro' | null;
type ProfileUpdate = { alias?: string, name?: string, photo?: string, intro?: string, web?: string, phone?: string, default_message_expire?: number };
Expand All @@ -29,8 +31,9 @@ export type AwsS3UploadItem = { baseUrl: string, fields: { key: string; file?: A
export namespace BackendRes {
export type FcHelpFeedback = { sent: boolean };
export type FcAccountLogin = { registered: boolean, verified: boolean, subscription: SubscriptionInfo };
export type FcAccountUpdate$result = { alias: string, email: string, intro: string, name: string, photo: string, default_message_expire: number };
export type FcAccountUpdate = { result: FcAccountUpdate$result, updated: boolean };
export type FcAccount$info = { alias: string, email: string, intro: string, name: string, photo: string, default_message_expire: number };
export type FcAccountGet = { account: FcAccount$info, subscription: SubscriptionInfo, domain_org_rules: DomainRules };
export type FcAccountUpdate = { result: FcAccount$info, updated: boolean };
export type FcAccountSubscribe = { subscription: SubscriptionInfo };
export type FcAccountCheck = { email: string | null, subscription: SubscriptionInfo | null };
export type FcBlogPost = { title: string, date: string, url: string };
Expand All @@ -53,13 +56,13 @@ export class Backend extends Api {
return Backend.apiCall(Backend.url('api'), path, vals, fmt, undefined, { 'api-version': '3', ...addHeaders });
}

public static url = (type: string, variable = '') => {
public static url = (type: 'api' | 'me' | 'pubkey' | 'decrypt' | 'web', resource = '') => {
return ({
'api': 'https://flowcrypt.com/api/',
'me': 'https://flowcrypt.com/me/' + variable,
'pubkey': 'https://flowcrypt.com/pub/' + variable,
'decrypt': 'https://flowcrypt.com/' + variable,
'web': 'https://flowcrypt.com/',
api: BACKEND_API_HOST,
me: `https://flowcrypt.com/me/${resource}`,
pubkey: `https://flowcrypt.com/pub/${resource}`,
decrypt: `https://flowcrypt.com/${resource}`,
web: 'https://flowcrypt.com/',
} as Dict<string>)[type];
}

Expand Down Expand Up @@ -115,17 +118,19 @@ export class Backend extends Api {
return r;
}

public static accountUpdate = async (fcAuth: FcUuidAuth, profileUpdate: ProfileUpdate = {}): Promise<BackendRes.FcAccountUpdate> => {
public static accountUpdate = async (fcAuth: FcUuidAuth, profileUpdate: ProfileUpdate): Promise<BackendRes.FcAccountUpdate> => {
Backend.throwIfMissingUuid(fcAuth);
const r = await Backend.request('account/update', {
return await Backend.request('account/update', {
...fcAuth,
...profileUpdate
}) as BackendRes.FcAccountUpdate;
return r;
}

public static accountGet = (fcAuth: FcUuidAuth) => {
return Backend.accountUpdate(fcAuth, {});
public static accountGetAndUpdateLocalStore = async (fcAuth: FcUuidAuth): Promise<BackendRes.FcAccountGet> => {
Backend.throwIfMissingUuid(fcAuth);
const r = await Backend.request('account/get', fcAuth) as BackendRes.FcAccountGet;
await Store.setAcct(fcAuth.account, { rules: r.domain_org_rules, subscription: r.subscription });
return r;
}

public static accountSubscribe = async (fcAuth: FcUuidAuth, product: string, method: string, paymentSourceToken?: string): Promise<BackendRes.FcAccountSubscribe> => {
Expand Down
19 changes: 19 additions & 0 deletions extension/js/common/api/google-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { tabsQuery, windowsCreate } from './chrome.js';
import { Buf } from '../core/buf.js';
import { GOOGLE_API_HOST, GOOGLE_OAUTH_SCREEN_HOST } from '../core/const.js';
import { GmailRes } from './email_provider/gmail/gmail-parser.js';
import { Backend } from './backend.js';
import { Rules } from '../rules.js';

type GoogleAuthTokenInfo = { issued_to: string, audience: string, scope: string, expires_in: number, access_type: 'offline' };
type GoogleAuthTokensResponse = { access_token: string, expires_in: number, refresh_token?: string, id_token: string, token_type: 'Bearer' };
Expand Down Expand Up @@ -135,6 +137,23 @@ export class GoogleAuth {
Catch.reportErr(e);
}
}
if (authRes.result === 'Success') {
if (!authRes.id_token) {
return { result: 'Error', error: 'Grant was successful but missing id_token', acctEmail: authRes.acctEmail, id_token: undefined };
}
if (!authRes.acctEmail) {
return { result: 'Error', error: 'Grant was successful but missing acctEmail', acctEmail: authRes.acctEmail, id_token: undefined };
}
if (!Rules.isPublicEmailProviderDomain(authRes.acctEmail)) {
try { // users on @custom-domain.com must check with backend to look for org rules, if any
const uuid = Api.randomFortyHexChars();
await Backend.loginWithOpenid(authRes.acctEmail, uuid, authRes.id_token);
await Backend.accountGetAndUpdateLocalStore({ account: authRes.acctEmail, uuid }); // will store org rules and subscription
} catch (e) {
return { result: 'Error', error: `Grant successful but error loging into fc account: ${String(e)}`, acctEmail: authRes.acctEmail, id_token: undefined };
}
}
}
return authRes;
}

Expand Down
2 changes: 2 additions & 0 deletions extension/js/common/core/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const VERSION = '[BUILD_REPLACEABLE_VERSION]';
export const GOOGLE_API_HOST = '[BUILD_REPLACEABLE_GOOGLE_API_HOST]';
export const GOOGLE_OAUTH_SCREEN_HOST = '[BUILD_REPLACEABLE_GOOGLE_OAUTH_SCREEN_HOST]';
export const GOOGLE_CONTACTS_API_HOST = '[BUILD_REPLACEABLE_GOOGLE_CONTACTS_API_HOST]';
export const BACKEND_API_HOST = '[BUILD_REPLACEABLE_BACKEND_API_HOST]';

/**
* Only put constants below if:
* - they are useful across web/extension/Nodejs environments, AND
Expand Down
8 changes: 5 additions & 3 deletions extension/js/common/platform/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { storageLocalSet, storageLocalGet, storageLocalRemove } from '../api/chr
import { PgpClient } from '../api/keyserver.js';
import { GmailRes } from '../api/email_provider/gmail/gmail-parser.js';
import { GoogleAuth } from '../api/google-auth.js';
import { DomainRules } from '../rules.js';
import { Env } from '../browser/env.js';
import { Ui } from '../browser/ui.js';

Expand All @@ -20,7 +21,7 @@ import { Ui } from '../browser/ui.js';
let KEY_CACHE: { [longidOrArmoredKey: string]: OpenPGP.key.Key } = {};
let KEY_CACHE_WIPE_TIMEOUT: number;

type SerializableTypes = FlatTypes | string[] | number[] | boolean[] | SubscriptionInfo;
type SerializableTypes = FlatTypes | string[] | number[] | boolean[] | SubscriptionInfo | DomainRules;
type StoredReplyDraftMeta = string; // draftId
type StoredComposeDraftMeta = { recipients: string[], subject: string, date: number };
type StoredAdminCode = { date: number, codes: string[] };
Expand Down Expand Up @@ -68,7 +69,7 @@ export type ContactUpdate = {
pubkey_last_check?: number | null;
};
export type Storable = FlatTypes | string[] | KeyInfo[] | Dict<StoredReplyDraftMeta> | Dict<StoredComposeDraftMeta> | Dict<StoredAdminCode>
| SubscriptionInfo | GmailRes.OpenId;
| SubscriptionInfo | GmailRes.OpenId | DomainRules;
export type Serializable = SerializableTypes | SerializableTypes[] | Dict<SerializableTypes> | Dict<SerializableTypes>[];

export interface RawStore {
Expand Down Expand Up @@ -129,6 +130,7 @@ export type AccountStore = {
openid?: GmailRes.OpenId;
subscription?: SubscriptionInfo;
uuid?: string;
rules?: DomainRules;
// temporary
tmp_submit_main?: boolean;
tmp_submit_all?: boolean;
Expand All @@ -144,7 +146,7 @@ export type AccountIndex = 'keys' | 'notification_setup_needed_dismissed' | 'ema
'google_token_refresh' | 'hide_message_password' | 'addresses' | 'sendAs' | 'drafts_reply' | 'drafts_compose' |
'pubkey_sent_to' | 'full_name' | 'cryptup_enabled' | 'setup_done' | 'setup_simple' | 'is_newly_created_key' | 'key_backup_method' |
'key_backup_prompt' | 'successfully_received_at_leat_one_message' | 'notification_setup_done_seen' | 'picture' |
'outgoing_language' | 'setup_date' | 'openid' | 'tmp_submit_main' | 'tmp_submit_all' | 'subscription' | 'uuid' | 'use_rich_text';
'outgoing_language' | 'setup_date' | 'openid' | 'tmp_submit_main' | 'tmp_submit_all' | 'subscription' | 'uuid' | 'use_rich_text' | 'rules';

export class Subscription implements SubscriptionInfo {
active?: boolean;
Expand Down
Loading