This commit is contained in:
Kiran 2022-02-18 09:37:27 +05:30
parent c3c09856bc
commit b9e9b53941
12 changed files with 56 additions and 237 deletions

View File

@ -3,3 +3,12 @@
- Parse the SAML Request - Parse the SAML Request
- Create the SAML Response - Create the SAML Response
- Fix the certificate - Fix the certificate
- Install prettify
// Start a session
// Store the RelayState in the session
// Parse the SAMLRequest
// Validate the SAMLRequest
// Create SAMLResponse
// POST the SAMLResponse to ACS URL
// Remove the RelayState from the session

View File

@ -1,6 +1,9 @@
const config = { const appUrl = process.env.APP_URL || 'http://localhost:4000';
appUrl: process.env.APP_URL || 'http://localhost:4000', const entityId = process.env.ENTITY_ID || 'http://saml.example.com';
entityId: process.env.ENTITY_ID || 'http://saml.example.com', const ssoUrl = `${appUrl}/saml/sso`;
}
export default config; export default {
appUrl,
entityId,
ssoUrl,
};

View File

@ -1,13 +1,9 @@
import '../styles/globals.css' import '../styles/globals.css'
import type { AppProps } from 'next/app' import type { AppProps } from 'next/app'
import Layout from '../components/Layout'
import 'rsuite/dist/rsuite.min.css';
function MyApp({ Component, pageProps }: AppProps) { function MyApp({ Component, pageProps }: AppProps) {
return ( return (
<Layout>
<Component {...pageProps} /> <Component {...pageProps} />
</Layout>
) )
} }

View File

@ -1,32 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { createCertificate, createIdPSSOUrl } from '../../../../utils';
import { IdPMetadata } from '../../../../types';
import config from '../../../../lib/env'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<IdPMetadata | string>
) {
switch (req.method) {
case 'GET':
return await getMetadata();
default:
return res.status(405).end(`Method ${req.method} Not Allowed`);
}
// Get metadata for an app
async function getMetadata() {
//const {id} = req.query;
const appId = '0480c44e-f200-4f72-8af0-a5a57611fd2d';
const metadata = {
certificate: await createCertificate(),
fingerprint: '',
sso_url: createIdPSSOUrl(appId),
entity_id: config.entityId,
}
return res.json(metadata);
}
}

View File

@ -1,5 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { createCertificate, createIdPMetadataXML, createIdPSSOUrl } from '../../../../utils'; import { createCertificate, createIdPMetadataXML } from '../../../../utils';
import { IdPMetadata } from '../../../../types'; import { IdPMetadata } from '../../../../types';
import stream from 'stream'; import stream from 'stream';
import { promisify } from 'util'; import { promisify } from 'util';
@ -19,18 +19,16 @@ export default async function handler(
return res.status(405).end(`Method ${req.method} Not Allowed`); return res.status(405).end(`Method ${req.method} Not Allowed`);
} }
// Download metadata for an app // Download metadata
async function downloadMetadata() { async function downloadMetadata() {
const appId = '0480c44e-f200-4f72-8af0-a5a57611fd2d';
const xml = await createIdPMetadataXML({ const xml = await createIdPMetadataXML({
idpEntityId: config.entityId, idpEntityId: config.entityId,
idpSsoUrl: createIdPSSOUrl(appId), idpSsoUrl: config.ssoUrl,
certificate: await createCertificate(), certificate: await createCertificate(),
}); });
res.setHeader('Content-type', 'text/xml'); res.setHeader('Content-type', 'text/xml');
res.setHeader('Content-Disposition', 'attachment; filename=metadata.xml'); res.setHeader('Content-Disposition', 'attachment; filename=mock-saml-metadata.xml');
await pipeline(xml, res); await pipeline(xml, res);
} }

View File

@ -1,44 +0,0 @@
import prisma from '../../lib/prisma';
import { GetServerSideProps } from 'next';
import { App } from '../../types';
import axios from 'axios';
import { IdPMetadata } from '../../types';
import React, { ChangeEvent, FormEvent, useState } from 'react';
// TODO: Remove this
export const getServerSideProps: GetServerSideProps = async ({ params }) => {
const app = await prisma.app.findUnique({
where: {
id: params?.id,
}
});
const metadata = await axios.get('http://localhost:4000/api/apps/metadata');
return {
props: {
app,
metadata: metadata.data,
},
};
};
const ShowApp: React.FC<{app: App, metadata: IdPMetadata}> = ({app, metadata}) => {
return (
<div>
<p>Id: {app.id}</p>
<p>name: {app.name}</p>
<p>acs_url: {app.acs_url}</p>
<p>entity_id: {app.entity_id}</p>
<strong>Metadata</strong>
<p>sso_url: {metadata.sso_url}</p>
<p>entity_id: {metadata.entity_id}</p>
<p>certificate: {metadata.certificate}</p>
<a href="/api/apps/metadata/download" className="px-3 py-2 text-white bg-red-500 rounded">Download Metadata</a>
</div>
);
};
export default ShowApp;

View File

