show proper login error details (fixes #35)

This commit is contained in:
Grant 2024-05-29 15:57:39 -06:00
parent 95bbd633c8
commit 1d00b53aba
3 changed files with 201 additions and 25 deletions

View File

@ -10,6 +10,7 @@ import { ChatContext } from "../contexts/ChatContext";
import "react-toastify/dist/ReactToastify.css";
import { ToastContainer } from "react-toastify";
import { AuthErrors } from "./AuthErrors";
const Chat = lazy(() => import("./Chat/Chat"));
@ -33,6 +34,7 @@ const AppInner = () => {
<DebugModal />
<SettingsSidebar />
<AuthErrors />
<ToastContainer position="top-left" />
</>

View File

@ -0,0 +1,143 @@
import {
Button,
Link,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
} from "@nextui-org/react";
import { useEffect, useState } from "react";
const Params = {
TYPE: "auth_type",
ERROR: "auth_error",
ERROR_DESC: "auth_error_desc",
CAN_RETRY: "auth_retry",
};
/**
* Show popups that detail auth error messages
* @returns
*/
export const AuthErrors = () => {
const [params, setParams] = useState(
new URLSearchParams(window.location.search)
);
const onClose = () => {
const url = new URL(window.location.href);
url.search = "";
// window.history.replaceState({}, "", url.toString());
setParams(new URLSearchParams(window.location.search));
};
return (
<>
<RPError
isOpen={params.get(Params.TYPE) === "rp"}
onClose={onClose}
params={params}
/>
<OPError
isOpen={params.get(Params.TYPE) === "op"}
onClose={onClose}
params={params}
/>
</>
);
};
/**
* This is for RP errors, which can be triggered by modifying data sent in callbacks
*
* These errors can typically be retried
*
* @param param0
* @returns
*/
const RPError = ({
isOpen,
onClose,
params,
}: {
isOpen: boolean;
onClose: () => void;
params: URLSearchParams;
}) => {
return (
<Modal isOpen={isOpen} onOpenChange={onClose} isDismissable={false}>
<ModalContent>
{(onClose) => (
<>
<ModalHeader>Login Error</ModalHeader>
<ModalBody>
<b>Error:</b> {params.get(Params.ERROR)}
<br />
<br />
<b>Error Description:</b> {params.get(Params.ERROR_DESC)}
</ModalBody>
<ModalFooter>
<Button color="primary" href="/api/login" as={Link}>
Login
</Button>
</ModalFooter>
</>
)}
</ModalContent>
</Modal>
);
};
/**
* This is for OP errors, these might not be retryable
* @param param0
* @returns
*/
const OPError = ({
isOpen,
onClose,
params,
}: {
isOpen: boolean;
onClose: () => void;
params: URLSearchParams;
}) => {
const canRetry = params.has(Params.CAN_RETRY);
const [error, setError] = useState(params.get(Params.ERROR));
const [errorDesc, setErrorDesc] = useState(params.get(Params.ERROR_DESC));
useEffect(() => {
switch (params.get(Params.ERROR)) {
case "invalid_grant":
setErrorDesc("Invalid token, try logging in again");
break;
}
}, [params]);
return (
<Modal isOpen={isOpen} onOpenChange={onClose} isDismissable={false}>
<ModalContent>
{(onClose) => (
<>
<ModalHeader>Login Error</ModalHeader>
<ModalBody>
<b>Error:</b> {error}
<br />
<br />
<b>Error Description:</b> {errorDesc}
</ModalBody>
<ModalFooter>
{canRetry && (
<Button color="primary" href="/api/login" as={Link}>
Login
</Button>
)}
</ModalFooter>
</>
)}
</ModalContent>
</Modal>
);
};

View File

@ -4,6 +4,22 @@ import { OpenID } from "../lib/oidc";
import { TokenSet, errors as OIDC_Errors } from "openid-client";
import { Logger } from "../lib/Logger";
const ClientParams = {
TYPE: "auth_type",
ERROR: "auth_error",
ERROR_DESC: "auth_error_desc",
CAN_RETRY: "auth_retry",
};
const buildQuery = (obj: { [k in keyof typeof ClientParams]?: string }) => {
const params = new URLSearchParams();
for (const [k, v] of Object.entries(obj)) {
const k_: keyof typeof ClientParams = k as any;
params.set(ClientParams[k_], v);
}
return "?" + params.toString();
};
const app = Router();
app.get("/login", (req, res) => {
@ -30,11 +46,14 @@ app.get("/callback", async (req, res) => {
if (e instanceof OIDC_Errors.RPError) {
// client error
res.status(400).json({
success: false,
error: e.name,
error_description: e.message,
});
res.redirect(
"/" +
buildQuery({
TYPE: "rp",
ERROR: e.name,
ERROR_DESC: e.message,
})
);
return;
}
@ -47,34 +66,46 @@ app.get("/callback", async (req, res) => {
Logger.error(
"OpenID is improperly configured. Cannot exchange tokens, do I have valid credetials?"
);
res.status(500).json({
success: false,
error: "internal server error",
error_description: "I'm misconfigured.",
});
res.redirect(
"/" +
buildQuery({
TYPE: "op",
ERROR: "Internal Server Error",
ERROR_DESC: "I'm misconfigured.",
})
);
return;
case "invalid_grant":
res.status(400).json({
success: false,
error: "invalid_grant",
error_description: "retry /api/login",
});
res.redirect(
"/" +
buildQuery({
TYPE: "op",
ERROR: "invalid_grant",
CAN_RETRY: "true",
})
);
return;
}
res.status(400).json({
success: false,
error: e.error,
error_description: e.error_description,
});
res.redirect(
"/" +
buildQuery({
TYPE: "op",
ERROR: e.error,
ERROR_DESC: e.error_description,
})
);
return;
}
res.status(500).json({
success: false,
error: "unknown error",
error_description: "report this",
});
res.redirect(
"/" +
buildQuery({
TYPE: "op",
ERROR: "unknown error",
ERROR_DESC: "report this",
})
);
return;
}