feat: integrated oidc with sso login

This commit is contained in:
Sheen Capadngan
2024-06-18 02:10:08 +08:00
parent 4f2f7b2f70
commit df51d05c46
8 changed files with 60 additions and 36 deletions

View File

@@ -59,7 +59,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
const oidcStrategy = await server.services.oidc.getOrgAuthStrategy(oidcOrgSlug);
await (
passport.authenticate(oidcStrategy as Strategy, {
failureRedirect: "/api/v1/oidc/login/error",
failureRedirect: "/api/v1/sso/oidc/login/error",
session: false,
failureMessage: true
}) as any
@@ -137,13 +137,13 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
schema: {
body: z
.object({
issuer: z.string(),
authorizationEndpoint: z.string(),
jwksUri: z.string(),
tokenEndpoint: z.string(),
userinfoEndpoint: z.string(),
clientId: z.string(),
clientSecret: z.string(),
issuer: z.string().trim(),
authorizationEndpoint: z.string().trim(),
jwksUri: z.string().trim(),
tokenEndpoint: z.string().trim(),
userinfoEndpoint: z.string().trim(),
clientId: z.string().trim(),
clientSecret: z.string().trim(),
isActive: z.boolean()
})
.partial()
@@ -182,15 +182,15 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
issuer: z.string(),
authorizationEndpoint: z.string(),
jwksUri: z.string(),
tokenEndpoint: z.string(),
userinfoEndpoint: z.string(),
clientId: z.string(),
clientSecret: z.string(),
issuer: z.string().trim(),
authorizationEndpoint: z.string().trim(),
jwksUri: z.string().trim(),
tokenEndpoint: z.string().trim(),
userinfoEndpoint: z.string().trim(),
clientId: z.string().trim(),
clientSecret: z.string().trim(),
isActive: z.boolean(),
orgSlug: z.string()
orgSlug: z.string().trim()
}),
response: {
200: OidcConfigsSchema.pick({

View File

@@ -520,7 +520,7 @@ export const oidcConfigServiceFactory = ({
const client = new openIdIssuer.Client({
client_id: oidcCfg.clientId,
client_secret: oidcCfg.clientSecret,
redirect_uris: [`${appCfg.SITE_URL}/api/v1/oidc/callback`]
redirect_uris: [`${appCfg.SITE_URL}/api/v1/sso/oidc/callback`]
});
const strategy = new OpenIdStrategy(

View File

@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { isLoggedIn } from "@app/reactQuery";
import { InitialStep, MFAStep, SAMLSSOStep } from "./components";
import { InitialStep, MFAStep, SSOStep } from "./components";
import { navigateUserToSelectOrg } from "./Login.utils";
export const Login = () => {
@@ -57,7 +57,9 @@ export const Login = () => {
/>
);
case 2:
return <SAMLSSOStep setStep={setStep} />;
return <SSOStep setStep={setStep} type="SAML" />;
case 3:
return <SSOStep setStep={setStep} type="OIDC" />;
default:
return <div />;
}

View File

@@ -40,11 +40,13 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
const { data: serverDetails } = useFetchServerStatus();
useEffect(() => {
if (serverDetails?.samlDefaultOrgSlug){
const callbackPort = queryParams.get("callback_port");
const redirectUrl = `/api/v1/sso/redirect/saml2/organizations/${serverDetails?.samlDefaultOrgSlug}${callbackPort ? `?callback_port=${callbackPort}` : ""}`
router.push(redirectUrl);
}
if (serverDetails?.samlDefaultOrgSlug) {
const callbackPort = queryParams.get("callback_port");
const redirectUrl = `/api/v1/sso/redirect/saml2/organizations/${
serverDetails?.samlDefaultOrgSlug
}${callbackPort ? `?callback_port=${callbackPort}` : ""}`;
router.push(redirectUrl);
}
}, [serverDetails?.samlDefaultOrgSlug]);
const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
@@ -217,6 +219,19 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
Continue with SAML
</Button>
</div>
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
<Button
colorSchema="primary"
variant="outline_bg"
onClick={() => {
setStep(3);
}}
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
className="mx-0 h-10 w-full"
>
Continue with OIDC
</Button>
</div>
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
<Button
colorSchema="primary"

View File

@@ -1 +0,0 @@
export { SAMLSSOStep } from "./SAMLSSOStep";

View File

@@ -5,9 +5,10 @@ import { Button, Input } from "@app/components/v2";
type Props = {
setStep: (step: number) => void;
type: "SAML" | "OIDC";
};
export const SAMLSSOStep = ({ setStep }: Props) => {
export const SSOStep = ({ setStep, type }: Props) => {
const [ssoIdentifier, setSSOIdentifier] = useState("");
const { t } = useTranslation();
@@ -16,21 +17,27 @@ export const SAMLSSOStep = ({ setStep }: Props) => {
const handleSubmission = (e: React.FormEvent) => {
e.preventDefault();
const callbackPort = queryParams.get("callback_port");
window.open(
`/api/v1/sso/redirect/saml2/organizations/${ssoIdentifier}${
callbackPort ? `?callback_port=${callbackPort}` : ""
}`
);
if (type === "SAML") {
window.open(
`/api/v1/sso/redirect/saml2/organizations/${ssoIdentifier}${
callbackPort ? `?callback_port=${callbackPort}` : ""
}`
);
} else {
// TODO: Sheen - Add callback support for CLI login
window.open(`/api/v1/sso/oidc/login?orgSlug=${ssoIdentifier}`);
}
window.close();
};
return (
<div className="mx-auto w-full max-w-md md:px-6">
<p className="mx-auto mb-6 mb-8 flex w-max justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
<p className="mx-auto mb-8 flex w-max justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
What&apos;s your organization slug?
</p>
<form onSubmit={handleSubmission}>
<div className="relative mx-auto flex max-h-24 w-1/4 w-full min-w-[20rem] items-center justify-center rounded-lg md:max-h-28 md:min-w-[22rem] lg:w-1/6">
<div className="relative mx-auto flex max-h-24 w-full min-w-[20rem] items-center justify-center rounded-lg md:max-h-28 md:min-w-[22rem] lg:w-1/6">
<div className="flex max-h-24 w-full items-center justify-center rounded-lg md:max-h-28">
<Input
value={ssoIdentifier}
@@ -44,7 +51,7 @@ export const SAMLSSOStep = ({ setStep }: Props) => {
/>
</div>
</div>
<div className="mx-auto mt-4 flex w-1/4 w-full min-w-[20rem] items-center justify-center rounded-md text-center md:min-w-[22rem] lg:w-1/6">
<div className="mx-auto mt-4 flex w-full min-w-[20rem] items-center justify-center rounded-md text-center md:min-w-[22rem] lg:w-1/6">
<Button
type="submit"
colorSchema="primary"
@@ -52,7 +59,7 @@ export const SAMLSSOStep = ({ setStep }: Props) => {
isFullWidth
className="h-14"
>
Continue with SAML
Continue with {type}
</Button>
</div>
</form>

View File

@@ -0,0 +1 @@
export { SSOStep } from "./SSOStep";

View File

@@ -1,6 +1,6 @@
export { InitialStep } from "./InitialStep";
export { MFAStep } from "./MFAStep";
export { SAMLSSOStep } from "./SAMLSSOStep";
export { SSOStep } from "./SSOStep";
// SSO-specific step
export { PasswordStep } from "./PasswordStep";