mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 14:27:23 +00:00
Update db schema for ssh login mappings
This commit is contained in:
@@ -1470,7 +1470,9 @@ interface CreateSshHost {
|
||||
hostCertTtl: string;
|
||||
loginMappings: {
|
||||
loginUser: string;
|
||||
allowedPrincipals: string[];
|
||||
allowedPrincipals: {
|
||||
usernames: string[];
|
||||
};
|
||||
}[];
|
||||
userSshCaId: string;
|
||||
hostSshCaId: string;
|
||||
@@ -1486,7 +1488,9 @@ interface UpdateSshHost {
|
||||
hostCertTtl?: string;
|
||||
loginMappings?: {
|
||||
loginUser: string;
|
||||
allowedPrincipals: string[];
|
||||
allowedPrincipals: {
|
||||
usernames: string[];
|
||||
};
|
||||
}[];
|
||||
userSshCaId?: string;
|
||||
hostSshCaId?: string;
|
||||
|
||||
@@ -11,52 +11,49 @@ export type TSshHostDALFactory = ReturnType<typeof sshHostDALFactory>;
|
||||
export const sshHostDALFactory = (db: TDbClient) => {
|
||||
const sshHostOrm = ormify(db, TableName.SshHost);
|
||||
|
||||
const findSshHostsWithPrincipalsAcrossProjects = async (projectIds: string[], principals: string[], tx?: Knex) => {
|
||||
const findSshHostsWithPrincipalsAcrossProjects = async (projectIds: string[], userId: string, tx?: Knex) => {
|
||||
try {
|
||||
const matchingSshHosts = await (tx || db.replicaNode())(TableName.SshHost)
|
||||
const user = await (tx || db.replicaNode())(TableName.Users).where({ id: userId }).select("username").first();
|
||||
|
||||
if (!user) {
|
||||
throw new DatabaseError({ name: `${TableName.Users}: UserNotFound`, error: new Error("User not found") });
|
||||
}
|
||||
|
||||
const rows = await (tx || db.replicaNode())(TableName.SshHost)
|
||||
.leftJoin(TableName.SshHostLoginUser, `${TableName.SshHost}.id`, `${TableName.SshHostLoginUser}.sshHostId`)
|
||||
.leftJoin(
|
||||
TableName.SshHostLoginMapping,
|
||||
`${TableName.SshHost}.id`,
|
||||
`${TableName.SshHostLoginMapping}.sshHostId`
|
||||
TableName.SshHostLoginUserMapping,
|
||||
`${TableName.SshHostLoginUser}.id`,
|
||||
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
||||
)
|
||||
.whereIn(`${TableName.SshHost}.projectId`, projectIds)
|
||||
.whereRaw(`"${TableName.SshHostLoginMapping}"."allowedPrincipals" && ?::text[]`, [principals])
|
||||
.andWhere(`${TableName.SshHostLoginUserMapping}.userId`, userId)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
||||
db.ref("projectId").withSchema(TableName.SshHost),
|
||||
db.ref("hostname").withSchema(TableName.SshHost),
|
||||
db.ref("userCertTtl").withSchema(TableName.SshHost),
|
||||
db.ref("hostCertTtl").withSchema(TableName.SshHost),
|
||||
db.ref("loginUser").withSchema(TableName.SshHostLoginMapping),
|
||||
db.ref("allowedPrincipals").withSchema(TableName.SshHostLoginMapping),
|
||||
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||
db.ref("username").withSchema(TableName.Users),
|
||||
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
|
||||
db.ref("userSshCaId").withSchema(TableName.SshHost),
|
||||
db.ref("hostSshCaId").withSchema(TableName.SshHost)
|
||||
)
|
||||
.orderBy(`${TableName.SshHost}.updatedAt`, "desc");
|
||||
|
||||
const grouped = groupBy(matchingSshHosts, (r) => r.sshHostId);
|
||||
const grouped = groupBy(rows, (r) => r.sshHostId);
|
||||
return Object.values(grouped).map((hostRows) => {
|
||||
const { sshHostId, hostname, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId, projectId } = hostRows[0];
|
||||
|
||||
const loginMappingGrouped = groupBy(
|
||||
hostRows.filter((r) => r.loginUser),
|
||||
(r) => r.loginUser
|
||||
);
|
||||
const loginMappingGrouped = groupBy(hostRows, (r) => r.loginUser);
|
||||
|
||||
const loginMappings = Object.entries(loginMappingGrouped)
|
||||
.map(([loginUser, entries]) => {
|
||||
const filteredPrincipals = unique(entries.flatMap((entry) => entry.allowedPrincipals ?? [])).filter(
|
||||
(principal) => principals.includes(principal)
|
||||
);
|
||||
|
||||
if (filteredPrincipals.length === 0) return null;
|
||||
|
||||
return {
|
||||
loginUser,
|
||||
allowedPrincipals: filteredPrincipals
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as { loginUser: string; allowedPrincipals: string[] }[];
|
||||
const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser]) => ({
|
||||
loginUser,
|
||||
allowedPrincipals: {
|
||||
usernames: [user.username]
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
id: sshHostId,
|
||||
@@ -77,11 +74,13 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
||||
const findSshHostsWithLoginMappings = async (projectId: string, tx?: Knex) => {
|
||||
try {
|
||||
const rows = await (tx || db.replicaNode())(TableName.SshHost)
|
||||
.leftJoin(TableName.SshHostLoginUser, `${TableName.SshHost}.id`, `${TableName.SshHostLoginUser}.sshHostId`)
|
||||
.leftJoin(
|
||||
TableName.SshHostLoginMapping,
|
||||
`${TableName.SshHost}.id`,
|
||||
`${TableName.SshHostLoginMapping}.sshHostId`
|
||||
TableName.SshHostLoginUserMapping,
|
||||
`${TableName.SshHostLoginUser}.id`,
|
||||
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
||||
)
|
||||
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
|
||||
.where(`${TableName.SshHost}.projectId`, projectId)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
||||
@@ -89,8 +88,9 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
||||
db.ref("hostname").withSchema(TableName.SshHost),
|
||||
db.ref("userCertTtl").withSchema(TableName.SshHost),
|
||||
db.ref("hostCertTtl").withSchema(TableName.SshHost),
|
||||
db.ref("loginUser").withSchema(TableName.SshHostLoginMapping),
|
||||
db.ref("allowedPrincipals").withSchema(TableName.SshHostLoginMapping),
|
||||
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||
db.ref("username").withSchema(TableName.Users),
|
||||
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
|
||||
db.ref("userSshCaId").withSchema(TableName.SshHost),
|
||||
db.ref("hostSshCaId").withSchema(TableName.SshHost)
|
||||
)
|
||||
@@ -107,13 +107,15 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
||||
|
||||
const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({
|
||||
loginUser,
|
||||
allowedPrincipals: unique(entries.flatMap((entry) => entry.allowedPrincipals ?? []))
|
||||
allowedPrincipals: {
|
||||
usernames: unique(entries.map((e) => e.username)).filter(Boolean)
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
id: sshHostId,
|
||||
projectId,
|
||||
hostname,
|
||||
projectId,
|
||||
userCertTtl,
|
||||
hostCertTtl,
|
||||
loginMappings,
|
||||
@@ -129,11 +131,13 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
||||
const findSshHostByIdWithLoginMappings = async (sshHostId: string, tx?: Knex) => {
|
||||
try {
|
||||
const rows = await (tx || db.replicaNode())(TableName.SshHost)
|
||||
.leftJoin(TableName.SshHostLoginUser, `${TableName.SshHost}.id`, `${TableName.SshHostLoginUser}.sshHostId`)
|
||||
.leftJoin(
|
||||
TableName.SshHostLoginMapping,
|
||||
`${TableName.SshHost}.id`,
|
||||
`${TableName.SshHostLoginMapping}.sshHostId`
|
||||
TableName.SshHostLoginUserMapping,
|
||||
`${TableName.SshHostLoginUser}.id`,
|
||||
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
||||
)
|
||||
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
|
||||
.where(`${TableName.SshHost}.id`, sshHostId)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
||||
@@ -141,8 +145,9 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
||||
db.ref("hostname").withSchema(TableName.SshHost),
|
||||
db.ref("userCertTtl").withSchema(TableName.SshHost),
|
||||
db.ref("hostCertTtl").withSchema(TableName.SshHost),
|
||||
db.ref("loginUser").withSchema(TableName.SshHostLoginMapping),
|
||||
db.ref("allowedPrincipals").withSchema(TableName.SshHostLoginMapping),
|
||||
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||
db.ref("username").withSchema(TableName.Users),
|
||||
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
|
||||
db.ref("userSshCaId").withSchema(TableName.SshHost),
|
||||
db.ref("hostSshCaId").withSchema(TableName.SshHost)
|
||||
);
|
||||
@@ -158,7 +163,9 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
||||
|
||||
const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({
|
||||
loginUser,
|
||||
allowedPrincipals: unique(entries.flatMap((entry) => entry.allowedPrincipals ?? []))
|
||||
allowedPrincipals: {
|
||||
usernames: unique(entries.map((e) => e.username)).filter(Boolean)
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
@@ -178,8 +185,8 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
||||
|
||||
return {
|
||||
...sshHostOrm,
|
||||
findSshHostsWithPrincipalsAcrossProjects,
|
||||
findSshHostsWithLoginMappings,
|
||||
findSshHostsWithPrincipalsAcrossProjects,
|
||||
findSshHostByIdWithLoginMappings
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TSshHostLoginMappingDALFactory = ReturnType<typeof sshHostLoginMappingDALFactory>;
|
||||
|
||||
export const sshHostLoginMappingDALFactory = (db: TDbClient) => {
|
||||
const sshHostLoginMappingOrm = ormify(db, TableName.SshHostLoginMapping);
|
||||
return sshHostLoginMappingOrm;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TSshHostLoginUserMappingDALFactory = ReturnType<typeof sshHostLoginUserMappingDALFactory>;
|
||||
|
||||
export const sshHostLoginUserMappingDALFactory = (db: TDbClient) => {
|
||||
const sshHostLoginUserMappingOrm = ormify(db, TableName.SshHostLoginUserMapping);
|
||||
return sshHostLoginUserMappingOrm;
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SshHostsSchema } from "@app/db/schemas";
|
||||
|
||||
export const sanitizedSshHost = SshHostsSchema.pick({
|
||||
@@ -9,3 +11,10 @@ export const sanitizedSshHost = SshHostsSchema.pick({
|
||||
userSshCaId: true,
|
||||
hostSshCaId: true
|
||||
});
|
||||
|
||||
export const loginMappingSchema = z.object({
|
||||
loginUser: z.string(),
|
||||
allowedPrincipals: z.object({
|
||||
usernames: z.array(z.string())
|
||||
})
|
||||
});
|
||||
|
||||
@@ -9,8 +9,10 @@ import { TSshCertificateBodyDALFactory } from "@app/ee/services/ssh-certificate/
|
||||
import { TSshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal";
|
||||
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
|
||||
import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
|
||||
import { TSshHostLoginMappingDALFactory } from "@app/ee/services/ssh-host/ssh-host-login-mapping-dal";
|
||||
import { TSshHostLoginUserMappingDALFactory } from "@app/ee/services/ssh-host/ssh-host-login-user-mapping-dal";
|
||||
import { TSshHostLoginUserDALFactory } from "@app/ee/services/ssh-host/ssh-login-user-dal";
|
||||
import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
@@ -34,8 +36,8 @@ import {
|
||||
TUpdateSshHostDTO
|
||||
} from "./ssh-host-types";
|
||||
|
||||
type TSshCertificateAuthorityServiceFactoryDep = {
|
||||
userDAL: Pick<TUserDALFactory, "findById">;
|
||||
type TSshHostServiceFactoryDep = {
|
||||
userDAL: Pick<TUserDALFactory, "findById" | "find">;
|
||||
projectDAL: Pick<TProjectDALFactory, "find">;
|
||||
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "findOne">;
|
||||
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "findById">;
|
||||
@@ -53,11 +55,9 @@ type TSshCertificateAuthorityServiceFactoryDep = {
|
||||
| "findSshHostByIdWithLoginMappings"
|
||||
| "findSshHostsWithPrincipalsAcrossProjects"
|
||||
>;
|
||||
sshHostLoginMappingDAL: Pick<
|
||||
TSshHostLoginMappingDALFactory,
|
||||
"transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne" | "insertMany" | "delete"
|
||||
>;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
sshHostLoginUserDAL: TSshHostLoginUserDALFactory;
|
||||
sshHostLoginUserMappingDAL: TSshHostLoginUserMappingDALFactory;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
};
|
||||
|
||||
@@ -72,26 +72,26 @@ export const sshHostServiceFactory = ({
|
||||
sshCertificateDAL,
|
||||
sshCertificateBodyDAL,
|
||||
sshHostDAL,
|
||||
sshHostLoginMappingDAL,
|
||||
sshHostLoginUserMappingDAL,
|
||||
sshHostLoginUserDAL,
|
||||
permissionService,
|
||||
kmsService
|
||||
}: TSshCertificateAuthorityServiceFactoryDep) => {
|
||||
}: TSshHostServiceFactoryDep) => {
|
||||
/**
|
||||
* Return list of all SSH hosts that a user can issue user SSH certificates for
|
||||
* (i.e. is able to access / connect to) across all SSH projects in the organization
|
||||
*/
|
||||
const listSshHosts = async ({ actorId, actorAuthMethod, actor, actorOrgId }: TListSshHostsDTO) => {
|
||||
if (actor !== ActorType.USER) {
|
||||
// (dangtony98): only support user for now
|
||||
throw new BadRequestError({ message: `Actor type ${actor} not supported` });
|
||||
}
|
||||
|
||||
const sshProjects = await projectDAL.find({
|
||||
orgId: actorOrgId,
|
||||
type: ProjectType.SSH
|
||||
});
|
||||
|
||||
const principals = await convertActorToPrincipals({
|
||||
actor,
|
||||
actorId,
|
||||
userDAL
|
||||
});
|
||||
|
||||
const allowedHosts = [];
|
||||
|
||||
for await (const project of sshProjects) {
|
||||
@@ -105,7 +105,7 @@ export const sshHostServiceFactory = ({
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
const projectHosts = await sshHostDAL.findSshHostsWithPrincipalsAcrossProjects([project.id], principals);
|
||||
const projectHosts = await sshHostDAL.findSshHostsWithPrincipalsAcrossProjects([project.id], actorId); // TODO: consider fn rename
|
||||
|
||||
allowedHosts.push(...projectHosts);
|
||||
} catch {
|
||||
@@ -208,15 +208,51 @@ export const sshHostServiceFactory = ({
|
||||
tx
|
||||
);
|
||||
|
||||
await sshHostLoginMappingDAL.insertMany(
|
||||
loginMappings.map(({ loginUser, allowedPrincipals }) => ({
|
||||
await sshHostLoginUserDAL.insertMany(
|
||||
loginMappings.map(({ loginUser }) => ({
|
||||
sshHostId: host.id,
|
||||
loginUser,
|
||||
allowedPrincipals
|
||||
loginUser
|
||||
})),
|
||||
tx
|
||||
);
|
||||
|
||||
for await (const { loginUser, allowedPrincipals } of loginMappings) {
|
||||
const sshHostLoginUser = await sshHostLoginUserDAL.create(
|
||||
{
|
||||
sshHostId: host.id,
|
||||
loginUser
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
const users = await userDAL.find(
|
||||
{
|
||||
$in: {
|
||||
username: allowedPrincipals.usernames
|
||||
}
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
|
||||
for await (const user of users) {
|
||||
await permissionService.getUserProjectPermission({
|
||||
userId: user.id,
|
||||
projectId,
|
||||
authMethod: actorAuthMethod,
|
||||
userOrgId: actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
}
|
||||
|
||||
await sshHostLoginUserMappingDAL.insertMany(
|
||||
users.map((user) => ({
|
||||
sshHostLoginUserId: sshHostLoginUser.id,
|
||||
userId: user.id
|
||||
})),
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
const newSshHostWithLoginMappings = await sshHostDAL.findSshHostByIdWithLoginMappings(host.id, tx);
|
||||
if (!newSshHostWithLoginMappings) {
|
||||
throw new NotFoundError({ message: `SSH host with ID '${host.id}' not found` });
|
||||
@@ -270,16 +306,58 @@ export const sshHostServiceFactory = ({
|
||||
);
|
||||
|
||||
if (loginMappings) {
|
||||
await sshHostLoginMappingDAL.delete({ sshHostId }, tx);
|
||||
await sshHostLoginUserDAL.delete({ sshHostId: host.id }, tx);
|
||||
if (loginMappings.length) {
|
||||
await sshHostLoginMappingDAL.insertMany(
|
||||
loginMappings.map(({ loginUser, allowedPrincipals }) => ({
|
||||
sshHostId: host.id,
|
||||
loginUser,
|
||||
allowedPrincipals
|
||||
})),
|
||||
tx
|
||||
);
|
||||
for await (const { loginUser, allowedPrincipals } of loginMappings) {
|
||||
const sshHostLoginUser = await sshHostLoginUserDAL.create(
|
||||
{
|
||||
sshHostId: host.id,
|
||||
loginUser
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
if (allowedPrincipals.usernames.length === 0) {
|
||||
continue; // or maybe insert no mappings and just skip validation
|
||||
}
|
||||
|
||||
const users = await userDAL.find(
|
||||
{
|
||||
$in: {
|
||||
username: allowedPrincipals.usernames
|
||||
}
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
|
||||
const foundUsernames = new Set(users.map((u) => u.username));
|
||||
|
||||
for (const uname of allowedPrincipals.usernames) {
|
||||
if (!foundUsernames.has(uname)) {
|
||||
throw new BadRequestError({
|
||||
message: `Invalid username: ${uname}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for await (const user of users) {
|
||||
await permissionService.getUserProjectPermission({
|
||||
userId: user.id,
|
||||
projectId: host.projectId,
|
||||
authMethod: actorAuthMethod,
|
||||
userOrgId: actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
}
|
||||
|
||||
await sshHostLoginUserMappingDAL.insertMany(
|
||||
users.map((user) => ({
|
||||
sshHostLoginUserId: sshHostLoginUser.id,
|
||||
userId: user.id
|
||||
})),
|
||||
tx
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,10 +392,7 @@ export const sshHostServiceFactory = ({
|
||||
})
|
||||
);
|
||||
|
||||
await sshHostDAL.transaction(async (tx) => {
|
||||
await sshHostLoginMappingDAL.delete({ sshHostId }, tx);
|
||||
await sshHostDAL.deleteById(sshHostId, tx);
|
||||
});
|
||||
await sshHostDAL.deleteById(sshHostId);
|
||||
|
||||
return host;
|
||||
};
|
||||
@@ -379,7 +454,9 @@ export const sshHostServiceFactory = ({
|
||||
});
|
||||
|
||||
const mapping = host.loginMappings.find(
|
||||
(m) => m.loginUser === loginUser && m.allowedPrincipals.some((allowed) => internalPrincipals.includes(allowed))
|
||||
(m) =>
|
||||
m.loginUser === loginUser &&
|
||||
m.allowedPrincipals.usernames.some((allowed) => internalPrincipals.includes(allowed))
|
||||
);
|
||||
|
||||
if (!mapping) {
|
||||
|
||||
@@ -8,7 +8,9 @@ export type TCreateSshHostDTO = {
|
||||
hostCertTtl: string;
|
||||
loginMappings: {
|
||||
loginUser: string;
|
||||
allowedPrincipals: string[];
|
||||
allowedPrincipals: {
|
||||
usernames: string[];
|
||||
};
|
||||
}[];
|
||||
userSshCaId?: string;
|
||||
hostSshCaId?: string;
|
||||
@@ -21,7 +23,9 @@ export type TUpdateSshHostDTO = {
|
||||
hostCertTtl?: string;
|
||||
loginMappings?: {
|
||||
loginUser: string;
|
||||
allowedPrincipals: string[];
|
||||
allowedPrincipals: {
|
||||
usernames: string[];
|
||||
};
|
||||
}[];
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TSshHostLoginUserDALFactory = ReturnType<typeof sshHostLoginUserDALFactory>;
|
||||
|
||||
export const sshHostLoginUserDALFactory = (db: TDbClient) => {
|
||||
const sshHostLoginUserOrm = ormify(db, TableName.SshHostLoginUser);
|
||||
return sshHostLoginUserOrm;
|
||||
};
|
||||
Reference in New Issue
Block a user