WIP: SAML login

feature/saml-login
Deepak Prabhakara 2021-10-22 17:54:15 +01:00
parent 40e915b5f1
commit 4dd0e0a95e
9 changed files with 54 additions and 6 deletions

View File

@ -21,6 +21,9 @@ JWT_SECRET='secret'
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Enable SAML login
SAML_LOGIN_URL=
# @see: https://github.com/calendso/calendso/issues/263
# Required for Vercel hosting - set NEXTAUTH_URL to equal your BASE_URL
# NEXTAUTH_URL='http://localhost:3000'

View File

@ -46,6 +46,9 @@ export enum ErrorCode {
export const identityProviderNameMap: { [key in IdentityProvider]: string } = {
[IdentityProvider.CAL]: "Cal",
[IdentityProvider.GOOGLE]: "Google",
[IdentityProvider.SAML]: "SAML",
};
export const isGoogleLoginEnabled = process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET;
export const isSAMLLoginEnabled = process.env.SAML_LOGIN_URL;

View File

@ -11,7 +11,7 @@
"db-migrate": "yarn prisma migrate dev",
"db-seed": "yarn ts-node scripts/seed.ts",
"db-nuke": "docker-compose down --volumes --remove-orphans",
"dx": "cross-env BASE_URL=http://localhost:3000 JWT_SECRET=secret DATABASE_URL=postgresql://postgres:@localhost:5450/calendso run-s db-up db-migrate db-seed dev",
"dx": "cross-env BASE_URL=http://localhost:3000 SAML_LOGIN_URL=http://localhost:5000 JWT_SECRET=secret DATABASE_URL=postgresql://postgres:@localhost:5450/calendso run-s db-up db-migrate db-seed dev",
"test": "jest",
"test-playwright": "jest --config jest.playwright.config.js",
"test-codegen": "yarn playwright codegen http://localhost:3000",

View File

@ -2,7 +2,7 @@ import NextAuth from "next-auth";
import Providers, { AppProviders } from "next-auth/providers";
import { authenticator } from "otplib";
import { ErrorCode, isGoogleLoginEnabled, Session, verifyPassword } from "@lib/auth";
import { ErrorCode, isGoogleLoginEnabled, isSAMLLoginEnabled, Session, verifyPassword } from "@lib/auth";
import { symmetricDecrypt } from "@lib/crypto";
import prisma from "@lib/prisma";
import slugify from "@lib/slugify";
@ -91,6 +91,29 @@ if (isGoogleLoginEnabled) {
);
}
if (isSAMLLoginEnabled) {
providers.push({
id: "boxyhq",
name: "BoxyHQ",
type: "oauth",
version: "2.0",
params: {
grant_type: "authorization_code",
},
accessTokenUrl: `${process.env.SAML_LOGIN_URL}/oauth/token`,
authorizationUrl: `${process.env.SAML_LOGIN_URL}/oauth/authorize?response_type=code&provider=saml`,
profileUrl: `${process.env.SAML_LOGIN_URL}/oauth/userinfo`,
profile: (profile) => {
return {
...profile,
name: `${profile.firstName} ${profile.lastName}`,
};
},
clientId: "tenant=boxyhq.com&product=demo",
clientSecret: "dummy",
});
}
export default NextAuth({
session: {
jwt: true,

View File

@ -3,14 +3,14 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import { ErrorCode, getSession, isGoogleLoginEnabled } from "@lib/auth";
import { ErrorCode, getSession, isGoogleLoginEnabled, isSAMLLoginEnabled } from "@lib/auth";
import { useLocale } from "@lib/hooks/useLocale";
import AddToHomescreen from "@components/AddToHomescreen";
import Loader from "@components/Loader";
import { HeadSeo } from "@components/seo/head-seo";
export default function Login({ csrfToken, isGoogleLoginEnabled }) {
export default function Login({ csrfToken, isGoogleLoginEnabled, isSAMLLoginEnabled }) {
const { t } = useLocale();
const router = useRouter();
const [email, setEmail] = useState("");
@ -180,6 +180,13 @@ export default function Login({ csrfToken, isGoogleLoginEnabled }) {
Sign in with Google
</button>
)}
{isSAMLLoginEnabled && (
<button
onClick={async () => await signIn("boxyhq")}
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-sm shadow-sm text-sm font-medium text-black bg-secondary-50 hover:bg-secondary-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
{t("signin_with_saml")}
</button>
)}
</div>
<div className="mt-4 text-neutral-600 text-center text-sm">
{t("dont_have_an_account")} {/* replace this with your account creation flow */}
@ -207,5 +214,6 @@ Login.getInitialProps = async (context) => {
return {
csrfToken: await getCsrfToken(context),
isGoogleLoginEnabled,
isSAMLLoginEnabled,
};
};

View File

@ -2,7 +2,7 @@ import { signIn } from "next-auth/client";
import { useRouter } from "next/router";
import { useState } from "react";
import { getSession, isGoogleLoginEnabled } from "@lib/auth";
import { getSession, isGoogleLoginEnabled, isSAMLLoginEnabled } from "@lib/auth";
import { useLocale } from "@lib/hooks/useLocale";
import prisma from "@lib/prisma";
@ -136,6 +136,13 @@ export default function Signup(props) {
{t("signin_with_google")}
</button>
)}
{props.isSAMLLoginEnabled && (
<button
onClick={async () => await signIn("boxyhq")}
className="w-full mt-6 flex justify-center py-2 px-4 border border-transparent rounded-sm shadow-sm text-sm font-medium text-black bg-secondary-50 hover:bg-secondary-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
{t("signin_with_saml")}
</button>
)}
</div>
</div>
</div>
@ -187,5 +194,5 @@ export async function getServerSideProps(ctx) {
};
}
return { props: { isGoogleLoginEnabled, email: verificationRequest.identifier } };
return { props: { isGoogleLoginEnabled, isSAMLLoginEnabled, email: verificationRequest.identifier } };
}

View File

@ -0,0 +1,2 @@
-- add the new value to the existing type
ALTER TYPE "IdentityProvider" ADD VALUE 'SAML';

View File

@ -66,6 +66,7 @@ enum UserPlan {
enum IdentityProvider {
CAL
GOOGLE
SAML
}
model User {

View File

@ -516,6 +516,7 @@
"settings": "Settings",
"google_account": "Your account was created using Google.",
"signin_with_google": "Sign in with Google",
"signin_with_saml": "Sign in with SAML",
"next_step": "Skip step",
"prev_step": "Prev step",
"installed": "Installed",