import React, { useEffect, useState } from "react"; import { Box, Button, Chip, TextField, Typography } from "@mui/material"; import { PublicClientApplication } from "@azure/msal-browser"; import { api, getApiErrorMessage } from "../api"; import { clearAuthClientState, getAuthPersistencePreference } from "../auth"; import TwoFactorChallenge from "./TwoFactorChallenge"; import { useToast } from "../toast"; import { useI18n } from "../i18n/I18nProvider"; import { PageSection } from "../layout/PageLayout"; type MeResponse = { provider?: "local" | "google" | "microsoft" | "external"; email?: string; userName?: string; displayName?: string; firstName?: string; lastName?: string; microsoftLink?: { linked: boolean; email?: string | null; linkedAt?: string | null; } | null; }; let msalInstance: PublicClientApplication | null = null; export function getMicrosoftMsalInstance(clientId: string): PublicClientApplication { const tenant = (process.env.NEXT_PUBLIC_MICROSOFT_TENANT || "common").trim() || "common"; msalInstance ??= new PublicClientApplication({ auth: { clientId, authority: `https://login.microsoftonline.com/${tenant}`, redirectUri: window.location.origin }, }); return msalInstance; } export default function MicrosoftAuthCard({ onSignedIn, presentation = "account" }: { onSignedIn?: () => void; presentation?: "account" | "sign-in" }) { const { toast } = useToast(); const { t, language } = useI18n(); const [me, setMe] = useState(null); const [working, setWorking] = useState(false); const [pendingToken, setPendingToken] = useState(null); const [currentPassword, setCurrentPassword] = useState(""); const clientId = (process.env.NEXT_PUBLIC_MICROSOFT_CLIENT_ID || "").trim(); const signInOnly = presentation === "sign-in"; const signedIn = Boolean(me?.provider); const actionLabel = signInOnly || !signedIn ? t("continueWithMicrosoft") : me?.provider === "local" && !me?.microsoftLink?.linked ? t("linkWithMicrosoft") : t("signInWithMicrosoft"); async function refreshMe() { try { const res = await api.get("/auth/me"); setMe(res.data); } catch { setMe(null); } } useEffect(() => { if (signInOnly) return; void refreshMe(); }, [signInOnly]); useEffect(() => { if (signInOnly) return; const onAuthChanged = () => { void refreshMe(); }; window.addEventListener("auth-changed", onAuthChanged); return () => window.removeEventListener("auth-changed", onAuthChanged); }, [signInOnly]); async function handleSignIn() { if (!clientId) return; setWorking(true); try { const msal = getMicrosoftMsalInstance(clientId); await msal.initialize(); const result = await msal.loginPopup({ scopes: ["openid", "profile", "email"] }); const idToken = result.idToken; if (!idToken) throw new Error(t("microsoftAuthFailed")); if (!signInOnly && me?.provider === "local") { const res = await api.post<{ linked: boolean; email?: string | null }>("/auth/microsoft/link", { token: idToken, currentPassword }); toast(res.data?.email ? t("microsoftLinkedSuccessWithEmail", { email: res.data.email }) : t("microsoftLinkedSuccess"), "success"); clearAuthClientState(); setMe(null); setCurrentPassword(""); toast(t("microsoftSecurityChangeSignInAgain"), "info"); } else { const res = await api.post<{ requiresTwoFactor?: boolean; pendingToken?: string; legacyRelinkRequired?: boolean }>("/auth/microsoft/exchange", { token: idToken, rememberMe: getAuthPersistencePreference() === "local" }); if (res.data?.legacyRelinkRequired) { toast(t("microsoftLegacyRelinkEmailSent"), "info"); return; } if (res.data?.requiresTwoFactor && res.data.pendingToken) { setPendingToken(res.data.pendingToken); } else { window.dispatchEvent(new Event("auth-changed")); toast(t("microsoftSignedIn"), "success"); onSignedIn?.(); } } } catch (e: any) { toast(getApiErrorMessage(e, t("microsoftAuthFailed")), "error"); } finally { setWorking(false); } } const signedInName = me?.userName || me?.displayName || [me?.firstName, me?.lastName].filter(Boolean).join(" ") || me?.email || ""; if (signInOnly) { if (pendingToken) { return setPendingToken(null)} onSuccess={() => { setPendingToken(null); window.dispatchEvent(new Event("auth-changed")); toast(t("microsoftSignedIn"), "success"); onSignedIn?.(); }} />; } return ; } return ( {!clientId && ( {t("microsoftSetupHint")} )} {clientId && pendingToken && ( setPendingToken(null)} onSuccess={() => { setPendingToken(null); window.dispatchEvent(new Event("auth-changed")); toast(t("microsoftSignedIn"), "success"); onSignedIn?.(); }} /> )} {clientId && !pendingToken && ( {me?.microsoftLink?.linkedAt ? : null} {!signedIn ? ( {t("microsoftSignInHint")} ) : me?.provider === "local" ? ( {me.microsoftLink?.linked ? t("microsoftLinkedTo", { email: me.microsoftLink.email || t("microsoftLinkedToYourAccount") }) : t("microsoftBindHint")} ) : ( {t("microsoftExchangeHint")} )} {actionLabel} {me?.provider === "local" ? ( setCurrentPassword(event.target.value)} autoComplete="current-password" /> ) : null} {signedIn ? ( ) : null} {me?.provider === "local" && me.microsoftLink?.linked ? ( ) : null} {signedIn && me?.email ? ( {t("signedInAs", { name: signedInName })} ) : null} )} ); }