feat: added token based communications

This commit is contained in:
Akhil Mohan
2024-01-12 20:49:19 +05:30
parent daa94db874
commit 20fb99f042
65 changed files with 699 additions and 360 deletions

View File

@@ -31,7 +31,6 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@octokit/webhooks-types": "^7.3.1",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"@types/jmespath": "^0.15.2", "@types/jmespath": "^0.15.2",
"@types/jsonwebtoken": "^9.0.5", "@types/jsonwebtoken": "^9.0.5",
@@ -80,6 +79,7 @@
"@node-saml/passport-saml": "^4.0.4", "@node-saml/passport-saml": "^4.0.4",
"@octokit/rest": "^20.0.2", "@octokit/rest": "^20.0.2",
"@ucast/mongo2js": "^1.3.4", "@ucast/mongo2js": "^1.3.4",
"@octokit/webhooks-types": "^7.3.1",
"ajv": "^8.12.0", "ajv": "^8.12.0",
"argon2": "^0.31.2", "argon2": "^0.31.2",
"aws-sdk": "^2.1532.0", "aws-sdk": "^2.1532.0",

View File

@@ -48,6 +48,7 @@ const getZodDefaultValue = (type: unknown, value: string | number | boolean | Ob
case "uuid": case "uuid":
return; return;
case "character varying": { case "character varying": {
if (value === "gen_random_uuid()") return;
if (typeof value === "string" && value.includes("::")) { if (typeof value === "string" && value.includes("::")) {
return `.default(${value.split("::")[0]})`; return `.default(${value.split("::")[0]})`;
} }
@@ -85,7 +86,7 @@ const main = async () => {
.whereRaw("table_schema = current_schema()") .whereRaw("table_schema = current_schema()")
.select<{ tableName: string }[]>("table_name as tableName") .select<{ tableName: string }[]>("table_name as tableName")
.orderBy("table_name") .orderBy("table_name")
).filter((el) => el.tableName.includes("migration")); ).filter((el) => !el.tableName.includes("_migrations"));
console.log("Select a table to generate schema"); console.log("Select a table to generate schema");
console.table(tables); console.table(tables);

View File

@@ -10,12 +10,13 @@ import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-ap
import { TSecretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service"; import { TSecretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service";
import { TSecretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service"; import { TSecretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service";
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service"; import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service"; import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service"; import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service"; import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service"; import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
import { AuthMode } from "@app/services/auth/auth-signup-type";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TIdentityServiceFactory } from "@app/services/identity/identity-service"; import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service"; import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service"; import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
@@ -36,7 +37,6 @@ import { TSecretImportServiceFactory } from "@app/services/secret-import/secret-
import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service"; import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
import { TServiceTokenServiceFactory } from "@app/services/service-token/service-token-service"; import { TServiceTokenServiceFactory } from "@app/services/service-token/service-token-service";
import { TSuperAdminServiceFactory } from "@app/services/super-admin/super-admin-service"; import { TSuperAdminServiceFactory } from "@app/services/super-admin/super-admin-service";
import { TAuthTokenServiceFactory } from "@app/services/token/token-service";
import { TUserDalFactory } from "@app/services/user/user-dal"; import { TUserDalFactory } from "@app/services/user/user-dal";
import { TUserServiceFactory } from "@app/services/user/user-service"; import { TUserServiceFactory } from "@app/services/user/user-service";
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service"; import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
@@ -50,13 +50,7 @@ declare module "fastify" {
user: TUsers; user: TUsers;
}; };
// identity injection. depending on which kinda of token the information is filled in auth // identity injection. depending on which kinda of token the information is filled in auth
auth: { auth: TAuthMode;
authMode: AuthMode.JWT | AuthMode.API_KEY_V2 | AuthMode.API_KEY;
actor: ActorType.USER;
userId: string;
tokenVersionId: string; // the session id of token used
user: TUsers;
};
permission: { permission: {
type: ActorType; type: ActorType;
id: string; id: string;

View File

@@ -6,7 +6,7 @@ import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.Project))) { if (!(await knex.schema.hasTable(TableName.Project))) {
await knex.schema.createTable(TableName.Project, (t) => { await knex.schema.createTable(TableName.Project, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.string("id").primary().defaultTo(knex.fn.uuid());
t.string("name").notNullable(); t.string("name").notNullable();
t.boolean("autoCapitalization").defaultTo(true); t.boolean("autoCapitalization").defaultTo(true);
t.uuid("orgId").notNullable(); t.uuid("orgId").notNullable();
@@ -22,7 +22,7 @@ export async function up(knex: Knex): Promise<void> {
t.string("name").notNullable(); t.string("name").notNullable();
t.string("slug").notNullable(); t.string("slug").notNullable();
t.integer("position").notNullable(); t.integer("position").notNullable();
t.uuid("projectId").notNullable(); t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
// this will ensure ever env has its position // this will ensure ever env has its position
t.unique(["projectId", "position"], { t.unique(["projectId", "position"], {
@@ -43,7 +43,7 @@ export async function up(knex: Knex): Promise<void> {
t.uuid("senderId"); t.uuid("senderId");
// if sender is deleted just don't do anything to this record // if sender is deleted just don't do anything to this record
t.foreign("senderId").references("id").inTable(TableName.Users).onDelete("SET NULL"); t.foreign("senderId").references("id").inTable(TableName.Users).onDelete("SET NULL");
t.uuid("projectId").notNullable(); t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.timestamps(true, true, true); t.timestamps(true, true, true);
}); });

View File

@@ -13,7 +13,7 @@ export async function up(knex: Knex): Promise<void> {
t.jsonb("permissions").notNullable(); t.jsonb("permissions").notNullable();
// does not need update trigger we will do it manually // does not need update trigger we will do it manually
t.timestamps(true, true, true); t.timestamps(true, true, true);
t.uuid("projectId").notNullable(); t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
}); });
} }
@@ -26,7 +26,7 @@ export async function up(knex: Knex): Promise<void> {
t.timestamps(true, true, true); t.timestamps(true, true, true);
t.uuid("userId").notNullable(); t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE"); t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.uuid("projectId").notNullable(); t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
// until role is changed/removed the role should not deleted // until role is changed/removed the role should not deleted
t.uuid("roleId"); t.uuid("roleId");

View File

@@ -13,7 +13,7 @@ export async function up(knex: Knex): Promise<void> {
t.timestamps(true, true, true); t.timestamps(true, true, true);
t.uuid("createdBy"); t.uuid("createdBy");
t.foreign("createdBy").references("id").inTable(TableName.Users).onDelete("SET NULL"); t.foreign("createdBy").references("id").inTable(TableName.Users).onDelete("SET NULL");
t.uuid("projectId").notNullable(); t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
}); });
} }

View File

@@ -12,7 +12,7 @@ export async function up(knex: Knex): Promise<void> {
t.text("saltTag").notNullable(); t.text("saltTag").notNullable();
t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM); t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM);
t.string("keyEncoding").notNullable().defaultTo(SecretKeyEncoding.UTF8); t.string("keyEncoding").notNullable().defaultTo(SecretKeyEncoding.UTF8);
t.uuid("projectId").notNullable().unique(); t.string("projectId").notNullable().unique();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.timestamps(true, true, true); t.timestamps(true, true, true);
}); });

View File

@@ -18,7 +18,7 @@ export async function up(knex: Knex): Promise<void> {
t.text("encryptedProjectKey"); t.text("encryptedProjectKey");
t.text("encryptedProjectKeyNonce"); t.text("encryptedProjectKeyNonce");
// one to one relationship // one to one relationship
t.uuid("projectId").notNullable().unique(); t.string("projectId").notNullable().unique();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.uuid("senderId"); t.uuid("senderId");
t.foreign("senderId").references("id").inTable(TableName.Users).onDelete("SET NULL"); t.foreign("senderId").references("id").inTable(TableName.Users).onDelete("SET NULL");

View File

@@ -25,7 +25,7 @@ export async function up(knex: Knex): Promise<void> {
t.jsonb("metadata"); t.jsonb("metadata");
t.string("algorithm").notNullable(); t.string("algorithm").notNullable();
t.string("keyEncoding").notNullable(); t.string("keyEncoding").notNullable();
t.uuid("projectId").notNullable(); t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.timestamps(true, true, true); t.timestamps(true, true, true);
}); });

View File

@@ -19,7 +19,7 @@ export async function up(knex: Knex): Promise<void> {
t.timestamps(true, true, true); t.timestamps(true, true, true);
// user is old one // user is old one
t.string("createdBy").notNullable(); t.string("createdBy").notNullable();
t.uuid("projectId").notNullable(); t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
}); });
} }

View File

@@ -25,7 +25,7 @@ export async function up(knex: Knex): Promise<void> {
t.string("role").notNullable(); t.string("role").notNullable();
t.uuid("roleId"); t.uuid("roleId");
t.foreign("roleId").references("id").inTable(TableName.ProjectRoles); t.foreign("roleId").references("id").inTable(TableName.ProjectRoles);
t.uuid("projectId").notNullable(); t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.uuid("identityId").notNullable(); t.uuid("identityId").notNullable();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE"); t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");

View File

@@ -18,7 +18,7 @@ export async function up(knex: Knex): Promise<void> {
// no trigger needed as this collection is append only // no trigger needed as this collection is append only
t.uuid("orgId"); t.uuid("orgId");
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE"); t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.uuid("projectId"); t.string("projectId");
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
}); });
} }

View File

@@ -20,7 +20,7 @@ export const AuditLogsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
orgId: z.string().uuid().nullable().optional(), orgId: z.string().uuid().nullable().optional(),
projectId: z.string().uuid().nullable().optional(), projectId: z.string().nullable().optional(),
}); });
export type TAuditLogs = z.infer<typeof AuditLogsSchema>; export type TAuditLogs = z.infer<typeof AuditLogsSchema>;

View File

@@ -11,7 +11,7 @@ export const IdentityProjectMembershipsSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
role: z.string(), role: z.string(),
roleId: z.string().uuid().nullable().optional(), roleId: z.string().uuid().nullable().optional(),
projectId: z.string().uuid(), projectId: z.string(),
identityId: z.string().uuid(), identityId: z.string().uuid(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),

