diff --git a/README.md b/README.md
index a32e3ed..08a4ccc 100644
--- a/README.md
+++ b/README.md
@@ -7,4 +7,4 @@
- Fix the SAML metadata URL
- Improve the UI
-http://localhost:4000/api/apps/saml?RelayState=boxyhq_jackson_2fd72712996df6104811ff8cc233d1e2&SAMLRequest=PD94bWwgdmVyc2lvbj0iMS4wIj8%2BPHNhbWxwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBJRD0iXzZiMmU1ZDdhMDRiNjEzMzAyZDhmIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAyMi0wMS0xNFQxNToxODoxNi4yNTlaIiBQcm90b2NvbEJpbmRpbmc9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpiaW5kaW5nczpIVFRQLVBPU1QiIEFzc2VydGlvbkNvbnN1bWVyU2VydmljZVVSTD0iaHR0cHM6Ly8yOGEyLTEwMy0xNTMtMTA0LTQzLm5ncm9rLmlvL3Nzby9hY3MiIFByb3ZpZGVyTmFtZT0iQm94eUhRIj48c2FtbDpJc3N1ZXIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI%2BaHR0cHM6Ly9zYW1sLmJveHlocS5jb208L3NhbWw6SXNzdWVyPjxTaWduYXR1cmUgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxTaWduZWRJbmZvPjxDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BPFNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiLz48UmVmZXJlbmNlIFVSST0iI182YjJlNWQ3YTA0YjYxMzMwMmQ4ZiI%2BPFRyYW5zZm9ybXM%2BPFRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8%2BPFRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvVHJhbnNmb3Jtcz48RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ii8%2BPERpZ2VzdFZhbHVlPlBXVFlGek9hV1FvZHNqK21haEdnam1VQkZsVFk5cGlQNy8yVVkvNEZkN289PC9EaWdlc3RWYWx1ZT48L1JlZmVyZW5jZT48L1NpZ25lZEluZm8%2BPFNpZ25hdHVyZVZhbHVlPm5HbEMrZVkwMC82eDZQNlBMcGFoOVR5QzlRTnNKR0U3V0Y1czQ1SXZRbWlJWmwrSmhNVGRQeExBSjJ1dVpQMWZzZ1NwUllPZG5HanRLNjlobS9LOHZDVGlab29EODdjdkJmdXp4NVVNQ3NiV0VQSERJV01rV1k3S2ZrYk5ySjNMdVZyYTR0SEZvY2luQnVwRzNMeVQ5dUtwRzI1NlQrNm9LUDNOdkJSTzRROUFpWlk4czlxaVhzRE9QQlJRd3NEaEdNdGRJcGVDaGZQU2RQdFV1RXV3UEdRS1pZellFY0d3WHpOTGYydm5PaS9QUE5rRHA4QTdPN2FJd09HWFNDVVlqL29oNUo4RWRQdVpyRzFuL0ZMdm04L0dNY2pNTTYwQWZ1NFg3WGMzRlFRUG5aZ2lxdFlsd2YwbjFDemlkbnYvbDE3NjF6T3grWXJqb08zaUp0SHB6Zz09PC9TaWduYXR1cmVWYWx1ZT48L1NpZ25hdHVyZT48c2FtbHA6TmFtZUlEUG9saWN5IHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIiBBbGxvd0NyZWF0ZT0idHJ1ZSIvPjwvc2FtbHA6QXV0aG5SZXF1ZXN0Pg%3D%3D
+http://localhost:4000/apps/saml?RelayState=boxyhq_jackson_2fd72712996df6104811ff8cc233d1e2&SAMLRequest=PD94bWwgdmVyc2lvbj0iMS4wIj8%2BPHNhbWxwOkF1dGhuUmVxdWVzdCB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBJRD0iXzZiMmU1ZDdhMDRiNjEzMzAyZDhmIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAyMi0wMS0xNFQxNToxODoxNi4yNTlaIiBQcm90b2NvbEJpbmRpbmc9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpiaW5kaW5nczpIVFRQLVBPU1QiIEFzc2VydGlvbkNvbnN1bWVyU2VydmljZVVSTD0iaHR0cHM6Ly8yOGEyLTEwMy0xNTMtMTA0LTQzLm5ncm9rLmlvL3Nzby9hY3MiIFByb3ZpZGVyTmFtZT0iQm94eUhRIj48c2FtbDpJc3N1ZXIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI%2BaHR0cHM6Ly9zYW1sLmJveHlocS5jb208L3NhbWw6SXNzdWVyPjxTaWduYXR1cmUgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxTaWduZWRJbmZvPjxDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8%2BPFNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZHNpZy1tb3JlI3JzYS1zaGEyNTYiLz48UmVmZXJlbmNlIFVSST0iI182YjJlNWQ3YTA0YjYxMzMwMmQ4ZiI%2BPFRyYW5zZm9ybXM%2BPFRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8%2BPFRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvVHJhbnNmb3Jtcz48RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ii8%2BPERpZ2VzdFZhbHVlPlBXVFlGek9hV1FvZHNqK21haEdnam1VQkZsVFk5cGlQNy8yVVkvNEZkN289PC9EaWdlc3RWYWx1ZT48L1JlZmVyZW5jZT48L1NpZ25lZEluZm8%2BPFNpZ25hdHVyZVZhbHVlPm5HbEMrZVkwMC82eDZQNlBMcGFoOVR5QzlRTnNKR0U3V0Y1czQ1SXZRbWlJWmwrSmhNVGRQeExBSjJ1dVpQMWZzZ1NwUllPZG5HanRLNjlobS9LOHZDVGlab29EODdjdkJmdXp4NVVNQ3NiV0VQSERJV01rV1k3S2ZrYk5ySjNMdVZyYTR0SEZvY2luQnVwRzNMeVQ5dUtwRzI1NlQrNm9LUDNOdkJSTzRROUFpWlk4czlxaVhzRE9QQlJRd3NEaEdNdGRJcGVDaGZQU2RQdFV1RXV3UEdRS1pZellFY0d3WHpOTGYydm5PaS9QUE5rRHA4QTdPN2FJd09HWFNDVVlqL29oNUo4RWRQdVpyRzFuL0ZMdm04L0dNY2pNTTYwQWZ1NFg3WGMzRlFRUG5aZ2lxdFlsd2YwbjFDemlkbnYvbDE3NjF6T3grWXJqb08zaUp0SHB6Zz09PC9TaWduYXR1cmVWYWx1ZT48L1NpZ25hdHVyZT48c2FtbHA6TmFtZUlEUG9saWN5IHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIiBBbGxvd0NyZWF0ZT0idHJ1ZSIvPjwvc2FtbHA6QXV0aG5SZXF1ZXN0Pg%3D%3D
diff --git a/data/idp-metadata.xml b/data/idp-metadata.xml
new file mode 100644
index 0000000..9b18bba
--- /dev/null
+++ b/data/idp-metadata.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ idp_certificate
+
+
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+
+
+
+
\ No newline at end of file
diff --git a/data/saml-response.xml b/data/saml-response.xml
new file mode 100644
index 0000000..391b2b0
--- /dev/null
+++ b/data/saml-response.xml
@@ -0,0 +1,58 @@
+
+
+ idp_entity_id
+
+
+
+
+
+
+
+
+
+
+ xPMc7SSHhFSWGljyk1L8mRE1M6otu0qlukR42E6QdiQ=
+
+
+ VXlnv1pN3BsHvfbwkugYfsgcjoiXzsbm8OiWczkbNQcadaYTHgIOPf9mNLLJXq1vSfBVLyDY+1Xq
+
+
+ ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.
+ MIIDdDCCAlygAwIBAgIGAXo6K+u/MA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ
+
+
+
+
+
+
+
+ idp_entity_id
+
+ user_email
+
+
+
+
+
+
+ https://saml.boxyhq.com
+
+
+
+
+ user_email
+
+
+ user_firstName
+
+
+ user_lastName
+
+
+
+
+ urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
+
+
+
+
\ No newline at end of file
diff --git a/pages/api/apps/metadata.ts b/pages/api/apps/metadata.ts
index 7bdf6ba..8136ce2 100644
--- a/pages/api/apps/metadata.ts
+++ b/pages/api/apps/metadata.ts
@@ -1,7 +1,5 @@
-import { promises as fs } from 'fs';
import type { NextApiRequest, NextApiResponse } from 'next';
-import path from 'path';
-import { metadata } from '../../../services';
+import { createCertificate, createIdPMetadataXML } from '../../../utils';
export default async function handler(
req: NextApiRequest,
@@ -12,12 +10,17 @@ export default async function handler(
}
async function download(req: NextApiRequest) {
- const { acs_url, entity_id } = req.body;
+ const { acs_url, sp_entity_id } = req.body;
- const certificateFilePath = path.join('data', 'x509cert.txt');
- const certificate = await fs.readFile(certificateFilePath, 'utf8');
+ const certificate = await createCertificate();
+ const idpEntityId = 'http://localhost:4000/sso';
+ const idpSsoUrl = 'http://localhost:4000/sso';
- const xml = await metadata.createXML(acs_url, entity_id, certificate);
+ const xml = await createIdPMetadataXML({
+ idpEntityId,
+ idpSsoUrl,
+ certificate,
+ });
res.setHeader('Content-type', 'text/xml');
res.setHeader('Content-Disposition', 'attachment; filename="metadata.xml"');
diff --git a/pages/api/apps/saml.ts b/pages/api/apps/saml.ts
index 460748b..22534e8 100644
--- a/pages/api/apps/saml.ts
+++ b/pages/api/apps/saml.ts
@@ -1,39 +1,33 @@
import type { NextApiRequest, NextApiResponse } from 'next';
-import xml2js from 'xml2js';
-
-const parseXML = (xml: string): Promise> => {
- return new Promise((resolve, reject) => {
- xml2js.parseString(xml, (err: Error, result: any) => {
- resolve(result);
- });
- });
-};
-
-const extractSAMLRequestAttribute = async (SAMLRequest: string | string[]) => {
- // @ts-ignore
- const result = await parseXML(Buffer.from(SAMLRequest, 'base64').toString());
- const sp = result['samlp:AuthnRequest']['$'];
-
- return {
- ID: sp['ID'],
- IssueInstant: sp['IssueInstant'],
- AssertionConsumerServiceURL: sp['AssertionConsumerServiceURL'],
- ProviderName: sp['ProviderName'],
- };
-};
+import { User } from '../../../types';
+import {
+ createSAMLResponseXML,
+ extractSAMLRequestAttributes,
+} from '../../../utils';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
- if (req.method === 'GET') {
+ if (req.method === 'POST') {
return await response(req);
}
+ if (req.method === 'GET') {
+ const user: User = {
+ id: '1',
+ email: 'kiran@demo.com',
+ firstName: 'Kiran',
+ lastName: 'K',
+ };
+
+ return res.status(200).json(await createSAMLResponseXML(user));
+ }
+
async function response(req: NextApiRequest) {
const { RelayState, SAMLRequest } = req.query;
- const attributes = await extractSAMLRequestAttribute(SAMLRequest);
+ const attributes = await extractSAMLRequestAttributes(SAMLRequest);
return res.status(200).json(attributes);
}
diff --git a/pages/apps/saml.tsx b/pages/apps/saml.tsx
new file mode 100644
index 0000000..7813ee7
--- /dev/null
+++ b/pages/apps/saml.tsx
@@ -0,0 +1,42 @@
+import type { NextPage } from 'next';
+import { useEffect, useRef, useState } from "react";
+
+export async function getServerSideProps(context: any) {
+ const {RelayState, SAMLRequest} = context.query;
+
+ return {
+ props: {
+ RelayState: RelayState,
+ SAMLRequest: SAMLRequest
+ },
+ }
+}
+
+// const createSAMLResponse = ({RelayState, SAMLRequest}: Prop) => {
+// const url = new URL('http://28a2-103-153-104-43.ngrok.io/sso/acs');
+
+// url.searchParams.append('RelayState', RelayState);
+// url.searchParams.append('SAMLResponse', 'SAMLResponse');
+
+// return url.href;
+// }
+
+const SAML: NextPage = (prop) => {
+ const [] = useState();
+ const formRef = useRef(null);
+
+ useEffect(() => {
+ // @ts-ignore
+ formRef?.current?.submit();
+ }, []);
+
+ return (
+
+
+
+ );
+};
+
+export default SAML;
\ No newline at end of file
diff --git a/services/metadata.ts b/services/metadata.ts
index 1b7d02d..6975b34 100644
--- a/services/metadata.ts
+++ b/services/metadata.ts
@@ -1,4 +1,3 @@
-import * as xmlbuilder from 'xmlbuilder';
import type { IdPMetadata } from '../types';
const baseUrl = 'http://localhost:3000/saml';
@@ -19,50 +18,3 @@ export const create = (
certificate: certificate,
};
};
-
-const extractCert = (certificate: string) => {
- return certificate
- .replace('-----BEGIN CERTIFICATE-----', '')
- .replace('-----END CERTIFICATE-----', '')
- .trim();
-};
-
-export const createXML = async (
- acs_url: string,
- entity_id: string,
- certificate: string
-) => {
- const metadata = create(acs_url, entity_id, certificate);
-
- const data = {
- 'md:EntityDescriptor': {
- '@xmlns:md': 'urn:oasis:names:tc:SAML:2.0:metadata',
- '@entityID': `${metadata.entity_id}`,
- '@validUntil': '2026-06-22T18:39:53.000Z',
- 'md:IDPSSODescriptor': {
- '@WantAuthnRequestsSigned': 'false',
- '@protocolSupportEnumeration': 'urn:oasis:names:tc:SAML:2.0:protocol',
- 'md:KeyDescriptor': {
- '@use': 'signing',
- 'ds:KeyInfo': {
- '@xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
- 'ds:X509Data': {
- 'ds:X509Certificate': {
- '#text': `${extractCert(certificate)}`,
- },
- },
- },
- },
- 'md:NameIDFormat': {
- '#text': 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
- },
- 'md:SingleSignOnService': {
- '@Binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
- '@Location': `${metadata.sso_url}`,
- },
- },
- },
- };
-
- return xmlbuilder.create(data).end({ pretty: true });
-};
diff --git a/types/index.ts b/types/index.ts
index 4136abc..3f8efcb 100644
--- a/types/index.ts
+++ b/types/index.ts
@@ -30,3 +30,10 @@ export type AuthNRequest = {
RelayState: string;
SAMLRequest: SAMLRequest;
};
+
+export type User = {
+ id: string;
+ email: string;
+ firstName: string;
+ lastName: string;
+};
diff --git a/utils/index.ts b/utils/index.ts
new file mode 100644
index 0000000..719cd59
--- /dev/null
+++ b/utils/index.ts
@@ -0,0 +1,84 @@
+// @ts-ignore
+import { promises as fs } from 'fs';
+import path from 'path';
+import xml2js from 'xml2js';
+import { User } from '../types';
+
+// Parse XML
+const parseXML = (xml: string): Promise> => {
+ return new Promise((resolve, reject) => {
+ xml2js.parseString(xml, (err: Error, result: any) => {
+ resolve(result);
+ });
+ });
+};
+
+// Extract SAML Request Attributes
+const extractSAMLRequestAttributes = async (SAMLRequest: string | string[]) => {
+ // @ts-ignore
+ const result = await parseXML(Buffer.from(SAMLRequest, 'base64').toString());
+ const attributes = result['samlp:AuthnRequest']['$'];
+
+ return {
+ ID: attributes['ID'],
+ IssueInstant: attributes['IssueInstant'],
+ AssertionConsumerServiceURL: attributes['AssertionConsumerServiceURL'],
+ ProviderName: attributes['ProviderName'],
+ };
+};
+
+const createIdPMetadataXML = async ({
+ idpEntityId,
+ idpSsoUrl,
+ certificate,
+}: {
+ idpEntityId: string;
+ idpSsoUrl: string;
+ certificate: string;
+}): Promise => {
+ const xmlPath = path.join('data', 'idp-metadata.xml');
+ const xml = await fs.readFile(xmlPath, 'utf8');
+
+ return xml
+ .replace('idp_entity_id', idpEntityId)
+ .replace('idp_certificate', extractCert(certificate))
+ .replace(/idp_sso_url/g, idpSsoUrl);
+};
+
+const createCertificate = async () => {
+ const certificateFilePath = path.join('data', 'x509cert.txt');
+
+ return await fs.readFile(certificateFilePath, 'utf8');
+};
+
+const extractCert = (certificate: string) => {
+ return certificate
+ .replace('-----BEGIN CERTIFICATE-----', '')
+ .replace('-----END CERTIFICATE-----', '')
+ .trim();
+};
+
+// Create SAML Response XML
+const createSAMLResponseXML = async (user: User): Promise => {
+ const xmlPath = path.join('data', 'saml-response.xml');
+ const xml = await fs.readFile(xmlPath, 'utf8');
+
+ return xml
+ .replace(
+ /idp_entity_id/g,
+ 'https://accounts.google.com/o/saml2?idpid=C02frd9s1'
+ )
+ .replace('sp_acs_url', 'some-url')
+ .replace(/user_email/g, 'kiran@demo.com')
+ .replace('user_firstName', 'Kiran')
+ .replace('user_lastName', 'K');
+};
+
+export {
+ parseXML,
+ extractSAMLRequestAttributes,
+ createIdPMetadataXML,
+ createSAMLResponseXML,
+ createCertificate,
+ extractCert,
+};