spike: create assertions

This commit is contained in:
Ulysse Carion 2024-05-13 16:19:21 -07:00
commit b049eb8fa5
23 changed files with 11112 additions and 0 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
APP_API_URL=http://localhost:8081/internal/connect

30
.eslintrc.json Normal file
View File

@ -0,0 +1,30 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"react"
],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"react/no-unescaped-entities": "off",
"@typescript-eslint/no-explicit-any": "off"
}
}

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/node_modules
/public
!/public/index.html

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
@fortawesome:registry=https://npm.fontawesome.com/
//npm.fontawesome.com/:_authToken=${FONTAWESOME_NPM_AUTH_TOKEN}

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v16.20.2

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
/public

1
.prettierrc.json Normal file
View File

@ -0,0 +1 @@
{}

29
build.mjs Normal file
View File

@ -0,0 +1,29 @@
import * as esbuild from "esbuild";
const APP_BUILD_IS_DEV = process.env.APP_BUILD_IS_DEV === "1";
const define = {
global: 'window',
...Object.fromEntries(
Object.entries(process.env)
.filter(([k, _v]) => k.startsWith("APP_"))
.map(([k, v]) => [`process.env.${k}`, JSON.stringify(v)]),
)}
const context = await esbuild.context({
entryPoints: ["./src"],
outfile: "./public/index.js",
minify: !APP_BUILD_IS_DEV,
bundle: true,
sourcemap: true,
target: ["chrome58", "firefox57", "safari11", "edge18"],
define,
});
if (APP_BUILD_IS_DEV) {
console.log("watching");
await context.watch();
} else {
await context.rebuild();
await context.dispose();
}

17
components.json Normal file
View File

@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

19
convertkey.js Normal file
View File

@ -0,0 +1,19 @@
const jose = require("node-jose");
const fs = require("fs");
// Function to convert RSA private key to JWK
async function convertToJWK() {
// Read the PEM formatted private key
const keyPEM = fs.readFileSync("dummyidp.key");
// Create a keystore
const keystore = jose.JWK.createKeyStore();
// Add the RSA private key to the keystore
const jwk = await keystore.add(keyPEM, "pem");
// Output the JWK
console.log(JSON.stringify(jwk.toJSON(true), null, 4));
}
convertToJWK().catch(console.error);

19
dummyidp.crt Normal file
View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDBzCCAe+gAwIBAgIUCLBK4f75EXEe4gyroYnVaqLoSp4wDQYJKoZIhvcNAQEL
BQAwEzERMA8GA1UEAwwIZHVtbXlpZHAwHhcNMjQwNTEzMjE1NDE2WhcNMzQwNTEx
MjE1NDE2WjATMREwDwYDVQQDDAhkdW1teWlkcDCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAKhmgQmWb8NvGhz952XY4SlJlpWIK72RilhOZS9frDYhqWVJ
HsGH9Z7sSzrM/0+YvCyEWuZV9gpMeIaHZxEPDqW3RJ7KG51fn/s/qFvwctf+CZDj
yfGDzYs+XIgf7p56U48EmYeWpB/aUW64gSbnPqrtWmVFBisOfIx5aY3NubtTsn+g
0XbdX0L57+NgSvPQHXh/GPXA7xCIWm54G5kqjozxbKEFA0DS3yb6oHRQWHqIAM/7
mJMdUVZNIV1q7c2JIgAl23uDWq+2KTE2R5liP/KjvjwKonVKtTqGqX6ei25rsTHO
aDpBH/LdQK2txgsm7R7+IThWNvUI0TttrmwBqyMCAwEAAaNTMFEwHQYDVR0OBBYE
FD142gxIAJMhpgMkgpzmRNoW9XbEMB8GA1UdIwQYMBaAFD142gxIAJMhpgMkgpzm
RNoW9XbEMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADQd6k6z
FIc20GfGHY5C2MFwyGOmP5/UG/JiTq7Zky28G6D0NA0je+GztzXx7VYDfCfHxLcm
2k5t9nYhb9kVawiLUUDVF6s+yZUXA4gUA3KoTWh1/oRxR3ggW7dKYm9fsNOdQAbx
UUkzp7HLZ45ZlpKUS0hO7es+fPyF5KVw0g0SrtQWwWucnQMAQE9m+B0aOf+92y7J
QkdgdR8Gd/XZ4NZfoOnKV7A1utT4rWxYCgICeRTHx9tly5OhPW4hQr5qOpngcsJ9
vhr86IjznQXhfj3hql5lA3VbHW04ro37ROIkh2bShDq5dwJJHpYCGrF3MQv8S3m+
jzGhYL6m9gFTm/8=
-----END CERTIFICATE-----

