This commit is contained in:
Kiran 2022-01-13 22:37:44 +05:30
parent d66a462e07
commit 42a1681dbc
18 changed files with 1379 additions and 130 deletions

View File

@ -1,17 +1,9 @@
- Add created_at and updated_at for all tables
- Feed some users
# Backlog
# Pages
- Create user (Done)
- List users (Done)
- Update user
- Delete user
- Create apps
- List app
- Update app
- Delete app
- Form validation
- UI
- Add timestamp to tables
- Feed 10 users
1. Autogenerate certificate
2. Request validation
@ -20,3 +12,19 @@
SAML certificate (PEM format)
you need to generate a set of public and private keys and an X.509 certificate that contains the public key. The public keys and certificates must be generated with either the RSA or DSA algorithm and registered with Google.
/apps/<id>
ACS URL
Entity ID
Certificate -> Use same Certificate for all apps
Metadata Properties
- entityID (IdP identity)
- validUntil (Hard coded)
- X509Certificate (Hard coded)
- SingleSignOnService -> Binding
/apps/metadata -> Download

0
components/Header.tsx Normal file
View File

0
components/Layout.tsx Normal file
View File

0
components/NavBar.tsx Normal file
View File

1205
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,15 +9,20 @@
},
"dependencies": {
"@prisma/client": "^3.7.0",
"axios": "^0.24.0",
"next": "12.0.7",
"react": "17.0.2",
"react-dom": "17.0.2"
"react-dom": "17.0.2",
"xmlbuilder2": "^3.0.2"
},
"devDependencies": {
"@types/node": "17.0.8",
"@types/react": "17.0.38",
"autoprefixer": "^10.4.2",
"eslint": "8.6.0",
"eslint-config-next": "12.0.7",
"postcss": "^8.4.5",
"tailwindcss": "^3.0.13",
"typescript": "4.5.4"
}
}

View File

@ -1,73 +0,0 @@
import { PrismaClient } from '@prisma/client';
import type { NextApiRequest, NextApiResponse } from 'next';
const prisma = new PrismaClient();
type ServiceProvider = {
sp_acs_url: string;
sp_entity_id: string;
};
type IdentityProvider = {
idp_sso_url: string;
idp_entity_id: string;
};
type App = {
id: string;
name: string;
description?: string;
certificate: string;
} & ServiceProvider;
const createApp = async (body: Omit<App, 'id'>): Promise<App> => {
return await prisma.app.create({ data: body });
};
const getAllApps = async (): Promise<App[]> => {
return await prisma.app.findMany();
};
const getAppById = async (id: string): Promise<App> => {
return await prisma.app.findUnique({
where: {
id,
},
});
};
const createKeyPairs = (): any => {};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<App | App[]>
) {
const { method } = req;
// if (method === 'GET') {
// const apps = await getAllApps();
// return res.status(200).json(apps);
// }
if (method === 'GET') {
const app = await getAppById('287f8e3d-234c-425c-bac3-cc68878582f5');
return res.status(200).json(app);
}
if (method === 'POST') {
const { name, sp_acs_url, sp_entity_id, description = null } = req.body;
const certificate = 'certificate';
const app = await createApp({
name,
description,
certificate,
sp_acs_url,
sp_entity_id,
});
return res.status(200).json(app);
}
}

37
pages/api/apps/index.ts Normal file
View File

@ -0,0 +1,37 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { apps, metadata } from '../../../services';
import type { App, IdPMetadata } from '../../../types';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<App | App[] | IdPMetadata | null>
) {
if (req.method === 'POST') {
return await create(req);
}
async function create(req: NextApiRequest) {
const {
sp_acs_url,
sp_entity_id,
name = 'My App',
description = null,
} = req.body;
const certificate = 'certificate';
const app = await apps.create({
sp_acs_url,
sp_entity_id,
name,
description,
certificate,
});
const idPMetadata = metadata.create(sp_acs_url, sp_entity_id, certificate);
return res.status(200).json(idPMetadata);
//return res.status(200).json(app);
}
}

View File

@ -1,12 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
type Data = {
name: string;
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
res.status(200).json({ name: 'Kiran' });
}

View File

@ -10,7 +10,7 @@ type User = {
email: string;
};
const getUserById = async (id: number): Promise<User> => {
const getUserById = async (id: number): Promise<User | null> => {
return await prisma.user.findUnique({
where: {
id,
@ -20,7 +20,7 @@ const getUserById = async (id: number): Promise<User> => {
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<User>
res: NextApiResponse<User | null>
) {
const { method } = req;

56
pages/apps/index.tsx Normal file
View File

@ -0,0 +1,56 @@
import axios from 'axios';
import type { NextPage } from 'next';
import { ChangeEvent, FormEvent, useState } from 'react';
// const a = new URLSearchParams({
// sp_acs_url: 'http://localhost:3000/apps',
// sp_entity_id: 'https://saml.boxyhq.com',
// }).toString();
// console.log(a);
const Apps: NextPage = () => {
const [formData, setFormData] = useState({
sp_acs_url: null,
sp_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 app = await axios.post('/api/apps', {
...formData
});
};
return (
<div>
<form onSubmit={createApp} className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
<div className="mb-4">
<label className="block text-sm mb-2">
ACS URL
<input type="text" name="sp_acs_url" onChange={handleInputChange} required className="border rounded w-full py-2 px-3" placeholder="ACS URL" />
</label>
</div>
<div className="mb-4">
<label className="block text-sm mb-2">
Entity ID
<input type="text" name="sp_entity_id" onChange={handleInputChange} required className="border rounded w-full py-2 px-3" placeholder="Entity ID" />
</label>
</div>
<button type="submit" className="bg-blue-500 text-white py-2 px-4 rounded">Build IdP Metadata</button>
</form>
</div>
);
};
export default Apps;

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

20
services/apps.ts Normal file
View File

@ -0,0 +1,20 @@
import { PrismaClient } from '@prisma/client';
import type { App } from '../types';
const prisma = new PrismaClient();
export async function create(body: Omit<App, 'id'>): Promise<App> {
return await prisma.app.create({ data: body });
}
export async function getAll(): Promise<App[]> {
return await prisma.app.findMany();
}
export async function getById(id: string): Promise<App | null> {
return await prisma.app.findUnique({
where: {
id,
},
});
}

2
services/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * as apps from './apps';
export * as metadata from './metadata';

16
services/metadata.ts Normal file
View File

@ -0,0 +1,16 @@
import * as xmlbuilder2 from 'xmlbuilder2';
import type { IdPMetadata } from '../types';
export const create = (
acsUrl: string,
entityId: string,
certificate: string
): IdPMetadata => {
const xml = xmlbuilder2.create();
return {
sso_url: 'string',
entity_id: 'string',
certificate: 'string',
};
};

View File

@ -1,3 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body {
padding: 0;

10
tailwind.config.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
};

23
types/index.ts Normal file
View File

@ -0,0 +1,23 @@
export type ServiceProvider = {
sp_acs_url: string;
sp_entity_id: string;
};
export type IdentityProvider = {
sso_url: string;
entity_id: string;
};
export type App = {
id: string;
name: string;
description?: string | null;
certificate: string;
} & ServiceProvider;
// export type IdPMetadata = {
// sso_url: string;
// entity_id: string;
// certificate: string;
// fingerprint?: string;
// };