feat: initialize base

This commit is contained in:
Sheen Capadngan
2025-03-17 22:35:37 +08:00
parent 8de7261c9a
commit 6ef358b172
14 changed files with 302 additions and 33 deletions
@@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "../schemas/models";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.SuperAdmin, "adminIdentityIds"))) {
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
t.specificType("adminIdentityIds", "text[]");
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.SuperAdmin, "adminIdentityIds")) {
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
t.dropColumn("adminIdentityIds");
});
}
}
+2 -1
View File
@@ -25,7 +25,8 @@ export const SuperAdminSchema = z.object({
encryptedSlackClientId: zodBuffer.nullable().optional(), encryptedSlackClientId: zodBuffer.nullable().optional(),
encryptedSlackClientSecret: zodBuffer.nullable().optional(), encryptedSlackClientSecret: zodBuffer.nullable().optional(),
authConsentContent: z.string().nullable().optional(), authConsentContent: z.string().nullable().optional(),
pageFrameContent: z.string().nullable().optional() pageFrameContent: z.string().nullable().optional(),
adminIdentityIds: z.string().array().nullable().optional()
}); });
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>; export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
@@ -8,6 +8,7 @@ import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type"; import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
export type TAuthMode = export type TAuthMode =
| { | {
@@ -43,6 +44,7 @@ export type TAuthMode =
identityName: string; identityName: string;
orgId: string; orgId: string;
authMethod: null; authMethod: null;
isInstanceAdmin?: boolean;
} }
| { | {
authMode: AuthMode.SCIM_TOKEN; authMode: AuthMode.SCIM_TOKEN;
@@ -129,13 +131,15 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
} }
case AuthMode.IDENTITY_ACCESS_TOKEN: { case AuthMode.IDENTITY_ACCESS_TOKEN: {
const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp); const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp);
const serverCfg = await getServerCfg();
req.auth = { req.auth = {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN, authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
actor, actor,
orgId: identity.orgId, orgId: identity.orgId,
identityId: identity.identityId, identityId: identity.identityId,
identityName: identity.name, identityName: identity.name,
authMethod: null authMethod: null,
isInstanceAdmin: serverCfg?.adminIdentityIds?.includes(identity.identityId)
}; };
break; break;
} }
@@ -1,16 +1,18 @@
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify"; import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
import { ForbiddenRequestError } from "@app/lib/errors"; import { ForbiddenRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type"; import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
export const verifySuperAdmin = <T extends FastifyRequest>( export const verifySuperAdmin = <T extends FastifyRequest>(
req: T, req: T,
_res: FastifyReply, _res: FastifyReply,
done: HookHandlerDoneFunction done: HookHandlerDoneFunction
) => { ) => {
if (req.auth.actor !== ActorType.USER || !req.auth.user.superAdmin) if (isSuperAdmin(req.auth)) {
throw new ForbiddenRequestError({ return done();
message: "Requires elevated super admin privileges" }
});
done(); throw new ForbiddenRequestError({
message: "Requires elevated super admin privileges"
});
}; };
+3
View File
@@ -637,6 +637,9 @@ export const registerRoutes = async (
userDAL, userDAL,
identityDAL, identityDAL,
userAliasDAL, userAliasDAL,
identityTokenAuthDAL,
identityAccessTokenDAL,
identityOrgMembershipDAL,
authService: loginService, authService: loginService,
serverCfgDAL: superAdminDAL, serverCfgDAL: superAdminDAL,
kmsRootConfigDAL, kmsRootConfigDAL,
+63 -9
View File
@@ -98,7 +98,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
} }
}, },
onRequest: (req, res, done) => { onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.API_KEY])(req, res, () => { verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done); verifySuperAdmin(req, res, done);
}); });
}, },
@@ -139,7 +139,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
} }
}, },
onRequest: (req, res, done) => { onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => { verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done); verifySuperAdmin(req, res, done);
}); });
}, },
@@ -171,12 +171,16 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
identities: IdentitiesSchema.pick({ identities: IdentitiesSchema.pick({
name: true, name: true,
id: true id: true
}).array() })
.extend({
isInstanceAdmin: z.boolean()
})
.array()
}) })
} }
}, },
onRequest: (req, res, done) => { onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => { verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done); verifySuperAdmin(req, res, done);
}); });
}, },
@@ -206,7 +210,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
} }
}, },
onRequest: (req, res, done) => { onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => { verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done); verifySuperAdmin(req, res, done);
}); });
}, },
@@ -240,7 +244,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
} }
}, },
onRequest: (req, res, done) => { onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => { verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done); verifySuperAdmin(req, res, done);
}); });
}, },
@@ -265,7 +269,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
}) })
}, },
onRequest: (req, res, done) => { onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => { verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done); verifySuperAdmin(req, res, done);
}); });
}, },
@@ -293,7 +297,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
} }
}, },
onRequest: (req, res, done) => { onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => { verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done); verifySuperAdmin(req, res, done);
}); });
}, },
@@ -316,7 +320,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
}) })
}, },
onRequest: (req, res, done) => { onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => { verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done); verifySuperAdmin(req, res, done);
}); });
}, },
@@ -394,4 +398,54 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
}; };
} }
}); });
server.route({
method: "POST",
url: "/initialize",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
email: z.string().email().trim(),
password: z.string().trim(),
organizationName: z.string().trim()
}),
response: {
200: z.object({
message: z.string(),
user: UsersSchema,
organization: OrganizationsSchema,
machineIdentity: IdentitiesSchema.extend({
credentials: z.object({
token: z.string()
}) // would just be Token AUTH for now
})
})
}
},
handler: async (req) => {
const { user, organization, machineIdentity } = await server.services.superAdmin.initializeInstance({
...req.body
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.AdminInit,
distinctId: user.user.username ?? "",
properties: {
username: user.user.username,
email: user.user.email ?? "",
lastName: user.user.lastName || "",
firstName: user.user.firstName || ""
}
});
return {
message: "Successfully initialized instance",
user: user.user,
organization,
machineIdentity
};
}
});
}; };
@@ -12,6 +12,7 @@ export type TUpdateIdentityDTO = {
role?: string; role?: string;
name?: string; name?: string;
metadata?: { key: string; value: string }[]; metadata?: { key: string; value: string }[];
isActorSuperAdmin?: boolean;
} & Omit<TOrgPermission, "orgId">; } & Omit<TOrgPermission, "orgId">;
export type TDeleteIdentityDTO = { export type TDeleteIdentityDTO = {
@@ -0,0 +1,15 @@
import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
import { ActorType } from "../auth/auth-type";
export const isSuperAdmin = (auth: TAuthMode) => {
if (auth.actor === ActorType.USER && auth.user.superAdmin) {
return true;
}
if (auth.actor === ActorType.IDENTITY && auth.isInstanceAdmin) {
return true;
}
return false;
};
@@ -1,16 +1,21 @@
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas"; import { IdentityAuthMethod, OrgMembershipRole, TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { getUserPrivateKey } from "@app/lib/crypto/srp"; import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TIdentityDALFactory } from "@app/services/identity/identity-dal"; import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
import { TAuthLoginFactory } from "../auth/auth-login-service"; import { TAuthLoginFactory } from "../auth/auth-login-service";
import { AuthMethod } from "../auth/auth-type"; import { AuthMethod, AuthTokenType } from "../auth/auth-type";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TIdentityTokenAuthDALFactory } from "../identity-token-auth/identity-token-auth-dal";
import { KMS_ROOT_CONFIG_UUID } from "../kms/kms-fns"; import { KMS_ROOT_CONFIG_UUID } from "../kms/kms-fns";
import { TKmsRootConfigDALFactory } from "../kms/kms-root-config-dal"; import { TKmsRootConfigDALFactory } from "../kms/kms-root-config-dal";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
@@ -20,10 +25,19 @@ import { TUserDALFactory } from "../user/user-dal";
import { TUserAliasDALFactory } from "../user-alias/user-alias-dal"; import { TUserAliasDALFactory } from "../user-alias/user-alias-dal";
import { UserAliasType } from "../user-alias/user-alias-types"; import { UserAliasType } from "../user-alias/user-alias-types";
import { TSuperAdminDALFactory } from "./super-admin-dal"; import { TSuperAdminDALFactory } from "./super-admin-dal";
import { LoginMethod, TAdminGetIdentitiesDTO, TAdminGetUsersDTO, TAdminSignUpDTO } from "./super-admin-types"; import {
LoginMethod,
TAdminGetIdentitiesDTO,
TAdminGetUsersDTO,
TAdminInitializeInstanceDTO,
TAdminSignUpDTO
} from "./super-admin-types";
type TSuperAdminServiceFactoryDep = { type TSuperAdminServiceFactoryDep = {
identityDAL: Pick<TIdentityDALFactory, "getIdentitiesByFilter">; identityDAL: TIdentityDALFactory;
identityTokenAuthDAL: TIdentityTokenAuthDALFactory;
identityAccessTokenDAL: TIdentityAccessTokenDALFactory;
identityOrgMembershipDAL: TIdentityOrgDALFactory;
serverCfgDAL: TSuperAdminDALFactory; serverCfgDAL: TSuperAdminDALFactory;
userDAL: TUserDALFactory; userDAL: TUserDALFactory;
userAliasDAL: Pick<TUserAliasDALFactory, "findOne">; userAliasDAL: Pick<TUserAliasDALFactory, "findOne">;
@@ -60,7 +74,10 @@ export const superAdminServiceFactory = ({
keyStore, keyStore,
kmsRootConfigDAL, kmsRootConfigDAL,
kmsService, kmsService,
licenseService licenseService,
identityAccessTokenDAL,
identityTokenAuthDAL,
identityOrgMembershipDAL
}: TSuperAdminServiceFactoryDep) => { }: TSuperAdminServiceFactoryDep) => {
const initServerCfg = async () => { const initServerCfg = async () => {
// TODO(akhilmhdh): bad pattern time less change this later to me itself // TODO(akhilmhdh): bad pattern time less change this later to me itself
@@ -274,6 +291,137 @@ export const superAdminServiceFactory = ({
return { token, user: userInfo, organization }; return { token, user: userInfo, organization };
}; };
const initializeInstance = async ({ email, password, organizationName }: TAdminInitializeInstanceDTO) => {
const appCfg = getConfig();
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
if (serverCfg?.initialized) {
throw new BadRequestError({ message: "Instance has already been set up" });
}
const existingUser = await userDAL.findOne({ email });
if (existingUser) throw new BadRequestError({ name: "Instance initialization", message: "User already exists" });
const userInfo = await userDAL.transaction(async (tx) => {
const newUser = await userDAL.create(
{
firstName: "Admin",
lastName: "User",
username: email,
email,
superAdmin: true,
isGhost: false,
isAccepted: true,
authMethods: [AuthMethod.EMAIL],
isEmailVerified: true
},
tx
);
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(password);
const encKeys = await generateUserSrpKeys(email, password);
const userEnc = await userDAL.createUserEncryption(
{
userId: newUser.id,
encryptionVersion: 2,
protectedKey: encKeys.protectedKey,
protectedKeyIV: encKeys.protectedKeyIV,
protectedKeyTag: encKeys.protectedKeyTag,
publicKey: encKeys.publicKey,
encryptedPrivateKey: encKeys.encryptedPrivateKey,
iv: encKeys.encryptedPrivateKeyIV,
tag: encKeys.encryptedPrivateKeyTag,
salt: encKeys.salt,
verifier: encKeys.verifier,
serverEncryptedPrivateKeyEncoding: encoding,
serverEncryptedPrivateKeyTag: tag,
serverEncryptedPrivateKeyIV: iv,
serverEncryptedPrivateKey: ciphertext
},
tx
);
return { user: newUser, enc: userEnc };
});
const initialOrganizationName = organizationName ?? "Admin Org";
const organization = await orgService.createOrganization({
userId: userInfo.user.id,
userEmail: userInfo.user.email,
orgName: initialOrganizationName
});
const { identity, credentials } = await identityDAL.transaction(async (tx) => {
const newIdentity = await identityDAL.create({ name: "Admin Identity" }, tx);
await identityOrgMembershipDAL.create(
{
identityId: newIdentity.id,
orgId: organization.id,
role: OrgMembershipRole.Admin
},
tx
);
const tokenAuth = await identityTokenAuthDAL.create(
{
identityId: newIdentity.id,
accessTokenMaxTTL: 0,
accessTokenTTL: 0,
accessTokenNumUsesLimit: 0,
accessTokenTrustedIps: JSON.stringify([
{
type: "ipv4",
prefix: 0,
ipAddress: "0.0.0.0"
},
{
type: "ipv6",
prefix: 0,
ipAddress: "::"
}
])
},
tx
);
const newToken = await identityAccessTokenDAL.create(
{
identityId: newIdentity.id,
isAccessTokenRevoked: false,
accessTokenTTL: tokenAuth.accessTokenTTL,
accessTokenMaxTTL: tokenAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: tokenAuth.accessTokenNumUsesLimit,
name: "Instance Admin Token",
authMethod: IdentityAuthMethod.TOKEN_AUTH
},
tx
);
const generatedAccessToken = jwt.sign(
{
identityId: newIdentity.id,
identityAccessTokenId: newToken.id,
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
} as TIdentityAccessTokenJwtPayload,
appCfg.AUTH_SECRET
);
return { identity: newIdentity, auth: tokenAuth, credentials: { token: generatedAccessToken } };
});
await updateServerCfg({ initialized: true, adminIdentityIds: [identity.id] }, userInfo.user.id);
return {
user: userInfo,
organization,
machineIdentity: {
...identity,
credentials
}
};
};
const getUsers = ({ offset, limit, searchTerm, adminsOnly }: TAdminGetUsersDTO) => { const getUsers = ({ offset, limit, searchTerm, adminsOnly }: TAdminGetUsersDTO) => {
return userDAL.getUsersByFilter({ return userDAL.getUsersByFilter({
limit, limit,
@@ -289,13 +437,19 @@ export const superAdminServiceFactory = ({
return user; return user;
}; };
const getIdentities = ({ offset, limit, searchTerm }: TAdminGetIdentitiesDTO) => { const getIdentities = async ({ offset, limit, searchTerm }: TAdminGetIdentitiesDTO) => {
return identityDAL.getIdentitiesByFilter({ const identities = await identityDAL.getIdentitiesByFilter({
limit, limit,
offset, offset,
searchTerm, searchTerm,
sortBy: "name" sortBy: "name"
}); });
const serverCfg = await getServerCfg();
return identities.map((identity) => ({
...identity,
isInstanceAdmin: Boolean(serverCfg?.adminIdentityIds?.includes(identity.id))
}));
}; };
const grantServerAdminAccessToUser = async (userId: string) => { const grantServerAdminAccessToUser = async (userId: string) => {
@@ -393,6 +547,7 @@ export const superAdminServiceFactory = ({
initServerCfg, initServerCfg,
updateServerCfg, updateServerCfg,
adminSignUp, adminSignUp,
initializeInstance,
getUsers, getUsers,
deleteUser, deleteUser,
getIdentities, getIdentities,
@@ -16,6 +16,12 @@ export type TAdminSignUpDTO = {
userAgent: string; userAgent: string;
}; };
export type TAdminInitializeInstanceDTO = {
email: string;
password: string;
organizationName: string;
};
export type TAdminGetUsersDTO = { export type TAdminGetUsersDTO = {
offset: number; offset: number;
limit: number; limit: number;
+1 -1
View File
@@ -1,6 +1,7 @@
import { useInfiniteQuery, useQuery, UseQueryOptions } from "@tanstack/react-query"; import { useInfiniteQuery, useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { Identity } from "@app/hooks/api/identities/types";
import { User } from "../types"; import { User } from "../types";
import { import {
@@ -10,7 +11,6 @@ import {
TGetServerRootKmsEncryptionDetails, TGetServerRootKmsEncryptionDetails,
TServerConfig TServerConfig
} from "./types"; } from "./types";
import { Identity } from "@app/hooks/api/identities/types";
export const adminStandaloneKeys = { export const adminStandaloneKeys = {
getUsers: "get-users", getUsers: "get-users",
@@ -15,6 +15,7 @@ export type Identity = {
authMethods: IdentityAuthMethod[]; authMethods: IdentityAuthMethod[];
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
isInstanceAdmin?: boolean;
}; };
export type IdentityAccessToken = { export type IdentityAccessToken = {
@@ -28,13 +28,13 @@ import {
useGetServerRootKmsEncryptionDetails, useGetServerRootKmsEncryptionDetails,
useUpdateServerConfig useUpdateServerConfig
} from "@app/hooks/api"; } from "@app/hooks/api";
import { IdentityPanel } from "@app/pages/admin/OverviewPage/components/IdentityPanel";
import { AuthPanel } from "./components/AuthPanel"; import { AuthPanel } from "./components/AuthPanel";
import { EncryptionPanel } from "./components/EncryptionPanel"; import { EncryptionPanel } from "./components/EncryptionPanel";
import { IntegrationPanel } from "./components/IntegrationPanel"; import { IntegrationPanel } from "./components/IntegrationPanel";
import { RateLimitPanel } from "./components/RateLimitPanel"; import { RateLimitPanel } from "./components/RateLimitPanel";
import { UserPanel } from "./components/UserPanel"; import { UserPanel } from "./components/UserPanel";
import { IdentityPanel } from "@app/pages/admin/OverviewPage/components/IdentityPanel";
enum TabSections { enum TabSections {
Settings = "settings", Settings = "settings",
@@ -59,8 +59,8 @@ const formSchema = z.object({
trustLdapEmails: z.boolean(), trustLdapEmails: z.boolean(),
trustOidcEmails: z.boolean(), trustOidcEmails: z.boolean(),
defaultAuthOrgId: z.string(), defaultAuthOrgId: z.string(),
authConsentContent: z.string().optional(), authConsentContent: z.string().optional().default(""),
pageFrameContent: z.string().optional() pageFrameContent: z.string().optional().default("")
}); });
type TDashboardForm = z.infer<typeof formSchema>; type TDashboardForm = z.infer<typeof formSchema>;
@@ -86,8 +86,8 @@ export const OverviewPage = () => {
trustLdapEmails: config.trustLdapEmails, trustLdapEmails: config.trustLdapEmails,
trustOidcEmails: config.trustOidcEmails, trustOidcEmails: config.trustOidcEmails,
defaultAuthOrgId: config.defaultAuthOrgId ?? "", defaultAuthOrgId: config.defaultAuthOrgId ?? "",
authConsentContent: config.authConsentContent, authConsentContent: config.authConsentContent ?? "",
pageFrameContent: config.pageFrameContent pageFrameContent: config.pageFrameContent ?? ""
} }
}); });
@@ -3,6 +3,7 @@ import { faMagnifyingGlass, faServer } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { import {
Badge,
Button, Button,
EmptyState, EmptyState,
Input, Input,
@@ -54,9 +55,16 @@ const IdentityPanelTable = () => {
{isPending && <TableSkeleton columns={2} innerKey="identities" />} {isPending && <TableSkeleton columns={2} innerKey="identities" />}
{!isPending && {!isPending &&
data?.pages?.map((identities) => data?.pages?.map((identities) =>
identities.map(({ name, id }) => ( identities.map(({ name, id, isInstanceAdmin }) => (
<Tr key={`identity-${id}`} className="w-full"> <Tr key={`identity-${id}`} className="w-full">
<Td>{name}</Td> <Td>
{name}
{isInstanceAdmin && (
<Badge variant="primary" className="ml-2">
Server Admin
</Badge>
)}
</Td>
</Tr> </Tr>
)) ))
)} )}