View File

@@ -27,7 +27,7 @@ export const IntegrationAuthsSchema = z.object({
metadata: z.unknown().nullable().optional(), metadata: z.unknown().nullable().optional(),
algorithm: z.string(), algorithm: z.string(),
keyEncoding: z.string(), keyEncoding: z.string(),
projectId: z.string().uuid(), projectId: z.string(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
}); });

View File

@@ -19,7 +19,7 @@ export const ProjectBotsSchema = z.object({
keyEncoding: z.string(), keyEncoding: z.string(),
encryptedProjectKey: z.string().nullable().optional(), encryptedProjectKey: z.string().nullable().optional(),
encryptedProjectKeyNonce: z.string().nullable().optional(), encryptedProjectKeyNonce: z.string().nullable().optional(),
projectId: z.string().uuid(), projectId: z.string(),
senderId: z.string().uuid().nullable().optional(), senderId: z.string().uuid().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),

View File

@@ -12,7 +12,7 @@ export const ProjectEnvironmentsSchema = z.object({
name: z.string(), name: z.string(),
slug: z.string(), slug: z.string(),
position: z.number(), position: z.number(),
projectId: z.string().uuid(), projectId: z.string(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
}); });

View File

@@ -13,7 +13,7 @@ export const ProjectKeysSchema = z.object({
nonce: z.string(), nonce: z.string(),
receiverId: z.string().uuid(), receiverId: z.string().uuid(),
senderId: z.string().uuid().nullable().optional(), senderId: z.string().uuid().nullable().optional(),
projectId: z.string().uuid(), projectId: z.string(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
}); });

View File

@@ -13,7 +13,7 @@ export const ProjectMembershipsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
userId: z.string().uuid(), userId: z.string().uuid(),
projectId: z.string().uuid(), projectId: z.string(),
roleId: z.string().uuid().nullable().optional(), roleId: z.string().uuid().nullable().optional(),
}); });

View File

@@ -15,7 +15,7 @@ export const ProjectRolesSchema = z.object({
permissions: z.unknown(), permissions: z.unknown(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
projectId: z.string().uuid(), projectId: z.string(),
}); });
export type TProjectRoles = z.infer<typeof ProjectRolesSchema>; export type TProjectRoles = z.infer<typeof ProjectRolesSchema>;

View File

@@ -8,7 +8,7 @@ import { z } from "zod";
import { TImmutableDBKeys } from "./models"; import { TImmutableDBKeys } from "./models";
export const ProjectsSchema = z.object({ export const ProjectsSchema = z.object({
id: z.string().uuid(), id: z.string(),
name: z.string(), name: z.string(),
autoCapitalization: z.boolean().default(true).nullable().optional(), autoCapitalization: z.boolean().default(true).nullable().optional(),
orgId: z.string().uuid(), orgId: z.string().uuid(),

View File

@@ -23,15 +23,15 @@ export const SaRequestSecretsSchema = z.object({
secretReminderNote: z.string().nullable().optional(), secretReminderNote: z.string().nullable().optional(),
secretReminderRepeatDays: z.number().nullable().optional(), secretReminderRepeatDays: z.number().nullable().optional(),
skipMultilineEncoding: z.boolean().default(false).nullable().optional(), skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
algorithm: z.string().default("aes-256-gcm"), algorithm: z.string().default('aes-256-gcm'),
keyEncoding: z.string().default("utf8"), keyEncoding: z.string().default('utf8'),
metadata: z.unknown().nullable().optional(), metadata: z.unknown().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
requestId: z.string().uuid(), requestId: z.string().uuid(),
op: z.string(), op: z.string(),
secretId: z.string().uuid().nullable().optional(), secretId: z.string().uuid().nullable().optional(),
secretVersion: z.string().uuid().nullable().optional() secretVersion: z.string().uuid().nullable().optional(),
}); });
export type TSaRequestSecrets = z.infer<typeof SaRequestSecretsSchema>; export type TSaRequestSecrets = z.infer<typeof SaRequestSecretsSchema>;

View File

@@ -14,7 +14,7 @@ export const SecretBlindIndexesSchema = z.object({
saltTag: z.string(), saltTag: z.string(),
algorithm: z.string().default('aes-256-gcm'), algorithm: z.string().default('aes-256-gcm'),
keyEncoding: z.string().default('utf8'), keyEncoding: z.string().default('utf8'),
projectId: z.string().uuid(), projectId: z.string(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
}); });

View File

@@ -15,7 +15,7 @@ export const SecretTagsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
createdBy: z.string().uuid().nullable().optional(), createdBy: z.string().uuid().nullable().optional(),
projectId: z.string().uuid(), projectId: z.string(),
}); });
export type TSecretTags = z.infer<typeof SecretTagsSchema>; export type TSecretTags = z.infer<typeof SecretTagsSchema>;

View File

@@ -10,7 +10,7 @@ import { TImmutableDBKeys } from "./models";
export const SecretVersionsSchema = z.object({ export const SecretVersionsSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
version: z.number().default(1), version: z.number().default(1),
type: z.string().default("shared"), type: z.string().default('shared'),
secretBlindIndex: z.string(), secretBlindIndex: z.string(),
secretKeyCiphertext: z.string(), secretKeyCiphertext: z.string(),
secretKeyIV: z.string(), secretKeyIV: z.string(),
@@ -24,15 +24,15 @@ export const SecretVersionsSchema = z.object({
secretReminderNote: z.string().nullable().optional(), secretReminderNote: z.string().nullable().optional(),
secretReminderRepeatDays: z.number().nullable().optional(), secretReminderRepeatDays: z.number().nullable().optional(),
skipMultilineEncoding: z.boolean().default(false).nullable().optional(), skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
algorithm: z.string().default("aes-256-gcm"), algorithm: z.string().default('aes-256-gcm'),
keyEncoding: z.string().default("utf8"), keyEncoding: z.string().default('utf8'),
metadata: z.unknown().nullable().optional(), metadata: z.unknown().nullable().optional(),
envId: z.string().uuid().nullable().optional(), envId: z.string().uuid().nullable().optional(),
secretId: z.string().uuid(), secretId: z.string().uuid(),
folderId: z.string().uuid(), folderId: z.string().uuid(),
userId: z.string().uuid().nullable().optional(), userId: z.string().uuid().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
}); });
export type TSecretVersions = z.infer<typeof SecretVersionsSchema>; export type TSecretVersions = z.infer<typeof SecretVersionsSchema>;

View File

@@ -10,7 +10,7 @@ import { TImmutableDBKeys } from "./models";
export const SecretsSchema = z.object({ export const SecretsSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
version: z.number().default(1), version: z.number().default(1),
type: z.string().default("shared"), type: z.string().default('shared'),
secretBlindIndex: z.string(), secretBlindIndex: z.string(),
secretKeyCiphertext: z.string(), secretKeyCiphertext: z.string(),
secretKeyIV: z.string(), secretKeyIV: z.string(),
@@ -24,13 +24,13 @@ export const SecretsSchema = z.object({
secretReminderNote: z.string().nullable().optional(), secretReminderNote: z.string().nullable().optional(),
secretReminderRepeatDays: z.number().nullable().optional(), secretReminderRepeatDays: z.number().nullable().optional(),
skipMultilineEncoding: z.boolean().default(false).nullable().optional(), skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
algorithm: z.string().default("aes-256-gcm"), algorithm: z.string().default('aes-256-gcm'),
keyEncoding: z.string().default("utf8"), keyEncoding: z.string().default('utf8'),
metadata: z.unknown().nullable().optional(), metadata: z.unknown().nullable().optional(),
userId: z.string().uuid().nullable().optional(), userId: z.string().uuid().nullable().optional(),
folderId: z.string().uuid(), folderId: z.string().uuid(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
}); });
export type TSecrets = z.infer<typeof SecretsSchema>; export type TSecrets = z.infer<typeof SecretsSchema>;

View File

@@ -21,7 +21,7 @@ export const ServiceTokensSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
createdBy: z.string(), createdBy: z.string(),
projectId: z.string().uuid(), projectId: z.string(),
}); });
export type TServiceTokens = z.infer<typeof ServiceTokensSchema>; export type TServiceTokens = z.infer<typeof ServiceTokensSchema>;

View File