28
dummyidp.key Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCoZoEJlm/Dbxoc
/edl2OEpSZaViCu9kYpYTmUvX6w2IallSR7Bh/We7Es6zP9PmLwshFrmVfYKTHiG
h2cRDw6lt0SeyhudX5/7P6hb8HLX/gmQ48nxg82LPlyIH+6eelOPBJmHlqQf2lFu
uIEm5z6q7VplRQYrDnyMeWmNzbm7U7J/oNF23V9C+e/jYErz0B14fxj1wO8QiFpu
eBuZKo6M8WyhBQNA0t8m+qB0UFh6iADP+5iTHVFWTSFdau3NiSIAJdt7g1qvtikx
NkeZYj/yo748CqJ1SrU6hql+notua7Exzmg6QR/y3UCtrcYLJu0e/iE4Vjb1CNE7
ba5sAasjAgMBAAECggEAEggj0gx/PCyF3cvcPr4Z4gtkqe9SS7KtXyZJ1GhIruUs
19EcD3oI9XL03T99KR9AKv4jI53ZwiGNGE6gXSXBGkKFAQHAMjo+ja8zzmBxU6p6
iL6zbX6BAGN1kgflS6fqkZpa/DdHrLd6V8I+5hUF01SmBMj+z5Z2BK6tfEcml6XC
VFBzd0QqZuACrdO9ScZvL1cd4DCHJJNBarCVgC9akxOi9THtgD72EYJQyVcsLbzK
qoxnT9JW6onCBBKtUaP8fEzp9WMK6APJCkYK5I+1lX8tSf48+lzgBrn3cb9Zo5DY
cGo0bJgVwzI4w+HS/etiOOrURmbbX1x9W/rGBhS8tQKBgQDscf1AyNmZoPRS8O12
tfVoGCjxkJZhKEx/HO3WAjLqNpKCZzIaYwl4H2z6cIainLwjJ3iOWCCaCdY/gKph
uXp+a9n7hQse5zQMZpHU/rOGwXh8jrMLFabqKPIhczvlDrtxyiQtB5HMg0qg6aQR
DaGAUqGJoo3hNV9qe58QJ++rNQKBgQC2U98Sipsapgm3EqU0PSKFcxONJUZNvBQF
RdR3NhTHuco4ZBJ4IthdumbgogERhrgY2m65vy+dkPxz4W7Mhv7IofrqcJr9Darz
OF8zvrikBqj0/U5KNteQDuYvqc7ovACTs4U1rbbPQfkZNGTNfwMmIAb3TP/ruqtA
+Z+YV9nv9wKBgQC4F6BI2pihhrH0CeW5cb6ax4TJX/vVtZyps4px/9BIjyjPIy3d
YZKz1jPxYb9RyJqq/EZe/bqUdGg9lR4TbGg1Gh/kNxgLfZQGu617mruIhgYbZLd+
P+NvmWW8KY5Or4O9+tbjwGsCQo7OblrxdB10XeGr2caBvB6IN6wG1jFCqQKBgDg/
Y6AatoLgGjsqO2EEQzQcLjnq9+dfUGXYBxXHz11WSbZf2PrK9SjlKnu+PsojX4P7
TxFqk8vuQJOXRlE+jDdlET1mA8pxfv2NtIEII3omu9TomFB43sOIdSbbIgPWi+8F
AOFwd+c0mR5XdYmX12bZloyQaptUeSSQXdXntEo9AoGBAMqa/z2FOVK1fy8Ntr6W
vMCfgv1MfZ4Ys+FrVOI4BF/0aH4DK2uIg1yFVC+gWo1mGe74+/wBTdrRC0zEN14X
3OBUPggUJL7C6Fzun87bXcQXf8jtNWLYo4k/2cKMzt8MM1bjeuRBjQkPYWhd7xFh
H0xDf/se0RusWZxbXpel/K6i
-----END PRIVATE KEY-----

