Update various SSO / SAML auth methods to support username

This commit is contained in:
Tuan Dang
2024-02-24 22:17:51 -08:00
parent bfee74ff4e
commit 8c31566e17
14 changed files with 37 additions and 33 deletions
@@ -329,7 +329,7 @@ export const ldapConfigServiceFactory = ({
if (user) { if (user) {
await userDAL.transaction(async (tx) => { await userDAL.transaction(async (tx) => {
const [orgMembership] = await orgDAL.findMembership({ userId: user.id, orgId }, { tx }); const [orgMembership] = await orgDAL.findMembership({ userId: user.id }, { tx });
if (!orgMembership) { if (!orgMembership) {
await orgDAL.createMembership( await orgDAL.createMembership(
{ {
@@ -366,7 +366,7 @@ export const ldapConfigServiceFactory = ({
await orgDAL.createMembership({ await orgDAL.createMembership({
orgId, orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited // should this be invited? status: OrgMembershipStatus.Invited
}); });
return newUser; return newUser;
}); });
@@ -378,6 +378,7 @@ export const ldapConfigServiceFactory = ({
{ {
authTokenType: AuthTokenType.PROVIDER_TOKEN, authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user.id, userId: user.id,
username: user.username,
firstName, firstName,
lastName, lastName,
organizationName: organization.name, organizationName: organization.name,
@@ -357,7 +357,7 @@ export const samlConfigServiceFactory = ({
{ {
authTokenType: AuthTokenType.PROVIDER_TOKEN, authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user.id, userId: user.id,
email: user.email, username: user.username,
firstName, firstName,
lastName, lastName,
organizationName: organization.name, organizationName: organization.name,
+2 -2
View File
@@ -12,7 +12,7 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
}, },
schema: { schema: {
body: z.object({ body: z.object({
email: z.string().email().trim(), email: z.string().trim(),
providerAuthToken: z.string().trim().optional(), providerAuthToken: z.string().trim().optional(),
clientPublicKey: z.string().trim() clientPublicKey: z.string().trim()
}), }),
@@ -42,7 +42,7 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
}, },
schema: { schema: {
body: z.object({ body: z.object({
email: z.string().email().trim(), email: z.string().trim(),
providerAuthToken: z.string().trim().optional(), providerAuthToken: z.string().trim().optional(),
clientProof: z.string().trim() clientProof: z.string().trim()
}), }),
@@ -88,7 +88,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
}, },
schema: { schema: {
body: z.object({ body: z.object({
email: z.string().email().trim(), email: z.string().trim(),
firstName: z.string().trim(), firstName: z.string().trim(),
lastName: z.string().trim().optional(), lastName: z.string().trim().optional(),
protectedKey: z.string().trim(), protectedKey: z.string().trim(),
+3 -2
View File
@@ -5,13 +5,14 @@ import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { AuthModeProviderJwtTokenPayload, AuthModeProviderSignUpTokenPayload, AuthTokenType } from "./auth-type"; import { AuthModeProviderJwtTokenPayload, AuthModeProviderSignUpTokenPayload, AuthTokenType } from "./auth-type";
export const validateProviderAuthToken = (providerToken: string, email?: string) => { export const validateProviderAuthToken = (providerToken: string, username?: string) => {
if (!providerToken) throw new UnauthorizedError(); if (!providerToken) throw new UnauthorizedError();
const appCfg = getConfig(); const appCfg = getConfig();
const decodedToken = jwt.verify(providerToken, appCfg.AUTH_SECRET) as AuthModeProviderJwtTokenPayload; const decodedToken = jwt.verify(providerToken, appCfg.AUTH_SECRET) as AuthModeProviderJwtTokenPayload;
if (decodedToken.authTokenType !== AuthTokenType.PROVIDER_TOKEN) throw new UnauthorizedError(); if (decodedToken.authTokenType !== AuthTokenType.PROVIDER_TOKEN) throw new UnauthorizedError();
if (decodedToken.email !== email) throw new Error("Invalid auth credentials");
if (decodedToken.username !== username) throw new Error("Invalid auth credentials");
if (decodedToken.organizationId) { if (decodedToken.organizationId) {
return { orgId: decodedToken.organizationId }; return { orgId: decodedToken.organizationId };
@@ -133,7 +133,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
providerAuthToken, providerAuthToken,
clientPublicKey clientPublicKey
}: TLoginGenServerPublicKeyDTO) => { }: TLoginGenServerPublicKeyDTO) => {
const userEnc = await userDAL.findUserEncKeyByEmail(email); const userEnc = await userDAL.findUserEncKeyByUsername(email);
if (!userEnc || (userEnc && !userEnc.isAccepted)) { if (!userEnc || (userEnc && !userEnc.isAccepted)) {
throw new Error("Failed to find user"); throw new Error("Failed to find user");
} }
@@ -160,7 +160,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
ip, ip,
userAgent userAgent
}: TLoginClientProofDTO) => { }: TLoginClientProofDTO) => {
const userEnc = await userDAL.findUserEncKeyByEmail(email); const userEnc = await userDAL.findUserEncKeyByUsername(email);
if (!userEnc) throw new Error("Failed to find user"); if (!userEnc) throw new Error("Failed to find user");
const cfg = getConfig(); const cfg = getConfig();
@@ -301,7 +301,8 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
{ {
authTokenType: AuthTokenType.PROVIDER_TOKEN, authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user.id, userId: user.id,
email: user.email, // email: user.email,
username: user.username,
firstName: user.firstName, firstName: user.firstName,
lastName: user.lastName, lastName: user.lastName,
authMethod, authMethod,
@@ -115,14 +115,14 @@ export const authSignupServiceFactory = ({
userAgent, userAgent,
authorization authorization
}: TCompleteAccountSignupDTO) => { }: TCompleteAccountSignupDTO) => {
const user = await userDAL.findUserByEmail(email); const user = await userDAL.findOne({ username: email });
if (!user || (user && user.isAccepted)) { if (!user || (user && user.isAccepted)) {
throw new Error("Failed to complete account for complete user"); throw new Error("Failed to complete account for complete user");
} }
let organizationId; let organizationId;
if (providerAuthToken) { if (providerAuthToken) {
const { orgId } = validateProviderAuthToken(providerAuthToken, user.email as string); const { orgId } = validateProviderAuthToken(providerAuthToken, user.username);
organizationId = orgId; organizationId = orgId;
} else { } else {
validateSignUpAuthorization(authorization, user.id); validateSignUpAuthorization(authorization, user.id);
@@ -152,7 +152,7 @@ export const authSignupServiceFactory = ({
if (!organizationId) { if (!organizationId) {
await orgService.createOrganization({ await orgService.createOrganization({
userId: user.id, userId: user.id,
userEmail: user.email, userEmail: user.email ?? user.username ?? "", // TODO: look into
orgName: organizationName orgName: organizationName
}); });
} }
+1 -1
View File
@@ -62,7 +62,7 @@ export type AuthModeRefreshJwtTokenPayload = {
export type AuthModeProviderJwtTokenPayload = { export type AuthModeProviderJwtTokenPayload = {
authTokenType: AuthTokenType.PROVIDER_TOKEN; authTokenType: AuthTokenType.PROVIDER_TOKEN;
email: string; username: string;
organizationId?: string; organizationId?: string;
}; };
+3 -3
View File
@@ -20,10 +20,10 @@ export const userDALFactory = (db: TDbClient) => {
// USER ENCRYPTION FUNCTIONS // USER ENCRYPTION FUNCTIONS
// ------------------------- // -------------------------
const findUserEncKeyByEmail = async (email: string) => { const findUserEncKeyByUsername = async (username: string) => {
try { try {
return await db(TableName.Users) return await db(TableName.Users)
.where({ email, isGhost: false }) .where({ username, isGhost: false })
.join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`) .join(TableName.UserEncryptionKey, `${TableName.Users}.id`, `${TableName.UserEncryptionKey}.userId`)
.first(); .first();
} catch (error) { } catch (error) {
@@ -119,7 +119,7 @@ export const userDALFactory = (db: TDbClient) => {
return { return {
...userOrm, ...userOrm,
findUserByEmail, findUserByEmail,
findUserEncKeyByEmail, findUserEncKeyByUsername,
findUserEncKeyByUserId, findUserEncKeyByUserId,
updateUserEncryptionByUserId, updateUserEncryptionByUserId,
findUserByProjectMembershipId, findUserByProjectMembershipId,
+3 -3
View File
@@ -15,7 +15,7 @@ export const LoginSSO = ({ providerAuthToken }: Props) => {
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const { const {
email, username,
isUserCompleted isUserCompleted
} = jwt_decode(providerAuthToken) as any; } = jwt_decode(providerAuthToken) as any;
@@ -35,7 +35,7 @@ export const LoginSSO = ({ providerAuthToken }: Props) => {
return ( return (
<PasswordStep <PasswordStep
providerAuthToken={providerAuthToken} providerAuthToken={providerAuthToken}
email={email} email={username}
password={password} password={password}
setPassword={setPassword} setPassword={setPassword}
setStep={setStep} setStep={setStep}
@@ -45,7 +45,7 @@ export const LoginSSO = ({ providerAuthToken }: Props) => {
return ( return (
<MFAStep <MFAStep
providerAuthToken={providerAuthToken} providerAuthToken={providerAuthToken}
email={email} email={username}
password={password} password={password}
/> />
); );
@@ -113,8 +113,8 @@ export const OrgMembersSection = () => {
/> />
<DeleteActionModal <DeleteActionModal
isOpen={popUp.removeMember.isOpen} isOpen={popUp.removeMember.isOpen}
title={`Are you sure want to remove member with email ${ title={`Are you sure want to remove member with username ${
(popUp?.removeMember?.data as { email: string })?.email || "" (popUp?.removeMember?.data as { username: string })?.username || ""
}?`} }?`}
onChange={(isOpen) => handlePopUpToggle("removeMember", isOpen)} onChange={(isOpen) => handlePopUpToggle("removeMember", isOpen)}
deleteKey="confirm" deleteKey="confirm"
@@ -41,7 +41,7 @@ type Props = {
popUpName: keyof UsePopUpState<["removeMember", "upgradePlan"]>, popUpName: keyof UsePopUpState<["removeMember", "upgradePlan"]>,
data?: { data?: {
orgMembershipId?: string; orgMembershipId?: string;
email?: string; username?: string;
description?: string; description?: string;
} }
) => void; ) => void;
@@ -174,6 +174,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
filterdUser?.map( filterdUser?.map(
({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status }) => { ({ user: u, inviteEmail, role, roleId, id: orgMembershipId, status }) => {
const name = u && u.firstName ? `${u.firstName} ${u.lastName}` : "-"; const name = u && u.firstName ? `${u.firstName} ${u.lastName}` : "-";
const email = u?.email || inviteEmail;
const username = u?.username ?? inviteEmail ?? "-"; const username = u?.username ?? inviteEmail ?? "-";
return ( return (
<Tr key={`org-membership-${orgMembershipId}`} className="w-full"> <Tr key={`org-membership-${orgMembershipId}`} className="w-full">
@@ -207,7 +208,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
))} ))}
</Select> </Select>
)} )}
{(status === "invited" || status === "verified") && {(status === "invited" || status === "verified") && email &&
serverDetails?.emailConfigured && ( serverDetails?.emailConfigured && (
<Button <Button
isDisabled={!isAllowed} isDisabled={!isAllowed}
@@ -240,7 +241,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
return; return;
} }
handlePopUpOpen("removeMember", { orgMembershipId, email }); handlePopUpOpen("removeMember", { orgMembershipId, username });
}} }}
size="lg" size="lg"
colorSchema="danger" colorSchema="danger"
+3 -3
View File
@@ -16,7 +16,7 @@ export const SignupSSO = ({
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const { const {
email, username,
organizationName, organizationName,
firstName, firstName,
lastName, lastName,
@@ -27,7 +27,7 @@ export const SignupSSO = ({
case 0: case 0:
return ( return (
<UserInfoSSOStep <UserInfoSSOStep
email={email} username={username}
name={`${firstName} ${lastName}`} name={`${firstName} ${lastName}`}
providerOrganizationName={organizationName} providerOrganizationName={organizationName}
password={password} password={password}
@@ -39,7 +39,7 @@ export const SignupSSO = ({
case 1: case 1:
return ( return (
<BackupPDFStep <BackupPDFStep
email={email} email={username}
password={password} password={password}
name={`${firstName} ${lastName}`} name={`${firstName} ${lastName}`}
/> />
@@ -24,7 +24,7 @@ const client = new jsrp.client();
type Props = { type Props = {
setStep: (step: number) => void; setStep: (step: number) => void;
email: string; username: string;
password: string; password: string;
setPassword: (value: string) => void; setPassword: (value: string) => void;
name: string; name: string;
@@ -57,7 +57,7 @@ type Errors = {
* @param {string} obj.setLastName - function managing the state of user's last name * @param {string} obj.setLastName - function managing the state of user's last name
*/ */
export const UserInfoSSOStep = ({ export const UserInfoSSOStep = ({
email, username,
name, name,
providerOrganizationName, providerOrganizationName,
password, password,
@@ -113,7 +113,7 @@ export const UserInfoSSOStep = ({
client.init( client.init(
{ {
username: email, username,
password password
}, },
async () => { async () => {
@@ -156,7 +156,7 @@ export const UserInfoSSOStep = ({
}); });
const response = await completeAccountSignup({ const response = await completeAccountSignup({
email, email: username,
firstName: name.split(" ")[0], firstName: name.split(" ")[0],
lastName: name.split(" ").slice(1).join(" "), lastName: name.split(" ").slice(1).join(" "),
protectedKey, protectedKey,