@@ -25,7 +25,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const secretSnapshots = await server.services.snapshot.listSnapshots({ const secretSnapshots = await server.services.snapshot.listSnapshots({
actor: req.permission.type, actor: req.permission.type,
@@ -54,7 +54,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const count = await server.services.snapshot.projectSecretSnapshotCount({ const count = await server.services.snapshot.projectSecretSnapshotCount({
actor: req.permission.type, actor: req.permission.type,
@@ -107,7 +107,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const auditLogs = await server.services.auditLog.listProjectAuditLogs({ const auditLogs = await server.services.auditLog.listProjectAuditLogs({
actorId: req.permission.id, actorId: req.permission.id,

View File

@@ -30,7 +30,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const secretSnapshot = await server.services.snapshot.getSnapshotData({ const secretSnapshot = await server.services.snapshot.getSnapshotData({
actor: req.permission.type, actor: req.permission.type,
@@ -54,7 +54,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const secretSnapshot = await server.services.snapshot.rollbackSnapshot({ const secretSnapshot = await server.services.snapshot.rollbackSnapshot({
actor: req.permission.type, actor: req.permission.type,

View File

@@ -0,0 +1,6 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
export type TLicenseDalFactory = ReturnType<typeof licenseDalFactory>;
export const licenseDalFactory = (db: TDbClient) => ({ });

View File

@@ -0,0 +1,34 @@
import axios from "axios";
import { getConfig } from "@app/lib/config/env";
import { TLicenseDalFactory } from "./license-dal";
type TLicenseServiceFactoryDep = {
licenseDal: TLicenseDalFactory;
};
export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
export const licenseServiceFactory = ({ licenseDal }: TLicenseServiceFactoryDep) => {
const appCfg = getConfig();
const licenceApi = axios.create({
baseURL: appCfg.LICENCE_SERVER_URL
});
const generateOrgCustomerId = async (orgName: string, email: string) => {
const {
data: { customerId }
} = await licenceApi.post("/api/license-server/v1/customers", { email, name: orgName });
return customerId;
};
const removeOrgCustomer = async (customerId: string) => {
await licenceApi.delete(`/api/license-server/v1/customers/${customerId}`);
};
return {
generateOrgCustomerId,
removeOrgCustomer
};
};

View File

@@ -1,12 +1,13 @@
import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability"; import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, unpackRules } from "@casl/ability/extra"; import { PackRule, unpackRules } from "@casl/ability/extra";
import { OrgMembershipRole, ProjectMembershipRole } from "@app/db/schemas"; import { OrgMembershipRole, ProjectMembershipRole, ServiceTokenScopes } from "@app/db/schemas";
import { conditionsMatcher } from "@app/lib/casl"; import { conditionsMatcher } from "@app/lib/casl";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
import { TOrgRoleDalFactory } from "@app/services/org/org-role-dal"; import { TOrgRoleDalFactory } from "@app/services/org/org-role-dal";
import { TProjectRoleDalFactory } from "@app/services/project-role/project-role-dal"; import { TProjectRoleDalFactory } from "@app/services/project-role/project-role-dal";
import { TServiceTokenDalFactory } from "@app/services/service-token/service-token-dal";
import { import {
orgAdminPermissions, orgAdminPermissions,
@@ -16,6 +17,7 @@ import {
} from "./org-permission"; } from "./org-permission";
import { TPermissionDalFactory } from "./permission-dal"; import { TPermissionDalFactory } from "./permission-dal";
import { import {
buildServiceTokenProjectPermission,
projectAdminPermissions, projectAdminPermissions,
projectMemberPermissions, projectMemberPermissions,
projectNoAccessPermissions, projectNoAccessPermissions,
@@ -25,6 +27,7 @@ import {
type TPermissionServiceFactoryDep = { type TPermissionServiceFactoryDep = {
orgRoleDal: Pick<TOrgRoleDalFactory, "findOne">; orgRoleDal: Pick<TOrgRoleDalFactory, "findOne">;
projectRoleDal: Pick<TProjectRoleDalFactory, "findOne">; projectRoleDal: Pick<TProjectRoleDalFactory, "findOne">;
serviceTokenDal: Pick<TServiceTokenDalFactory, "findById">;
permissionDal: TPermissionDalFactory; permissionDal: TPermissionDalFactory;
}; };
@@ -33,7 +36,8 @@ export type TPermissionServiceFactory = ReturnType<typeof permissionServiceFacto
export const permissionServiceFactory = ({ export const permissionServiceFactory = ({
permissionDal, permissionDal,
orgRoleDal, orgRoleDal,
projectRoleDal projectRoleDal,
serviceTokenDal
}: TPermissionServiceFactoryDep) => { }: TPermissionServiceFactoryDep) => {
const buildOrgPermission = (role: string, permission?: unknown) => { const buildOrgPermission = (role: string, permission?: unknown) => {
switch (role) { switch (role) {
@@ -157,10 +161,25 @@ export const permissionServiceFactory = ({
}; };
}; };
const getServiceTokenProjectPermission = async (serviceTokenId: string, projectId: string) => {
const serviceToken = await serviceTokenDal.findById(serviceTokenId);
if (serviceToken.projectId !== projectId)
throw new UnauthorizedError({
message: "Failed to find service authorization for given project"
});
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
return {
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
member: undefined
};
};
const getProjectPermission = async (type: ActorType, id: string, projectId: string) => { const getProjectPermission = async (type: ActorType, id: string, projectId: string) => {
switch (type) { switch (type) {
case ActorType.USER: case ActorType.USER:
return getUserProjectPermission(id, projectId); return getUserProjectPermission(id, projectId);
case ActorType.SERVICE:
return getServiceTokenProjectPermission(id, projectId);
case ActorType.IDENTITY: case ActorType.IDENTITY:
return getIdentityProjectPermission(id, projectId); return getIdentityProjectPermission(id, projectId);
default: default:

View File

@@ -230,6 +230,33 @@ const buildNoAccessProjectPermission = () => {
return build({ conditionsMatcher }); return build({ conditionsMatcher });
}; };
export const buildServiceTokenProjectPermission = (
scopes: Array<{ secretPath: string; environment: string }>,
permission: string[]
) => {
const canWrite = permission.includes("write");
const canRead = permission.includes("read");
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
scopes.forEach(({ secretPath, environment }) => {
if (canWrite) {
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets, { secretPath, environment });
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets, {
secretPath,
environment
});
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets, {
secretPath,
environment
});
}
if (canRead) {
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets, { secretPath, environment });
}
});
return build({ conditionsMatcher });
};
export const projectNoAccessPermissions = buildNoAccessProjectPermission(); export const projectNoAccessPermissions = buildNoAccessProjectPermission();
/** /**

View File

@@ -81,7 +81,10 @@ const envSchema = z
SECRET_SCANNING_WEBHOOK_PROXY: zpStr(z.string().optional()), SECRET_SCANNING_WEBHOOK_PROXY: zpStr(z.string().optional()),
SECRET_SCANNING_WEBHOOK_SECRET: zpStr(z.string().optional()), SECRET_SCANNING_WEBHOOK_SECRET: zpStr(z.string().optional()),
SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()), SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()),
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()) SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
// LICENCE
LICENCE_SERVER_URL: zpStr(z.string().optional()),
LICENCE_SERVER_KEY: zpStr(z.string().optional())
}) })
.transform((data) => ({ .transform((data) => ({
...data, ...data,

View File

@@ -4,9 +4,9 @@ export class DatabaseError extends Error {
error: unknown; error: unknown;
constructor({ name, error, message }: { message?: string; name: string; error: unknown }) { constructor({ name, error, message }: { message?: string; name?: string; error: unknown }) {
super(message || "Failed to execute db ops"); super(message || "Failed to execute db ops");
this.name = name; this.name = name || "DatabaseError";
this.error = error; this.error = error;
} }
} }

View File

@@ -103,6 +103,11 @@ export const isValidIpOrCidr = (ip: string): boolean => {
return false; return false;
}; };
export type TIp = {
ipAddress: string;
type: IPType;
prefix: number;
};
/** /**
* Validates the IP address [ipAddress] against the trusted IPs [trustedIps]. * Validates the IP address [ipAddress] against the trusted IPs [trustedIps].
*/ */
@@ -111,11 +116,7 @@ export const checkIPAgainstBlocklist = ({
trustedIps trustedIps
}: { }: {
ipAddress: string; ipAddress: string;
trustedIps: { trustedIps: TIp[];
ipAddress: string;
type: IPType;
prefix: number;
}[];
}) => { }) => {
const blockList = new net.BlockList(); const blockList = new net.BlockList();

View File

@@ -47,6 +47,22 @@ export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
userId: req.auth.userId userId: req.auth.userId
} }
}; };
} else if (req.auth.actor === ActorType.SERVICE) {
payload.actor = {
type: ActorType.SERVICE,
metadata: {
name: req.auth.serviceToken.name,
serviceId: req.auth.serviceTokenId
}
};
} else if (req.auth.actor === ActorType.IDENTITY) {
payload.actor = {
type: ActorType.IDENTITY,
metadata: {
name: req.auth.identityName,
identityId: req.auth.identityId
}
};
} else { } else {
throw new BadRequestError({ message: "Missing logic for other actor" }); throw new BadRequestError({ message: "Missing logic for other actor" });
} }

View File

@@ -2,6 +2,7 @@ import { FastifyRequest } from "fastify";
import fp from "fastify-plugin"; import fp from "fastify-plugin";
import jwt, { JwtPayload } from "jsonwebtoken"; import jwt, { JwtPayload } from "jsonwebtoken";
import { TServiceTokens, TUsers } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors"; import { UnauthorizedError } from "@app/lib/errors";
import { import {
@@ -10,6 +11,34 @@ import {
AuthModeJwtTokenPayload, AuthModeJwtTokenPayload,
AuthTokenType AuthTokenType
} from "@app/services/auth/auth-type"; } from "@app/services/auth/auth-type";
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
export type TAuthMode =
| {
authMode: AuthMode.JWT;
actor: ActorType.USER;
userId: string;
tokenVersionId: string; // the session id of token used
user: TUsers;
}
| {
authMode: AuthMode.API_KEY;
actor: ActorType.USER;
userId: string;
user: TUsers;
}
| {
authMode: AuthMode.SERVICE_TOKEN;
serviceToken: TServiceTokens;
actor: ActorType.SERVICE;
serviceTokenId: string;
}
| {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN;
actor: ActorType.IDENTITY;
identityId: string;
identityName: string;
};
const extractAuth = async (req: FastifyRequest, jwtSecret: string) => { const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
const apiKey = req.headers?.["x-api-key"]; const apiKey = req.headers?.["x-api-key"];
@@ -24,7 +53,7 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
return { return {
authMode: AuthMode.SERVICE_TOKEN, authMode: AuthMode.SERVICE_TOKEN,
token: authTokenValue, token: authTokenValue,
actor: ActorType.USER actor: ActorType.SERVICE
} as const; } as const;
} }
@@ -37,58 +66,62 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
actor: ActorType.USER actor: ActorType.USER
} as const; } as const;
case AuthTokenType.API_KEY: case AuthTokenType.API_KEY:
return { authMode: AuthMode.API_KEY_V2, token: decodedToken, actor: ActorType.USER } as const; return { authMode: AuthMode.API_KEY, token: decodedToken, actor: ActorType.USER } as const;
case AuthMode.SERVICE_ACCESS_TOKEN: case AuthTokenType.IDENTITY_ACCESS_TOKEN:
return { return {
authMode: AuthMode.SERVICE_ACCESS_TOKEN, authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
token: decodedToken, token: decodedToken as TIdentityAccessTokenJwtPayload,
actor: ActorType.USER actor: ActorType.IDENTITY
} as const; } as const;
default: default:
return { authMode: null, token: null } as const; return { authMode: null, token: null } as const;
} }
}; };
const getJwtIdentity = async (server: FastifyZodProvider, token: AuthModeJwtTokenPayload) => {
const session = await server.services.authToken.getUserTokenSessionById(
token.tokenVersionId,
token.userId
);
if (!session) throw new UnauthorizedError({ name: "Session not found" });
if (token.accessVersion !== session.accessVersion)
throw new UnauthorizedError({ name: "Stale session" });
const user = await server.store.user.findById(session.userId);
if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" });
return { user, tokenVersionId: token.tokenVersionId };
};
export const injectIdentity = fp(async (server: FastifyZodProvider) => { export const injectIdentity = fp(async (server: FastifyZodProvider) => {
server.decorateRequest("auth", null); server.decorateRequest("auth", null);
server.addHook("onRequest", async (req) => { server.addHook("onRequest", async (req) => {
const appCfg = getConfig(); const appCfg = getConfig();
const { authMode, token, actor } = await extractAuth(req, appCfg.JWT_AUTH_SECRET); const { authMode, token, actor } = await extractAuth(req, appCfg.JWT_AUTH_SECRET);
if (!authMode) return; if (!authMode) return;
// TODO(akhilmhdh-pg): fill in rest of auth mode logic
switch (authMode) { switch (authMode) {
case AuthMode.JWT: { case AuthMode.JWT: {
const { user, tokenVersionId } = await getJwtIdentity( const { user, tokenVersionId } =
server, await server.services.authToken.fnValidateJwtIdentity(token);
token as AuthModeJwtTokenPayload
);
req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor }; req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor };
break; break;
} }
case AuthMode.SERVICE_TOKEN: case AuthMode.IDENTITY_ACCESS_TOKEN: {
const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(
token,
req.realIp
);
req.auth = {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
actor,
identityId: identity.identityId,
identityName: identity.name
};
break; break;
case AuthMode.SERVICE_ACCESS_TOKEN: }
case AuthMode.SERVICE_TOKEN: {
const serviceToken = await server.services.serviceToken.fnValidateServiceToken(
token as string
);
req.auth = {
authMode: AuthMode.SERVICE_TOKEN as const,
serviceToken,
serviceTokenId: serviceToken.id,
actor
};
break; break;
case AuthMode.API_KEY: }
break; case AuthMode.API_KEY: {
case AuthMode.API_KEY_V2: const user = await server.services.apiKey.fnValidateApiKey(token as string);
req.auth = { authMode: AuthMode.API_KEY as const, userId: user.id, actor, user };
break; break;
}
default: default:
throw new UnauthorizedError({ name: "Unknown token strategy" }); throw new UnauthorizedError({ name: "Unknown token strategy" });
} }

View File

@@ -10,6 +10,10 @@ export const injectPermission = fp(async (server) => {
if (req.auth.actor === ActorType.USER) { if (req.auth.actor === ActorType.USER) {
req.permission = { type: ActorType.USER, id: req.auth.userId }; req.permission = { type: ActorType.USER, id: req.auth.userId };
} else if (req.auth.actor === ActorType.IDENTITY) {
req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId };
} else if (req.auth.actor === ActorType.SERVICE) {
req.permission = { type: ActorType.SERVICE, id: req.auth.serviceTokenId };
} }
}); });
}); });

View File

@@ -169,7 +169,12 @@ export const registerRoutes = async (
const gitAppOrgDal = gitAppDalFactory(db); const gitAppOrgDal = gitAppDalFactory(db);
const secretScanningDal = secretScanningDalFactory(db); const secretScanningDal = secretScanningDalFactory(db);
const permissionService = permissionServiceFactory({ permissionDal, orgRoleDal, projectRoleDal }); const permissionService = permissionServiceFactory({
permissionDal,
orgRoleDal,
projectRoleDal,
serviceTokenDal
});
const auditLogQueue = auditLogQueueServiceFactory({ auditLogDal, queueService }); const auditLogQueue = auditLogQueueServiceFactory({ auditLogDal, queueService });
const auditLogService = auditLogServiceFactory({ auditLogDal, permissionService, auditLogQueue }); const auditLogService = auditLogServiceFactory({ auditLogDal, permissionService, auditLogQueue });
const sapService = secretApprovalPolicyServiceFactory({ const sapService = secretApprovalPolicyServiceFactory({
@@ -187,7 +192,7 @@ export const registerRoutes = async (
samlConfigDal samlConfigDal
}); });
const tokenService = tokenServiceFactory({ tokenDal: authTokenDal }); const tokenService = tokenServiceFactory({ tokenDal: authTokenDal, userDal });
const userService = userServiceFactory({ userDal }); const userService = userServiceFactory({ userDal });
const loginService = authLoginServiceFactory({ userDal, smtpService, tokenService }); const loginService = authLoginServiceFactory({ userDal, smtpService, tokenService });
const passwordService = authPaswordServiceFactory({ const passwordService = authPaswordServiceFactory({
@@ -220,7 +225,7 @@ export const registerRoutes = async (
authService: loginService, authService: loginService,
serverCfgDal: superAdminDal serverCfgDal: superAdminDal
}); });
const apiKeyService = apiKeyServiceFactory({ apiKeyDal }); const apiKeyService = apiKeyServiceFactory({ apiKeyDal, userDal });
const secretScanningQueue = secretScanningQueueFactory({ const secretScanningQueue = secretScanningQueueFactory({
userDal, userDal,
@@ -425,7 +430,7 @@ export const registerRoutes = async (
user: userDal user: userDal
}); });
await server.register(injectIdentity); await server.register(injectIdentity, { userDal, serviceTokenDal });
await server.register(injectPermission); await server.register(injectPermission);
await server.register(injectAuditLogInfo); await server.register(injectAuditLogInfo);

View File

@@ -24,7 +24,9 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req, res) => { handler: async (req, res) => {
const appCfg = getConfig(); const appCfg = getConfig();
await server.services.login.logout(req.auth.userId, req.auth.tokenVersionId); if (req.auth.authMode === AuthMode.JWT) {
await server.services.login.logout(req.auth.userId, req.auth.tokenVersionId);
}
res.cookie("jid", "", { res.cookie("jid", "", {
httpOnly: true, httpOnly: true,
path: "/", path: "/",
@@ -35,6 +37,20 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
url: "/checkAuth",
method: "POST",
schema: {
response: {
200: z.object({
message: z.literal("Authenticated")
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: () => ({ message: "Authenticated" as const })
});
server.route({ server.route({
url: "/token", url: "/token",
method: "POST", method: "POST",

View File

@@ -25,7 +25,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const environment = await server.services.projectEnv.createEnvironment({ const environment = await server.services.projectEnv.createEnvironment({
actorId: req.permission.id, actorId: req.permission.id,
@@ -74,7 +74,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const { environment, old } = await server.services.projectEnv.updateEnvironment({ const { environment, old } = await server.services.projectEnv.updateEnvironment({
actorId: req.permission.id, actorId: req.permission.id,
@@ -124,7 +124,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const environment = await server.services.projectEnv.deleteEnvironment({ const environment = await server.services.projectEnv.deleteEnvironment({
actorId: req.permission.id, actorId: req.permission.id,

View File

@@ -11,8 +11,6 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => { export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
// TODO(akhilmhdh-pg): missing adding multiple user workspace refer v2/membership
server.route({ server.route({
url: "/:workspaceId/memberships", url: "/:workspaceId/memberships",
method: "GET", method: "GET",
@@ -37,7 +35,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const memberships = await server.services.projectMembership.getProjectMemberships({ const memberships = await server.services.projectMembership.getProjectMemberships({
actorId: req.permission.id, actorId: req.permission.id,
@@ -72,7 +70,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const data = await server.services.projectMembership.addUsersToProject({ const data = await server.services.projectMembership.addUsersToProject({
actorId: req.permission.id, actorId: req.permission.id,
@@ -114,7 +112,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const membership = await server.services.projectMembership.updateProjectMembership({ const membership = await server.services.projectMembership.updateProjectMembership({
actorId: req.permission.id, actorId: req.permission.id,
@@ -155,7 +153,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const membership = await server.services.projectMembership.deleteProjectMembership({ const membership = await server.services.projectMembership.deleteProjectMembership({
actorId: req.permission.id, actorId: req.permission.id,

View File

@@ -17,6 +17,7 @@ import { sanitizedServiceTokenSchema } from "../v2/service-token-router";
const projectWithEnv = ProjectsSchema.merge( const projectWithEnv = ProjectsSchema.merge(
z.object({ z.object({
_id: z.string(),
environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array() environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array()
}) })
); );

View File

@@ -11,10 +11,12 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
method: "POST", method: "POST",
schema: { schema: {
body: z.object({ body: z.object({
projectId: z.string().trim(), workspaceId: z.string().trim(),
environment: z.string().trim(), environment: z.string().trim(),
name: z.string().trim(), name: z.string().trim(),
path: z.string().trim().default("/") path: z.string().trim().default("/"),
// backward compatiability with cli
directory: z.string().trim().default("/")
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -22,23 +24,31 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const path = req.body.path || req.body.directory;
const folder = await server.services.folder.createFolder({ const folder = await server.services.folder.createFolder({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
...req.body ...req.body,
projectId: req.body.workspaceId,
path
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
projectId: req.body.projectId, projectId: req.body.workspaceId,
event: { event: {
type: EventType.CREATE_FOLDER, type: EventType.CREATE_FOLDER,
metadata: { metadata: {
environment: req.body.environment, environment: req.body.environment,
folderId: folder.id, folderId: folder.id,
folderName: folder.name, folderName: folder.name,
folderPath: req.body.path folderPath: path
} }
} }
}); });
@@ -51,13 +61,16 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
method: "PATCH", method: "PATCH",
schema: { schema: {
params: z.object({ params: z.object({
// old way this was name
folderId: z.string() folderId: z.string()
}), }),
body: z.object({ body: z.object({
projectId: z.string().trim(), workspaceId: z.string().trim(),
environment: z.string().trim(), environment: z.string().trim(),
name: z.string().trim(), name: z.string().trim(),
path: z.string().trim().default("/") path: z.string().trim().default("/"),
// backward compatiability with cli
directory: z.string().trim().default("/")
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -65,23 +78,31 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const path = req.body.path || req.body.directory;
const { folder, old } = await server.services.folder.updateFolder({ const { folder, old } = await server.services.folder.updateFolder({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
...req.body, ...req.body,
id: req.params.folderId projectId: req.body.workspaceId,
id: req.params.folderId,
path
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
projectId: req.body.projectId, projectId: req.body.workspaceId,
event: { event: {
type: EventType.UPDATE_FOLDER, type: EventType.UPDATE_FOLDER,
metadata: { metadata: {
environment: req.body.environment, environment: req.body.environment,
folderId: folder.id, folderId: folder.id,
folderPath: req.body.path, folderPath: path,
newFolderName: folder.name, newFolderName: folder.name,
oldFolderName: old.name oldFolderName: old.name
} }
@@ -99,9 +120,11 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
folderId: z.string() folderId: z.string()
}), }),
body: z.object({ body: z.object({
projectId: z.string().trim(), workspaceId: z.string().trim(),
environment: z.string().trim(), environment: z.string().trim(),
path: z.string().trim().default("/") path: z.string().trim().default("/"),
// keep this here as cli need directory
directory: z.string().trim().default("/")
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -109,23 +132,31 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const path = req.body.path || req.body.directory;
const folder = await server.services.folder.deleteFolder({ const folder = await server.services.folder.deleteFolder({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
...req.body, ...req.body,
id: req.params.folderId projectId: req.body.workspaceId,
id: req.params.folderId,
path
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
projectId: req.body.projectId, projectId: req.body.workspaceId,
event: { event: {
type: EventType.DELETE_FOLDER, type: EventType.DELETE_FOLDER,
metadata: { metadata: {
environment: req.body.environment, environment: req.body.environment,
folderId: folder.id, folderId: folder.id,
folderPath: req.body.path, folderPath: path,
folderName: folder.name folderName: folder.name
} }
} }
@@ -139,9 +170,11 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
method: "GET", method: "GET",
schema: { schema: {
querystring: z.object({ querystring: z.object({
projectId: z.string().trim(), workspaceId: z.string().trim(),
environment: z.string().trim(), environment: z.string().trim(),
path: z.string().trim().default("/") path: z.string().trim().default("/"),
// backward compatiability with cli
directory: z.string().trim().default("/")
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -149,12 +182,20 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const path = req.query.path || req.query.directory;
const folders = await server.services.folder.getFolders({ const folders = await server.services.folder.getFolders({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
...req.query ...req.query,
projectId: req.query.workspaceId,
path
}); });
return { folders }; return { folders };
} }

View File

@@ -11,7 +11,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
method: "POST", method: "POST",
schema: { schema: {
body: z.object({ body: z.object({
projectId: z.string().trim(), workspaceId: z.string().trim(),
environment: z.string().trim(), environment: z.string().trim(),
path: z.string().trim().default("/"), path: z.string().trim().default("/"),
import: z.object({ import: z.object({
@@ -30,18 +30,24 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const secretImport = await server.services.secretImport.createImport({ const secretImport = await server.services.secretImport.createImport({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
...req.body, ...req.body,
projectId: req.body.workspaceId,
data: req.body.import data: req.body.import
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
projectId: req.body.projectId, projectId: req.body.workspaceId,
event: { event: {
type: EventType.CREATE_SECRET_IMPORT, type: EventType.CREATE_SECRET_IMPORT,
metadata: { metadata: {
@@ -66,7 +72,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
secretImportId: z.string().trim() secretImportId: z.string().trim()
}), }),
body: z.object({ body: z.object({
projectId: z.string().trim(), workspaceId: z.string().trim(),
environment: z.string().trim(), environment: z.string().trim(),
path: z.string().trim().default("/"), path: z.string().trim().default("/"),
import: z.object({ import: z.object({
@@ -86,19 +92,25 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const secretImport = await server.services.secretImport.updateImport({ const secretImport = await server.services.secretImport.updateImport({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
id: req.params.secretImportId, id: req.params.secretImportId,
...req.body, ...req.body,
projectId: req.body.workspaceId,
data: req.body.import data: req.body.import
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
projectId: req.body.projectId, projectId: req.body.workspaceId,
event: { event: {
type: EventType.UPDATE_SECRET_IMPORT, type: EventType.UPDATE_SECRET_IMPORT,
metadata: { metadata: {
@@ -123,7 +135,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
secretImportId: z.string().trim() secretImportId: z.string().trim()
}), }),
body: z.object({ body: z.object({
projectId: z.string().trim(), workspaceId: z.string().trim(),
environment: z.string().trim(), environment: z.string().trim(),
path: z.string().trim().default("/") path: z.string().trim().default("/")
}), }),
@@ -138,18 +150,24 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const secretImport = await server.services.secretImport.deleteImport({ const secretImport = await server.services.secretImport.deleteImport({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
id: req.params.secretImportId, id: req.params.secretImportId,
...req.body ...req.body,
projectId: req.body.workspaceId
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
projectId: req.body.projectId, projectId: req.body.workspaceId,
event: { event: {
type: EventType.DELETE_SECRET_IMPORT, type: EventType.DELETE_SECRET_IMPORT,
metadata: { metadata: {
@@ -171,7 +189,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
method: "GET", method: "GET",
schema: { schema: {
querystring: z.object({ querystring: z.object({
projectId: z.string().trim(), workspaceId: z.string().trim(),
environment: z.string().trim(), environment: z.string().trim(),
path: z.string().trim().default("/") path: z.string().trim().default("/")
}), }),
@@ -188,17 +206,23 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const secretImports = await server.services.secretImport.getImports({ const secretImports = await server.services.secretImport.getImports({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
...req.query ...req.query,
projectId: req.query.workspaceId
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
projectId: req.query.projectId, projectId: req.query.workspaceId,
event: { event: {
type: EventType.GET_SECRET_IMPORTS, type: EventType.GET_SECRET_IMPORTS,
metadata: { metadata: {
@@ -217,7 +241,7 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
method: "GET", method: "GET",
schema: { schema: {
querystring: z.object({ querystring: z.object({
projectId: z.string().trim(), workspaceId: z.string().trim(),
environment: z.string().trim(), environment: z.string().trim(),
path: z.string().trim().default("/") path: z.string().trim().default("/")
}), }),
@@ -238,12 +262,18 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const importedSecrets = await server.services.secretImport.getSecretsFromImports({ const importedSecrets = await server.services.secretImport.getSecretsFromImports({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
...req.query ...req.query,
projectId: req.query.workspaceId
}); });
return { secrets: importedSecrets }; return { secrets: importedSecrets };
} }

View File

@@ -34,7 +34,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const users = await server.services.org.findAllOrgMembers( const users = await server.services.org.findAllOrgMembers(
req.auth.userId, req.auth.userId,
@@ -58,7 +58,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const membership = await server.services.org.updateOrgMembership({ const membership = await server.services.org.updateOrgMembership({
userId: req.auth.userId, userId: req.auth.userId,
@@ -81,7 +81,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const membership = await server.services.org.deleteOrgMembership({ const membership = await server.services.org.deleteOrgMembership({
userId: req.auth.userId, userId: req.auth.userId,

View File

@@ -42,7 +42,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const secrets = await server.services.secret.getSecrets({ const secrets = await server.services.secret.getSecrets({
actorId: req.permission.id, actorId: req.permission.id,
@@ -92,7 +97,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const secret = await server.services.secret.getASecret({ const secret = await server.services.secret.getASecret({
actorId: req.permission.id, actorId: req.permission.id,
@@ -157,7 +167,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
]) ])
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const { const {
workspaceId: projectId, workspaceId: projectId,
@@ -307,7 +322,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
]) ])
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const { const {
secretValueCiphertext, secretValueCiphertext,
@@ -452,7 +472,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
]) ])
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const { secretPath, type, workspaceId: projectId, secretId, environment } = req.body; const { secretPath, type, workspaceId: projectId, secretId, environment } = req.body;
if (req.body.type !== SecretType.Personal && req.permission.type === ActorType.USER) { if (req.body.type !== SecretType.Personal && req.permission.type === ActorType.USER) {
@@ -564,7 +589,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
]) ])
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const { environment, workspaceId: projectId, secretPath, secrets: inputSecrets } = req.body; const { environment, workspaceId: projectId, secretPath, secrets: inputSecrets } = req.body;
if (req.permission.type === ActorType.USER) { if (req.permission.type === ActorType.USER) {
@@ -672,7 +702,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
]) ])
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const { environment, workspaceId: projectId, secretPath, secrets: inputSecrets } = req.body; const { environment, workspaceId: projectId, secretPath, secrets: inputSecrets } = req.body;
if (req.permission.type === ActorType.USER) { if (req.permission.type === ActorType.USER) {
@@ -768,7 +803,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
]) ])
} }
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]), onRequest: verifyAuth([
AuthMode.JWT,
AuthMode.API_KEY,
AuthMode.SERVICE_TOKEN,
AuthMode.IDENTITY_ACCESS_TOKEN
]),
handler: async (req) => { handler: async (req) => {
const { environment, workspaceId: projectId, secretPath, secrets: inputSecrets } = req.body; const { environment, workspaceId: projectId, secretPath, secrets: inputSecrets } = req.body;
if (req.permission.type === ActorType.USER) { if (req.permission.type === ActorType.USER) {

View File

@@ -4,19 +4,21 @@ import bcrypt from "bcrypt";
import { TApiKeys } from "@app/db/schemas/api-keys"; import { TApiKeys } from "@app/db/schemas/api-keys";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { TUserDalFactory } from "../user/user-dal";
import { TApiKeyDalFactory } from "./api-key-dal"; import { TApiKeyDalFactory } from "./api-key-dal";
type TApiKeyServiceFactoryDep = { type TApiKeyServiceFactoryDep = {
apiKeyDal: TApiKeyDalFactory; apiKeyDal: TApiKeyDalFactory;
userDal: Pick<TUserDalFactory, "findById">;
}; };
export type TApiKeyServiceFactory = ReturnType<typeof apiKeyServiceFactory>; export type TApiKeyServiceFactory = ReturnType<typeof apiKeyServiceFactory>;
const formatApiKey = ({ secretHash, ...data }: TApiKeys) => data; const formatApiKey = ({ secretHash, ...data }: TApiKeys) => data;
export const apiKeyServiceFactory = ({ apiKeyDal }: TApiKeyServiceFactoryDep) => { export const apiKeyServiceFactory = ({ apiKeyDal, userDal }: TApiKeyServiceFactoryDep) => {
const getMyApiKeys = async (userId: string) => { const getMyApiKeys = async (userId: string) => {
const apiKeys = await apiKeyDal.find({ userId }); const apiKeys = await apiKeyDal.find({ userId });
return apiKeys.map((key) => formatApiKey(key)); return apiKeys.map((key) => formatApiKey(key));
@@ -48,9 +50,27 @@ export const apiKeyServiceFactory = ({ apiKeyDal }: TApiKeyServiceFactoryDep) =>
return formatApiKey(apiKeyData); return formatApiKey(apiKeyData);
}; };
const fnValidateApiKey = async (token: string) => {
const [, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>token.split(".", 3);
const apiKey = await apiKeyDal.findById(TOKEN_IDENTIFIER);
if (!apiKey) throw new UnauthorizedError();
if (apiKey.expiresAt && new Date(apiKey.expiresAt) < new Date()) {
await apiKeyDal.deleteById(apiKey.id);
throw new UnauthorizedError();
}
const isMatch = await bcrypt.compare(TOKEN_SECRET, apiKey.secretHash);
if (!isMatch) throw new UnauthorizedError();
await apiKeyDal.updateById(apiKey.id, { lastUsed: new Date() });
const user = await userDal.findById(apiKey.userId);
return user;
};
return { return {
getMyApiKeys, getMyApiKeys,
createApiKey, createApiKey,
deleteApiKey deleteApiKey,
fnValidateApiKey
}; };
}; };

View File

@@ -4,7 +4,10 @@ import bcrypt from "bcrypt";
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";
import { UnauthorizedError } from "@app/lib/errors";
import { AuthModeJwtTokenPayload } from "../auth/auth-type";
import { TUserDalFactory } from "../user/user-dal";
import { TTokenDalFactory } from "./auth-token-dal"; import { TTokenDalFactory } from "./auth-token-dal";
import { import {
TCreateTokenForUserDTO, TCreateTokenForUserDTO,
@@ -15,7 +18,7 @@ import {
type TAuthTokenServiceFactoryDep = { type TAuthTokenServiceFactoryDep = {
tokenDal: TTokenDalFactory; tokenDal: TTokenDalFactory;
// adjust the expiry from env through here userDal: Pick<TUserDalFactory, "findById">;
}; };
export type TAuthTokenServiceFactory = ReturnType<typeof tokenServiceFactory>; export type TAuthTokenServiceFactory = ReturnType<typeof tokenServiceFactory>;
@@ -56,7 +59,7 @@ export const getTokenConfig = (tokenType: TokenType) => {
} }
}; };
export const tokenServiceFactory = ({ tokenDal }: TAuthTokenServiceFactoryDep) => { export const tokenServiceFactory = ({ tokenDal, userDal }: TAuthTokenServiceFactoryDep) => {
const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => { const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => {
const { token, ...tkCfg } = getTokenConfig(type); const { token, ...tkCfg } = getTokenConfig(type);
const appCfg = getConfig(); const appCfg = getConfig();
@@ -122,26 +125,43 @@ export const tokenServiceFactory = ({ tokenDal }: TAuthTokenServiceFactoryDep) =
return session; return session;
}; };
const getUserTokenSessionById = async (id: string, userId: string) =>
tokenDal.findOneTokenSession({ id, userId });
const clearTokenSessionById = async ( const clearTokenSessionById = async (
userId: string, userId: string,
sessionId: string sessionId: string
): Promise<TAuthTokenSessions | undefined> => ): Promise<TAuthTokenSessions | undefined> =>
tokenDal.incrementTokenSessionVersion(userId, sessionId); tokenDal.incrementTokenSessionVersion(userId, sessionId);
const getUserTokenSessionById = async (id: string, userId: string) =>
tokenDal.findOneTokenSession({ id, userId });
const getTokenSessionByUser = async (userId: string) => tokenDal.findTokenSessions({ userId }); const getTokenSessionByUser = async (userId: string) => tokenDal.findTokenSessions({ userId });
const revokeAllMySessions = async (userId: string) => tokenDal.deleteTokenSession({ userId }); const revokeAllMySessions = async (userId: string) => tokenDal.deleteTokenSession({ userId });
// to parse jwt identity in inject identity plugin
const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload) => {
const session = await tokenDal.findOneTokenSession({
id: token.tokenVersionId,
userId: token.userId
});
if (!session) throw new UnauthorizedError({ name: "Session not found" });
if (token.accessVersion !== session.accessVersion)
throw new UnauthorizedError({ name: "Stale session" });
const user = await userDal.findById(session.userId);
if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" });
return { user, tokenVersionId: token.tokenVersionId };
};
return { return {
createTokenForUser, createTokenForUser,
validateTokenForUser, validateTokenForUser,
getUserTokenSession, getUserTokenSession,
clearTokenSessionById, clearTokenSessionById,
getUserTokenSessionById,
getTokenSessionByUser, getTokenSessionByUser,
revokeAllMySessions revokeAllMySessions,
fnValidateJwtIdentity,
getUserTokenSessionById
}; };
}; };

View File

@@ -23,9 +23,7 @@ export enum AuthTokenType {
export enum AuthMode { export enum AuthMode {
JWT = "jwt", JWT = "jwt",
SERVICE_TOKEN = "serviceToken", SERVICE_TOKEN = "serviceToken",
SERVICE_ACCESS_TOKEN = "serviceAccessToken",
API_KEY = "apiKey", API_KEY = "apiKey",
API_KEY_V2 = "apiKeyV2",
IDENTITY_ACCESS_TOKEN = "identityAccessToken" IDENTITY_ACCESS_TOKEN = "identityAccessToken"
} }

View File

@@ -1,10 +1,45 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas"; import { TableName,TIdentityAccessTokens } from "@app/db/schemas";
import { ormify } from "@app/lib/knex"; import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TIdentityAccessTokenDalFactory = ReturnType<typeof identityAccessTokenDalFactory>; export type TIdentityAccessTokenDalFactory = ReturnType<typeof identityAccessTokenDalFactory>;
export const identityAccessTokenDalFactory = (db: TDbClient) => { export const identityAccessTokenDalFactory = (db: TDbClient) => {
const identityAccessTokenOrm = ormify(db, TableName.IdentityAccessToken); const identityAccessTokenOrm = ormify(db, TableName.IdentityAccessToken);
return identityAccessTokenOrm;
const findOne = async (filter: Partial<TIdentityAccessTokens>, tx?: Knex) => {
try {
const doc = await (tx || db)(TableName.IdentityAccessToken)
.where(filter)
.join(
TableName.Identity,
`${TableName.Identity}.id`,
`${TableName.IdentityAccessToken}.identityId`
)
.leftJoin(
TableName.IdentityUaClientSecret,
`${TableName.IdentityAccessToken}.identityUAClientSecretId`,
`${TableName.IdentityUaClientSecret}.id`
)
.leftJoin(
TableName.IdentityUniversalAuth,
`${TableName.IdentityUaClientSecret}.identityUAId`,
`${TableName.IdentityUniversalAuth}.id`
)
.select(selectAllTableCols(TableName.IdentityAccessToken))
.select(
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityUniversalAuth),
db.ref("name").withSchema(TableName.Identity)
)
.first();
return doc;
} catch (error) {
throw new DatabaseError({ error, name: "IdAccessTokenFindOne" });
}
};
return { ...identityAccessTokenOrm, findOne };
}; };

View File

@@ -1,11 +1,16 @@
import jwt, { JwtPayload } from "jsonwebtoken"; import jwt, { JwtPayload } from "jsonwebtoken";
import { TableName, TIdentityAccessTokens } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { checkIPAgainstBlocklist, TIp } from "@app/lib/ip";
import { AuthTokenType } from "../auth/auth-type"; import { AuthTokenType } from "../auth/auth-type";
import { TIdentityAccessTokenDalFactory } from "./identity-access-token-dal"; import { TIdentityAccessTokenDalFactory } from "./identity-access-token-dal";
import { TRenewAccessTokenDTO } from "./identity-access-token-types"; import {
TIdentityAccessTokenJwtPayload,
TRenewAccessTokenDTO
} from "./identity-access-token-types";
type TIdentityAccessTokenServiceFactoryDep = { type TIdentityAccessTokenServiceFactoryDep = {
identityAccessTokenDal: TIdentityAccessTokenDalFactory; identityAccessTokenDal: TIdentityAccessTokenDalFactory;
@@ -18,25 +23,22 @@ export type TIdentityAccessTokenServiceFactory = ReturnType<
export const identityAccessTokenServiceFactory = ({ export const identityAccessTokenServiceFactory = ({
identityAccessTokenDal identityAccessTokenDal
}: TIdentityAccessTokenServiceFactoryDep) => { }: TIdentityAccessTokenServiceFactoryDep) => {
const renewAccessToken = async ({ accessToken }: TRenewAccessTokenDTO) => { const validateAccessTokenExp = async (identityAccessToken: TIdentityAccessTokens) => {
const appCfg = getConfig();
const decodedToken = jwt.verify(accessToken, appCfg.JWT_AUTH_SECRET) as JwtPayload;
if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN)
throw new UnauthorizedError();
const identityAccessToken = await identityAccessTokenDal.findOne({
id: decodedToken.identityAccessTokenId,
isAccessTokenRevoked: false
});
if (!identityAccessToken) throw new UnauthorizedError();
const { const {
accessTokenTTL, accessTokenTTL,
accessTokenNumUses,
accessTokenNumUsesLimit,
accessTokenLastRenewedAt, accessTokenLastRenewedAt,
accessTokenMaxTTL, accessTokenMaxTTL,
createdAt: accessTokenCreatedAt createdAt: accessTokenCreatedAt
} = identityAccessToken; } = identityAccessToken;
if (accessTokenNumUses > 0 && accessTokenNumUses >= accessTokenNumUsesLimit) {
throw new BadRequestError({
message: "Unable to renew because access token number of uses limit reached"
});
}
// ttl check // ttl check
if (accessTokenTTL > 0) { if (accessTokenTTL > 0) {
const currentDate = new Date(); const currentDate = new Date();
@@ -81,6 +83,22 @@ export const identityAccessTokenServiceFactory = ({
message: "Failed to renew MI access token past its Max TTL expiration" message: "Failed to renew MI access token past its Max TTL expiration"
}); });
} }
};
const renewAccessToken = async ({ accessToken }: TRenewAccessTokenDTO) => {
const appCfg = getConfig();
const decodedToken = jwt.verify(accessToken, appCfg.JWT_AUTH_SECRET) as JwtPayload;
if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN)
throw new UnauthorizedError();
const identityAccessToken = await identityAccessTokenDal.findOne({
[`${TableName.IdentityAccessToken}.id` as "id"]: decodedToken.identityAccessTokenId,
isAccessTokenRevoked: false
});
if (!identityAccessToken) throw new UnauthorizedError();
validateAccessTokenExp(identityAccessToken);
const updatedIdentityAccessToken = await identityAccessTokenDal.updateById( const updatedIdentityAccessToken = await identityAccessTokenDal.updateById(
identityAccessToken.id, identityAccessToken.id,
@@ -92,5 +110,26 @@ export const identityAccessTokenServiceFactory = ({
return { accessToken, identityAccessToken: updatedIdentityAccessToken }; return { accessToken, identityAccessToken: updatedIdentityAccessToken };
}; };
return { renewAccessToken }; const fnValidateIdentityAccessToken = async (
token: TIdentityAccessTokenJwtPayload,
ipAddress?: string
) => {
const identityAccessToken = await identityAccessTokenDal.findOne({
[`${TableName.IdentityAccessToken}.id` as "id"]: token.identityAccessTokenId,
isAccessTokenRevoked: false
});
if (!identityAccessToken) throw new UnauthorizedError();
if (ipAddress) {
checkIPAgainstBlocklist({
ipAddress,
trustedIps: identityAccessToken?.accessTokenTrustedIps as TIp[]
});
}
validateAccessTokenExp(identityAccessToken);
return identityAccessToken;
};
return { renewAccessToken, fnValidateIdentityAccessToken };
}; };

View File

@@ -1,3 +1,10 @@
export type TRenewAccessTokenDTO = { export type TRenewAccessTokenDTO = {
accessToken: string; accessToken: string;
}; };
export type TIdentityAccessTokenJwtPayload = {
identityId: string;
clientSecretId: string;
identityAccessTokenId: string;
authTokenType: string;
};

View File

@@ -19,6 +19,7 @@ import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDalFactory } from "../identity/identity-dal"; import { TIdentityDalFactory } from "../identity/identity-dal";
import { TIdentityOrgDalFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDalFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDalFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDalFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TIdentityUaClientSecretDalFactory } from "./identity-ua-client-secret-dal"; import { TIdentityUaClientSecretDalFactory } from "./identity-ua-client-secret-dal";
import { TIdentityUaDalFactory } from "./identity-ua-dal"; import { TIdentityUaDalFactory } from "./identity-ua-dal";
import { import {
@@ -123,7 +124,7 @@ export const identityUaServiceFactory = ({
clientSecretId: validClientSecretInfo.id, clientSecretId: validClientSecretInfo.id,
identityAccessTokenId: identityAccessToken.id, identityAccessTokenId: identityAccessToken.id,
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
}, } as TIdentityAccessTokenJwtPayload,
appCfg.JWT_AUTH_SECRET, appCfg.JWT_AUTH_SECRET,
{ {
expiresIn: expiresIn:

View File

@@ -1,16 +1,14 @@
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { TableName, TProjects } from "@app/db/schemas"; import { ProjectsSchema, TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { mergeOneToManyRelation, ormify } from "@app/lib/knex"; import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
export type TProjectDalFactory = ReturnType<typeof projectDalFactory>; export type TProjectDalFactory = ReturnType<typeof projectDalFactory>;
export const projectDalFactory = (db: TDbClient) => { export const projectDalFactory = (db: TDbClient) => {
const projectOrm = ormify(db, TableName.Project); const projectOrm = ormify(db, TableName.Project);
const findAllProjects = async ( const findAllProjects = async (userId: string) => {
userId: string
): Promise<(TProjects & { environments: { id: string; slug: string; name: string }[] })[]> => {
try { try {
const workspaces = await db(TableName.ProjectMembership) const workspaces = await db(TableName.ProjectMembership)
.where({ userId }) .where({ userId })
@@ -25,34 +23,35 @@ export const projectDalFactory = (db: TDbClient) => {
`${TableName.Project}.id` `${TableName.Project}.id`
) )
.select( .select(
db.ref("id").withSchema(TableName.Project), selectAllTableCols(TableName.Project),
db.ref("name").withSchema(TableName.Project), db.ref("id").withSchema(TableName.Project).as("_id"),
db.ref("autoCapitalization").withSchema(TableName.Project),
db.ref("orgId").withSchema(TableName.Project),
db.ref("createdAt").withSchema(TableName.Project),
db.ref("updatedAt").withSchema(TableName.Project),
db.ref("id").withSchema(TableName.Environment).as("envId"), db.ref("id").withSchema(TableName.Environment).as("envId"),
db.ref("slug").withSchema(TableName.Environment).as("envSlug"), db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
db.ref("name").withSchema(TableName.Environment).as("envName") db.ref("name").withSchema(TableName.Environment).as("envName")
) )
.orderBy("createdAt", "asc", "last"); .orderBy("createdAt", "asc", "last");
return mergeOneToManyRelation( return sqlNestRelationships({
workspaces, data: workspaces,
"id", key: "id",
({ envId, envSlug, envName, ...data }) => data, parentMapper: ({ _id, ...el }) => ({ _id, ...ProjectsSchema.parse(el) }),
({ envName, envSlug, envId }) => ({ id: envId, slug: envSlug, name: envName }), childrenMapper: [
"environments" {
); key: "envId",
label: "environments" as const,
mapper: ({ envId: id, envSlug: slug, envName: name }) => ({
id,
slug,
name
})
}
]
});
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Find all projects" }); throw new DatabaseError({ error, name: "Find all projects" });
} }
}; };
const findProjectById = async ( const findProjectById = async (id: string) => {
id: string
): Promise<
(TProjects & { environments: { id: string; slug: string; name: string }[] }) | undefined
> => {
try { try {
const workspaces = await db(TableName.ProjectMembership) const workspaces = await db(TableName.ProjectMembership)
.where(`${TableName.Project}.id`, id) .where(`${TableName.Project}.id`, id)
@@ -67,24 +66,28 @@ export const projectDalFactory = (db: TDbClient) => {
`${TableName.Project}.id` `${TableName.Project}.id`
) )
.select( .select(
db.ref("id").withSchema(TableName.Project), selectAllTableCols(TableName.Project),
db.ref("name").withSchema(TableName.Project), db.ref("id").withSchema(TableName.Project).as("_id"),
db.ref("autoCapitalization").withSchema(TableName.Project),
db.ref("orgId").withSchema(TableName.Project),
db.ref("createdAt").withSchema(TableName.Project),
db.ref("updatedAt").withSchema(TableName.Project),
db.ref("id").withSchema(TableName.Environment).as("envId"), db.ref("id").withSchema(TableName.Environment).as("envId"),
db.ref("slug").withSchema(TableName.Environment).as("envSlug"), db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
db.ref("name").withSchema(TableName.Environment).as("envName") db.ref("name").withSchema(TableName.Environment).as("envName")
); );
const [doc] = mergeOneToManyRelation( return sqlNestRelationships({
workspaces, data: workspaces,
"id", key: "id",
({ envId, envSlug, envName, ...data }) => data, parentMapper: ({ _id, ...el }) => ({ _id, ...ProjectsSchema.parse(el) }),
({ envName, envSlug, envId }) => ({ id: envId, slug: envSlug, name: envName }), childrenMapper: [
"environments" {
); key: "envId",
return doc; label: "environments" as const,
mapper: ({ envId, envSlug: slug, envName: name }) => ({
id: envId,
slug,
name
})
}
]
})?.[0];
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Find all projects" }); throw new DatabaseError({ error, name: "Find all projects" });
} }

View File

@@ -90,7 +90,8 @@ export const projectServiceFactory = ({
envs.map(({ id }) => ({ name: ROOT_FOLDER_NAME, envId: id, version: 1 })), envs.map(({ id }) => ({ name: ROOT_FOLDER_NAME, envId: id, version: 1 })),
tx tx
); );
return { ...project, environments: envs }; // _id for backward compat
return { ...project, environments: envs, _id: project.id };
}); });
return newProject; return newProject;

View File

@@ -97,12 +97,17 @@ export const secretFolderServiceFactory = ({
const env = await projectEnvDal.findOne({ projectId, slug: environment }); const env = await projectEnvDal.findOne({ projectId, slug: environment });
if (!env) if (!env)
throw new BadRequestError({ message: "Environment not found", name: "Update folder" }); throw new BadRequestError({ message: "Environment not found", name: "Update folder" });
const folder = await folderDal.findOne({ envId: env.id, id, parentId: parentFolder.id }); let folder = await folderDal.findOne({ envId: env.id, id, parentId: parentFolder.id });
// now folder api accepts id based change
// this is for cli and when cli removes this will remove this logic
if (!folder) {
folder = await folderDal.findOne({ envId: env.id, name: id, parentId: parentFolder.id });
}
if (!folder) throw new BadRequestError({ message: "Folder not found" }); if (!folder) throw new BadRequestError({ message: "Folder not found" });
const newFolder = await folderDal.transaction(async (tx) => { const newFolder = await folderDal.transaction(async (tx) => {
const [doc] = await folderDal.update( const [doc] = await folderDal.update(
{ envId: env.id, id, parentId: parentFolder.id }, { envId: env.id, id: folder.id, parentId: parentFolder.id },
{ name }, { name },
tx tx
); );

View File

@@ -9,7 +9,7 @@ import {
ProjectPermissionSub ProjectPermissionSub
} from "@app/ee/services/permission/project-permission"; } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { ActorType } from "../auth/auth-type"; import { ActorType } from "../auth/auth-type";
import { TProjectEnvDalFactory } from "../project-env/project-env-dal"; import { TProjectEnvDalFactory } from "../project-env/project-env-dal";
@@ -130,10 +130,29 @@ export const serviceTokenServiceFactory = ({
return tokens; return tokens;
}; };
const fnValidateServiceToken = async (token: string) => {
const [, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>token.split(".", 3);
const serviceToken = await serviceTokenDal.findById(TOKEN_IDENTIFIER);
if (!serviceToken) throw new UnauthorizedError();
if (serviceToken.expiresAt && new Date(serviceToken.expiresAt) < new Date()) {
await serviceTokenDal.deleteById(serviceToken.id);
throw new UnauthorizedError({ message: "failed to authenticate expired service token" });
}
const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceToken.secretHash);
if (!isMatch) throw new UnauthorizedError();
const updatedToken = await serviceTokenDal.updateById(serviceToken.id, {
lastUsed: new Date()
});
return updatedToken;
};
return { return {
createServiceToken, createServiceToken,
deleteServiceToken, deleteServiceToken,
getServiceToken, getServiceToken,
getProjectServiceTokens getProjectServiceTokens,
fnValidateServiceToken
}; };
}; };

View File

@@ -69,19 +69,21 @@ export const extractAuthMode = async ({
return { authMode: AuthMode.SERVICE_TOKEN, authTokenValue }; return { authMode: AuthMode.SERVICE_TOKEN, authTokenValue };
} }
switch (decodedToken.authTokenType) { const decodedToken = <jwt.AuthnJwtPayload>jwt.verify(authTokenValue, await getAuthSecret());
case AuthTokenType.ACCESS_TOKEN:
return { authMode: AuthMode.JWT, authTokenValue }; switch (decodedToken.authTokenType) {
case AuthTokenType.API_KEY: case AuthTokenType.ACCESS_TOKEN:
return { authMode: AuthMode.API_KEY_V2, authTokenValue }; return { authMode: AuthMode.JWT, authTokenValue };
case AuthTokenType.IDENTITY_ACCESS_TOKEN: case AuthTokenType.API_KEY:
return { authMode: AuthMode.IDENTITY_ACCESS_TOKEN, authTokenValue }; return { authMode: AuthMode.API_KEY_V2, authTokenValue };
default: case AuthTokenType.IDENTITY_ACCESS_TOKEN:
throw UnauthorizedRequestError({ return { authMode: AuthMode.IDENTITY_ACCESS_TOKEN, authTokenValue };
message: "Failed to authenticate unknown authentication method" default:
}); throw UnauthorizedRequestError({
} message: "Failed to authenticate unknown authentication method"
} });
}
};
export const getAuthData = async ({ export const getAuthData = async ({
authMode, authMode,
@@ -97,112 +99,9 @@ export const getAuthData = async ({
authTokenValue authTokenValue
}); });
switch (authMode) {
case AuthMode.SERVICE_TOKEN: {
const serviceTokenData = await validateServiceTokenV2({
authTokenValue
});
return {
actor: {
type: ActorType.SERVICE,
metadata: {
serviceId: serviceTokenData._id.toString(),
name: serviceTokenData.name
}
},
authPayload: serviceTokenData,
ipAddress,
userAgent,
userAgentType
}
}
case AuthMode.IDENTITY_ACCESS_TOKEN: {
const identity = await validateIdentity({
authTokenValue,
ipAddress
});
return {
actor: {
type: ActorType.IDENTITY,
metadata: {
identityId: identity._id.toString(),
name: identity.name
}
},
authPayload: identity,
ipAddress,
userAgent,
userAgentType
}
}
case AuthMode.API_KEY: {
const user = await validateAPIKey({
authTokenValue
});
return {
actor: {
type: ActorType.USER,
metadata: {
userId: user._id.toString(),
email: user.email
}
},
authPayload: user,
ipAddress,
userAgent,
userAgentType
}
}
case AuthMode.API_KEY_V2: {
const user = await validateAPIKeyV2({
authTokenValue
});
return {
actor: {
type: ActorType.USER,
metadata: {
userId: user._id.toString(),
email: user.email
}
},
authPayload: user,
ipAddress,
userAgent,
userAgentType
}
}
case AuthMode.JWT: {
const user = await validateJWT({
authTokenValue
});
return {
actor: {
type: ActorType.USER,
metadata: {
userId: user._id.toString(),
email: user.email
}
},
authPayload: user,
ipAddress,
userAgent,
userAgentType
}
}
}
case AuthMode.SERVICE_ACCESS_TOKEN: {
const serviceTokenData = await validateServiceTokenV3({
authTokenValue
});
return { return {
actor: { actor: {
type: ActorType.SERVICE_V3, type: ActorType.SERVICE,
metadata: { metadata: {
serviceId: serviceTokenData._id.toString(), serviceId: serviceTokenData._id.toString(),
name: serviceTokenData.name name: serviceTokenData.name
@@ -214,6 +113,26 @@ export const getAuthData = async ({
userAgentType userAgentType
}; };
} }
case AuthMode.IDENTITY_ACCESS_TOKEN: {
const identity = await validateIdentity({
authTokenValue,
ipAddress
});
return {
actor: {
type: ActorType.IDENTITY,
metadata: {
identityId: identity._id.toString(),
name: identity.name
}
},
authPayload: identity,
ipAddress,
userAgent,
userAgentType
};
}
case AuthMode.API_KEY: { case AuthMode.API_KEY: {
const user = await validateAPIKey({ const user = await validateAPIKey({
authTokenValue authTokenValue

View File

@@ -24,10 +24,10 @@ export const folderQueryKeys = {
["secret-folders", { projectId, environment, path }] as const ["secret-folders", { projectId, environment, path }] as const
}; };
const fetchProjectFolders = async (projectId: string, environment: string, path = "/") => { const fetchProjectFolders = async (workspaceId: string, environment: string, path = "/") => {
const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v1/folders", { const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v1/folders", {
params: { params: {
projectId, workspaceId,
environment, environment,
path path
} }
@@ -102,7 +102,10 @@ export const useCreateFolder = () => {
return useMutation<{}, {}, TCreateFolderDTO>({ return useMutation<{}, {}, TCreateFolderDTO>({
mutationFn: async (dto) => { mutationFn: async (dto) => {
const { data } = await apiRequest.post("/api/v1/folders", dto); const { data } = await apiRequest.post("/api/v1/folders", {
...dto,
workspaceId: dto.projectId
});
return data; return data;
}, },
onSuccess: (_, { projectId, environment, path }) => { onSuccess: (_, { projectId, environment, path }) => {
@@ -127,7 +130,7 @@ export const useUpdateFolder = () => {
const { data } = await apiRequest.patch(`/api/v1/folders/${folderId}`, { const { data } = await apiRequest.patch(`/api/v1/folders/${folderId}`, {
name, name,
environment, environment,
projectId, workspaceId: projectId,
path path
}); });
return data; return data;
@@ -154,7 +157,7 @@ export const useDeleteFolder = () => {
const { data } = await apiRequest.delete(`/api/v1/folders/${folderId}`, { const { data } = await apiRequest.delete(`/api/v1/folders/${folderId}`, {
data: { data: {
environment, environment,
projectId, workspaceId: projectId,
path path
} }
}); });

View File

@@ -13,7 +13,7 @@ export const useCreateSecretImport = () => {
const { data } = await apiRequest.post("/api/v1/secret-imports", { const { data } = await apiRequest.post("/api/v1/secret-imports", {
import: secretImport, import: secretImport,
environment, environment,
projectId, workspaceId: projectId,
path path
}); });
return data; return data;
@@ -38,7 +38,7 @@ export const useUpdateSecretImport = () => {
import: secretImports, import: secretImports,
environment, environment,
path, path,
projectId workspaceId: projectId
}); });
return data; return data;
}, },
@@ -60,7 +60,7 @@ export const useDeleteSecretImport = () => {
mutationFn: async ({ id, projectId, path, environment }) => { mutationFn: async ({ id, projectId, path, environment }) => {
const { data } = await apiRequest.delete(`/api/v1/secret-imports/${id}`, { const { data } = await apiRequest.delete(`/api/v1/secret-imports/${id}`, {
data: { data: {
projectId, workspaceId: projectId,
path, path,
environment environment
} }

View File

@@ -25,7 +25,7 @@ const fetchSecretImport = async ({ projectId, environment, path = "/" }: TGetSec
"/api/v1/secret-imports", "/api/v1/secret-imports",
{ {
params: { params: {
projectId, workspaceId: projectId,
environment, environment,
path path
} }
@@ -66,7 +66,7 @@ const fetchImportedSecrets = async (
"/api/v1/secret-imports/secrets", "/api/v1/secret-imports/secrets",
{ {
params: { params: {
projectId: workspaceId, workspaceId,
environment, environment,
path: directory path: directory
} }

View File

@@ -18,8 +18,8 @@ export {
useGetWorkspaceUsers, useGetWorkspaceUsers,
useNameWorkspaceSecrets, useNameWorkspaceSecrets,
useRenameWorkspace, useRenameWorkspace,
useReorderWsEnvironment,
useToggleAutoCapitalization, useToggleAutoCapitalization,
useUpdateIdentityWorkspaceRole, useUpdateIdentityWorkspaceRole,
useUpdateUserWorkspaceRole, useUpdateUserWorkspaceRole,
useUpdateWsEnvironment} from "./queries"; useUpdateWsEnvironment
} from "./queries";