mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 08:27:22 +00:00
fix(dashboard): creation of new org when user is apart of no orgs
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
|||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -363,21 +364,35 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
organization: OrganizationsSchema
|
organization: OrganizationsSchema,
|
||||||
|
accessToken: z.string()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
handler: async (req) => {
|
handler: async (req, res) => {
|
||||||
if (req.auth.actor !== ActorType.USER) return;
|
if (req.auth.actor !== ActorType.USER) return;
|
||||||
|
|
||||||
const organization = await server.services.org.deleteOrganizationById(
|
const cfg = getConfig();
|
||||||
req.permission.id,
|
|
||||||
req.params.organizationId,
|
const { organization, tokens } = await server.services.org.deleteOrganizationById({
|
||||||
req.permission.authMethod,
|
userId: req.permission.id,
|
||||||
req.permission.orgId
|
orgId: req.params.organizationId,
|
||||||
);
|
actorAuthMethod: req.permission.authMethod,
|
||||||
return { organization };
|
actorOrgId: req.permission.orgId,
|
||||||
|
authorizationHeader: req.headers.authorization,
|
||||||
|
userAgentHeader: req.headers["user-agent"],
|
||||||
|
ipAddress: req.realIp
|
||||||
|
});
|
||||||
|
|
||||||
|
void res.setCookie("jid", tokens.refreshToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: cfg.HTTPS_ENABLED
|
||||||
|
});
|
||||||
|
|
||||||
|
return { organization, accessToken: tokens.accessToken };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,10 +54,11 @@ export const tokenDALFactory = (db: TDbClient) => {
|
|||||||
const insertTokenSession = async (
|
const insertTokenSession = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
ip: string,
|
ip: string,
|
||||||
userAgent: string
|
userAgent: string,
|
||||||
|
tx?: Knex
|
||||||
): Promise<TAuthTokenSessions | undefined> => {
|
): Promise<TAuthTokenSessions | undefined> => {
|
||||||
try {
|
try {
|
||||||
const [session] = await db(TableName.AuthTokenSession)
|
const [session] = await (tx || db)(TableName.AuthTokenSession)
|
||||||
.insert({
|
.insert({
|
||||||
userId,
|
userId,
|
||||||
ip,
|
ip,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
|
import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
@@ -123,14 +124,13 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu
|
|||||||
return deletedToken?.[0];
|
return deletedToken?.[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserTokenSession = async ({
|
const getUserTokenSession = async (
|
||||||
userId,
|
{ userId, ip, userAgent }: TIssueAuthTokenDTO,
|
||||||
ip,
|
tx?: Knex
|
||||||
userAgent
|
): Promise<TAuthTokenSessions | undefined> => {
|
||||||
}: TIssueAuthTokenDTO): Promise<TAuthTokenSessions | undefined> => {
|
|
||||||
let session = await tokenDAL.findOneTokenSession({ userId, ip, userAgent });
|
let session = await tokenDAL.findOneTokenSession({ userId, ip, userAgent });
|
||||||
if (!session) {
|
if (!session) {
|
||||||
session = await tokenDAL.insertTokenSession(userId, ip, userAgent);
|
session = await tokenDAL.insertTokenSession(userId, ip, userAgent, tx);
|
||||||
}
|
}
|
||||||
return session;
|
return session;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TUsers, UserDeviceSchema } from "@app/db/schemas";
|
import { TUsers, UserDeviceSchema } from "@app/db/schemas";
|
||||||
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
|
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
|
||||||
@@ -50,13 +51,13 @@ export const authLoginServiceFactory = ({
|
|||||||
* Not exported. This is to update user device list
|
* Not exported. This is to update user device list
|
||||||
* If new device is found. Will be saved and a mail will be send
|
* If new device is found. Will be saved and a mail will be send
|
||||||
*/
|
*/
|
||||||
const updateUserDeviceSession = async (user: TUsers, ip: string, userAgent: string) => {
|
const updateUserDeviceSession = async (user: TUsers, ip: string, userAgent: string, tx?: Knex) => {
|
||||||
const devices = await UserDeviceSchema.parseAsync(user.devices || []);
|
const devices = await UserDeviceSchema.parseAsync(user.devices || []);
|
||||||
const isDeviceSeen = devices.some((device) => device.ip === ip && device.userAgent === userAgent);
|
const isDeviceSeen = devices.some((device) => device.ip === ip && device.userAgent === userAgent);
|
||||||
|
|
||||||
if (!isDeviceSeen) {
|
if (!isDeviceSeen) {
|
||||||
const newDeviceList = devices.concat([{ ip, userAgent }]);
|
const newDeviceList = devices.concat([{ ip, userAgent }]);
|
||||||
await userDAL.updateById(user.id, { devices: JSON.stringify(newDeviceList) });
|
await userDAL.updateById(user.id, { devices: JSON.stringify(newDeviceList) }, tx);
|
||||||
if (user.email) {
|
if (user.email) {
|
||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.NewDeviceJoin,
|
template: SmtpTemplates.NewDeviceJoin,
|
||||||
@@ -97,30 +98,36 @@ export const authLoginServiceFactory = ({
|
|||||||
* Check user device and send mail if new device
|
* Check user device and send mail if new device
|
||||||
* generate the auth and refresh token. fn shared by mfa verification and login verification with mfa disabled
|
* generate the auth and refresh token. fn shared by mfa verification and login verification with mfa disabled
|
||||||
*/
|
*/
|
||||||
const generateUserTokens = async ({
|
const generateUserTokens = async (
|
||||||
user,
|
{
|
||||||
ip,
|
user,
|
||||||
userAgent,
|
|
||||||
organizationId,
|
|
||||||
authMethod,
|
|
||||||
isMfaVerified,
|
|
||||||
mfaMethod
|
|
||||||
}: {
|
|
||||||
user: TUsers;
|
|
||||||
ip: string;
|
|
||||||
userAgent: string;
|
|
||||||
organizationId?: string;
|
|
||||||
authMethod: AuthMethod;
|
|
||||||
isMfaVerified?: boolean;
|
|
||||||
mfaMethod?: MfaMethod;
|
|
||||||
}) => {
|
|
||||||
const cfg = getConfig();
|
|
||||||
await updateUserDeviceSession(user, ip, userAgent);
|
|
||||||
const tokenSession = await tokenService.getUserTokenSession({
|
|
||||||
userAgent,
|
|
||||||
ip,
|
ip,
|
||||||
userId: user.id
|
userAgent,
|
||||||
});
|
organizationId,
|
||||||
|
authMethod,
|
||||||
|
isMfaVerified,
|
||||||
|
mfaMethod
|
||||||
|
}: {
|
||||||
|
user: TUsers;
|
||||||
|
ip: string;
|
||||||
|
userAgent: string;
|
||||||
|
organizationId?: string;
|
||||||
|
authMethod: AuthMethod;
|
||||||
|
isMfaVerified?: boolean;
|
||||||
|
mfaMethod?: MfaMethod;
|
||||||
|
},
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
const cfg = getConfig();
|
||||||
|
await updateUserDeviceSession(user, ip, userAgent, tx);
|
||||||
|
const tokenSession = await tokenService.getUserTokenSession(
|
||||||
|
{
|
||||||
|
userAgent,
|
||||||
|
ip,
|
||||||
|
userId: user.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
if (!tokenSession) throw new Error("Failed to create token");
|
if (!tokenSession) throw new Error("Failed to create token");
|
||||||
|
|
||||||
const accessToken = jwt.sign(
|
const accessToken = jwt.sign(
|
||||||
|
|||||||
@@ -77,11 +77,16 @@ export const selectOrganization = async (data: {
|
|||||||
export const useSelectOrganization = () => {
|
export const useSelectOrganization = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (details: { organizationId: string; userAgent?: UserAgentType }) => {
|
mutationFn: async (details: {
|
||||||
|
organizationId: string;
|
||||||
|
userAgent?: UserAgentType;
|
||||||
|
forceSetCredentials?: boolean;
|
||||||
|
}) => {
|
||||||
const data = await selectOrganization(details);
|
const data = await selectOrganization(details);
|
||||||
|
|
||||||
// If a custom user agent is set, then this session is meant for another consuming application, not the web application.
|
// If a custom user agent is set, then this session is meant for another consuming application, not the web application.
|
||||||
if (!details.userAgent && !data.isMfaEnabled) {
|
if ((!details.userAgent && !data.isMfaEnabled) || details.forceSetCredentials) {
|
||||||
|
localStorage.setItem("orgData.id", details.organizationId);
|
||||||
SecurityClient.setToken(data.token);
|
SecurityClient.setToken(data.token);
|
||||||
SecurityClient.setProviderAuthToken("");
|
SecurityClient.setProviderAuthToken("");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient, UseQueryOptions } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient, UseQueryOptions } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ export const useCreateOrg = (options: { invalidate: boolean } = { invalidate: tr
|
|||||||
mutationFn: async ({ name }: { name: string }) => {
|
mutationFn: async ({ name }: { name: string }) => {
|
||||||
const {
|
const {
|
||||||
data: { organization }
|
data: { organization }
|
||||||
} = await apiRequest.post("/api/v2/organizations", {
|
} = await apiRequest.post<{ organization: { id: string } }>("/api/v2/organizations", {
|
||||||
name
|
name
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -437,10 +438,13 @@ export const useDeleteOrgById = () => {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ organizationId }: { organizationId: string }) => {
|
mutationFn: async ({ organizationId }: { organizationId: string }) => {
|
||||||
const {
|
const {
|
||||||
data: { organization }
|
data: { organization, accessToken }
|
||||||
} = await apiRequest.delete<{ organization: Organization }>(
|
} = await apiRequest.delete<{ organization: Organization; accessToken: string }>(
|
||||||
`/api/v2/organizations/${organizationId}`
|
`/api/v2/organizations/${organizationId}`
|
||||||
);
|
);
|
||||||
|
SecurityClient.setToken(accessToken);
|
||||||
|
localStorage.removeItem("orgData.id");
|
||||||
|
|
||||||
return organization;
|
return organization;
|
||||||
},
|
},
|
||||||
onSuccess(_, dto) {
|
onSuccess(_, dto) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ProjectType } from "@app/hooks/api/workspace/types";
|
|||||||
import { queryClient } from "@app/reactQuery";
|
import { queryClient } from "@app/reactQuery";
|
||||||
|
|
||||||
export const navigateUserToOrg = async (router: NextRouter, organizationId?: string) => {
|
export const navigateUserToOrg = async (router: NextRouter, organizationId?: string) => {
|
||||||
const userOrgs = await fetchOrganizations();
|
const userOrgs = await fetchOrganizations().catch(() => []);
|
||||||
|
|
||||||
const nonAuthEnforcedOrgs = userOrgs.filter((org) => !org.authEnforced);
|
const nonAuthEnforcedOrgs = userOrgs.filter((org) => !org.authEnforced);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import z from "zod";
|
|||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
|
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
|
||||||
import { useCreateOrg, useSelectOrganization } from "@app/hooks/api";
|
import { useCreateOrg, useGetOrganizations, useSelectOrganization } from "@app/hooks/api";
|
||||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
const schema = z
|
const schema = z
|
||||||
@@ -23,9 +23,10 @@ interface CreateOrgModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) => {
|
export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) => {
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { refetch: refetchOrganizations } = useGetOrganizations();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@@ -50,19 +51,21 @@ export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
await selectOrg({
|
await selectOrg({
|
||||||
organizationId: organization.id
|
organizationId: organization.id,
|
||||||
|
forceSetCredentials: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await refetchOrganizations();
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Successfully created organization",
|
text: "Successfully created organization",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (router.isReady) router.push(`/org/${organization.id}/${ProjectType.SecretManager}/overview`);
|
if (router.isReady)
|
||||||
|
router.push(`/org/${organization.id}/${ProjectType.SecretManager}/overview`);
|
||||||
else window.location.href = `/org/${organization.id}/${ProjectType.SecretManager}/overview`;
|
else window.location.href = `/org/${organization.id}/${ProjectType.SecretManager}/overview`;
|
||||||
|
|
||||||
localStorage.setItem("orgData.id", organization.id);
|
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user