copy and appearance updates

This commit is contained in:
Ulysse Carion 2024-05-17 10:13:27 -07:00
parent 9f9f5d83c8
commit 0a70f1cbf2
5 changed files with 168 additions and 102 deletions

View File

@ -9,9 +9,15 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet">
<style>
body {
background-image: url("/wordart.png");
}
</style>
</head> </head>
<body> <body>
<div id="react-root" class="h-full min-h-screen bg-muted/40"></div> <div id="react-root" class="h-full min-h-screen"></div>
</body> </body>
<script src="/index.js"></script> <script src="/index.js"></script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -11,10 +11,10 @@ export function App() {
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
<Route path="/instant-setup" element={<InstantSetupPage />} /> <Route path="/instant-setup" element={<InstantSetupPage />} />
<Route path="/apps/:appId/sso" element={<SSOPage />} />
<Route path="/" element={<Page />}> <Route path="/" element={<Page />}>
<Route path="/" element={<HomePage />} /> <Route path="/" element={<HomePage />} />
<Route path="/apps/:appId" element={<ViewAppPage />} /> <Route path="/apps/:appId" element={<ViewAppPage />} />
<Route path="/apps/:appId/sso" element={<SSOPage />} />
</Route> </Route>
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>

View File

@ -0,0 +1,29 @@
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }

View File

@ -27,6 +27,7 @@ import {
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import moment from "moment"; import moment from "moment";
import { clsx } from "clsx"; import { clsx } from "clsx";
import { Separator } from "@/components/ui/separator";
const formSchema = z.object({ const formSchema = z.object({
email: z.string().min(1, { message: "Email is required." }), email: z.string().min(1, { message: "Email is required." }),
@ -82,118 +83,148 @@ export function SSOPage() {
}); });
async function onSubmit(values: z.infer<typeof formSchema>, e: any) { async function onSubmit(values: z.infer<typeof formSchema>, e: any) {
const key = await window.crypto.subtle.importKey( setLoading(true);
"jwk",
GLOBAL_NONSECURE_KEY,
{
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
},
true,
["sign"],
);
const now = moment(new Date()).add(-1, "hour"); setTimeout(async () => {
const expire = moment(new Date()).add(1, "hour"); const key = await window.crypto.subtle.importKey(
"jwk",
GLOBAL_NONSECURE_KEY,
{
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
},
true,
["sign"],
);
inputRef.current!.value = await encodeAssertion(key, { const now = moment(new Date()).add(-1, "hour");
idpEntityId: `https://dummyidp.com/apps/${app.id}`, const expire = moment(new Date()).add(1, "hour");
subjectId: `${values.email}@${app.requiredDomain}`,
firstName: values.firstName, inputRef.current!.value = await encodeAssertion(key, {
lastName: values.lastName, idpEntityId: `https://dummyidp.com/apps/${app.id}`,
spEntityId: app.spEntityId, subjectId: `${values.email}@${app.requiredDomain}`,
sessionId: sessionId, firstName: values.firstName,
now: now.format(), lastName: values.lastName,
expire: expire.format(), spEntityId: app.spEntityId,
}); sessionId: sessionId,
inputRef.current!.form!.action = app.spAcsUrl; now: now.format(),
inputRef.current!.form!.submit(); expire: expire.format(),
});
inputRef.current!.form!.action = app.spAcsUrl;
inputRef.current!.form!.submit();
}, 1000);
} }
const [loading, setLoading] = useState(false);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
return ( return (
<> <div className="flex w-screen h-screen">
<form method="post"> <form method="post">
<input type="hidden" name="SAMLResponse" ref={inputRef} /> <input type="hidden" name="SAMLResponse" ref={inputRef} />
</form> </form>
<Card className="mx-auto max-w-xl"> <div className="m-auto max-w-xl relative">
<CardHeader> {loading && (
<CardTitle>Log on</CardTitle> <div className="absolute bg-black/80 inset-0 flex justify-center items-center z-10 dark text-white">
<CardDescription> <img className="m-auto" alt="loading" src="/loading.gif" />
Enter some details about who you want DummyIDP to log you in as. </div>
</CardDescription> )}
</CardHeader> <Card>
<CardContent> <CardHeader>
<Form {...form}> <CardTitle>Log on</CardTitle>
<form <CardDescription>
onSubmit={form.handleSubmit(onSubmit)} Enter some details about who you want DummyIDP to say you are.
className="grid grid-cols-2 gap-4" </CardDescription>
> </CardHeader>
<FormField <CardContent>
control={form.control} <Form {...form}>
name="email" <form
render={({ field }) => ( onSubmit={form.handleSubmit(onSubmit)}
<FormItem className="col-span-2"> className="grid grid-cols-2 gap-4"
<FormLabel>Email</FormLabel> >
<FormControl> <FormField
<div className="flex"> control={form.control}
<Input className="rounded-r-none" {...field} /> name="email"
<span className="inline-flex text-sm items-center rounded-r-md border border-l-0 border-input px-3 text-muted-foreground"> render={({ field }) => (
@{app.requiredDomain} <FormItem className="col-span-2">
</span> <FormLabel>Email</FormLabel>
</div> <FormControl>
</FormControl> <div className="flex">
<Input className="rounded-r-none" {...field} />
<span className="inline-flex text-sm items-center rounded-r-md border border-l-0 border-input px-3 text-muted-foreground">
@{app.requiredDomain}
</span>
</div>
</FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)}
/>
<FormField
control={form.control}
name="firstName"
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>First Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="lastName"
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Last Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="mt-4 col-span-2">
<Button
className={clsx(
"w-full",
email &&
"bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 shadow-xl",
)} )}
> />
Log on
</Button> <FormField
</div> control={form.control}
</form> name="firstName"
</Form> render={({ field }) => (
</CardContent> <FormItem className="col-span-1">
</Card> <FormLabel>First Name</FormLabel>
</> <FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="lastName"
render={({ field }) => (
<FormItem className="col-span-1">
<FormLabel>Last Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="mt-4 col-span-2">
<Button
className={clsx(
"w-full",
email &&
"bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 shadow-xl",
)}
>
Log on
</Button>
</div>
</form>
</Form>
<Separator className="my-8" />
<div className="text-xs text-muted-foreground space-y-2">
<p>
DummyIDP is a fake identity provider. It's a dummy stand-in for
something like Okta, Google Workspace, or Microsoft Entra.
</p>
<p>
In the real world, your customers would never see or interact
with DummyIDP in any way. In the real world, your customers
would see their own IDP, not this fake one.
</p>
<p>
We made DummyIDP because there doesn't exist any free, no-hassle
SAML Identity Provider out there that developers can test with.
</p>
</div>
</CardContent>
</Card>
</div>
</div>
); );
} }