Add Google SSO

This commit is contained in:
Tuan Dang
2023-07-21 17:48:36 +07:00
parent 44108621b4
commit fd3cf70e13
12 changed files with 2026 additions and 1882 deletions
+1866 -1797
View File
File diff suppressed because it is too large Load Diff
+25 -1
View File
@@ -18,6 +18,30 @@ import {
getSiteURL
} from "../../../config";
router.get(
"/redirect/google",
authLimiter,
passport.authenticate("google", {
scope: ["profile", "email"],
session: false,
})
);
router.get(
"/google",
passport.authenticate("google", {
failureRedirect: "/login/provider/error",
session: false
}),
async (req, res) => {
if (req.isUserCompleted) {
res.redirect(`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
} else {
res.redirect(`${await getSiteURL()}/signup/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
}
}
);
router.get(
"/redirect/saml2/:ssoIdentifier",
authLimiter,
@@ -32,7 +56,7 @@ router.post("/saml2/:ssoIdentifier",
failureFlash: true,
session: false
}),
async (req, res) => {
async (req, res) => { // TODO: move this into controller
if (req.isUserCompleted) {
return res.redirect(`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
}
@@ -30,6 +30,7 @@ interface FeatureSet {
customRateLimits: boolean;
customAlerts: boolean;
auditLogs: boolean;
samlSSO: boolean;
status: "incomplete" | "incomplete_expired" | "trialing" | "active" | "past_due" | "canceled" | "unpaid" | null;
trial_end: number | null;
has_used_trial: boolean;
@@ -63,6 +64,7 @@ class EELicenseService {
customRateLimits: true,
customAlerts: true,
auditLogs: false,
samlSSO: false,
status: null,
trial_end: null,
has_used_trial: true
-16
View File
@@ -1,7 +1,6 @@
import express from "express";
const router = express.Router();
import { body } from "express-validator";
import passport from "passport";
import { requireAuth, validateRequest } from "../../middleware";
import { authController } from "../../controllers/v1";
import { authLimiter } from "../../helpers/rateLimiter";
@@ -44,21 +43,6 @@ router.post(
authController.checkAuth
);
router.get(
"/redirect/google",
authLimiter,
passport.authenticate("google", {
scope: ["profile", "email"],
session: false,
}),
);
router.get(
"/callback/google",
passport.authenticate("google", { failureRedirect: "/login/provider/error", session: false }),
authController.handleAuthProviderCallback,
);
router.get(
"/common-passwords",
authLimiter,
+34 -17
View File
@@ -18,16 +18,15 @@ import {
getJwtProviderAuthSecret,
} from "../config";
import { getSSOConfigHelper } from "../ee/helpers/organizations";
import { OrganizationNotFoundError } from "./errors";
import { OrganizationNotFoundError, InternalServerError } from "./errors";
import { MEMBER, INVITED } from "../variables";
import { getSiteURL } from "../config";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const GoogleStrategy = require("passport-google-oauth20").Strategy;
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { MultiSamlStrategy } = require("@node-saml/passport-saml");
// TODO: find a more optimal folder structure to store these types of functions
/**
* Returns an object containing the id of the authentication data payload
* @param {AuthData} authData - authentication data object
@@ -75,45 +74,57 @@ const initializePassport = async () => {
passReqToCallback: true,
clientID: googleClientId,
clientSecret: googleClientSecret,
callbackURL: "/api/v1/auth/callback/google",
callbackURL: "/api/v1/sso/google",
scope: ["profile", " email"],
}, async (
req: express.Request,
accessToken: string,
refreshToken: string,
profile: any,
cb: any
done: any
) => {
try {
const email = profile.emails[0].value;
const firstName = profile.name.givenName;
const lastName = profile.name.familyName;
let user = await User.findOne({
authProvider: AuthProvider.GOOGLE,
authId: profile.id,
}).select("+publicKey")
email
}).select("+publicKey");
if (user && user.authProvider !== AuthProvider.GOOGLE) {
done(InternalServerError());
}
if (!user) {
user = await new User({
email,
authProvider: AuthProvider.GOOGLE,
authId: profile.id,
firstName,
lastName
}).save();
}
const isUserCompleted = !!user.publicKey;
const providerAuthToken = createToken({
payload: {
userId: user._id.toString(),
email: user.email,
firstName,
lastName,
authProvider: user.authProvider,
isUserCompleted: !!user.publicKey,
isUserCompleted
},
expiresIn: await getJwtProviderAuthLifetime(),
secret: await getJwtProviderAuthSecret(),
});
req.isUserCompleted = isUserCompleted;
req.providerAuthToken = providerAuthToken;
cb(null, profile);
done(null, profile);
} catch (err) {
cb(null, false);
done(null, false);
}
}));
@@ -129,7 +140,7 @@ const initializePassport = async () => {
const samlConfig = ({
path: "/api/v1/auth/callback/saml",
callbackURL: "http://localhost:8080/api/v1/auth/callback/saml", // TODO: get rid of localhost:8080 here
callbackURL: `${await getSiteURL()}/api/v1/auth/callback/saml`,
entryPoint: ssoConfig.entryPoint,
issuer: ssoConfig.issuer,
cert: ssoConfig.cert,
@@ -142,24 +153,30 @@ const initializePassport = async () => {
},
},
async (req: any, profile: any, done: any) => {
if (!req.ssoConfig.isActive) return done(InternalServerError());
const organization = await Organization.findById(req.ssoConfig.organization);
if (!organization) done(OrganizationNotFoundError());
if (!organization) return done(OrganizationNotFoundError());
const email = profile.email;
const firstName = profile.firstName;
const lastName = profile.lastName;
let user = await User.findOne({
authProvider: AuthProvider.OKTA_SAML,
email
}).select("+publicKey");
if (user && user.authProvider !== AuthProvider.OKTA_SAML) {
done(InternalServerError());
}
if (!user) {
user = await new User({
email,
authProvider: AuthProvider.OKTA_SAML,
authId: profile.id,
firstName,
lastName
}).save();
@@ -178,8 +195,8 @@ const initializePassport = async () => {
payload: {
userId: user._id.toString(),
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
firstName,
lastName,
organizationName: organization?.name,
authProvider: user.authProvider,
isUserCompleted