4
netlify.toml Normal file
View File

@ -0,0 +1,4 @@
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

10560
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

68
package.json Normal file
View File

@ -0,0 +1,68 @@
{
"name": "app",
"version": "0.0.0",
"scripts": {
"build": "npm-run-all tailwind esbuild",
"dev": "dotenv -- npm-run-all -l -p serve tailwind-watch esbuild-watch tsc-watch",
"serve": "http-server -c-1 -p 8084 -P http://localhost:8084?",
"tailwind": "tailwindcss -i ./src/index.css -o ./public/index.css",
"tailwind-watch": "tailwindcss -i ./src/index.css -o ./public/index.css --watch",
"esbuild": "node ./build.mjs",
"esbuild-watch": "APP_BUILD_IS_DEV=1 node ./build.mjs",
"tsc": "tsc --noEmit",
"tsc-watch": "tsc --noEmit --watch --preserveWatchOutput",
"fmt": "prettier --write .",
"fmt-check": "prettier --check ."
},
"dependencies": {
"@bufbuild/buf": "^1.31.0",
"@bufbuild/protobuf": "^1.9.0",
"@connectrpc/connect": "^1.4.0",
"@connectrpc/connect-query": "^1.3.1",
"@connectrpc/connect-web": "^1.4.0",
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@react-oauth/google": "^0.12.1",
"@tailwindcss/forms": "^0.5.7",
"@tanstack/react-query": "^5.32.0",
"@types/node": "^20.12.7",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"dotenv-cli": "^7.4.1",
"eckles": "^1.4.1",
"esbuild": "^0.20.2",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.2",
"highlight.js": "^11.9.0",
"http-server": "^14.1.1",
"lucide-react": "^0.376.0",
"moment": "^2.30.1",
"next-themes": "^0.3.0",
"node-jose": "^2.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.2.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.51.3",
"react-router": "^6.23.0",
"react-router-dom": "^6.23.0",
"sonner": "^1.4.41",
"tailwind-merge": "^2.3.0",
"tailwindcss": "^3.4.3",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.4.5",
"xml-formatter": "^3.6.2",
"zod": "^3.23.5"
}
}

18
public/index.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/index.css">
<link rel="icon" href="/apple-touch-icon.png">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet">
</head>
<body>
<div id="react-root" class="h-full min-h-screen bg-muted/40"></div>
</body>
<script src="/index.js"></script>
</html>

13
src/App.tsx Normal file
View File

@ -0,0 +1,13 @@
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { SSOPage } from "@/pages/SSOPage";
export function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/apps/:appId/sso" element={<SSOPage />} />
</Routes>
</BrowserRouter>
);
}

60
src/index.css Normal file
View File

