feat: URL navigation in auth (#1603)

* bump to react 18 and install react-router-dom

* Upgrade to react 18 root

* update vite

* add cs api's

* convert state/auth to ts

* add client config context

* add auto discovery context

* add spec version context

* add auth flow context

* add background dot pattern css

* add promise utils

* init url based routing

* update auth route server path as effect

* add auth server hook

* always use server from discovery info in context

* login - WIP

* upgrade jotai to v2

* add atom with localStorage util

* add multi account sessions atom

* add default IGNORE res to auto discovery

* add error type in async callback hook

* handle password login error

* fix async callback hook

* allow password login

* Show custom server not allowed error in mxId login

* add sso login component

* add token login

* fix hardcoded m.login.password in login func

* update server input on url change

* Improve sso login labels

* update folds

* fix async callback batching state update in safari

* wrap async callback set state in queueMicrotask

* wip

* wip - register

* arrange auth file structure

* add error codes

* extract filed error component form password login

* add register util function

* handle register flow - WIP

* update unsupported auth flow method reasons

* improve password input styles

* Improve UIA flow next stage calculation
complete stages can have any order so we will look for first stage which is not in completed

* process register UIA flow stages

* Extract register UIA stages component

* improve register error messages

* add focus trap & step count in UIA stages

* add reset password path and path utils

* add path with origin hook

* fix sso redirect url

* rename register token query param to token

* restyle auth screen header

* add reset password component - WIP

* add reset password form

* add netlify rewrites

* fix netlify file indentation

* test netlify redirect

* fix vite to include netlify toml

* add more netlify redirects

* add splat to public and assets path

* fix vite base name

* add option to use hash router in config and remove appVersion

* add splash screen component

* add client config loading and error screen

* fix server picker bug

* fix reset password email input type

* make auth page small screen responsive

* fix typo in reset password screen
This commit is contained in:
Ajay Bura
2024-01-21 23:50:56 +11:00
committed by GitHub
parent bb88eb7154
commit 20db27fa7e
103 changed files with 4772 additions and 543 deletions

View File

@@ -0,0 +1,118 @@
import to from 'await-to-js';
import { LoginRequest, LoginResponse, MatrixError, createClient } from 'matrix-js-sdk';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { ClientConfig, clientAllowedServer } from '../../../hooks/useClientConfig';
import { autoDiscovery, specVersions } from '../../../cs-api';
import { updateLocalStore } from '../../../../client/action/auth';
import { ROOT_PATH } from '../../paths';
import { ErrorCode } from '../../../cs-errorcode';
export enum GetBaseUrlError {
NotAllow = 'NotAllow',
NotFound = 'NotFound',
}
export const factoryGetBaseUrl = (clientConfig: ClientConfig, server: string) => {
const getBaseUrl = async (): Promise<string> => {
if (!clientAllowedServer(clientConfig, server)) {
throw new Error(GetBaseUrlError.NotAllow);
}
const [, discovery] = await to(autoDiscovery(fetch, server));
let mxIdBaseUrl: string | undefined;
const [, discoveryInfo] = discovery ?? [];
if (discoveryInfo) {
mxIdBaseUrl = discoveryInfo['m.homeserver'].base_url;
}
if (!mxIdBaseUrl) {
throw new Error(GetBaseUrlError.NotFound);
}
const [, versions] = await to(specVersions(fetch, mxIdBaseUrl));
if (!versions) {
throw new Error(GetBaseUrlError.NotFound);
}
return mxIdBaseUrl;
};
return getBaseUrl;
};
export enum LoginError {
ServerNotAllowed = 'ServerNotAllowed',
InvalidServer = 'InvalidServer',
Forbidden = 'Forbidden',
UserDeactivated = 'UserDeactivated',
InvalidRequest = 'InvalidRequest',
RateLimited = 'RateLimited',
Unknown = 'Unknown',
}
export type CustomLoginResponse = {
baseUrl: string;
response: LoginResponse;
};
export const login = async (
serverBaseUrl: string | (() => Promise<string>),
data: LoginRequest
): Promise<CustomLoginResponse> => {
const [urlError, url] =
typeof serverBaseUrl === 'function' ? await to(serverBaseUrl()) : [undefined, serverBaseUrl];
if (urlError) {
throw new MatrixError({
errcode:
urlError.message === GetBaseUrlError.NotAllow
? LoginError.ServerNotAllowed
: LoginError.InvalidServer,
});
}
const mx = createClient({ baseUrl: url });
const [err, res] = await to<LoginResponse, MatrixError>(mx.login(data.type, data));
if (err) {
if (err.httpStatus === 400) {
throw new MatrixError({
errcode: LoginError.InvalidRequest,
});
}
if (err.httpStatus === 429) {
throw new MatrixError({
errcode: LoginError.RateLimited,
});
}
if (err.errcode === ErrorCode.M_USER_DEACTIVATED) {
throw new MatrixError({
errcode: LoginError.UserDeactivated,
});
}
if (err.httpStatus === 403) {
throw new MatrixError({
errcode: LoginError.Forbidden,
});
}
throw new MatrixError({
errcode: LoginError.Unknown,
});
}
return {
baseUrl: url,
response: res,
};
};
export const useLoginComplete = (data?: CustomLoginResponse) => {
const navigate = useNavigate();
useEffect(() => {
if (data) {
const { response: loginRes, baseUrl: loginBaseUrl } = data;
updateLocalStore(loginRes.access_token, loginRes.device_id, loginRes.user_id, loginBaseUrl);
// TODO: add after login redirect url
navigate(ROOT_PATH, { replace: true });
}
}, [data, navigate]);
};