Files
cinny/src/app/pages/auth/login/Login.tsx
T
Lotus CIandClaude Opus 5.5 70206d6033
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 3m24s
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 16m50s
chore(deps): matrix-js-sdk 41.7 -> 44.0 (#201)
Picks up three releases of MatrixRTC / delayed-event fixes and
matrix-sdk-crypto-wasm 18.3.1 -> 18.9.0.

SDK 43 moved OAuth (MSC3861) into the SDK and dropped oidc-client-ts, so
the native-OAuth sign-in is ported:
- login discovers the auth server via /auth_metadata, registers with
  OAuth2.registerClient, and keeps the PKCE verifier + device in its own
  sessionStorage entry keyed by `state` (single use; CSRF check);
- refresh is done by the SDK (oauthClientId + onTokenRefresh); the #16
  cross-tab protection is kept by a narrow guard on refresh-token grants
  (the SDK calls the global fetch for OAuth, not fetchFn), which
  serialises them with a Web Lock and adopts a sibling tab's tokens;
- logout and forced logout revoke via mx.logout() (the SDK revokes at
  the auth server for OAuth sessions);
- the callback page parsed its params on every render, re-running the
  single-use code exchange in a loop; parsed once now.

Also: notification_type is deprecated in MSC4075 (ring in DMs, notify
elsewhere when omitted), _onRTCSessionMemberUpdate ->
ensureRecalculateSessionMembers, precache limit 6 -> 10 MB for the
7.8 MB crypto wasm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-24 00:32:13 -04:00

123 lines
4.5 KiB
TypeScript

import React, { useMemo } from 'react';
import { Box, Text, color } from 'folds';
import { Link, useSearchParams } from 'react-router-dom';
import { SSOAction } from 'matrix-js-sdk';
import { useAuthFlows } from '../../../hooks/useAuthFlows';
import { useAuthServer } from '../../../hooks/useAuthServer';
import { useParsedLoginFlows } from '../../../hooks/useParsedLoginFlows';
import { PasswordLoginForm } from './PasswordLoginForm';
import { SSOLogin } from '../SSOLogin';
import { TokenLogin } from './TokenLogin';
import { OidcLogin } from './OidcLogin';
import { useAutoDiscoveryInfo } from '../../../hooks/useAutoDiscoveryInfo';
import { getOidcIssuer } from '../../../cs-api';
import { OrDivider } from '../OrDivider';
import { getLoginPath, getRegisterPath, withSearchParam } from '../../pathUtils';
import { usePathWithOrigin } from '../../../hooks/usePathWithOrigin';
import { LoginPathSearchParams } from '../../paths';
import { useClientConfig } from '../../../hooks/useClientConfig';
const getLoginTokenSearchParam = () => {
// when using hasRouter query params in existing route
// gets ignored by react-router, so we need to read it ourself
// we only need to read loginToken as it's the only param that
// is provided by external entity. example: SSO login
const parmas = new URLSearchParams(window.location.search);
const loginToken = parmas.get('loginToken');
return loginToken ?? undefined;
};
const useLoginSearchParams = (searchParams: URLSearchParams): LoginPathSearchParams =>
useMemo(
() => ({
username: searchParams.get('username') ?? undefined,
email: searchParams.get('email') ?? undefined,
loginToken: searchParams.get('loginToken') ?? undefined,
}),
[searchParams],
);
export function Login() {
const server = useAuthServer();
const discovery = useAutoDiscoveryInfo();
const { hashRouter } = useClientConfig();
const { loginFlows } = useAuthFlows();
const [searchParams] = useSearchParams();
const loginSearchParams = useLoginSearchParams(searchParams);
const ssoRedirectUrl = usePathWithOrigin(getLoginPath(server));
const loginTokenForHashRouter = getLoginTokenSearchParam();
const absoluteLoginPath = usePathWithOrigin(getLoginPath(server));
if (hashRouter?.enabled && loginTokenForHashRouter) {
window.location.replace(
withSearchParam(absoluteLoginPath, {
loginToken: loginTokenForHashRouter,
}),
);
}
const parsedFlows = useParsedLoginFlows(loginFlows.flows);
// Next-gen auth (MSC3861/MSC2965): when the server advertises an OIDC issuer in
// its `.well-known` discovery, use the native OIDC flow and suppress the
// password / legacy-SSO forms (those servers, e.g. MAS, reject `m.login.password`).
const oidc = getOidcIssuer(discovery);
if (oidc.issuer) {
return (
<Box direction="Column" gap="500">
<Text as="h1" size="H2" priority="400">
Login
</Text>
<OidcLogin homeserverBaseUrl={discovery['m.homeserver'].base_url} serverName={server} />
<span data-spacing-node />
<Text align="Center">
Do not have an account? <Link to={getRegisterPath(server)}>Register</Link>
</Text>
</Box>
);
}
return (
<Box direction="Column" gap="500">
<Text as="h1" size="H2" priority="400">
Login
</Text>
{parsedFlows.token && loginSearchParams.loginToken && (
<TokenLogin token={loginSearchParams.loginToken} />
)}
{parsedFlows.password && (
<>
<PasswordLoginForm
defaultUsername={loginSearchParams.username}
defaultEmail={loginSearchParams.email}
/>
<span data-spacing-node />
{parsedFlows.sso && <OrDivider />}
</>
)}
{parsedFlows.sso && (
<>
<SSOLogin
providers={parsedFlows.sso.identity_providers}
redirectUrl={ssoRedirectUrl}
action={SSOAction.LOGIN}
saveScreenSpace={parsedFlows.password !== undefined}
/>
<span data-spacing-node />
</>
)}
{!parsedFlows.password && !parsedFlows.sso && (
<>
<Text style={{ color: color.Critical.Main }}>
{`This client does not support login on "${server}" homeserver. Password and SSO based login method not found.`}
</Text>
<span data-spacing-node />
</>
)}
<Text align="Center">
Do not have an account? <Link to={getRegisterPath(server)}>Register</Link>
</Text>
</Box>
);
}