@ -1,60 +0,0 @@
import axios from 'axios';
import type { NextPage } from 'next';
import { ChangeEvent, FormEvent, useState } from 'react';
import Router from 'next/router';
const Apps: NextPage = () => {
const [formData, setFormData] = useState({
name: null,
acs_url: null,
entity_id: null,
});
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value.trim()
});
}
const createApp = async (e: FormEvent) => {
e.preventDefault();
const { data: app } = await axios.post('/api/apps', {
...formData
});
await Router.push(`/apps/${app.id}`);
};
return (
<div>
<form onSubmit={createApp} className="px-8 pt-6 pb-8 mb-4 bg-white rounded shadow-md">
<div className="mb-4">
<label className="block mb-2 text-sm">
App Name
<input type="text" name="name" onChange={handleInputChange} required className="w-full px-3 py-2 border rounded" placeholder="App Name" />
</label>
</div>
<div className="mb-4">
<label className="block mb-2 text-sm">
ACS URL
<input type="text" name="acs_url" onChange={handleInputChange} required className="w-full px-3 py-2 border rounded" placeholder="ACS URL" />
</label>
</div>
<div className="mb-4">
<label className="block mb-2 text-sm">
Entity ID
<input type="text" name="entity_id" onChange={handleInputChange} required className="w-full px-3 py-2 border rounded" placeholder="Entity ID" />
</label>
</div>
<button type="submit" className="px-4 py-2 text-white bg-blue-500 rounded">Create App</button>
</form>
</div>
);
};
export default Apps;

View File

@ -1,70 +1,34 @@
import type { NextPage } from 'next' import type { NextPage } from 'next'
import Head from 'next/head' import { GetServerSideProps } from 'next';
import Image from 'next/image' import { IdPMetadata } from '../types'
import styles from '../styles/Home.module.css' import config from '../lib/env';
import {createCertificate} from '../utils'
import React from 'react';
import Link from 'next/link'
const Home: NextPage = () => { export const getServerSideProps: GetServerSideProps = async () => {
const metadata: IdPMetadata = {
ssoUrl: config.ssoUrl,
entityId: config.entityId,
certificate: await createCertificate(),
}
return {
props: {
metadata
},
};
};
const Home: React.FC<{metadata: IdPMetadata}> = ({ metadata }) => {
return ( return (
<div className={styles.container}> <div>
<Head> <strong>Mock IdP Metadata</strong>
<title>Create Next App</title> <p>SSO URL: {metadata.ssoUrl}</p>
<meta name="description" content="Generated by create next app" /> <p>Entity ID: {metadata.entityId}</p>
<link rel="icon" href="/favicon.ico" /> <p>Certificate: {metadata.certificate}</p>
</Head> <br></br>
<p><Link href="/api/saml/metadata/download">Download Metadata</Link></p>
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1>
<p className={styles.description}>
Get started by editing{' '}
<code className={styles.code}>pages/index.tsx</code>
</p>
<div className={styles.grid}>
<a href="https://nextjs.org/docs" className={styles.card}>
<h2>Documentation &rarr;</h2>
<p>Find in-depth information about Next.js features and API.</p>
</a>
<a href="https://nextjs.org/learn" className={styles.card}>
<h2>Learn &rarr;</h2>
<p>Learn about Next.js in an interactive course with quizzes!</p>
</a>
<a
href="https://github.com/vercel/next.js/tree/master/examples"
className={styles.card}
>
<h2>Examples &rarr;</h2>
<p>Discover and deploy boilerplate example Next.js projects.</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card}
>
<h2>Deploy &rarr;</h2>
<p>
Instantly deploy your Next.js site to a public URL with Vercel.
</p>
</a>
</div>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div> </div>
) )
} }

View File

@ -1,7 +1,7 @@
import type { GetServerSideProps } from 'next'; import type { GetServerSideProps } from 'next';
import React from "react"; import React from "react";
import { AuthNRequest } from '../../../types' import { AuthNRequest } from '../../types'
import {extractSAMLRequestAttributes} from '../../../utils' import { extractSAMLRequestAttributes } from '../../utils'
export const getServerSideProps: GetServerSideProps = async ({query, params}) => { export const getServerSideProps: GetServerSideProps = async ({query, params}) => {
const relayState = query.RelayState as string; const relayState = query.RelayState as string;
@ -20,19 +20,9 @@ export const getServerSideProps: GetServerSideProps = async ({query, params}) =>
} }
const ProcessRequest: React.FC<AuthNRequest> = ({relayState, samlRequest}) => { const ProcessRequest: React.FC<AuthNRequest> = ({relayState, samlRequest}) => {
return ( return (
<div>Process Request</div> <div>Process Request</div>
); );
} }
export default ProcessRequest; export default ProcessRequest;
// Start a session
// Store the RelayState in the session
// Parse the SAMLRequest
// Validate the SAMLRequest
// Create SAMLResponse
// POST the SAMLResponse to ACS URL
// Remove the RelayState from the session

0
pages/saml/sso.tsx Normal file
View File

View File

@ -4,8 +4,8 @@ export type ServiceProvider = {
}; };
export type IdentityProvider = { export type IdentityProvider = {
sso_url: string; ssoUrl: string;
entity_id: string; entityId: string;
}; };
export type App = { export type App = {

View File

@ -76,10 +76,6 @@ const createSAMLResponseXML = async (user: User): Promise<string> => {
.replace('user_lastName', 'K'); .replace('user_lastName', 'K');
}; };
const createIdPSSOUrl = (appId: string) => {
return `${config.appUrl}/saml2/apps/${appId}`;
}
export { export {
parseXML, parseXML,
extractSAMLRequestAttributes, extractSAMLRequestAttributes,
@ -87,5 +83,4 @@ export {
createSAMLResponseXML, createSAMLResponseXML,
createCertificate, createCertificate,
extractCert, extractCert,
createIdPSSOUrl,
}; };