feat/Update_Controllers_to_Allow_for_Premium_Membership

This commit is contained in:
cesnimda
2026-08-03 09:17:28 +02:00
parent de937d25dc
commit c3f4a57195
187 changed files with 26062 additions and 991 deletions
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";
import { Box, Button, Chip, Paper, Typography } from "@mui/material";
import { Box, Button, Chip, Paper, TextField, Typography } from "@mui/material";
import { PublicClientApplication } from "@azure/msal-browser";
import { api, getApiErrorMessage } from "../api";
@@ -24,9 +24,10 @@ type MeResponse = {
};
let msalInstance: PublicClientApplication | null = null;
function getMsalInstance(clientId: string): PublicClientApplication {
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/common", redirectUri: window.location.origin },
auth: { clientId, authority: `https://login.microsoftonline.com/${tenant}`, redirectUri: window.location.origin },
});
return msalInstance;
}
@@ -37,6 +38,7 @@ export default function MicrosoftAuthCard({ onSignedIn }: { onSignedIn?: () => v
const [me, setMe] = useState<MeResponse | null>(null);
const [working, setWorking] = useState(false);
const [pendingToken, setPendingToken] = useState<string | null>(null);
const [currentPassword, setCurrentPassword] = useState("");
const clientId = (process.env.NEXT_PUBLIC_MICROSOFT_CLIENT_ID || "").trim();
const signedIn = Boolean(me?.provider);
@@ -69,18 +71,25 @@ export default function MicrosoftAuthCard({ onSignedIn }: { onSignedIn?: () => v
if (!clientId) return;
setWorking(true);
try {
const msal = getMsalInstance(clientId);
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 (me?.provider === "local") {
const res = await api.post<{ linked: boolean; email?: string | null }>("/auth/microsoft/link", { token: idToken, rememberMe: getAuthPersistencePreference() === "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");
await refreshMe();
clearAuthClientState();
setMe(null);
setCurrentPassword("");
toast(t("microsoftSecurityChangeSignInAgain"), "info");
} else {
const res = await api.post<{ requiresTwoFactor?: boolean; pendingToken?: string }>("/auth/microsoft/exchange", { token: idToken, rememberMe: getAuthPersistencePreference() === "local" });
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 {
@@ -150,12 +159,22 @@ export default function MicrosoftAuthCard({ onSignedIn }: { onSignedIn?: () => v
<Typography variant="caption" sx={{ color: "text.secondary", fontWeight: 700, letterSpacing: 0.4, textTransform: "uppercase" }}>
{actionLabel}
</Typography>
<Button variant="outlined" disabled={working} onClick={() => void handleSignIn()}>
<Button variant="outlined" disabled={working || (me?.provider === "local" && !currentPassword)} onClick={() => void handleSignIn()}>
{actionLabel}
</Button>
</Box>
<Box sx={{ display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap" }}>
{me?.provider === "local" ? (
<TextField
size="small"
label={t("profileCurrentPassword")}
type="password"
value={currentPassword}
onChange={(event) => setCurrentPassword(event.target.value)}
autoComplete="current-password"
/>
) : null}
{signedIn ? (
<Button
variant="outlined"
@@ -175,12 +194,15 @@ export default function MicrosoftAuthCard({ onSignedIn }: { onSignedIn?: () => v
<Button
variant="outlined"
color="warning"
disabled={working}
disabled={working || !currentPassword}
onClick={async () => {
try {
await api.delete("/auth/microsoft/link");
await api.delete("/auth/microsoft/link", { data: { currentPassword } });
clearAuthClientState();
setMe(null);
setCurrentPassword("");
toast(t("microsoftUnlinked"), "info");
await refreshMe();
toast(t("microsoftSecurityChangeSignInAgain"), "info");
} catch (e: any) {
const msg = e?.response?.data || e?.message || t("microsoftUnlinkFailed");
toast(String(msg), "error");