CI / Build & Quality Checks (push) Skipped
CI / Secret scan (gitleaks) (push) Skipped
CI / Playwright smoke (e2e) (push) Skipped
CI / Build & Quality Checks (pull_request) Successful in 3m41s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 10s
CI / Playwright smoke (e2e) (pull_request) Successful in 16m45s
The login page looked for an OAuth issuer in .well-known (m.authentication), which matrix.org no longer publishes, so its users got the password / SSO compatibility forms. Detect native OAuth the way the SDK finds the auth server for refresh: load /auth_metadata next to the login flows. When present: - Login shows "Continue with <server>" only (as Element does); a legacy SSO loginToken coming back from a redirect still completes. - Register opens the auth server's sign-up page (prompt=create, where prompt_values_supported says so) instead of the legacy forms. Servers without it (Lotus) are unchanged. Checked against the live servers: matrix.org login and register show "Continue with matrix.org" and no password form; matrix.lotusguild.org keeps its password form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
import { ReactNode, useCallback, useEffect, useMemo } from 'react';
|
|
import { MatrixError, createClient } from 'matrix-js-sdk';
|
|
import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback';
|
|
import { useAutoDiscoveryInfo } from '../hooks/useAutoDiscoveryInfo';
|
|
import { promiseFulfilledResult, promiseRejectedResult } from '../utils/common';
|
|
import {
|
|
AuthFlows,
|
|
RegisterFlowStatus,
|
|
RegisterFlowsResponse,
|
|
parseRegisterErrResp,
|
|
} from '../hooks/useAuthFlows';
|
|
|
|
type AuthFlowsLoaderProps = {
|
|
fallback?: () => ReactNode;
|
|
error?: (err: unknown) => ReactNode;
|
|
children: (authFlows: AuthFlows) => ReactNode;
|
|
};
|
|
export function AuthFlowsLoader({ fallback, error, children }: AuthFlowsLoaderProps) {
|
|
const autoDiscoveryInfo = useAutoDiscoveryInfo();
|
|
const baseUrl = autoDiscoveryInfo['m.homeserver'].base_url;
|
|
|
|
const mx = useMemo(() => createClient({ baseUrl }), [baseUrl]);
|
|
|
|
const [state, load] = useAsyncCallback(
|
|
useCallback(async () => {
|
|
const result = await Promise.allSettled([
|
|
mx.loginFlows(),
|
|
mx.registerRequest({}),
|
|
// Rejects when the server has no native OAuth: normal for most servers.
|
|
mx.getAuthMetadata(),
|
|
]);
|
|
const loginFlows = promiseFulfilledResult(result[0]);
|
|
const registerResp = promiseRejectedResult(result[1]) as MatrixError | undefined;
|
|
let registerFlows: RegisterFlowsResponse = { status: RegisterFlowStatus.InvalidRequest };
|
|
|
|
if (typeof registerResp === 'object' && registerResp.httpStatus) {
|
|
registerFlows = parseRegisterErrResp(registerResp);
|
|
}
|
|
|
|
if (!loginFlows) {
|
|
throw new Error('Missing auth flow!');
|
|
}
|
|
if ('errcode' in loginFlows) {
|
|
throw new Error('Failed to load auth flow!');
|
|
}
|
|
|
|
const authFlows: AuthFlows = {
|
|
loginFlows,
|
|
registerFlows,
|
|
authMetadata: promiseFulfilledResult(result[2]),
|
|
};
|
|
|
|
return authFlows;
|
|
}, [mx]),
|
|
);
|
|
|
|
useEffect(() => {
|
|
load().catch(() => {});
|
|
}, [load]);
|
|
|
|
if (state.status === AsyncStatus.Idle || state.status === AsyncStatus.Loading) {
|
|
return fallback?.();
|
|
}
|
|
|
|
if (state.status === AsyncStatus.Error) {
|
|
return error?.(state.error);
|
|
}
|
|
|
|
return children(state.data);
|
|
}
|