mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Move user profile to new page and begin MFA work
Summary: The user profile dialog is now a separate page, in preparation for upcoming work to enable MFA. This commit also contains some MFA changes, but the UI is currently disabled and the implementation is limited to software tokens (TOTP) only. Test Plan: Updated browser tests for new profile page. Tests for MFAConfig and CognitoClient will be added in a later diff, once the UI is enabled. Reviewers: paulfitz Reviewed By: paulfitz Subscribers: dsagal Differential Revision: https://phab.getgrist.com/D3199
This commit is contained in:
@@ -266,6 +266,22 @@ export interface DocStateComparisonDetails {
|
||||
rightChanges: ActionSummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* User multi-factor authentication preferences, as fetched from Cognito.
|
||||
*/
|
||||
export interface UserMFAPreferences {
|
||||
isSmsMfaEnabled: boolean;
|
||||
isSoftwareTokenMfaEnabled: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cognito response to initiating software token MFA registration.
|
||||
*/
|
||||
export interface SoftwareTokenRegistrationInfo {
|
||||
session: string;
|
||||
secretCode: string;
|
||||
}
|
||||
|
||||
export {UserProfile} from 'app/common/LoginSessionAPI';
|
||||
|
||||
export interface UserAPI {
|
||||
@@ -304,6 +320,7 @@ export interface UserAPI {
|
||||
unpinDoc(docId: string): Promise<void>;
|
||||
moveDoc(docId: string, workspaceId: number): Promise<void>;
|
||||
getUserProfile(): Promise<FullUser>;
|
||||
getUserMfaPreferences(): Promise<UserMFAPreferences>;
|
||||
updateUserName(name: string): Promise<void>;
|
||||
getWorker(key: string): Promise<string>;
|
||||
getWorkerAPI(key: string): Promise<DocWorkerAPI>;
|
||||
@@ -320,6 +337,8 @@ export interface UserAPI {
|
||||
onUploadProgress?: (ev: ProgressEvent) => void,
|
||||
}): Promise<string>;
|
||||
deleteUser(userId: number, name: string): Promise<void>;
|
||||
registerSoftwareToken(): Promise<SoftwareTokenRegistrationInfo>;
|
||||
unregisterSoftwareToken(): Promise<void>;
|
||||
getBaseUrl(): string; // Get the prefix for all the endpoints this object wraps.
|
||||
forRemoved(): UserAPI; // Get a version of the API that works on removed resources.
|
||||
getWidgets(): Promise<ICustomWidget[]>;
|
||||
@@ -582,6 +601,10 @@ export class UserAPIImpl extends BaseAPI implements UserAPI {
|
||||
return this.requestJson(`${this._url}/api/profile/user`);
|
||||
}
|
||||
|
||||
public async getUserMfaPreferences(): Promise<UserMFAPreferences> {
|
||||
return this.requestJson(`${this._url}/api/profile/mfa_preferences`);
|
||||
}
|
||||
|
||||
public async updateUserName(name: string): Promise<void> {
|
||||
await this.request(`${this._url}/api/profile/user/name`, {
|
||||
method: 'POST',
|
||||
@@ -668,6 +691,14 @@ export class UserAPIImpl extends BaseAPI implements UserAPI {
|
||||
body: JSON.stringify({name})});
|
||||
}
|
||||
|
||||
public async registerSoftwareToken(): Promise<SoftwareTokenRegistrationInfo> {
|
||||
return this.requestJson(`${this._url}/api/auth/register_totp`, {method: 'POST'});
|
||||
}
|
||||
|
||||
public async unregisterSoftwareToken(): Promise<void> {
|
||||
await this.request(`${this._url}/api/auth/unregister_totp`, {method: 'POST'});
|
||||
}
|
||||
|
||||
public getBaseUrl(): string { return this._url; }
|
||||
|
||||
// Recomputes the URL on every call to pick up changes in the URL when switching orgs.
|
||||
|
||||
@@ -20,6 +20,9 @@ export type IHomePage = typeof HomePage.type;
|
||||
export const WelcomePage = StringUnion('user', 'info', 'teams', 'signup', 'verify', 'select-account');
|
||||
export type WelcomePage = typeof WelcomePage.type;
|
||||
|
||||
export const AccountPage = StringUnion('profile');
|
||||
export type AccountPage = typeof AccountPage.type;
|
||||
|
||||
// Overall UI style. "full" is normal, "light" is a single page focused, panels hidden experience.
|
||||
export const InterfaceStyle = StringUnion('light', 'full');
|
||||
export type InterfaceStyle = typeof InterfaceStyle.type;
|
||||
@@ -62,6 +65,7 @@ export interface IGristUrlState {
|
||||
fork?: UrlIdParts;
|
||||
docPage?: IDocPage;
|
||||
newui?: boolean;
|
||||
account?: AccountPage;
|
||||
billing?: BillingPage;
|
||||
welcome?: WelcomePage;
|
||||
welcomeTour?: boolean;
|
||||
@@ -183,6 +187,8 @@ export function encodeUrl(gristConfig: Partial<GristLoadConfig>,
|
||||
parts.push(`p/${state.homePage}`);
|
||||
}
|
||||
|
||||
if (state.account) { parts.push('account'); }
|
||||
|
||||
if (state.billing) {
|
||||
parts.push(state.billing === 'billing' ? 'billing' : `billing/${state.billing}`);
|
||||
}
|
||||
@@ -271,6 +277,7 @@ export function decodeUrl(gristConfig: Partial<GristLoadConfig>, location: Locat
|
||||
}
|
||||
if (map.has('m')) { state.mode = OpenDocMode.parse(map.get('m')); }
|
||||
if (sp.has('newui')) { state.newui = useNewUI(sp.get('newui') ? sp.get('newui') === '1' : undefined); }
|
||||
if (map.has('account')) { state.account = AccountPage.parse('account') || 'profile'; }
|
||||
if (map.has('billing')) { state.billing = BillingSubPage.parse(map.get('billing')) || 'billing'; }
|
||||
if (map.has('welcome')) { state.welcome = WelcomePage.parse(map.get('welcome')) || 'user'; }
|
||||
if (sp.has('billingPlan')) { state.params!.billingPlan = sp.get('billingPlan')!; }
|
||||
|
||||
Reference in New Issue
Block a user