Login, logout and middleware updates
This commit is contained in:
parent
f552f8024f
commit
9fb3665ced
18 changed files with 96 additions and 50 deletions
|
|
@ -13,7 +13,7 @@ export const Setup: FC = () => {
|
|||
} = useLeafcutterContext();
|
||||
const router = useRouter();
|
||||
useLayoutEffect(() => {
|
||||
setTimeout(() => router.push("/"), 4000);
|
||||
setTimeout(() => router.push("/"), 2000);
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
|
|||
typeof window !== "undefined" && window.location.origin
|
||||
? window.location.origin
|
||||
: "";
|
||||
const callbackUrl = `${origin}/setup`;
|
||||
const [provider, setProvider] = useState(undefined);
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
|
@ -157,7 +158,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
|
|||
sx={buttonStyles}
|
||||
onClick={() =>
|
||||
signIn("google", {
|
||||
callbackUrl: `${origin}`,
|
||||
callbackUrl,
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
@ -173,7 +174,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
|
|||
sx={buttonStyles}
|
||||
onClick={() =>
|
||||
signIn("apple", {
|
||||
callbackUrl: `${window.location.origin}`,
|
||||
callbackUrl,
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
@ -214,7 +215,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
|
|||
signIn("credentials", {
|
||||
email,
|
||||
password,
|
||||
callbackUrl: `${origin}/setup`,
|
||||
callbackUrl,
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
type ClientOnlyProps = { children: JSX.Element };
|
||||
type ClientOnlyProps = { children: ReactNode };
|
||||
const ClientOnly = (props: ClientOnlyProps) => {
|
||||
const { children } = props;
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
|||
}, [session]);
|
||||
|
||||
if (!session || !authenticated) {
|
||||
console.log("Not authenticated");
|
||||
return (
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Grid
|
||||
|
|
@ -89,7 +88,6 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
|||
}
|
||||
|
||||
if (session && authenticated) {
|
||||
console.log("Session and authenticated");
|
||||
return (
|
||||
<Iframe
|
||||
id={id}
|
||||
|
|
@ -102,10 +100,6 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
|||
const linkElement = document.querySelector(
|
||||
`#${id}`,
|
||||
) as HTMLIFrameElement;
|
||||
|
||||
console.log({ path });
|
||||
console.log({ id });
|
||||
console.log({ linkElement });
|
||||
if (
|
||||
linkElement.contentDocument &&
|
||||
linkElement.contentDocument?.querySelector &&
|
||||
|
|
|
|||
|
|
@ -1,9 +1,20 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { signOut } from "next-auth/react";
|
||||
|
||||
export default function Page() {
|
||||
signOut({ callbackUrl: "/login" });
|
||||
useEffect(() => {
|
||||
const multistepSignOut = async () => {
|
||||
const response = await fetch("/api/logout", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "same-origin",
|
||||
});
|
||||
signOut({ callbackUrl: "/login" });
|
||||
};
|
||||
multistepSignOut();
|
||||
}, []);
|
||||
|
||||
return <div />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState, useEffect } from "react";
|
||||
import { useFormState } from "react-dom";
|
||||
import { FC, useState, useEffect, useActionState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Grid } from "@mui/material";
|
||||
import {
|
||||
|
|
@ -44,7 +43,7 @@ export const TicketCreateDialog: FC<TicketCreateDialogProps> = ({
|
|||
},
|
||||
},
|
||||
};
|
||||
const [formState, formAction] = useFormState(
|
||||
const [formState, formAction] = useActionState(
|
||||
createTicketAction,
|
||||
initialState,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ export const TicketList: FC<TicketListProps> = ({ title, tickets }) => {
|
|||
columns={gridColumns}
|
||||
onRowClick={onRowClick}
|
||||
getRowID={(row: any) => {
|
||||
console.log({ row });
|
||||
return row.internalId;
|
||||
}}
|
||||
buttons={
|
||||
|
|
|
|||
|
|
@ -13,16 +13,18 @@ type ZammadOverviewProps = {
|
|||
export const ZammadOverview: FC<ZammadOverviewProps> = ({ name }) => {
|
||||
const [tickets, setTickets] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const hash = window?.location?.hash;
|
||||
if (typeof window !== "undefined") {
|
||||
useEffect(() => {
|
||||
const hash = window?.location?.hash;
|
||||
|
||||
if (hash) {
|
||||
const ticketID = hash.replace("#ticket/zoom/", "");
|
||||
if (ticketID && !isNaN(parseInt(ticketID, 10))) {
|
||||
redirect(`/tickets/${ticketID}`);
|
||||
if (hash) {
|
||||
const ticketID = hash.replace("#ticket/zoom/", "");
|
||||
if (ticketID && !isNaN(parseInt(ticketID, 10))) {
|
||||
redirect(`/tickets/${ticketID}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [window?.location?.hash]);
|
||||
}, [window?.location?.hash]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTickets = async () => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@ import { ZammadWrapper } from "app/(main)/_components/ZammadWrapper";
|
|||
export const Setup: FC = () => {
|
||||
const router = useRouter();
|
||||
useLayoutEffect(() => {
|
||||
const fingerprint = localStorage.getItem("fingerprint");
|
||||
if (!fingerprint || fingerprint === "") {
|
||||
const newFingerprint = `${Math.floor(
|
||||
Math.random() * 100000000,
|
||||
)}`.padStart(8, "0");
|
||||
localStorage.setItem("fingerprint", newFingerprint);
|
||||
}
|
||||
setTimeout(() => router.push("/"), 4000);
|
||||
}, [router]);
|
||||
|
||||
|
|
|
|||
|
|
@ -37,11 +37,6 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
|||
const [agents, setAgents] = useState<any>();
|
||||
const [pendingVisible, setPendingVisible] = useState(false);
|
||||
|
||||
const filteredStates =
|
||||
ticketStates?.filter(
|
||||
(state: any) => !["new", "merged", "removed"].includes(state.label),
|
||||
) ?? [];
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAgents = async () => {
|
||||
const groupID = formState?.values?.group?.split("/")?.pop();
|
||||
|
|
@ -96,7 +91,7 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
|||
[name]: value,
|
||||
},
|
||||
});
|
||||
const stateName = filteredStates?.find(
|
||||
const stateName = ticketStates?.find(
|
||||
(state: any) => state.id === formState.values.state,
|
||||
)?.name;
|
||||
setPendingVisible(stateName?.includes("pending") ?? false);
|
||||
|
|
@ -141,7 +136,7 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
|||
label="State"
|
||||
formState={formState}
|
||||
updateFormState={updateFormState}
|
||||
getOptions={() => filteredStates}
|
||||
getOptions={() => ticketStates}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
|
|
|
|||
|
|
@ -47,10 +47,10 @@ export const getOverviewTicketsAction = async (name: string) => {
|
|||
try {
|
||||
if (name === "Recent") {
|
||||
const recent = await executeREST({ path: "/api/v1/recent_view" });
|
||||
|
||||
for (const rec of recent) {
|
||||
const uniqueIDs = new Set(recent.map((rec: any) => rec.o_id));
|
||||
for (const id of uniqueIDs) {
|
||||
const tkt = await executeREST({
|
||||
path: `/api/v1/tickets/${rec.o_id}`,
|
||||
path: `/api/v1/tickets/${id}`,
|
||||
});
|
||||
tickets.push({
|
||||
...tkt,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { getTicketQuery } from "app/_graphql/getTicketQuery";
|
||||
import { getTicketArticlesQuery } from "app/_graphql/getTicketArticlesQuery";
|
||||
import { createTicketMutation } from "app/_graphql/createTicketMutation";
|
||||
|
|
@ -157,13 +156,14 @@ export const getTicketStatesAction = async () => {
|
|||
const states = await executeREST({
|
||||
path: "/api/v1/ticket_states",
|
||||
});
|
||||
|
||||
console.log({ states });
|
||||
const formattedStates =
|
||||
states?.map((state: any) => ({
|
||||
value: `gid://zammad/Ticket::State/${state.id}`,
|
||||
label: state.name,
|
||||
disabled: ["new", "merged", "removed"].includes(state.name),
|
||||
})) ?? [];
|
||||
|
||||
console.log({ formattedStates });
|
||||
return formattedStates;
|
||||
} catch (e) {
|
||||
console.error(e.message);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export const ZammadLoginProvider: FC<PropsWithChildren> = ({ children }) => {
|
|||
const response = await fetch("/api/v1/users/me", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"X-Browser-Fingerprint": `${session.expires}`,
|
||||
"X-Browser-Fingerprint": localStorage.getItem("fingerprint") || "",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export const authOptions: NextAuthOptions = {
|
|||
},
|
||||
providers,
|
||||
session: {
|
||||
maxAge: 7 * 24 * 60 * 60,
|
||||
maxAge: 3 * 24 * 60 * 60,
|
||||
},
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
callbacks: {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,12 @@
|
|||
import { getServerSession } from "app/_lib/authentication";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import crypto from "crypto";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
const getHeaders = async () => {
|
||||
const userAgent = (await headers()).get("user-agent");
|
||||
const allCookies = (await cookies()).getAll();
|
||||
const hashedUserAgent = crypto
|
||||
.createHash("sha256")
|
||||
.update(userAgent)
|
||||
.digest("hex");
|
||||
const session = await getServerSession();
|
||||
const finalHeaders = {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
"X-Browser-Fingerprint": hashedUserAgent,
|
||||
// @ts-ignore
|
||||
"X-CSRF-Token": session.user.zammadCsrfToken,
|
||||
Cookie: allCookies
|
||||
|
|
|
|||
29
apps/link/app/api/logout/route.ts
Normal file
29
apps/link/app/api/logout/route.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const allCookies = request.cookies.getAll();
|
||||
const zammadURL = process.env.ZAMMAD_URL ?? "http://zammad-nginx:8080";
|
||||
const signOutURL = `${zammadURL}/api/v1/signout`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
Cookie: allCookies
|
||||
.map((cookie) => `${cookie.name}=${cookie.value}`)
|
||||
.join("; "),
|
||||
};
|
||||
|
||||
await fetch(signOutURL, { headers });
|
||||
|
||||
const cookiePrefixesToRemove = ["_zammad"];
|
||||
const response = NextResponse.json({ message: "ok" });
|
||||
|
||||
for (const cookie of allCookies) {
|
||||
if (
|
||||
cookiePrefixesToRemove.some((prefix) => cookie.name.startsWith(prefix))
|
||||
) {
|
||||
response.cookies.set(cookie.name, "", { path: "/", maxAge: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
|
@ -14,6 +14,10 @@ const rewriteURL = (
|
|||
const destinationURL = `${destinationBaseURL}/${path}`;
|
||||
console.log(`Rewriting ${request.url} to ${destinationURL}`);
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
for (const [key, value] of requestHeaders.entries()) {
|
||||
console.log(`${key}: ${value}`);
|
||||
}
|
||||
|
||||
requestHeaders.delete("x-forwarded-user");
|
||||
requestHeaders.delete("x-forwarded-roles");
|
||||
requestHeaders.delete("connection");
|
||||
|
|
@ -61,6 +65,12 @@ const checkRewrites = async (request: NextRequestWithAuth) => {
|
|||
return rewriteURL(request, `${linkBaseURL}/zammad`, zammadURL, headers);
|
||||
} else if (zammadPaths.some((p) => request.nextUrl.pathname.startsWith(p))) {
|
||||
return rewriteURL(request, linkBaseURL, zammadURL, headers);
|
||||
} else if (request.nextUrl.pathname.startsWith("/api/v1")) {
|
||||
if (email && email !== "unknown") {
|
||||
return NextResponse.next();
|
||||
} else {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
} else {
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
|
||||
|
|
@ -75,7 +85,7 @@ const checkRewrites = async (request: NextRequestWithAuth) => {
|
|||
object-src 'none';
|
||||
base-uri 'self';
|
||||
form-action 'self';
|
||||
frame-ancestors 'none';
|
||||
frame-ancestors 'self';
|
||||
upgrade-insecure-requests;
|
||||
`;
|
||||
const contentSecurityPolicyHeaderValue = cspHeader
|
||||
|
|
@ -132,5 +142,5 @@ export default withAuth(checkRewrites, {
|
|||
});
|
||||
|
||||
export const config = {
|
||||
matcher: ["/((?!ws|wss|api|_next/static|_next/image|favicon.ico).*)"],
|
||||
matcher: ["/((?!ws|wss|_next/static|_next/image|favicon.ico).*)"],
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue