Merge pull request #793 from OpenSignLabs/feat_sso

This commit is contained in:
Amol
2024-05-31 19:02:40 +05:30
committed by GitHub
14 changed files with 792 additions and 163 deletions
+2
View File
@@ -16,6 +16,7 @@ import PlaceHolderSign from "./pages/PlaceHolderSign";
import PdfRequestFiles from "./pages/PdfRequestFiles";
import LazyPage from "./primitives/LazyPage";
import { isEnableSubscription } from "./constant/const";
import SSOVerify from "./pages/SSOVerify";
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
@@ -181,6 +182,7 @@ function App() {
element={<PdfRequestFiles />}
/>
</Route>
<Route path="/sso" element={<SSOVerify />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
+3 -1
View File
@@ -44,7 +44,9 @@ const Header = ({ showSidebar }) => {
const closeDropdown = () => {
setIsOpen(false);
Parse.User.logOut();
if (Parse?.User?.current()) {
Parse.User.logOut();
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
let appName = localStorage.getItem("appName");
+5 -1
View File
@@ -10,7 +10,11 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
? `/${item.pageType}/${item.objectId}`
: `/${item.objectId}`
}
className="mx-auto flex items-center hover:bg-[#eef1f5] p-3 lg:p-4 cursor-pointer focus:text-[#0056b3] focus:bg-[#eef1f5]"
className={({ isActive }) =>
`${
isActive ? "text-[#0056b3] bg-[#eef1f5]" : ""
} mx-auto flex items-center hover:bg-[#eef1f5] p-3 lg:p-4 cursor-pointer`
}
onClick={closeSidebar}
tabIndex={isOpen ? 0 : -1}
role="menuitem"
+6 -2
View File
@@ -12,8 +12,12 @@ export const isMobile = window.innerWidth < 767;
export const textInputWidget = "text input";
export const textWidget = "text";
export const radioButtonWidget = "radio button";
export const openInNewTab = (url) => {
window.open(url, "_blank", "noopener,noreferrer");
export const openInNewTab = (url, target) => {
if (target) {
window.open(url, target, "noopener,noreferrer");
} else {
window.open(url, "_blank", "noopener,noreferrer");
}
};
export async function fetchSubscription(
+137 -122
View File
@@ -19,7 +19,7 @@ import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import { showTenant } from "../redux/reducers/ShowTenant";
import { fetchSubscription, getAppLogo } from "../constant/Utils";
import { fetchSubscription, getAppLogo, openInNewTab } from "../constant/Utils";
function Login() {
const navigate = useNavigate();
const location = useLocation();
@@ -45,6 +45,7 @@ function Login() {
});
const [isModal, setIsModal] = useState(false);
const [image, setImage] = useState();
const [isLoginSSO, setIsLoginSSO] = useState(false);
useEffect(() => {
if (localStorage.getItem("accesstoken")) {
@@ -1016,8 +1017,9 @@ function Login() {
const handleCloseModal = () => {
setIsModal(false);
Parse.User.logOut();
if (Parse?.User?.current()) {
Parse.User.logOut();
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
let appName = localStorage.getItem("appName");
@@ -1040,6 +1042,24 @@ function Login() {
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
};
// `handleSignInWithSSO` is trigger when user click sign in with sso and open sso authorize endpoint
const handleSignInWithSSO = () => {
if (state?.email) {
setIsLoginSSO(true);
const encodedEmail = encodeURIComponent(state.email);
const clientUrl = window.location.origin;
const domain = state.email.split("@")?.pop();
const ssoApiUrl =
process.env.SSO_API_URL || "https://sso.opensignlabs.com/api";
openInNewTab(
`${ssoApiUrl}/oauth/authorize?response_type=code&provider=saml&tenant=${domain}&product=OpenSign&redirect_uri=${clientUrl}/sso&state=${encodedEmail}`,
"_self"
);
} else {
alert("Please provide email.");
}
};
return (
<div className="bg-white">
<Title title={"Login Page"} />
@@ -1087,136 +1107,131 @@ function Login() {
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
<div>
<div>
<form onSubmit={handleSubmit} aria-label="Login Form">
<h1 className="text-[30px] mt-6">Welcome Back!</h1>
<fieldset>
<legend className="text-[12px] text-[#878787]">
Login to your account
</legend>
<div className="px-6 py-4 outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
<label className="block text-xs" htmlFor="email">
Username
</label>
<input
id="email"
type="text"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs"
name="email"
value={state.email}
onChange={handleChange}
required
/>
<hr className="my-2 border-none" />
<label className="block text-xs" htmlFor="password">
Password
</label>
<div className="relative">
<input
id="password"
type={state.passwordVisible ? "text" : "password"}
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs"
name="password"
value={state.password}
onChange={handleChange}
required
/>
<span
className={`absolute top-[50%] right-[10px] -translate-y-[50%] cursor-pointer ${
state.passwordVisible
? "text-[#007bff]"
: "text-black"
}`}
onClick={togglePasswordVisibility}
>
{state.passwordVisible ? (
<i className="fa fa-eye-slash text-xs pb-1" /> // Close eye icon
) : (
<i className="fa fa-eye text-xs pb-1 " /> // Open eye icon
)}
</span>
</div>
<div className="relative mt-1">
<NavLink
to="/forgetpassword"
className="text-[13px] text-[#002864] hover:underline underline-offset-1 focus:outline-none cursor-pointer ml-1"
>
Forgot Password?
</NavLink>
</div>
<form onSubmit={handleSubmit} aria-label="Login Form">
<h1 className="text-[30px] mt-6">Welcome Back!</h1>
<fieldset>
<legend className="text-[12px] text-[#878787]">
Login to your account
</legend>
<div className="px-6 py-4 outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
<label className="block text-xs" htmlFor="email">
Email
</label>
<input
id="email"
type="text"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs"
name="email"
value={state.email}
onChange={handleChange}
required
/>
<hr className="my-2 border-none" />
{isLoginSSO && (
<>
<label className="block text-xs" htmlFor="password">
Password
</label>
<div className="relative">
<input
id="password"
type={
state.passwordVisible ? "text" : "password"
}
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs"
name="password"
value={state.password}
onChange={handleChange}
required
/>
<span
className={`absolute top-[50%] right-[10px] -translate-y-[50%] cursor-pointer ${
state.passwordVisible
? "text-[#007bff]"
: "text-black"
}`}
onClick={togglePasswordVisibility}
>
{state.passwordVisible ? (
<i className="fa fa-eye-slash text-xs pb-1" /> // Close eye icon
) : (
<i className="fa fa-eye text-xs pb-1 " /> // Open eye icon
)}
</span>
</div>
</>
)}
<div className="relative mt-1">
<NavLink
to="/forgetpassword"
className="text-[13px] text-[#002864] hover:underline underline-offset-1 focus:outline-none cursor-pointer ml-1"
>
Forgot Password?
</NavLink>
</div>
</fieldset>
<div className="flex flex-col md:flex-row justify-between items-stretch gap-8 text-center text-xs font-bold mt-2">
<button
type="submit"
className="rounded-sm bg-[#3ac9d6] text-white w-full py-3 shadow outline-none uppercase focus:ring-2 focus:ring-blue-600"
disabled={state.loading}
>
{state.loading ? "Loading..." : "Login"}
</button>
<NavLink
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase"
to={
location.search
? "/signup" + location.search
: "/signup"
}
style={width < 768 ? { textAlign: "center" } : {}}
>
Create Account
</NavLink>
</div>
</form>
<br />
{(appInfo.fbAppId || appInfo.googleClietId) && (
<div className="text-sm flex justify-center items-center">
<hr className="border-[1px] border-gray-300 w-full" />
<span className="px-2 text-gray-500 cursor-default">
OR
</span>
<hr className="border-[1px] border-gray-300 w-full" />
</div>
)}
<br />
<div
style={{
textAlign: "center",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
{/* {appInfo.fbAppId && appInfo.fbAppId !== "" ? (
</fieldset>
<div className="flex flex-col md:flex-row justify-between items-stretch gap-8 text-center text-xs font-bold mt-2">
<button
type="submit"
className="rounded-sm bg-[#3ac9d6] text-white w-full py-3 shadow outline-none uppercase focus:ring-2 focus:ring-blue-600"
disabled={state.loading}
>
{state.loading ? "Loading..." : "Login"}
</button>
<NavLink
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase"
to={
location.search
? "/signup" + location.search
: "/signup"
}
style={width < 768 ? { textAlign: "center" } : {}}
>
Create Account
</NavLink>
</div>
</form>
<br />
{appInfo.googleClietId && (
<div className="text-sm flex justify-center items-center">
<hr className="border-[1px] border-gray-300 w-full" />
<span className="px-2 text-gray-500 cursor-default">
OR
</span>
<hr className="border-[1px] border-gray-300 w-full" />
</div>
)}
<br />
<div className="flex flex-col justify-center items-center gap-y-3">
{/* {appInfo?.fbAppId && (
<LoginFacebook
FBCred={appInfo.fbAppId}
thirdpartyLoginfn={thirdpartyLoginfn}
thirdpartyLoader={state.thirdpartyLoader}
setThirdpartyLoader={setThirdpartyLoader}
/>
) : null} */}
</div>
<div style={{ margin: "10px 0" }}></div>
<div
style={{
textAlign: "center",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
{appInfo.googleClietId && appInfo.googleClietId !== "" ? (
<GoogleSignInBtn
GoogleCred={appInfo.googleClietId}
thirdpartyLoginfn={thirdpartyLoginfn}
thirdpartyLoader={state.thirdpartyLoader}
setThirdpartyLoader={setThirdpartyLoader}
/>
) : null}
</div>
)} */}
{appInfo?.googleClietId && (
<GoogleSignInBtn
GoogleCred={appInfo.googleClietId}
thirdpartyLoginfn={thirdpartyLoginfn}
thirdpartyLoader={state.thirdpartyLoader}
setThirdpartyLoader={setThirdpartyLoader}
/>
)}
{isEnableSubscription && (
<div
className="cursor-pointer border-[1px] border-gray-300 rounded px-[40px] py-2 font-semibold text-sm hover:border-[#d2e3fc] hover:bg-[#ecf3feb7]"
onClick={() => handleSignInWithSSO()}
>
Sign in with SSO
</div>
)}
</div>
</div>
{width >= 768 && (
<div className="self-center">
<div className="place-self-center">
<div className="mx-auto md:w-[300px] lg:w-[400px] xl:w-[500px]">
<img
src={login_img}
+413
View File
@@ -0,0 +1,413 @@
import React, { useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import Parse from "parse";
import axios from "axios";
import { appInfo } from "../constant/appinfo";
import {
isEnableSubscription,
modalCancelBtnColor,
modalSubmitBtnColor
} from "../constant/const";
import { fetchSubscription } from "../constant/Utils";
import { useDispatch } from "react-redux";
import { showTenant } from "../redux/reducers/ShowTenant";
import ModalUi from "../primitives/ModalUi";
import loader from "../assets/images/loader2.gif";
const SSOVerify = () => {
const location = useLocation();
const navigate = useNavigate();
const dispatch = useDispatch();
const [isModal, setIsModal] = useState(false);
const [message, setMessage] = useState("Verifying SSO...");
const [isLoader, setIsLoader] = useState(false);
const [userDetails, setUserDetails] = useState({
name: "",
email: "",
phone: "",
Destination: "",
Company: ""
});
useEffect(() => {
linkUserWithSSO();
// eslint-disable-next-line
}, []);
// `linkUserWithSSO` is used to sign in or sign up a user using an SSO code and check if the user is present in the extended class
const linkUserWithSSO = async () => {
const param = new URLSearchParams(location.search);
const code = param?.get("code");
const state = param?.get("state");
try {
// The `ssosign` cloud function is used to sign in or sign up a user
const ssosign = await Parse.Cloud.run("ssosign", {
code: code,
email: state
});
localStorage.setItem("accesstoken", ssosign.sessiontoken);
// `checkExtUser` checks if the user is present in the extended class `contracts_Users` and if not, initiates the new user flow
await checkExtUser(ssosign);
} catch (err) {
setMessage("Error: " + err.message);
console.log("err", err.message);
}
};
const checkExtUser = async (ssosign) => {
const params = { email: ssosign?.email };
try {
// `isextenduser` checks if the current user is present in the extended class
const extRes = await Parse.Cloud.run("isextenduser", params);
if (extRes?.isUserExist) {
// `getUserDetails` retrieves the current user's details from the extended class
const extRes = await Parse.Cloud.run("getUserDetails", params);
if (ssosign && ssosign.sessiontoken) {
const LocalUserDetails = {
name: extRes.Name,
email: extRes.email,
phone: extRes?.get("Phone") || "",
company: extRes.get("Company")
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
thirdpartyLoginfn(ssosign.sessiontoken);
}
} else {
setIsModal(true);
setUserDetails((prev) => ({
...prev,
name: ssosign.name,
email: ssosign.email,
phone: ssosign?.phone || ""
}));
}
} catch (err) {
console.log("Err in isextenduser or getuserdetails cloud function", err);
setMessage("Error: " + err.message);
}
};
// `handleSubmitbtn` is used to create a user in the extended class
const handleSubmitbtn = async (e) => {
e.preventDefault();
setIsLoader(true);
let phone;
if (userDetails?.phone) {
phone = validateInput(userDetails?.phone);
} else {
phone = true;
}
if (userDetails.Destination && userDetails.Company && phone) {
const payload = { sessionToken: localStorage.getItem("accesstoken") };
if (payload && payload.sessionToken) {
const params = {
userDetails: {
name: userDetails.name,
email: userDetails.email,
phone: userDetails?.phone || "",
role: "contracts_User",
company: userDetails.Company,
jobTitle: userDetails.Destination
}
};
try {
const userSignUp = await Parse.Cloud.run("usersignup", params);
if (userSignUp && userSignUp.sessionToken) {
const LocalUserDetails = params.userDetails;
localStorage.setItem(
"userDetails",
JSON.stringify(LocalUserDetails)
);
await thirdpartyLoginfn(userSignUp.sessionToken);
setIsLoader(false);
} else {
alert(userSignUp.message);
setIsLoader(false);
}
} catch (err) {
console.log("error in usersignup", err);
localStorage.removeItem("accesstoken");
alert("Something went wrong.");
setIsLoader(false);
}
} else {
localStorage.removeItem("accesstoken");
alert("Internal server error.");
setIsLoader(false);
}
} else {
alert("Please fill required details correctly.");
setIsLoader(false);
}
};
// `thirdpartyLoginfn` is used to save necessary parameters locally for the logged-in user
const thirdpartyLoginfn = async (sessionToken) => {
const baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId");
try {
const validUser = await Parse.User.become(sessionToken);
if (validUser) {
localStorage.setItem("accesstoken", sessionToken);
const _user = JSON.parse(JSON.stringify(validUser));
localStorage.setItem("UserInformation", JSON.stringify(_user));
if (_user.ProfilePic) {
localStorage.setItem("profileImg", _user.ProfilePic);
} else {
localStorage.setItem("profileImg", "");
}
// Check extended class user role and tenentId
try {
let userRoles = [];
const userSettings = appInfo.settings;
//Get Current user roles
const url = `${baseUrl}functions/UserGroups`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessionToken: _user.sessionToken
};
const body = { appname: appInfo.appname };
const UserGroupsRes = await axios.post(url, JSON.stringify(body), {
headers: headers
});
userRoles = (UserGroupsRes.data && UserGroupsRes.data.result) || [];
if (userRoles) {
let _currentRole = "";
const excludeRoles = ["contracts_Guest", `contracts_appeditor`];
const filteredRole = userRoles.filter(
(x) => !excludeRoles.includes(x)
);
if (filteredRole?.length > 1) {
console.log("user has two roles");
// _currentRole = filteredRole.filter((x) => x === "contracts_User");
} else {
_currentRole = filteredRole?.[0] || "";
}
if (_currentRole) {
const roleSetting = userSettings?.find(
(setting) => setting.role === _currentRole
);
const redirectUrl =
location?.state?.from ||
`/${roleSetting.pageType}/${roleSetting.pageId}`;
const _role = _currentRole.replace(`${appInfo.appname}_`, "");
localStorage.setItem("_user_role", _role);
// Get TenentID from Extendend Class
localStorage.setItem(
"extended_class",
roleSetting.extended_class
);
try {
const extUser = await Parse.Cloud.run("getUserDetails", {
email: _user.email
});
if (extUser) {
const _extUser = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("userEmail", _extUser.Email);
localStorage.setItem("username", _extUser.Name);
localStorage.setItem("scriptId", true);
let tenentInfo = [];
const results = [extUser];
if (results) {
let extendedInfo_stringify = JSON.stringify(results);
localStorage.setItem(
"Extand_Class",
extendedInfo_stringify
);
if (_extUser.TenantId) {
const obj = {
tenentId: _extUser.TenantId.objectId,
tenentName: _extUser.TenantId.TenantName || ""
};
localStorage.setItem("TenantId", obj.tenentId);
tenentInfo.push(obj);
dispatch(showTenant(obj.tenentName || ""));
localStorage.setItem("TenantName", obj.tenentName || "");
}
localStorage.setItem("PageLanding", roleSetting.pageId);
localStorage.setItem("defaultmenuid", roleSetting.menuId);
localStorage.setItem("pageType", roleSetting.pageType);
if (isEnableSubscription) {
const res = await fetchSubscription();
const plan = res.plan;
const billingDate = res.billingDate;
if (plan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
localStorage.removeItem("userDetails");
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
} else {
navigate(`/subscription`, {
replace: true
});
}
} else {
navigate(redirectUrl);
}
}
}
} catch (err) {
alert("user not exist.");
setMessage("Error: User not exist.");
console.log("err in get extUser", err);
}
} else {
alert("Role does not exists.");
setMessage("Error: Role does not exists.");
}
} else {
alert("Role does not exists.");
setMessage("Error: Role does not exists.");
}
} catch (err) {
console.log("err in usergroups", err);
}
}
} catch (err) {
console.log("Err in become method", err);
}
};
// `handleCloseModal` is triggered when the user wants to close the new user flow modal
const handleCloseModal = () => {
setIsModal(false);
if (Parse?.User?.current()) {
Parse.User.logOut();
}
};
// `validateInput` is used to verify the phone pattern
function validateInput(input) {
if (input) {
const pattern = /^(?!.*\+.*\+)[\d+-]*$/;
return pattern.test(input);
} else {
return true;
}
}
return (
<div>
<div className="w-full h-screen flex flex-col justify-center items-center text-sm md:text-xl ">
{message === "Verifying SSO..." && (
<img alt="loader" src={loader} className="w-[80px] h-[80px]" />
)}
<div className="text-[gray]">{message}</div>
</div>
<ModalUi isOpen={isModal} title="Additional Info" showClose={false}>
<div className="relative">
{isLoader && (
<div className="absolute w-full h-full bg-black bg-opacity-25 flex justify-center items-center">
<div
style={{
fontSize: "45px",
color: "#3dd3e0"
}}
className="loader-37"
></div>
</div>
)}
<form className="px-4 py-3" onSubmit={handleSubmitbtn}>
<div className="mb-3">
<label
htmlFor="Company"
style={{ display: "flex" }}
className="block text-xs text-gray-700 font-semibold"
>
Phone <span style={{ fontSize: 13, color: "red" }}>*</span>
</label>
<input
type="tel"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
id="Phone"
value={userDetails.phone}
onChange={(e) =>
setUserDetails({
...userDetails,
phone: e.target.value
})
}
disabled={isLoader}
required
/>
<p className="text-[10px] text-[red] ml-2">
{!validateInput(userDetails.phone) && "Invalid phone value"}
</p>
</div>
<div className="mb-3">
<label
htmlFor="Company"
style={{ display: "flex" }}
className="block text-xs text-gray-700 font-semibold"
>
Company <span style={{ fontSize: 13, color: "red" }}>*</span>
</label>
<input
type="text"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
id="Company"
value={userDetails.Company}
onChange={(e) =>
setUserDetails({
...userDetails,
Company: e.target.value
})
}
disabled={isLoader}
required
/>
</div>
<div className="mb-3">
<label
htmlFor="JobTitle"
style={{ display: "flex" }}
className="block text-xs text-gray-700 font-semibold"
>
Job Title
<span style={{ fontSize: 13, color: "red" }}>*</span>
</label>
<input
type="text"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
id="JobTitle"
value={userDetails.Destination}
onChange={(e) =>
setUserDetails({
...userDetails,
Destination: e.target.value
})
}
disabled={isLoader}
required
/>
</div>
<div className="mt-4">
<button
type="submit"
className="px-3 py-1.5 text-white rounded shadow-md text-center focus:outline-none "
style={{
marginRight: 10,
backgroundColor: modalSubmitBtnColor
}}
>
Login
</button>
<button
type="button"
className="py-1.5 text-black border-[1px] border-[#ccc] shadow-md rounded focus:outline-none"
onClick={handleCloseModal}
style={{ width: 75, backgroundColor: modalCancelBtnColor }}
>
Cancel
</button>
</div>
</form>
</div>
</ModalUi>
</div>
);
};
export default SSOVerify;
+28
View File
@@ -0,0 +1,28 @@
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const ssoApiUrl = process.env.SSO_API_URL || 'https://sso.opensignlabs.com/api'; //'https://osl-jacksonv2.vercel.app/api';
export const SSOAuth = {
// Returns a promise that fulfills if this user mail is valid.
validateAuthData: async authData => {
try {
const response = await axios.get(ssoApiUrl + '/oauth/userinfo', {
headers: {
Authorization: `Bearer ${authData.access_token}`,
},
});
if (response.data && response.data.id && response.data.email === authData.id) {
return;
}
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'SSO auth is invalid for this user.');
} catch (error) {
console.log('error in sso adapter', error?.response);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'SSO auth is invalid for this user.');
}
},
// Returns a promise that fulfills if this app id is valid.
validateAppId: () => {
return Promise.resolve();
},
};
+4
View File
@@ -42,6 +42,8 @@ import linkContactToDoc from './parsefunction/linkContactToDoc.js';
import CreatePublicTemplate from './parsefunction/CreatePublicTemplate.js';
import GetPublicUserName from './parsefunction/GetPublicUserName.js';
import GetPublicTemplate from './parsefunction/GetPublicTemplate.js';
import ssoSignin from './parsefunction/ssoSignin.js';
import isextenduser from './parsefunction/isextenduser.js';
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
@@ -94,3 +96,5 @@ Parse.Cloud.define('linkcontacttodoc', linkContactToDoc);
Parse.Cloud.define('createpublictemplate', CreatePublicTemplate);
Parse.Cloud.define('getpublicusername', GetPublicUserName);
Parse.Cloud.define('getpublictemplate', GetPublicTemplate);
Parse.Cloud.define('ssosign', ssoSignin);
Parse.Cloud.define('isextenduser', isextenduser);
@@ -39,7 +39,7 @@ async function sendMailOTPv1(request) {
code +
'</p></div> </div> </div></body></html>',
});
console.log('OTP sent');
console.log('OTP sent', code);
if (request.params?.docId) {
const extUserId = await getDocument(request.params?.docId);
if (extUserId) {
@@ -2,41 +2,41 @@
//-- Export Modules
import dotenv from 'dotenv';
dotenv.config()
import axios from "axios";
dotenv.config();
import axios from 'axios';
export async function getUserGroups(request) {
try {
var appname = request.params.appname;
if (appname == "") {
return Promise.reject("Error:please provide appname");
if (appname == '') {
return Promise.reject('Error:please provide appname');
}
var response = {};
var rolelist = {};
appname = appname + "_";
appname = appname + '_';
//--function to get the userid from session token
function getuserid(request) {
return new Promise(function (resolve, reject) {
const options = {
url: process.env.SERVER_URL + "/users/me",
method: "get",
url: process.env.SERVER_URL + '/users/me',
method: 'get',
headers: {
"X-Parse-Application-Id": process.env.APP_ID,
"X-Parse-Session-Token": request.headers["sessiontoken"],
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
};
axios(options)
.then((x) => {
.then(x => {
const body = x.data;
var error = body == "" ? true : false;
var error = body == '' ? true : false;
if (error) {
reject("result not found!");
reject('result not found!');
} else {
resolve(body);
}
})
.catch((err) => {
.catch(err => {
if (err) {
console.error(err);
return;
@@ -46,7 +46,7 @@ export async function getUserGroups(request) {
}
var userData = await getuserid(request);
var userid = userData.objectId;
console.log("userid " + userid);
// console.log("userid " + userid);
var url =
process.env.SERVER_URL +
'/roles?where={"users":{"__type":"Pointer","className":"_User","objectId":"' +
@@ -60,29 +60,29 @@ export async function getUserGroups(request) {
return new Promise(function (resolve, reject) {
const options = {
url: url,
method: "get",
method: 'get',
headers: {
"X-Parse-Application-Id": process.env.APP_ID,
'X-Parse-Application-Id': process.env.APP_ID,
},
};
axios(options)
.then((x) => {
.then(x => {
const body = x.data;
var roleres = [];
for (var i = 0; i < body["results"].length; i++) {
var rolename = body["results"][i]["name"];
for (var i = 0; i < body['results'].length; i++) {
var rolename = body['results'][i]['name'];
//var roleprefix = rolename.split("_")[0];
roleres.push(rolename);
}
var error = roleres == "" ? true : false;
var error = roleres == '' ? true : false;
if (error) {
reject("result not found!");
reject('result not found!');
} else {
resolve(roleres);
}
})
.catch((err) => {
.catch(err => {
if (err) {
console.error(err);
return;
@@ -92,7 +92,7 @@ export async function getUserGroups(request) {
}
rolelist = await getRoleList(request);
console.log(rolelist);
// console.log(rolelist);
//--check user roles according to appId
var rolesInapp = [];
for (let i = 0; i < rolelist.length; i++) {
@@ -102,11 +102,11 @@ export async function getUserGroups(request) {
rolesInapp.push(rolelist[i]);
}
}
console.log(rolesInapp);
// console.log(rolesInapp);
return rolesInapp;
} catch (err) {
console.log("err in usergroup");
console.log('err in usergroup');
console.log(err);
return Promise.reject("Error:Result not found");
return Promise.reject('Error:Result not found');
}
}
@@ -0,0 +1,30 @@
/**
* Checks if a user exists in the extended class 'contracts_Users' based on the provided email.
* @param email - The request contains parameters, such as the user's email.
* @returns {Object} - Returns an object indicating whether the user exists.
*/
export default async function isextenduser(request) {
try {
// Query the 'contracts_Users' class in the database based on the provided email
const userQuery = new Parse.Query('contracts_Users');
userQuery.equalTo('Email', request.params.email);
// Execute the query
const res = await userQuery.first({ useMasterKey: true });
// Check if a user was found
if (res) {
// If user exists, return object with 'isUserExist' set to true
return { isUserExist: true };
} else {
// If user does not exist, return object with 'isUserExist' set to false
return { isUserExist: false };
}
} catch (err) {
// Handle errors
console.log('Error in userexist', err);
const code = err?.code || 400;
const message = err?.message || 'Something went wrong.';
throw new Parse.Error(code, message);
}
}
@@ -0,0 +1,132 @@
import axios from 'axios';
const serverUrl = process.env.SERVER_URL;
const APPID = process.env.APP_ID;
const masterKEY = process.env.MASTER_KEY;
const clientUrl = process.env.PUBLIC_URL;
const ssoApiUrl = process.env.SSO_API_URL || 'https://sso.opensignlabs.com/api'; //'https://osl-jacksonv2.vercel.app/api';
/**
* ssoSign is function which is used to sign up/sign in with SSO
* @param code It is code return by jackson using authorize endpoint
* @param email It is user's email with user sign in/sign up
* @returns if success {email, name, phone message, sessiontoken} else on reject error {code, message}
*/
export default async function ssoSignin(request) {
const code = request.params.code;
const userEmail = request.params.email;
try {
const headers = { 'content-type': 'application/x-www-form-urlencoded' };
const axiosRes = await axios.post(
ssoApiUrl + '/oauth/token',
{
grant_type: 'authorization_code',
client_id: 'dummy',
tenant: 'Okta-dev-nxglabs-in',
product: 'OpenSign',
client_secret: 'dummy',
redirect_uri: clientUrl + '/sso',
code: code,
},
{ headers: headers }
);
const ssoAccessToken = axiosRes.data && axiosRes.data.access_token;
const authData = { sso: { id: userEmail, access_token: ssoAccessToken } };
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('username', userEmail);
const res = await userQuery.first({ useMasterKey: true });
if (res) {
try {
const SignIn = await axios.put(
serverUrl + '/users/' + res.id,
{ authData: authData },
{
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Master-key': masterKEY,
},
}
);
if (SignIn.data) {
const response = await axios.get(ssoApiUrl + '/oauth/userinfo', {
headers: {
Authorization: `Bearer ${ssoAccessToken}`,
},
});
const sessiontoken = SignIn.data.sessionToken;
// console.log('sso sessiontoken', sessiontoken);
const payload = {
email: userEmail,
name: response.data?.firstName + ' ' + response.data?.lastName,
phone: response?.data?.phone || '',
message: 'User Sign In',
sessiontoken: sessiontoken,
};
return payload;
}
} catch (err) {
const errCode = err?.response?.data?.code || err?.response?.status || err?.code || 400;
const message =
err?.response?.data?.error ||
err?.response?.data ||
err?.message ||
'Internal server error.';
console.log('err in user sso sign in', errCode, message);
throw new Parse.Error(errCode, message);
}
} else {
// console.log("in sign up condition");
const response = await axios.get('https://osl-jacksonv2.vercel.app/api/oauth/userinfo', {
headers: {
Authorization: `Bearer ${ssoAccessToken}`,
},
});
if (response.data && response.data.id) {
try {
const SignUp = await axios.post(
serverUrl + '/users',
{
authData: authData,
username: response.data.email,
email: response.data.email,
phone: response.data?.phone,
name: response.data?.firstName + ' ' + response.data?.lastName,
},
{
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Revocable-Session': '1',
},
}
);
if (SignUp.data) {
const sessiontoken = SignUp.data.sessionToken;
const payload = {
email: userEmail,
name: SignUp?.data?.name,
phone: SignUp?.data?.phone || '',
message: 'User Sign Up',
sessiontoken: sessiontoken,
};
return payload;
}
} catch (err) {
const errCode = err?.response?.data?.code || err?.response?.status || err?.code || 400;
const message =
err?.response?.data?.error ||
err?.response?.data ||
err?.message ||
'Internal server error.';
console.log('err in user sso sign up', errCode, message);
throw new Parse.Error(errCode, message);
}
}
}
} catch (err) {
const errCode = err?.response?.status || err?.code || 400;
const message = err?.response?.data || err?.message || 'Internal server error.';
console.log('err in ssoSign', errCode, message);
throw new Parse.Error(errCode, message);
}
}
@@ -132,7 +132,9 @@ export default async function usersignup(request) {
newObj.set('JobTitle', userDetails.jobTitle);
}
const extRes = await newObj.save(null, { useMasterKey: true });
await saveSubscription(extRes.id, user.id, tenantRes.id, subscription);
if (subscription) {
await saveSubscription(extRes.id, user.id, tenantRes.id, subscription);
}
return { message: 'User sign up', sessionToken: user.sessionToken };
}
} catch (err) {
+2 -9
View File
@@ -18,7 +18,7 @@ import { createTransport } from 'nodemailer';
import { app as v1 } from './cloud/customRoute/v1/apiV1.js';
import { PostHog } from 'posthog-node';
import { useLocal } from './Utils.js';
import { SSOAuth } from './auth/authadapter.js';
let fsAdapter;
if (useLocal !== 'true') {
try {
@@ -147,14 +147,7 @@ export const config = {
google: {
enabled: true,
},
ldap: {
enabled: true,
url: 'ldap://ldap.forumsys.com:389',
suffix: 'dc=example,dc=com',
// dn: 'ou=mathematicians, dc=example, dc=com',
groupCn: 'mathematicians',
groupFilter: '(&(uniqueMember=uid=,dc=example,dc=com)(objectClass=groupOfUniqueNames))',
},
sso: SSOAuth,
},
};
// Client-keys like the javascript key or the .NET key are not necessary with parse-server