Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Apply strictNullChecks to src/components/views/auth/* (#10299
Browse files Browse the repository at this point in the history
* Apply `strictNullChecks` to src/components/views/auth/*

* Iterate PR
  • Loading branch information
t3chguy authored Mar 7, 2023
1 parent c79eff2 commit 32aa18f
Show file tree
Hide file tree
Showing 16 changed files with 127 additions and 122 deletions.
10 changes: 0 additions & 10 deletions src/@types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,6 @@ export type RecursivePartial<T> = {
: T[P];
};

// Inspired by https://stackoverflow.com/a/60206860
export type KeysWithObjectShape<Input> = {
[P in keyof Input]: Input[P] extends object
? // Arrays are counted as objects - exclude them
Input[P] extends Array<unknown>
? never
: P
: never;
}[keyof Input];

export type KeysStartingWith<Input extends object, Str extends string> = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[P in keyof Input]: P extends `${Str}${infer _X}` ? P : never; // we don't use _X
Expand Down
5 changes: 2 additions & 3 deletions src/SdkConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { Optional } from "matrix-events-sdk";

import { SnakedObject } from "./utils/SnakedObject";
import { IConfigOptions, ISsoRedirectOptions } from "./IConfigOptions";
import { KeysWithObjectShape } from "./@types/common";

// see element-web config.md for docs, or the IConfigOptions interface for dev docs
export const DEFAULTS: IConfigOptions = {
Expand Down Expand Up @@ -78,10 +77,10 @@ export default class SdkConfig {
return SdkConfig.fallback.get(key, altCaseName);
}

public static getObject<K extends KeysWithObjectShape<IConfigOptions>>(
public static getObject<K extends keyof IConfigOptions>(
key: K,
altCaseName?: string,
): Optional<SnakedObject<IConfigOptions[K]>> {
): Optional<SnakedObject<NonNullable<IConfigOptions[K]>>> {
const val = SdkConfig.get(key, altCaseName);
if (val !== null && val !== undefined) {
return new SnakedObject(val);
Expand Down
7 changes: 4 additions & 3 deletions src/components/structures/InteractiveAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ interface IProps {
// Called when the stage changes, or the stage's phase changes. First
// argument is the stage, second is the phase. Some stages do not have
// phases and will be counted as 0 (numeric).
onStagePhaseChange?(stage: AuthType, phase: number): void;
onStagePhaseChange?(stage: AuthType | null, phase: number): void;
}

interface IState {
Expand Down Expand Up @@ -170,7 +170,8 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
busy: true,
});
try {
return await this.props.requestEmailToken(email, secret, attempt, session);
// We know this method only gets called on flows where requestEmailToken is passed but types don't
return await this.props.requestEmailToken!(email, secret, attempt, session);
} finally {
this.setState({
busy: false,
Expand Down Expand Up @@ -231,7 +232,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
};

private onPhaseChange = (newPhase: number): void => {
this.props.onStagePhaseChange?.(this.state.authStage, newPhase || 0);
this.props.onStagePhaseChange?.(this.state.authStage ?? null, newPhase || 0);
};

private onStageCancel = (): void => {
Expand Down
3 changes: 2 additions & 1 deletion src/components/structures/MatrixChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2015,7 +2015,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {

public render(): React.ReactNode {
const fragmentAfterLogin = this.getFragmentAfterLogin();
let view = null;
let view: JSX.Element;

if (this.state.view === Views.LOADING) {
view = (
Expand Down Expand Up @@ -2132,6 +2132,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
view = <UseCaseSelection onFinished={(useCase): Promise<void> => this.onShowPostLoginScreen(useCase)} />;
} else {
logger.error(`Unknown view ${this.state.view}`);
return null;
}

return (
Expand Down
49 changes: 30 additions & 19 deletions src/components/structures/auth/Registration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,29 @@ interface IProps {
}

interface IState {
// true if we're waiting for the user to complete
busy: boolean;
errorText?: ReactNode;
// true if we're waiting for the user to complete
// We remember the values entered by the user because
// the registration form will be unmounted during the
// course of registration, but if there's an error we
// want to bring back the registration form with the
// values the user entered still in it. We can keep
// them in this component's state since this component
// persist for the duration of the registration process.
formVals: Record<string, string>;
formVals: Record<string, string | undefined>;
// user-interactive auth
// If we've been given a session ID, we're resuming
// straight back into UI auth
doingUIAuth: boolean;
// If set, we've registered but are not going to log
// the user in to their new account automatically.
completedNoSignin: boolean;
flows: {
stages: string[];
}[];
flows:
| {
stages: string[];
}[]
| null;
// We perform liveliness checks later, but for now suppress the errors.
// We also track the server dead errors independently of the regular errors so
// that we can render it differently, and override any other error the user may
Expand Down Expand Up @@ -158,7 +160,7 @@ export default class Registration extends React.Component<IProps, IState> {
window.removeEventListener("beforeunload", this.unloadCallback);
}

private unloadCallback = (event: BeforeUnloadEvent): string => {
private unloadCallback = (event: BeforeUnloadEvent): string | undefined => {
if (this.state.doingUIAuth) {
event.preventDefault();
event.returnValue = "";
Expand Down Expand Up @@ -215,7 +217,7 @@ export default class Registration extends React.Component<IProps, IState> {
this.loginLogic.setHomeserverUrl(hsUrl);
this.loginLogic.setIdentityServerUrl(isUrl);

let ssoFlow: ISSOFlow;
let ssoFlow: ISSOFlow | undefined;
try {
const loginFlows = await this.loginLogic.getFlows();
if (serverConfig !== this.latestServerConfig) return; // discard, serverConfig changed from under us
Expand Down Expand Up @@ -289,6 +291,7 @@ export default class Registration extends React.Component<IProps, IState> {
sendAttempt: number,
sessionId: string,
): Promise<IRequestTokenResponse> => {
if (!this.state.matrixClient) throw new Error("Matrix client has not yet been loaded");
return this.state.matrixClient.requestRegisterEmailToken(
emailAddress,
clientSecret,
Expand All @@ -303,6 +306,8 @@ export default class Registration extends React.Component<IProps, IState> {
};

private onUIAuthFinished: InteractiveAuthCallback = async (success, response): Promise<void> => {
if (!this.state.matrixClient) throw new Error("Matrix client has not yet been loaded");

debuglog("Registration: ui authentication finished: ", { success, response });
if (!success) {
let errorText: ReactNode = (response as Error).message || (response as Error).toString();
Expand All @@ -327,10 +332,8 @@ export default class Registration extends React.Component<IProps, IState> {
</div>
);
} else if ((response as IAuthData).required_stages?.includes(AuthType.Msisdn)) {
let msisdnAvailable = false;
for (const flow of (response as IAuthData).available_flows) {
msisdnAvailable = msisdnAvailable || flow.stages.includes(AuthType.Msisdn);
}
const flows = (response as IAuthData).available_flows ?? [];
const msisdnAvailable = flows.some((flow) => flow.stages.includes(AuthType.Msisdn));
if (!msisdnAvailable) {
errorText = _t("This server does not support authentication with a phone number.");
}
Expand All @@ -348,12 +351,16 @@ export default class Registration extends React.Component<IProps, IState> {
return;
}

MatrixClientPeg.setJustRegisteredUserId((response as IAuthData).user_id);
const userId = (response as IAuthData).user_id;
const accessToken = (response as IAuthData).access_token;
if (!userId || !accessToken) throw new Error("Registration failed");

MatrixClientPeg.setJustRegisteredUserId(userId);

const newState: Partial<IState> = {
doingUIAuth: false,
registeredUsername: (response as IAuthData).user_id,
differentLoggedInUserId: null,
differentLoggedInUserId: undefined,
completedNoSignin: false,
// we're still busy until we get unmounted: don't show the registration form again
busy: true,
Expand Down Expand Up @@ -393,13 +400,13 @@ export default class Registration extends React.Component<IProps, IState> {
// the email, not the client that started the registration flow
await this.props.onLoggedIn(
{
userId: (response as IAuthData).user_id,
userId,
deviceId: (response as IAuthData).device_id,
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
accessToken: (response as IAuthData).access_token,
accessToken,
},
this.state.formVals.password,
this.state.formVals.password!,
);

this.setupPushers();
Expand Down Expand Up @@ -457,6 +464,8 @@ export default class Registration extends React.Component<IProps, IState> {
};

private makeRegisterRequest = (auth: IAuthData | null): Promise<IAuthData> => {
if (!this.state.matrixClient) throw new Error("Matrix client has not yet been loaded");

const registerParams: IRegisterRequestParams = {
username: this.state.formVals.username,
password: this.state.formVals.password,
Expand Down Expand Up @@ -494,7 +503,7 @@ export default class Registration extends React.Component<IProps, IState> {
return sessionLoaded;
};

private renderRegisterComponent(): JSX.Element {
private renderRegisterComponent(): ReactNode {
if (this.state.matrixClient && this.state.doingUIAuth) {
return (
<InteractiveAuth
Expand All @@ -517,8 +526,8 @@ export default class Registration extends React.Component<IProps, IState> {
<Spinner />
</div>
);
} else if (this.state.flows.length) {
let ssoSection;
} else if (this.state.matrixClient && this.state.flows.length) {
let ssoSection: JSX.Element | undefined;
if (this.state.ssoFlow) {
let continueWithSection;
const providers = this.state.ssoFlow.identity_providers || [];
Expand Down Expand Up @@ -571,6 +580,8 @@ export default class Registration extends React.Component<IProps, IState> {
</React.Fragment>
);
}

return null;
}

public render(): React.ReactNode {
Expand Down
40 changes: 20 additions & 20 deletions src/components/views/auth/InteractiveAuthEntryComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const DEFAULT_PHASE = 0;
interface IAuthEntryProps {
matrixClient: MatrixClient;
loginType: string;
authSessionId: string;
authSessionId?: string;
errorText?: string;
errorCode?: string;
// Is the auth logic currently waiting for something to happen?
Expand Down Expand Up @@ -120,7 +120,7 @@ export class PasswordAuthEntry extends React.Component<IAuthEntryProps, IPasswor
type: AuthType.Password,
// TODO: Remove `user` once servers support proper UIA
// See https://github.com/vector-im/element-web/issues/10312
user: this.props.matrixClient.credentials.userId,
user: this.props.matrixClient.credentials.userId ?? undefined,
identifier: {
type: "m.id.user",
user: this.props.matrixClient.credentials.userId,
Expand Down Expand Up @@ -286,7 +286,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
// }
// }

const allPolicies = this.props.stageParams.policies || {};
const allPolicies = this.props.stageParams?.policies || {};
const prefLang = SettingsStore.getValue("language");
const initToggles: Record<string, boolean> = {};
const pickedPolicies: {
Expand All @@ -300,12 +300,12 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
// Pick a language based on the user's language, falling back to english,
// and finally to the first language available. If there's still no policy
// available then the homeserver isn't respecting the spec.
let langPolicy = policy[prefLang];
let langPolicy: LocalisedPolicy | undefined = policy[prefLang];
if (!langPolicy) langPolicy = policy["en"];
if (!langPolicy) {
// last resort
const firstLang = Object.keys(policy).find((e) => e !== "version");
langPolicy = policy[firstLang];
langPolicy = firstLang ? policy[firstLang] : undefined;
}
if (!langPolicy) throw new Error("Failed to find a policy to show the user");

Expand Down Expand Up @@ -358,7 +358,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
return <Spinner />;
}

const checkboxes = [];
const checkboxes: JSX.Element[] = [];
let allChecked = true;
for (const policy of this.state.policies) {
const checked = this.state.toggledPolicies[policy.id];
Expand All @@ -384,7 +384,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
);
}

let submitButton;
let submitButton: JSX.Element | undefined;
if (this.props.showContinue !== false) {
// XXX: button classes
submitButton = (
Expand Down Expand Up @@ -462,7 +462,7 @@ export class EmailIdentityAuthEntry extends React.Component<
// We only have a session ID if the user has clicked the link in their email,
// so show a loading state instead of "an email has been sent to..." because
// that's confusing when you've already read that email.
if (this.props.inputs.emailAddress === undefined || this.props.stageState?.emailSid) {
if (this.props.inputs?.emailAddress === undefined || this.props.stageState?.emailSid) {
if (errorSection) {
return errorSection;
}
Expand Down Expand Up @@ -549,13 +549,13 @@ interface IMsisdnAuthEntryProps extends IAuthEntryProps {
interface IMsisdnAuthEntryState {
token: string;
requestingToken: boolean;
errorText: string;
errorText: string | null;
}

export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsisdnAuthEntryState> {
public static LOGIN_TYPE = AuthType.Msisdn;

private submitUrl: string;
private submitUrl?: string;
private sid: string;
private msisdn: string;

Expand Down Expand Up @@ -798,11 +798,13 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
public static PHASE_POSTAUTH = 2; // button to confirm SSO completed

private ssoUrl: string;
private popupWindow: Window;
private popupWindow: Window | null;

public constructor(props: ISSOAuthEntryProps) {
super(props);

if (!this.props.authSessionId) throw new Error("This UIA flow requires an authSessionId");

// We actually send the user through fallback auth so we don't have to
// deal with a redirect back to us, losing application context.
this.ssoUrl = props.matrixClient.getFallbackAuthUrl(this.props.loginType, this.props.authSessionId);
Expand Down Expand Up @@ -858,10 +860,10 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
};

public render(): React.ReactNode {
let continueButton = null;
let continueButton: JSX.Element;
const cancelButton = (
<AccessibleButton
onClick={this.props.onCancel}
onClick={this.props.onCancel ?? null}
kind={this.props.continueKind ? this.props.continueKind + "_outline" : "primary_outline"}
>
{_t("Cancel")}
Expand Down Expand Up @@ -909,7 +911,7 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
}

export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
private popupWindow: Window;
private popupWindow: Window | null;
private fallbackButton = createRef<HTMLButtonElement>();

public constructor(props: IAuthEntryProps) {
Expand All @@ -927,18 +929,16 @@ export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {

public componentWillUnmount(): void {
window.removeEventListener("message", this.onReceiveMessage);
if (this.popupWindow) {
this.popupWindow.close();
}
this.popupWindow?.close();
}

public focus = (): void => {
if (this.fallbackButton.current) {
this.fallbackButton.current.focus();
}
this.fallbackButton.current?.focus();
};

private onShowFallbackClick = (e: MouseEvent): void => {
if (!this.props.authSessionId) return;

e.preventDefault();
e.stopPropagation();

Expand Down
2 changes: 1 addition & 1 deletion src/components/views/auth/LanguageSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import LanguageDropdown from "../elements/LanguageDropdown";
function onChange(newLang: string): void {
if (getCurrentLanguage() !== newLang) {
SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLang);
PlatformPeg.get().reload();
PlatformPeg.get()?.reload();
}
}

Expand Down
Loading

0 comments on commit 32aa18f

Please sign in to comment.