copy and appearance updates
This commit is contained in:
parent
9f9f5d83c8
commit
0a70f1cbf2
@ -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 |
@ -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>
|
||||||
|
|||||||
29
src/components/ui/separator.tsx
Normal file
29
src/components/ui/separator.tsx
Normal 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 }
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user