Files
cinny/src/app/components/AuthFlowsLoader.tsx
T
Lotus CIandClaude Opus 5.5 3c3bb2e41c
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
feat(auth): offer native OAuth sign-in on servers like matrix.org
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
2026-10-24 16:15:24 -04:00

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);
}