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
- Create the SAML Response
- 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 = {
appUrl: process.env.APP_URL || 'http://localhost:4000',
entityId: process.env.ENTITY_ID || 'http://saml.example.com',
}
const appUrl = process.env.APP_URL || 'http://localhost:4000';
const 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 type { AppProps } from 'next/app'
import Layout from '../components/Layout'
import 'rsuite/dist/rsuite.min.css';
function MyApp({ Component, pageProps }: AppProps) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
<Component {...pageProps} />
)
}

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 { createCertificate, createIdPMetadataXML, createIdPSSOUrl } from '../../../../utils';
import { createCertificate, createIdPMetadataXML } from '../../../../utils';
import { IdPMetadata } from '../../../../types';
import stream from 'stream';
import { promisify } from 'util';
@ -19,18 +19,16 @@ export default async function handler(
return res.status(405).end(`Method ${req.method} Not Allowed`);
}
// Download metadata for an app
// Download metadata
async function downloadMetadata() {
const appId = '0480c44e-f200-4f72-8af0-a5a57611fd2d';
const xml = await createIdPMetadataXML({
idpEntityId: config.entityId,
idpSsoUrl: createIdPSSOUrl(appId),
idpSsoUrl: config.ssoUrl,
certificate: await createCertificate(),
});
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);
}

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 Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import { GetServerSideProps } from 'next';
import { IdPMetadata } from '../types'
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 (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<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>
<strong>Mock IdP Metadata</strong>
<p>SSO URL: {metadata.ssoUrl}</p>
<p>Entity ID: {metadata.entityId}</p>
<p>Certificate: {metadata.certificate}</p>
<br></br>
<p><Link href="/api/saml/metadata/download">Download Metadata</Link></p>
</div>
)
}

View File

@ -1,7 +1,7 @@
import type { GetServerSideProps } from 'next';
import React from "react";
import { AuthNRequest } from '../../../types'
import {extractSAMLRequestAttributes} from '../../../utils'
import { AuthNRequest } from '../../types'
import { extractSAMLRequestAttributes } from '../../utils'
export const getServerSideProps: GetServerSideProps = async ({query, params}) => {
const relayState = query.RelayState as string;
@ -20,19 +20,9 @@ export const getServerSideProps: GetServerSideProps = async ({query, params}) =>
}
const ProcessRequest: React.FC<AuthNRequest> = ({relayState, samlRequest}) => {
return (
<div>Process Request</div>
);
}
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 = {
sso_url: string;
entity_id: string;
ssoUrl: string;
entityId: string;
};
export type App = {

View File

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