Something went wrong. Try again.
[WIP] Matrix Client for web and desktop
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131import { createSignal, Show } from "solid-js";import { useNavigate } from "@solidjs/router";import { OAuth2, createClient } from "matrix-js-sdk";import { adoptMatrixClient, setActiveAccountId, upsertSession } from "../../matrix";
// The stored context produced during login (shape matches OAuth2's Context)interface StoredContext { clientId: string; deviceId?: string; codeVerifier?: string; redirectUri: string;}
interface MappedClient { homeserverUrl: string; context: StoredContext;}
function parseCallbackParams(): URLSearchParams { // response_mode is "query", but handle "fragment" too since the spec says // the IdP may answer in either fragment or query depending on the request. const query = new URLSearchParams(window.location.search); const hashValue = window.location.hash.slice(1); if (!query.get("state") && hashValue.includes("=")) { return new URLSearchParams(hashValue); } return query;}
function mapClient(state: string): MappedClient | null { const stored = sessionStorage.getItem(`asterisk.oauth.${state}`); if (!stored) return null;
sessionStorage.removeItem(`asterisk.oauth.${state}`); return JSON.parse(stored) as MappedClient;}
export default function AuthCallback() { const navigate = useNavigate(); const [error, setError] = createSignal<string | null>(null); const [errorUri, setErrorUri] = createSignal<string | null>(null);
// Component body runs once on mount in Solid v2 — no onMount needed. void (async () => { const params = parseCallbackParams();
// Error case: the authorisation failed, params carry error info. const errorParam = params.get("error"); if (errorParam) { setError( params.get("error_description") ? `${errorParam}: ${params.get("error_description")}` : errorParam, ); setErrorUri(params.get("error_uri")); return; }
const code = params.get("code"); const state = params.get("state"); if (!code || !state) { setError("Missing code or state in callback URL."); return; }
const mapped = mapClient(state); if (!mapped) { setError("No matching authorisation request found for this state."); return; }
const { homeserverUrl, context } = mapped; const oauth = new OAuth2( await createClient({ baseUrl: homeserverUrl }).getAuthMetadata(), context, );
try { // Exchange the code for tokens at the token endpoint. const token = await oauth.completeAuthorizationCodeGrant(code);
const clientOpts = { baseUrl: homeserverUrl, deviceId: context.deviceId, accessToken: token.access_token, refreshToken: token.refresh_token, };
// whoami validates the token and resolves the user id so that the // resulting client's getUserId() is non-null for adoption. const who = await createClient(clientOpts).whoami();
const session = adoptMatrixClient( createClient({ ...clientOpts, userId: who.user_id }), ); // clientId is needed for refresh-token grants; it lives only in the // SDK's OAuth context, so we have to patch it into the session here. upsertSession({ ...session, clientId: context.clientId }); setActiveAccountId(session.id); navigate("/"); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } })();
return ( <Show // The authorisation failed or the token exchange could not complete. when={error()} fallback={<p>Completing sign in…</p>} > {(getError) => ( <div> <h1>Sign in failed</h1> <p>{getError()}</p> <Show when={errorUri()}> {(getUri) => ( <p> <a href={getUri()}>More information about this error</a> </p> )} </Show> <p> <a href="/auth/login">Back to login</a> </p> </div> )} </Show> );}