@ -0,0 +1,60 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 224 71.4% 4.1%;
--card: 0 0% 100%;
--card-foreground: 224 71.4% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 224 71.4% 4.1%;
--primary: 262.1 83.3% 57.8%;
--primary-foreground: 210 20% 98%;
--secondary: 220 14.3% 95.9%;
--secondary-foreground: 220.9 39.3% 11%;
--muted: 220 14.3% 95.9%;
--muted-foreground: 220 8.9% 46.1%;
--accent: 220 14.3% 95.9%;
--accent-foreground: 220.9 39.3% 11%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 20% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 262.1 83.3% 57.8%;
--radius: 0.5rem;
}
.dark {
--background: 224 71.4% 4.1%;
--foreground: 210 20% 98%;
--card: 224 71.4% 4.1%;
--card-foreground: 210 20% 98%;
--popover: 224 71.4% 4.1%;
--popover-foreground: 210 20% 98%;
--primary: 263.4 70% 50.4%;
--primary-foreground: 210 20% 98%;
--secondary: 215 27.9% 16.9%;
--secondary-foreground: 210 20% 98%;
--muted: 215 27.9% 16.9%;
--muted-foreground: 217.9 10.6% 64.9%;
--accent: 215 27.9% 16.9%;
--accent-foreground: 210 20% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 20% 98%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;
--ring: 263.4 70% 50.4%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

6
src/index.tsx Normal file
View File

@ -0,0 +1,6 @@
import React from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
const root = createRoot(document.getElementById("react-root")!);
root.render(<App />);

77
src/lib/saml.ts Normal file
View File

@ -0,0 +1,77 @@
export interface AssertionData {
idpEntityId: string;
subjectId: string;
sessionId: string;
now: string;
expire: string;
spEntityId: string;
}
export async function encodeAssertion(
key: CryptoKey,
assertionData: AssertionData,
): Promise<string> {
return btoa(await signAssertion(key, assertionData));
}
async function signAssertion(
key: CryptoKey,
assertionData: AssertionData,
): Promise<string> {
const digest = await digestValue(assertionData);
const signature = await signatureValue(key, assertionData);
return signedAssertion(assertionData, digest, signature);
}
function signedAssertion(
assertionData: AssertionData,
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>`;
}
async function signatureValue(
key: CryptoKey,
assertionData: AssertionData,
): Promise<string> {
const digest = await digestValue(assertionData);
const enc = new TextEncoder();
console.log("signature part");
console.log(signaturePart(digest));
const signatureData = await crypto.subtle.sign(
"RSASSA-PKCS1-v1_5",
key,
enc.encode(signaturePart(digest)),
);
return arrayBufferToBase64(signatureData);
}
function signaturePart(digest: string): string {
return `<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></ds:SignatureMethod><ds:Reference><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod><ds:DigestValue>${digest}</ds:DigestValue></ds:Reference></ds:SignedInfo>`;
}
async function digestValue(assertionData: AssertionData): Promise<string> {
const enc = new TextEncoder();
console.log("digest part");
console.log(digestPart(assertionData));
const digestData = await crypto.subtle.digest(
"SHA-256",
enc.encode(digestPart(assertionData)),
);
return arrayBufferToBase64(digestData);
}
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>`;
}
function arrayBufferToBase64(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
let binary = "";
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}

62
src/pages/SSOPage.tsx Normal file
View File

@ -0,0 +1,62 @@
import React, { useEffect, useRef, useState } from "react";
import { encodeAssertion } from "@/lib/saml";
import { useNavigate } from "react-router";
export function SSOPage() {
const navigate = useNavigate();
useEffect(() => {
(async () => {
const jwk = {
kty: "RSA",
kid: "hSDIRRN-JumgcJ6YrpvCHpc0X46FLAzWXNFxPdmfmz0",
n: "qGaBCZZvw28aHP3nZdjhKUmWlYgrvZGKWE5lL1-sNiGpZUkewYf1nuxLOsz_T5i8LIRa5lX2Ckx4hodnEQ8OpbdEnsobnV-f-z-oW_By1_4JkOPJ8YPNiz5ciB_unnpTjwSZh5akH9pRbriBJuc-qu1aZUUGKw58jHlpjc25u1Oyf6DRdt1fQvnv42BK89AdeH8Y9cDvEIhabngbmSqOjPFsoQUDQNLfJvqgdFBYeogAz_uYkx1RVk0hXWrtzYkiACXbe4Nar7YpMTZHmWI_8qO-PAqidUq1Ooapfp6LbmuxMc5oOkEf8t1Ara3GCybtHv4hOFY29QjRO22ubAGrIw",
e: "AQAB",
d: "Eggj0gx_PCyF3cvcPr4Z4gtkqe9SS7KtXyZJ1GhIruUs19EcD3oI9XL03T99KR9AKv4jI53ZwiGNGE6gXSXBGkKFAQHAMjo-ja8zzmBxU6p6iL6zbX6BAGN1kgflS6fqkZpa_DdHrLd6V8I-5hUF01SmBMj-z5Z2BK6tfEcml6XCVFBzd0QqZuACrdO9ScZvL1cd4DCHJJNBarCVgC9akxOi9THtgD72EYJQyVcsLbzKqoxnT9JW6onCBBKtUaP8fEzp9WMK6APJCkYK5I-1lX8tSf48-lzgBrn3cb9Zo5DYcGo0bJgVwzI4w-HS_etiOOrURmbbX1x9W_rGBhS8tQ",
p: "7HH9QMjZmaD0UvDtdrX1aBgo8ZCWYShMfxzt1gIy6jaSgmcyGmMJeB9s-nCGopy8Iyd4jlggmgnWP4CqYbl6fmvZ-4ULHuc0DGaR1P6zhsF4fI6zCxWm6ijyIXM75Q67ccokLQeRzINKoOmkEQ2hgFKhiaKN4TVfanufECfvqzU",
q: "tlPfEoqbGqYJtxKlND0ihXMTjSVGTbwUBUXUdzYUx7nKOGQSeCLYXbpm4KIBEYa4GNpuub8vnZD8c-FuzIb-yKH66nCa_Q2q8zhfM764pAao9P1OSjbXkA7mL6nO6LwAk7OFNa22z0H5GTRkzX8DJiAG90z_67qrQPmfmFfZ7_c",
dp: "uBegSNqYoYax9AnluXG-mseEyV_71bWcqbOKcf_QSI8ozyMt3WGSs9Yz8WG_UciaqvxGXv26lHRoPZUeE2xoNRof5DcYC32UBrute5q7iIYGG2S3fj_jb5llvCmOTq-DvfrW48BrAkKOzm5a8XQddF3hq9nGgbweiDesBtYxQqk",
dq: "OD9joBq2guAaOyo7YQRDNBwuOer3519QZdgHFcfPXVZJtl_Y-sr1KOUqe74-yiNfg_tPEWqTy-5Ak5dGUT6MN2URPWYDynF-_Y20gQgjeia71OiYUHjew4h1JtsiA9aL7wUA4XB35zSZHld1iZfXZtmWjJBqm1R5JJBd1ee0Sj0",
qi: "ypr_PYU5UrV_Lw22vpa8wJ-C_Ux9nhiz4WtU4jgEX_RofgMra4iDXIVUL6BajWYZ7vj7_AFN2tELTMQ3Xhfc4FQ-CBQkvsLoXO6fzttdxBd_yO01YtijiT_ZwozO3wwzVuN65EGNCQ9haF3vEWEfTEN_-x7RG6xZnFtel6X8rqI",
};
const key = await window.crypto.subtle.importKey(
"jwk",
jwk,
{
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
},
true,
["sign"],
);
const assertion = await encodeAssertion(key, {
idpEntityId: "IDP_ENTITY_ID",
subjectId: "ulysse@dummyidp.com",
spEntityId:
"http://localhost:8080/saml/saml_conn_e4wryo0hq30mcrzc32b67otha",
sessionId: "",
expire: "2025-01-01T00:00:00Z",
now: "2022-01-01T00:00:00Z",
});
inputRef.current!.value = assertion;
inputRef.current!.form!.submit();
})();
});
// const formRef = useRef<HTMLFormElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
return (
<div>
<form
method="post"
action="http://localhost:8080/saml/saml_conn_e4wryo0hq30mcrzc32b67otha/acs"
// ref={formRef}
>
<input type="hidden" name="SAMLResponse" ref={inputRef} />
</form>
</div>
);
}

76
tailwind.config.js Normal file
View File

@ -0,0 +1,76 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: ["./src/**/*.{ts,tsx}", "./public/index.html"],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
fontFamily: {
sans: `"Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"`,
mono: `"Roboto Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace`,
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
};

17
tsconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"rootDir": "src",
"module": "commonjs",
"target": "es2015",
"sourceMap": true,
"jsx": "react",
"allowSyntheticDefaultImports": true,
"lib": ["es2017", "dom"],
"strict": true
},
"exclude": ["node_modules"]
}