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
+16 -16
View File
@@ -59,7 +59,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
const oidcStrategy = await server.services.oidc.getOrgAuthStrategy(oidcOrgSlug); const oidcStrategy = await server.services.oidc.getOrgAuthStrategy(oidcOrgSlug);
await ( await (
passport.authenticate(oidcStrategy as Strategy, { passport.authenticate(oidcStrategy as Strategy, {
failureRedirect: "/api/v1/oidc/login/error", failureRedirect: "/api/v1/sso/oidc/login/error",
session: false, session: false,
failureMessage: true failureMessage: true
}) as any }) as any
@@ -137,13 +137,13 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
schema: { schema: {
body: z body: z
.object({ .object({
issuer: z.string(), issuer: z.string().trim(),
authorizationEndpoint: z.string(), authorizationEndpoint: z.string().trim(),
jwksUri: z.string(), jwksUri: z.string().trim(),
tokenEndpoint: z.string(), tokenEndpoint: z.string().trim(),
userinfoEndpoint: z.string(), userinfoEndpoint: z.string().trim(),
clientId: z.string(), clientId: z.string().trim(),
clientSecret: z.string(), clientSecret: z.string().trim(),
isActive: z.boolean() isActive: z.boolean()
}) })
.partial() .partial()
@@ -182,15 +182,15 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
schema: { schema: {
body: z.object({ body: z.object({
issuer: z.string(), issuer: z.string().trim(),
authorizationEndpoint: z.string(), authorizationEndpoint: z.string().trim(),
jwksUri: z.string(), jwksUri: z.string().trim(),
tokenEndpoint: z.string(), tokenEndpoint: z.string().trim(),
userinfoEndpoint: z.string(), userinfoEndpoint: z.string().trim(),
clientId: z.string(), clientId: z.string().trim(),
clientSecret: z.string(), clientSecret: z.string().trim(),
isActive: z.boolean(), isActive: z.boolean(),
orgSlug: z.string() orgSlug: z.string().trim()
}), }),
response: { response: {
200: OidcConfigsSchema.pick({ 200: OidcConfigsSchema.pick({
@@ -520,7 +520,7 @@ export const oidcConfigServiceFactory = ({
const client = new openIdIssuer.Client({ const client = new openIdIssuer.Client({
client_id: oidcCfg.clientId, client_id: oidcCfg.clientId,
client_secret: oidcCfg.clientSecret, 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( const strategy = new OpenIdStrategy(
+4 -2
View File
@@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import { isLoggedIn } from "@app/reactQuery"; import { isLoggedIn } from "@app/reactQuery";
import { InitialStep, MFAStep, SAMLSSOStep } from "./components"; import { InitialStep, MFAStep, SSOStep } from "./components";
import { navigateUserToSelectOrg } from "./Login.utils"; import { navigateUserToSelectOrg } from "./Login.utils";
export const Login = () => { export const Login = () => {
@@ -57,7 +57,9 @@ export const Login = () => {
/> />
); );
case 2: case 2:
return <SAMLSSOStep setStep={setStep} />; return <SSOStep setStep={setStep} type="SAML" />;
case 3:
return <SSOStep setStep={setStep} type="OIDC" />;
default: default:
return <div />; return <div />;
} }
@@ -40,11 +40,13 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
const { data: serverDetails } = useFetchServerStatus(); const { data: serverDetails } = useFetchServerStatus();
useEffect(() => { useEffect(() => {
if (serverDetails?.samlDefaultOrgSlug){ if (serverDetails?.samlDefaultOrgSlug) {
const callbackPort = queryParams.get("callback_port"); const callbackPort = queryParams.get("callback_port");
const redirectUrl = `/api/v1/sso/redirect/saml2/organizations/${serverDetails?.samlDefaultOrgSlug}${callbackPort ? `?callback_port=${callbackPort}` : ""}` const redirectUrl = `/api/v1/sso/redirect/saml2/organizations/${
router.push(redirectUrl); serverDetails?.samlDefaultOrgSlug
} }${callbackPort ? `?callback_port=${callbackPort}` : ""}`;
router.push(redirectUrl);
}
}, [serverDetails?.samlDefaultOrgSlug]); }, [serverDetails?.samlDefaultOrgSlug]);
const handleLogin = async (e: FormEvent<HTMLFormElement>) => { const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
@@ -217,6 +219,19 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
Continue with SAML Continue with SAML
</Button> </Button>
</div> </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"> <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 <Button
colorSchema="primary" colorSchema="primary"
@@ -1 +0,0 @@
export { SAMLSSOStep } from "./SAMLSSOStep";
@@ -5,9 +5,10 @@ import { Button, Input } from "@app/components/v2";
type Props = { type Props = {
setStep: (step: number) => void; setStep: (step: number) => void;
type: "SAML" | "OIDC";
}; };
export const SAMLSSOStep = ({ setStep }: Props) => { export const SSOStep = ({ setStep, type }: Props) => {
const [ssoIdentifier, setSSOIdentifier] = useState(""); const [ssoIdentifier, setSSOIdentifier] = useState("");
const { t } = useTranslation(); const { t } = useTranslation();
@@ -16,21 +17,27 @@ export const SAMLSSOStep = ({ setStep }: Props) => {
const handleSubmission = (e: React.FormEvent) => { const handleSubmission = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
const callbackPort = queryParams.get("callback_port"); const callbackPort = queryParams.get("callback_port");
window.open( if (type === "SAML") {
`/api/v1/sso/redirect/saml2/organizations/${ssoIdentifier}${ window.open(
callbackPort ? `?callback_port=${callbackPort}` : "" `/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(); window.close();
}; };
return ( return (
<div className="mx-auto w-full max-w-md md:px-6"> <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? What&apos;s your organization slug?
</p> </p>
<form onSubmit={handleSubmission}> <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"> <div className="flex max-h-24 w-full items-center justify-center rounded-lg md:max-h-28">
<Input <Input
value={ssoIdentifier} value={ssoIdentifier}
@@ -44,7 +51,7 @@ export const SAMLSSOStep = ({ setStep }: Props) => {
/> />
</div> </div>
</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 <Button
type="submit" type="submit"
colorSchema="primary" colorSchema="primary"
@@ -52,7 +59,7 @@ export const SAMLSSOStep = ({ setStep }: Props) => {
isFullWidth isFullWidth
className="h-14" className="h-14"
> >
Continue with SAML Continue with {type}
</Button> </Button>
</div> </div>
</form> </form>
@@ -0,0 +1 @@
export { SSOStep } from "./SSOStep";
@@ -1,6 +1,6 @@
export { InitialStep } from "./InitialStep"; export { InitialStep } from "./InitialStep";
export { MFAStep } from "./MFAStep"; export { MFAStep } from "./MFAStep";
export { SAMLSSOStep } from "./SAMLSSOStep"; export { SSOStep } from "./SSOStep";
// SSO-specific step // SSO-specific step
export { PasswordStep } from "./PasswordStep"; export { PasswordStep } from "./PasswordStep";