Skip to content

Commit

Permalink
feat(auth): Unauthenticated user will be block by our auth-guard (#555)
Browse files Browse the repository at this point in the history
Because

- User without authenticated can not use the services

This commit

- Unauthenticated user will be block by our auth-guard
  • Loading branch information
EiffelFly authored Sep 22, 2023
1 parent 5d91e0b commit 47eaac7
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 0 deletions.
40 changes: 40 additions & 0 deletions public/images/auth-page-bg-strip.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 65 additions & 0 deletions src/components/AuthPageBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Logo } from "@instill-ai/design-system";
import Image from "next/image";

export const AuthPageBase = ({ children }: { children: React.ReactNode }) => {
return <div className="min-w-screen min-h-screen">{children}</div>;
};

const Container = ({ children }: { children: React.ReactNode }) => {
return <div className="flex h-screen w-full flex-1">{children}</div>;
};

const Content = ({ children }: { children: React.ReactNode }) => {
return (
<div className="grid h-full w-full grid-flow-row grid-cols-2">
<div className="flex h-full flex-col bg-[#F9FAFB]">
<div className="ml-auto flex h-full w-full max-w-[720px] flex-col">
<div className="mb-auto flex flex-col p-8">
<Logo
className="!mb-[100px] !mt-0"
variant="ColourLogomarkBlackType"
width={215}
/>
<div className="mb-auto flex flex-col gap-y-[60px] px-8">
<div className="flex flex-col gap-y-1">
<p className="font-sans text-5xl font-bold leading-[60px]">
<span className="text-semantic-fg-primary">Meet</span>{" "}
<span className="text-semantic-accent-default">
Instill Cloud
</span>
</p>
<p className="font-sans text-xl font-bold leading-4 text-semantic-fg-primary">
The Backbone for All Your AI Needs
</p>
</div>
<p className="font-sans text-[40px] font-semibold leading-[50px]">
A no-code/low-code platform to build AI-first applications to
process your text, image, video and audio in minutes
</p>
</div>
</div>

<div className="flex flex-row">
<p className="mr-auto mt-auto p-8 font-sans text-semantic-fg-disabled product-body-text-3-regular">
© Instill AI 2023
</p>
<Image
src="/images/auth-page-bg-strip.svg"
alt="auth-page-bg-strip"
width={407}
height={392}
/>
</div>
</div>
</div>
<div className="flex h-full bg-semantic-bg-primary">
<div className="mr-auto flex h-full w-full max-w-[720px]">
{children}
</div>
</div>
</div>
);
};

AuthPageBase.Container = Container;
AuthPageBase.Content = Content;
78 changes: 78 additions & 0 deletions src/components/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Button, Form, Input } from "@instill-ai/design-system";
import * as z from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

const LoginFormSchema = z.object({
username: z.string(),
password: z.string(),
});

export const LoginForm = () => {
const form = useForm<z.infer<typeof LoginFormSchema>>({
resolver: zodResolver(LoginFormSchema),
});

function onSubmit(data: z.infer<typeof LoginFormSchema>) {
alert(JSON.stringify(data));
}

return (
<Form.Root {...form}>
<form
className="flex flex-col space-y-5"
onSubmit={form.handleSubmit(onSubmit)}
>
<div className="flex flex-col gap-y-5">
<Form.Field
control={form.control}
name="username"
render={({ field }) => {
return (
<Form.Item>
<Form.Label htmlFor={field.name}>Username</Form.Label>
<Form.Control>
<Input.Root>
<Input.Core
{...field}
id={field.name}
placeholder="Username"
type="text"
/>
</Input.Root>
</Form.Control>
<Form.Message />
</Form.Item>
);
}}
/>
<Form.Field
control={form.control}
name="password"
render={({ field }) => {
return (
<Form.Item>
<Form.Label htmlFor={field.name}>Password</Form.Label>
<Form.Control>
<Input.Root>
<Input.Core
{...field}
id={field.name}
placeholder="Password"
type="password"
/>
</Input.Root>
</Form.Control>
<Form.Message />
</Form.Item>
);
}}
/>
</div>
<Button variant="primary" className="!w-full !flex-1" size="lg">
Continue
</Button>
</form>
</Form.Root>
);
};
2 changes: 2 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export * from "./AuthPageBase";
export * from "./ConsoleCorePageHead";
export * from "./Sidebar";
export * from "./ErrorBoundary";
export * from "./LoginForm";
export * from "./ModelReadmeMarkdown";
export * from "./OnboardingForm";
1 change: 1 addition & 0 deletions src/lib/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./withMiddlewareAuthRequired";
67 changes: 67 additions & 0 deletions src/lib/auth/withMiddlewareAuthRequired.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { NextMiddleware, NextResponse } from "next/server";

export type WithMiddlewareAuthRequiredOptions = {
middleware?: NextMiddleware;
};

export function withMiddlewareAuthRequired(
props?: NextMiddleware | WithMiddlewareAuthRequiredOptions
): NextMiddleware {
return async function wrappedMiddleware(...args) {
const [req] = args;
let middleware: NextMiddleware | undefined;
const { pathname, origin } = req.nextUrl;

if (typeof props === "function") {
middleware = props;
} else if (props?.middleware) {
middleware = props.middleware;
}

const ignorePaths = ["/_next", "/favicon.ico"];
if (ignorePaths.some((p) => pathname.startsWith(p))) {
return;
}

const authRes = NextResponse.next();
const sessionCookie = req.cookies.get("instill-ai-session");
const session = JSON.parse(sessionCookie?.value || "{}");

if (!session.access_token) {
if (pathname.startsWith("/api")) {
return NextResponse.json(
{
error: "not_authenticated",
description:
"The user does not have an active session or is not authenticated",
},
{ status: 401 }
);
}
return NextResponse.redirect(new URL("/login", origin));
}

const providedMiddlewareRes = await (middleware && middleware(...args));

if (providedMiddlewareRes) {
const nextRes = new NextResponse(
providedMiddlewareRes.body,
providedMiddlewareRes
);
const cookies = authRes.cookies.getAll();
if ("cookies" in providedMiddlewareRes) {
for (const cookie of providedMiddlewareRes.cookies.getAll()) {
nextRes.cookies.set(cookie);
}
}
for (const cookie of cookies) {
if (!nextRes.cookies.get(cookie.name)) {
nextRes.cookies.set(cookie);
}
}
return nextRes;
} else {
return authRes;
}
};
}
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./auth";
export * from "./mgmtRoleOptions";
export * from "./useTrackingToken";
13 changes: 13 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { withMiddlewareAuthRequired } from "./lib";

export default withMiddlewareAuthRequired();

export const config = {
matcher: [
"/resources/:path*",
"/dashboard/:path*",
"/model-hub/:path*",
"/pipelines/:path*",
"/settings/:path*",
],
};
25 changes: 25 additions & 0 deletions src/pages/login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NextPageWithLayout } from "./_app";
import { AuthPageBase, LoginForm } from "@/components";

const LoginPage: NextPageWithLayout = () => {
return (
<div className="m-auto flex w-[360px] flex-col">
<h1 className="mb-8 text-semantic-fg-primary product-headings-heading-1">
Login
</h1>
<LoginForm />
</div>
);
};

LoginPage.getLayout = (page) => {
return (
<AuthPageBase>
<AuthPageBase.Container>
<AuthPageBase.Content>{page}</AuthPageBase.Content>
</AuthPageBase.Container>
</AuthPageBase>
);
};

export default LoginPage;

0 comments on commit 47eaac7

Please sign in to comment.