instant setup flow
This commit is contained in:
parent
c31ad152b9
commit
2b64f1de76
@ -4,11 +4,13 @@ import { SSOPage } from "@/pages/SSOPage";
|
||||
import { HomePage } from "@/pages/HomePage";
|
||||
import { ViewAppPage } from "@/pages/ViewAppPage";
|
||||
import { Page } from "@/components/Page";
|
||||
import { InstantSetupPage } from "@/pages/InstantSetupPage";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/instant-setup" element={<InstantSetupPage />} />
|
||||
<Route path="/" element={<Page />}>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/apps/:appId" element={<ViewAppPage />} />
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
export interface AssertionData {
|
||||
idpEntityId: string;
|
||||
subjectId: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
sessionId: string;
|
||||
now: string;
|
||||
expire: string;
|
||||
@ -28,7 +30,7 @@ function signedAssertion(
|
||||
digest: string,
|
||||
signature: string,
|
||||
): string {
|
||||
return `<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"><saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>${digest}</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>${signature}</ds:SignatureValue></ds:Signature><saml2:Issuer>${assertionData.idpEntityId}</saml2:Issuer><saml2:Subject><saml2:NameID>${assertionData.subjectId}</saml2:NameID><saml2:SubjectConfirmation><saml2:SubjectConfirmationData InResponseTo="${assertionData.sessionId}"></saml2:SubjectConfirmationData></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore="${assertionData.now}" NotOnOrAfter="${assertionData.expire}"><saml2:AudienceRestriction><saml2:Audience>${assertionData.spEntityId}</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions></saml2:Assertion></saml2p:Response>`;
|
||||
return `<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"><saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>${digest}</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>${signature}</ds:SignatureValue></ds:Signature><saml2:Issuer>${assertionData.idpEntityId}</saml2:Issuer><saml2:Subject><saml2:NameID>${assertionData.subjectId}</saml2:NameID><saml2:SubjectConfirmation><saml2:SubjectConfirmationData InResponseTo="${assertionData.sessionId}"></saml2:SubjectConfirmationData></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore="${assertionData.now}" NotOnOrAfter="${assertionData.expire}"><saml2:AudienceRestriction><saml2:Audience>${assertionData.spEntityId}</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions><saml2:AttributeStatement><saml2:Attribute Name="firstName"><saml2:AttributeValue>${assertionData.firstName}</saml2:AttributeValue></saml2:Attribute><saml2:Attribute Name="lastName"><saml2:AttributeValue>${assertionData.lastName}</saml2:AttributeValue></saml2:Attribute></saml2:AttributeStatement></saml2:Assertion></saml2p:Response>`;
|
||||
}
|
||||
|
||||
async function signatureValue(
|
||||
@ -59,7 +61,7 @@ async function digestValue(assertionData: AssertionData): Promise<string> {
|
||||
}
|
||||
|
||||
function digestPart(assertionData: AssertionData): string {
|
||||
return `<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:Issuer>${assertionData.idpEntityId}</saml2:Issuer><saml2:Subject><saml2:NameID>${assertionData.subjectId}</saml2:NameID><saml2:SubjectConfirmation><saml2:SubjectConfirmationData InResponseTo="${assertionData.sessionId}"></saml2:SubjectConfirmationData></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore="${assertionData.now}" NotOnOrAfter="${assertionData.expire}"><saml2:AudienceRestriction><saml2:Audience>${assertionData.spEntityId}</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions></saml2:Assertion>`;
|
||||
return `<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:Issuer>${assertionData.idpEntityId}</saml2:Issuer><saml2:Subject><saml2:NameID>${assertionData.subjectId}</saml2:NameID><saml2:SubjectConfirmation><saml2:SubjectConfirmationData InResponseTo="${assertionData.sessionId}"></saml2:SubjectConfirmationData></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore="${assertionData.now}" NotOnOrAfter="${assertionData.expire}"><saml2:AudienceRestriction><saml2:Audience>${assertionData.spEntityId}</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions><saml2:AttributeStatement><saml2:Attribute Name="firstName"><saml2:AttributeValue>${assertionData.firstName}</saml2:AttributeValue></saml2:Attribute><saml2:Attribute Name="lastName"><saml2:AttributeValue>${assertionData.lastName}</saml2:AttributeValue></saml2:Attribute></saml2:AttributeStatement></saml2:Assertion>`;
|
||||
}
|
||||
|
||||
function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
||||
|
||||
@ -9,18 +9,17 @@ export function HomePage() {
|
||||
const navigate = useNavigate();
|
||||
const handleCreateApp = () => {
|
||||
const id = uuidv4();
|
||||
const newState = {
|
||||
setStoreData({
|
||||
...storeData,
|
||||
apps: {
|
||||
...storeData.apps,
|
||||
[id]: {
|
||||
id,
|
||||
spAcsUrl: "",
|
||||
spEntityId: "",
|
||||
},
|
||||
},
|
||||
};
|
||||
console.log("want new state", newState);
|
||||
setStoreData(newState);
|
||||
});
|
||||
|
||||
navigate(`/apps/${id}`);
|
||||
};
|
||||
|
||||
38
src/pages/InstantSetupPage.tsx
Normal file
38
src/pages/InstantSetupPage.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { createSearchParams, useSearchParams } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useStore } from "@/lib/store";
|
||||
|
||||
export function InstantSetupPage() {
|
||||
const [storeData, setStoreData] = useStore();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const appId = searchParams.get("appId")!;
|
||||
const spAcsUrl = searchParams.get("spAcsUrl")!;
|
||||
const spEntityId = searchParams.get("spEntityId")!;
|
||||
const email = searchParams.get("email")!;
|
||||
const firstName = searchParams.get("firstName")!;
|
||||
const lastName = searchParams.get("lastName")!;
|
||||
|
||||
const navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
setStoreData({
|
||||
...storeData,
|
||||
apps: {
|
||||
...storeData.apps,
|
||||
[appId]: {
|
||||
id: appId,
|
||||
spAcsUrl,
|
||||
spEntityId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
navigate({
|
||||
pathname: `/apps/${appId}/sso`,
|
||||
search: createSearchParams({ email, firstName, lastName }).toString(),
|
||||
});
|
||||
}, [appId, spAcsUrl, spEntityId]);
|
||||
|
||||
return <></>;
|
||||
}
|
||||
@ -26,6 +26,7 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import moment from "moment";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z.string().email({ message: "Email must be a well-formed email." }),
|
||||
@ -39,6 +40,9 @@ export function SSOPage() {
|
||||
const app = storeData.apps[appId!];
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const email = searchParams.get("email");
|
||||
const firstName = searchParams.get("firstName");
|
||||
const lastName = searchParams.get("lastName");
|
||||
const samlRequest = searchParams.get("SAMLRequest");
|
||||
|
||||
const [sessionId, setSessionId] = useState("");
|
||||
@ -71,9 +75,9 @@ export function SSOPage() {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
email: "",
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: email ?? "",
|
||||
firstName: firstName ?? "",
|
||||
lastName: lastName ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
@ -95,6 +99,8 @@ export function SSOPage() {
|
||||
inputRef.current!.value = await encodeAssertion(key, {
|
||||
idpEntityId: `https://dummyidp.com/apps/${app.id}`,
|
||||
subjectId: values.email,
|
||||
firstName: values.firstName,
|
||||
lastName: values.lastName,
|
||||
spEntityId: app.spEntityId,
|
||||
sessionId: sessionId,
|
||||
now: now.format(),
|
||||
@ -134,7 +140,7 @@ export function SSOPage() {
|
||||
<FormItem className="col-span-2">
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="email" {...field} />
|
||||
<Input disabled={!!email} type="email" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -148,7 +154,7 @@ export function SSOPage() {
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>First Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
<Input disabled={!!firstName} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -162,7 +168,7 @@ export function SSOPage() {
|
||||
<FormItem className="col-span-1">
|
||||
<FormLabel>Last Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
<Input disabled={!!lastName} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -170,7 +176,14 @@ export function SSOPage() {
|
||||
/>
|
||||
|
||||
<div className="col-span-2 flex justify-end">
|
||||
<Button>Log on</Button>
|
||||
<Button
|
||||
className={clsx(
|
||||
email &&
|
||||
"bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 shadow-xl",
|
||||
)}
|
||||
>
|
||||
Log on
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user