Skip to content

feat: mostly just duplicate audit logs and other clickhouse stuff#2134

Merged
chronark merged 2 commits intomainfrom
tinybird-clickhouse-audit-logs
Sep 26, 2024
Merged

feat: mostly just duplicate audit logs and other clickhouse stuff#2134
chronark merged 2 commits intomainfrom
tinybird-clickhouse-audit-logs

Conversation

@chronark
Copy link
Collaborator

@chronark chronark commented Sep 25, 2024

Summary by CodeRabbit

  • New Features

    • Introduced new telemetry data tables and materialized views to enhance data tracking.
    • Added a method for inserting SDK telemetry data into the database.
    • Implemented detailed logging for key creation events, capturing additional context.
  • Bug Fixes

    • Improved error logging for telemetry ingestion failures.
  • Documentation

    • Updated naming conventions for aggregated tables and materialized views for consistency.
  • Chores

    • Removed outdated migration task from configuration.

@vercel
Copy link

vercel bot commented Sep 25, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
dashboard ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 26, 2024 9:34am
play ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 26, 2024 9:34am
workflows ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 26, 2024 9:34am
www ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 26, 2024 9:34am
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
planetfall ⬜️ Ignored (Inspect) Visit Preview Sep 26, 2024 9:34am

@changeset-bot
Copy link

changeset-bot bot commented Sep 25, 2024

⚠️ No Changeset found

Latest commit: 0f0b198

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions
Copy link
Contributor

github-actions bot commented Sep 25, 2024

Thank you for following the naming conventions for pull request titles! 🙏

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request includes several changes across multiple files, primarily focusing on the removal of a database migration task, updates to naming conventions for ClickHouse tables, and the introduction of new SQL tables and materialized views for telemetry and key verification data. Additionally, modifications were made to the Analytics class for telemetry data insertion, and new functions for logging audit data were introduced. Overall, the changes enhance the structure and clarity of the codebase related to telemetry and audit logging.

Changes

Files Change Summary
apps/agent/Taskfile.yml Removed the migrate task for database migrations.
apps/agent/pkg/clickhouse/schema/000_README.md Updated naming conventions for aggregated tables and materialized views, changing suffixes from _daily to _per_day and _monthly to _per_month.
apps/agent/pkg/clickhouse/schema/003_create_raw_telemetry_sdks_table.sql Created a new table raw_telemetry_sdks_v1 for storing SDK telemetry data.
apps/agent/pkg/clickhouse/schema/004_create_key_verifications_per_day.sql Created a new table key_verifications_per_day_v1 for key verification data.
apps/agent/pkg/clickhouse/schema/005_create_mv_key_verifications_per_day.sql Created a materialized view mv_key_verifications_per_day_v1 aggregating data from raw_key_verifications_v1.
apps/api/src/pkg/analytics.ts Added insertSdkTelemetry method and renamed several existing methods to include "Tinybird."
apps/api/src/pkg/audit.ts Introduced functions for inserting audit logs: insertUnkeyAuditLog and insertGenericAuditLogs.
apps/api/src/pkg/cache/index.ts Added a new cache namespace for audit logs.
apps/api/src/pkg/cache/namespaces.ts Added auditLogBucketByWorkspaceIdAndName property to CacheNamespaces type.
apps/api/src/pkg/db.ts Enhanced type definitions related to database transactions with a new Transaction type alias.
apps/api/src/pkg/key_migration/handler.ts Updated analytics logging calls to use the new ingestUnkeyAuditLogsTinybird method.
apps/api/src/pkg/middleware/metrics.ts Added telemetry insertion process for SDK telemetry and enhanced error logging.
apps/api/src/routes/legacy_keys_createKey.ts Updated logging process to use ingestUnkeyAuditLogsTinybird and introduced insertUnkeyAuditLog for auditing.

Possibly related PRs

Suggested reviewers

  • mcstepp
  • perkinsjr

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Member

@perkinsjr perkinsjr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 57

🧹 Outside diff range and nitpick comments (37)
apps/agent/pkg/clickhouse/schema/004_create_key_verifications_per_day.sql (1)

1-14: LGTM! Well-structured table for key verifications.

The table structure is well-designed for storing and aggregating key verification data. Good job on using appropriate column types, the AggregatingMergeTree engine, and a clear ordering strategy.

Consider these potential improvements:

  1. For higher precision timestamps, you might want to use DateTime64:

    time DateTime64(3)
  2. If you expect high data volume or specific query patterns, consider adding a PARTITION BY clause for better performance:

    ENGINE = AggregatingMergeTree()
    PARTITION BY toYYYYMM(time)
    ORDER BY (workspace_id, key_space_id, time, identity_id, key_id)
  3. Add a comment to the table for better documentation:

    COMMENT 'Stores daily aggregated key verification data'
apps/agent/pkg/clickhouse/schema/003_create_raw_telemetry_sdks_table.sql (3)

2-2: Table naming convention looks good, consider schema organization.

The table name raw_telemetry_sdks_v1 follows a clear and informative naming convention. The _v1 suffix allows for future versioning, which is a good practice.

Consider creating a separate schema for telemetry data (e.g., telemetry) instead of using the default schema. This can help with organization as the project grows.


3-15: Column definitions look good, consider clarifying the time column comment.

The column definitions and data types are well-chosen for their intended purposes. The use of Int64 for the time column and Array(String) for the versions column are particularly good choices.

Consider updating the comment for the time column to be more specific:

-  -- unix milli
+  -- timestamp in Unix milliseconds (UTC)
   time Int64,

This clarifies that the timestamp is in UTC, which is important for data consistency.


17-18: Good engine choice and ordering, consider future partitioning.

The MergeTree engine is an excellent choice for telemetry data, and ordering by request_id and time aligns well with typical query patterns.

As the dataset grows, consider adding partitioning to improve query performance and data management. For example:

ENGINE = MergeTree()
PARTITION BY toYYYYMM(fromUnixTimestamp64Milli(time))
ORDER BY (request_id, time)

This partitions the data by month, which can significantly improve query performance for time-based queries and make data retention policies easier to implement.

apps/agent/pkg/clickhouse/schema/005_create_mv_key_verifications_per_day.sql (2)

1-2: LGTM! Consider adding a corresponding down migration.

The creation statement for the materialized view looks good. The use of versioning in the view name is a good practice for managing schema changes.

Consider adding a corresponding -- +goose down migration to drop the materialized view. This would make it easier to roll back changes if needed:

-- +goose down
DROP VIEW IF EXISTS default.mv_key_verifications_per_day_v1;

3-10: LGTM! Consider adding comments for clarity.

The SELECT clause looks good. The use of countState() for aggregation and the transformation of the time field for daily grouping are appropriate.

Consider adding comments to explain the purpose of the countState() function and the time transformation. This can help other developers understand the logic:

SELECT
  workspace_id,
  key_space_id,
  identity_id,
  key_id,
  outcome,
  -- Use countState() for incremental updates in the materialized view
  countState() as count,
  -- Group by day by transforming Unix timestamp to start of day
  toStartOfDay(fromUnixTimestamp64Milli(time)) AS time
internal/db/src/schema/index.ts (1)

16-16: LGTM! Consider specific exports if needed.

The new export statement for "audit_logs" is consistent with the existing pattern in the file. It successfully makes all exports from the audit_logs module available.

While the wildcard export is consistent with the file's pattern, consider using specific exports if you want more control over what's exposed from the audit_logs module. This can help with better tracking of imports and potentially reduce unintended exposures. For example:

export { AuditLog, createAuditLog /* other specific exports */ } from "./audit_logs";

This approach allows for more granular control over the public API of your schema module.

apps/api/src/pkg/cache/namespaces.ts (1)

67-70: LGTM! Consider adding a type for the compound key.

The addition of auditLogBucketByWorkspaceIdAndName to the CacheNamespaces type is well-structured and consistent with the existing code. The comment explaining the compound key is helpful.

To improve type safety and readability, consider defining a separate type for the compound key:

type AuditLogBucketKey = [workspaceId: string, name: string];

// Then use it in CacheNamespaces
auditLogBucketByWorkspaceIdAndName: Record<AuditLogBucketKey, { id: string }>;

This change would make the structure of the key more explicit and provide better type checking when using this cache namespace.

apps/api/src/pkg/db.ts (1)

13-16: LGTM: Improved type safety for transactions

The new Transaction type alias enhances type safety for database operations by combining PlanetScaleTransaction with the schema and its relations. This is a positive change that will provide better type inference when working with transactions.

Consider adding a brief JSDoc comment above the Transaction type alias to explain its purpose and usage. For example:

/**
 * Represents a type-safe transaction for our database schema.
 * This type combines PlanetScale's transaction type with our specific schema and its relations.
 */
export type Transaction = PlanetScaleTransaction<
  typeof schema,
  ExtractTablesWithRelations<typeof schema>
>;
internal/schema/src/auditlog.ts (1)

69-69: LGTM! Consider adding a comment to explain the default behavior.

The addition of a default value for the time property is a good improvement. It ensures that every audit log entry will have a timestamp, even if not explicitly provided, which simplifies the process of creating audit log entries.

However, be aware that this automatic timestamping could potentially lead to inconsistencies if not used carefully. For instance, if there's a delay between an event occurring and it being logged, the timestamp might not accurately reflect the event time.

Consider adding a comment above this line to explain the default behavior:

  /**
   * The time of the audit log entry. Defaults to the current timestamp if not provided.
   */
  time: z.number().default(() => Date.now()),

This will help other developers understand the automatic timestamping behavior at a glance.

tools/local/src/db.ts (2)

62-70: LGTM: Audit log bucket insertion implemented correctly.

The changes effectively replace the webhook key space insertion with an audit log bucket insertion. The use of newId for generating unique IDs and enabling deleteProtection are good practices.

However, consider adding error handling to this database operation to gracefully handle potential insertion failures.

Consider wrapping the insertion in a try-catch block:

try {
  await db
    .insert(schema.auditLogBucket)
    .values({
      id: newId("auditLogBucket"),
      workspaceId: ROW_IDS.rootWorkspace,
      name: "unkey_mutations",
      deleteProtection: true,
    })
    .onDuplicateKeyUpdate({ set: { createdAt: Date.now() } });
  s.message("Created audit log bucket");
} catch (error) {
  s.message(`Failed to create audit log bucket: ${error.message}`);
  // Consider whether to throw the error or handle it differently
}

Line range hint 8-15: Remove unused webhook-related code.

With the removal of webhook key space and API setup, there are still some remnants of webhook-related code that should be cleaned up:

  1. In the ROW_IDS object, remove the following entries:

    • webhookKeySpace
    • webhookApi
  2. In the function's return type and value, remove the webhooksApi property.

These changes will ensure consistency and prevent potential confusion or errors in the future.

Apply the following changes:

const ROW_IDS = {
  rootWorkspace: "ws_local_root",
  rootKeySpace: "ks_local_root_keys",
  rootApi: "api_local_root_keys",
-  webhookKeySpace: "ks_local_webhook_keys",
-  webhookApi: "api_local_webhook_keys",
};

export async function prepareDatabase(url?: string): Promise<{
  workspace: { id: string };
  api: { id: string };
-  webhooksApi: { id: string };
}> {
  // ... (rest of the function)

  return {
    workspace: {
      id: ROW_IDS.rootWorkspace,
    },
    api: { id: ROW_IDS.rootApi },
-    webhooksApi: { id: ROW_IDS.webhookApi },
  };
}

Also applies to: 101-105

apps/api/src/pkg/middleware/metrics.ts (2)

65-80: New SDK telemetry insertion logic looks good, with a minor suggestion.

The addition of SDK telemetry insertion logic enhances data collection capabilities. The use of waitUntil() and proper error handling are commendable practices.

Consider simplifying the event object spread in the insertSdkTelemetry call:

 analytics
   .insertSdkTelemetry({
     ...event,
-    request_id: event.requestId,
   })

The requestId property is already present in the event object, so explicitly setting request_id is redundant.


Line range hint 56-80: Consider combining telemetry operations for improved efficiency.

While the additions to SDK telemetry handling are valuable, having two separate telemetry operations (ingestion and insertion) might increase the system load unnecessarily.

Consider refactoring the telemetry handling to combine both operations into a single method call. This could potentially reduce the number of database operations and simplify the code. For example:

c.executionCtx.waitUntil(
  analytics.handleSdkTelemetry(event).catch((err) => {
    logger.error("Error handling SDK telemetry", {
      method: c.req.method,
      path: c.req.path,
      error: err.message,
      telemetry,
      event,
    });
  })
);

This approach would require modifying the analytics service to handle both ingestion and insertion in a single method, potentially improving efficiency and maintainability.

apps/api/src/pkg/key_migration/handler.ts (1)

Line range hint 187-207: LGTM! Consider refactoring to reduce duplication.

The change from ingestUnkeyAuditLogs to ingestUnkeyAuditLogsTinybird is consistent with the previous updates. The function now logs multiple audit entries for permission connections, which is appropriate for this context.

Consider refactoring the audit logging for roles and permissions to reduce code duplication. You could create a helper function like this:

function logConnections(
  analytics: Analytics,
  connections: Array<{ id: string, type: 'role' | 'permission' }>,
  keyId: string,
  workspaceId: string,
  rootKeyId: string,
  auditLogContext: any
) {
  return analytics.ingestUnkeyAuditLogsTinybird(
    connections.map((connection) => ({
      workspaceId,
      actor: { type: "key", id: rootKeyId },
      event: `authorization.connect_${connection.type}_and_key`,
      description: `Connected ${connection.id} and ${keyId}`,
      resources: [
        { type: "key", id: keyId },
        { type: connection.type, id: connection.id },
      ],
      context: auditLogContext,
    }))
  );
}

Then use it like this:

await logConnections(
  analytics,
  Object.values(roles).map(roleId => ({ id: roleId, type: 'role' as const })),
  keyId,
  message.workspaceId,
  message.rootKeyId,
  message.auditLogContext
);

// ... and similarly for permissions

This would make the code more DRY and easier to maintain.

apps/api/src/pkg/analytics.ts (4)

44-55: LGTM! Consider adding a comment for clarity.

The new insertSdkTelemetry method is well-structured and uses an appropriate schema for SDK telemetry data. The use of ClickHouse for this purpose is suitable for handling high-volume, time-series data.

Consider adding a brief comment explaining the purpose of this method and the structure of the data being inserted. This would enhance code readability and maintainability.

+  /**
+   * Inserts SDK telemetry data into ClickHouse.
+   * Data includes request ID, timestamp, runtime, platform, and SDK versions.
+   */
  public get insertSdkTelemetry() {
    return this.clickhouse.insert({
      table: "default.raw_telemetry_sdks_v1",
      schema: z.object({
        request_id: z.string(),
        time: z.number().int(),
        runtime: z.string(),
        platform: z.string(),
        versions: z.array(z.string()),
      }),
    });
  }

70-71: Approved. Consider enhancing the comment for clarity.

The renaming of ingestUnkeyAuditLogs to ingestUnkeyAuditLogsTinybird is consistent with the apparent shift towards Tinybird-specific naming conventions. The functionality remains unchanged, which is good for maintaining consistency.

The current comment "//tinybird" is quite brief. Consider expanding it to provide more context about the method's purpose and its relationship to Tinybird. For example:

-//tinybird
+// Ingests Unkey audit logs into Tinybird data storage
+// This method is specific to Tinybird integration and handles audit log events
  public ingestUnkeyAuditLogsTinybird(logs: MaybeArray<UnkeyAuditLog>) {

This would improve code readability and make the purpose of the method clearer to other developers.


95-96: Approved. Consider enhancing the comment for clarity.

The renaming of ingestGenericAuditLogs to ingestGenericAuditLogsTinybird is consistent with the apparent shift towards Tinybird-specific naming conventions. The functionality remains unchanged, which is good for maintaining consistency.

As with the previous method, the current comment "//tinybird" is quite brief. Consider expanding it to provide more context about the method's purpose and its relationship to Tinybird. For example:

-//tinybird
+// Ingests generic audit logs into Tinybird data storage
+// This method is specific to Tinybird integration and handles various audit log events
  public get ingestGenericAuditLogsTinybird() {

This would improve code readability and make the purpose of the method clearer to other developers.


Line range hint 1-238: Overall assessment: Good changes with minor improvements needed.

The changes in this file appear to be part of a larger refactoring effort to integrate with Tinybird and improve telemetry data handling. The code quality is generally good, with clear and consistent naming conventions applied.

Key points:

  1. The new insertSdkTelemetry method is well-structured and appropriate for its purpose.
  2. The renaming of methods to include "Tinybird" in their names is consistent and clear.
  3. Comments could be improved to provide more context, especially for the Tinybird-related methods.
  4. The deprecated ingestKeyVerification method should be properly marked and scheduled for removal.

Consider creating a migration plan to fully transition from the old methods to the new Tinybird and ClickHouse implementations. This could include:

  1. Updating all call sites to use the new methods.
  2. Creating a deprecation schedule for the old methods.
  3. Ensuring that the new data storage solutions (Tinybird and ClickHouse) are properly set up and optimized for your use case.

These steps will help ensure a smooth transition and maintain code quality as you evolve your analytics infrastructure.

apps/api/src/routes/v1_identities_updateIdentity.ts (2)

388-389: LGTM with suggestions: Updated audit log handling.

The changes improve the audit log handling by separating the insertion and ingestion processes. The use of c.executionCtx.waitUntil for the Tinybird ingestion is a good practice for non-blocking operations.

However, I have two suggestions:

  1. Could you clarify the purpose of the undefined parameter in insertUnkeyAuditLog? It might be helpful to add a comment explaining its significance or consider using a named parameter for better readability.
  2. Consider adding a comment explaining that the Tinybird ingestion is intentionally non-blocking:
// Ingest audit logs to Tinybird asynchronously (non-blocking)
c.executionCtx.waitUntil(analytics.ingestUnkeyAuditLogsTinybird(auditLogs));

Line range hint 1-400: Summary: Improved audit log handling.

The changes in this file focus on enhancing the audit log handling process. The introduction of insertUnkeyAuditLog and the separation of log insertion and ingestion improve the overall audit logging capabilities. These modifications are minimal and don't alter the core functionality of the identity update route.

To further improve the code:

  1. Consider adding documentation for the insertUnkeyAuditLog function, explaining its parameters and purpose.
  2. Evaluate if any existing tests need to be updated to reflect these changes in audit log handling.
tools/migrate/auditlog-import.ts (1)

86-97: Handle empty resources array to avoid unnecessary database insertion.

If resources is empty, the script attempts to insert an empty array into the database, which may not be necessary and could lead to errors. Check if resources is non-empty before attempting the insertion.

Apply this diff to add the check:

-      await db.insert(schema.auditLogTarget).values(
-        resources.map((r) => ({
-          workspaceId: log.workspaceId,
-          bucketId,
-          auditLogId,
-          displayName: r.name ?? "",
-          type: r.type,
-          id: r.id,
-          name: r.name,
-          meta: r.meta,
-        })),
-      );
+      if (resources.length > 0) {
+        await db.insert(schema.auditLogTarget).values(
+          resources.map((r) => ({
+            workspaceId: log.workspaceId,
+            bucketId,
+            auditLogId,
+            displayName: r.name ?? "",
+            type: r.type,
+            id: r.id,
+            name: r.name,
+            meta: r.meta,
+          })),
+        );
+      }
internal/db/src/schema/audit_logs.ts (1)

100-100: Consider renaming the 'id' field to 'targetId' in auditLogTarget

The id field represents the target's ID, which might be clearer if named targetId to avoid confusion with other id fields.

Apply this diff:

-        id: varchar("id", { length: 256 }).notNull(),
+        targetId: varchar("target_id", { length: 256 }).notNull(),

Remember to update all references to this field in related code and database queries.

apps/api/src/routes/v1_permissions_createPermission.ts (1)

Line range hint 121-137: Ensure proper error handling for asynchronous analytics ingestion.

The call to analytics.ingestUnkeyAuditLogsTinybird is executed asynchronously using c.executionCtx.waitUntil(). While this allows the main request to complete without waiting for the analytics process, any errors during ingestion might go unnoticed.

Consider implementing error logging or monitoring to capture and handle potential failures in the analytics ingestion process.

apps/api/src/routes/v1_keys_deleteKey.ts (1)

Line range hint 147-160: Ensure consistency between audit logs and analytics data

The call to analytics.ingestUnkeyAuditLogsTinybird is placed outside the transaction. If the transaction commits but the analytics ingestion fails, or vice versa, this could lead to inconsistencies between the audit logs and analytics data. Consider the following options:

  • Option 1: Move the analytics ingestion inside the transaction to ensure both operations succeed or fail together.
  • Option 2: Implement retry mechanisms or error handling for the analytics ingestion to handle failures gracefully.

Apply this diff if you choose to move the analytics ingestion into the transaction:

 await db.primary.transaction(async (tx) => {
   await tx.update(schema.keys).set({ deletedAt: new Date() }).where(eq(schema.keys.id, key.id));

   try {
     await insertUnkeyAuditLog(c, tx, {
       // ... audit log data ...
     });
+    await analytics.ingestUnkeyAuditLogsTinybird({
+      // ... analytics data ...
+    });
   } catch (error) {
     // Handle errors appropriately
     console.error('Failed to process audit log or analytics ingestion:', error);
   }
 });
apps/api/src/routes/v1_identities_deleteIdentity.ts (1)

114-118: Simplify code by removing unnecessary as const assertions

The as const assertions on string literals like "ratelimit.delete", "key", "identity", and "ratelimit" may be unnecessary if TypeScript can infer the literal types automatically. Removing these assertions can make the code cleaner and more readable.

Also applies to: 121-129

apps/api/src/routes/v1_keys_removeRoles.ts (2)

143-143: Typo in the description field

There is a typo in the word "Disconnected" in the description field. It is currently spelled as "Disonnected".

Apply this diff to fix the typo:

-              description: `Disonnected ${r.roleId} and ${req.keyId}`,
+              description: `Disconnected ${r.roleId} and ${req.keyId}`,

Also applies to: 173-173


Line range hint 98-112: Correct the filtering logic in deleteRoles

The current filtering logic in deleteRoles may not work as intended. The filter callback function should return a boolean, but since it uses return inside a for loop, it exits after the first iteration, potentially missing other matches.

Here's the corrected code using some to properly check all deleteRequest items:

-        const deleteRoles = connectedRoles.filter((cr) => {
-          for (const deleteRequest of req.roles) {
-            if ("id" in deleteRequest) {
-              return cr.roleId === deleteRequest.id;
-            }
-            if ("name" in deleteRequest) {
-              return cr.role.name === deleteRequest.name;
-            }
-          }
-        });
+        const deleteRoles = connectedRoles.filter((cr) =>
+          req.roles.some((deleteRequest) => {
+            if ("id" in deleteRequest) {
+              return cr.roleId === deleteRequest.id;
+            }
+            if ("name" in deleteRequest) {
+              return cr.role.name === deleteRequest.name;
+            }
+            return false;
+          })
+        );

This ensures that deleteRoles includes all roles that match any of the req.roles criteria.

apps/api/src/routes/v1_keys_removePermissions.ts (1)

144-144: Improve clarity of the 'description' field in audit logs

Consider rephrasing the description to more clearly convey the action taken. This enhances readability and comprehension of the audit logs.

Suggested change:

-              description: `Disconnected ${r.permissionId} and ${req.keyId}`,
+              description: `Disconnected permission '${r.permissionId}' from key '${req.keyId}'`,

Also applies to: 174-174

apps/api/src/routes/v1_apis_deleteApi.ts (1)

100-116: Ensure proper error handling in audit logging

When calling analytics.ingestUnkeyAuditLogsTinybird and insertUnkeyAuditLog, consider adding error handling to catch and log any exceptions that may occur during the audit logging process. This ensures that failures in logging do not interrupt the main transaction flow but are still recorded for troubleshooting.

Consider wrapping the audit logging calls in try-catch blocks:

try {
  await analytics.ingestUnkeyAuditLogsTinybird({ /* parameters */ });
} catch (error) {
  // Handle or log the error appropriately
}

try {
  await insertUnkeyAuditLog(c, tx, { /* parameters */ });
} catch (error) {
  // Handle or log the error appropriately
}
apps/api/src/routes/v1_keys_updateRemaining.ts (1)

Line range hint 199-213: Synchronize Logging with Shared Data

After refactoring the logging data into a shared variable, you can use it here to avoid duplication.

Update the code as follows:

c.executionCtx.waitUntil(
  analytics.ingestUnkeyAuditLogsTinybird(logData),
);

This ensures consistency between the two logging mechanisms and simplifies future updates.

apps/api/src/routes/v1_identities_createIdentity.ts (2)

Line range hint 159-201: Refactor to eliminate duplication in audit log creation

The audit log events in lines 159–201 and 203–247 are nearly identical. Refactoring to create a shared auditLogs variable reduces code duplication and improves maintainability.

Refactored code:

const auditLogs = [
  {
    workspaceId: authorizedWorkspaceId,
    event: "identity.create",
    actor: {
      type: "key",
      id: rootKeyId,
    },
    description: `Created ${identity.id}`,
    resources: [
      {
        type: "identity",
        id: identity.id,
      },
    ],
    context: {
      location: c.get("location"),
      userAgent: c.get("userAgent"),
    },
  },
  ...ratelimits.map((r) => ({
    workspaceId: authorizedWorkspaceId,
    event: "ratelimit.create" as const,
    actor: {
      type: "key" as const,
      id: rootKeyId,
    },
    description: `Created ${r.id}`,
    resources: [
      {
        type: "identity" as const,
        id: identity.id,
      },
      {
        type: "ratelimit" as const,
        id: r.id,
      },
    ],
    context: {
      location: c.get("location"),
      userAgent: c.get("userAgent"),
    },
  })),
];

// Insert audit logs into the database
await insertUnkeyAuditLog(c, tx, auditLogs);

// Ingest audit logs into Tinybird analytics
c.executionCtx.waitUntil(
  analytics.ingestUnkeyAuditLogsTinybird(auditLogs),
);

Also applies to: 203-247


Line range hint 159-201: Optimize context retrieval by storing in variables

To improve readability and performance, store c.get("location") and c.get("userAgent") in variables since they're used multiple times.

Apply this change:

const context = {
  location: c.get("location"),
  userAgent: c.get("userAgent"),
};

// Use `context` in audit logs
await insertUnkeyAuditLog(c, tx, [
  {
    // ...other properties
    context,
  },
  // ...additional audit logs
]);

Apply the same change in the analytics ingestion code.

apps/api/src/routes/v1_keys_addRoles.ts (1)

225-225: Enhance audit log descriptions for clarity

Consider making the description field more informative by including both the role names and IDs to improve audit log readability.

Suggested changes:

For createRoles audit logs:

-            description: `Created ${r.id}`,
+            description: `Created role ${r.name} (ID: ${r.id})`,

For addRoles audit logs:

-            description: `Connected ${r.id} and ${req.keyId}`,
+            description: `Connected role ${r.name} (ID: ${r.id}) to key ${req.keyId}`,

Also applies to: 248-248

apps/api/src/routes/v1_migrations_createKey.ts (3)

Line range hint 500-507: Refactor duplicated audit log insertion code

The code for inserting audit logs using insertUnkeyAuditLog is duplicated in lines 500-507 and 556-580. This duplication can make the code harder to maintain and increases the risk of inconsistencies. Consider refactoring this repetitive code into a reusable function to adhere to the DRY (Don't Repeat Yourself) principle.

Here's how you might refactor the duplicated code:

+ // Create a reusable function for inserting audit logs
+ const insertAuditLogs = async (
+   c,
+   tx,
+   items,
+   event,
+   actorId,
+   descriptionFn,
+   resourcesFn
+ ) => {
+   await insertUnkeyAuditLog(
+     c,
+     tx,
+     items.map((item) => ({
+       workspaceId: authorizedWorkspaceId,
+       event,
+       actor: {
+         type: "key",
+         id: actorId,
+       },
+       description: descriptionFn(item),
+       resources: resourcesFn(item),
+       context: {
+         location: c.get("location"),
+         userAgent: c.get("userAgent"),
+       },
+     }))
+   );
+ };

- // Original code block at lines 500-507
- await insertUnkeyAuditLog(
-   c,
-   tx,
-   keys.map((k) => ({
-     workspaceId: authorizedWorkspaceId,
-     event: "key.create",
-     actor: {
-       type: "key",
-       id: auth.key.id,
-     },
-     description: `Created ${k.id} in ${k.keyAuthId}`,
-     resources: [
-       { type: "key", id: k.id },
-       { type: "keyAuth", id: k.keyAuthId! },
-     ],
-     context: {
-       location: c.get("location"),
-       userAgent: c.get("userAgent"),
-     },
-   }))
- );

+ // Refactored code for key creation audit logs
+ await insertAuditLogs(
+   c,
+   tx,
+   keys,
+   "key.create",
+   auth.key.id,
+   (k) => `Created ${k.id} in ${k.keyAuthId}`,
+   (k) => [
+     { type: "key", id: k.id },
+     { type: "keyAuth", id: k.keyAuthId! },
+   ]
+ );

- // Original code block at lines 556-580
- await insertUnkeyAuditLog(
-   c,
-   tx,
-   roleConnections.map((rc) => ({
-     workspaceId: authorizedWorkspaceId,
-     actor: { type: "key", id: rootKeyId },
-     event: "authorization.connect_role_and_key",
-     description: `Connected ${rc.roleId} and ${rc.keyId}`,
-     resources: [
-       { type: "key", id: rc.keyId },
-       { type: "role", id: rc.roleId },
-     ],
-     context: {
-       location: c.get("location"),
-       userAgent: c.get("userAgent"),
-     },
-   }))
- );

+ // Refactored code for role and key connection audit logs
+ await insertAuditLogs(
+   c,
+   tx,
+   roleConnections,
+   "authorization.connect_role_and_key",
+   rootKeyId,
+   (rc) => `Connected ${rc.roleId} and ${rc.keyId}`,
+   (rc) => [
+     { type: "key", id: rc.keyId },
+     { type: "role", id: rc.roleId },
+   ]
+ );

Also applies to: 556-580


Line range hint 500-507: Consider combining audit log insertion and analytics ingestion

After inserting audit logs using insertUnkeyAuditLog, the same data is used for analytics ingestion with analytics.ingestUnkeyAuditLogsTinybird. To streamline the process and reduce redundancy, consider enhancing the insertUnkeyAuditLog function to handle analytics ingestion internally. This consolidation can simplify the code and ensure consistency between logging and analytics data.

Also applies to: 529-555, 556-580, 580-593


Line range hint 250-274: Deprecation warnings for ratelimit properties

In the request schema definition, several properties within the ratelimit object are marked as deprecated (e.g., type, refillRate, refillInterval). While it's important to maintain backward compatibility, consider guiding users towards the preferred properties and updating the documentation accordingly.

You might update the OpenAPI specification to emphasize the new properties and suggest alternatives to the deprecated ones.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between ffb418d and 992307e.

🔇 Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (46)
  • apps/agent/Taskfile.yml (0 hunks)
  • apps/agent/pkg/clickhouse/schema/000_README.md (2 hunks)
  • apps/agent/pkg/clickhouse/schema/003_create_raw_telemetry_sdks_table.sql (1 hunks)
  • apps/agent/pkg/clickhouse/schema/004_create_key_verifications_per_day.sql (1 hunks)
  • apps/agent/pkg/clickhouse/schema/005_create_mv_key_verifications_per_day.sql (1 hunks)
  • apps/api/src/pkg/analytics.ts (5 hunks)
  • apps/api/src/pkg/audit.ts (1 hunks)
  • apps/api/src/pkg/cache/index.ts (1 hunks)
  • apps/api/src/pkg/cache/namespaces.ts (1 hunks)
  • apps/api/src/pkg/db.ts (1 hunks)
  • apps/api/src/pkg/key_migration/handler.ts (3 hunks)
  • apps/api/src/pkg/middleware/metrics.ts (2 hunks)
  • apps/api/src/routes/legacy_keys_createKey.ts (2 hunks)
  • apps/api/src/routes/v1_apis_createApi.ts (2 hunks)
  • apps/api/src/routes/v1_apis_deleteApi.ts (3 hunks)
  • apps/api/src/routes/v1_identities_createIdentity.ts (3 hunks)
  • apps/api/src/routes/v1_identities_deleteIdentity.ts (2 hunks)
  • apps/api/src/routes/v1_identities_updateIdentity.ts (2 hunks)
  • apps/api/src/routes/v1_keys_addPermissions.ts (2 hunks)
  • apps/api/src/routes/v1_keys_addRoles.ts (2 hunks)
  • apps/api/src/routes/v1_keys_createKey.ts (2 hunks)
  • apps/api/src/routes/v1_keys_deleteKey.ts (2 hunks)
  • apps/api/src/routes/v1_keys_removePermissions.ts (2 hunks)
  • apps/api/src/routes/v1_keys_removeRoles.ts (2 hunks)
  • apps/api/src/routes/v1_keys_setPermissions.ts (2 hunks)
  • apps/api/src/routes/v1_keys_setRoles.ts (2 hunks)
  • apps/api/src/routes/v1_keys_updateKey.ts (2 hunks)
  • apps/api/src/routes/v1_keys_updateRemaining.ts (2 hunks)
  • apps/api/src/routes/v1_migrations_createKey.ts (3 hunks)
  • apps/api/src/routes/v1_permissions_createPermission.ts (2 hunks)
  • apps/api/src/routes/v1_permissions_createRole.ts (2 hunks)
  • apps/api/src/routes/v1_permissions_deletePermission.ts (2 hunks)
  • apps/api/src/routes/v1_permissions_deleteRole.ts (2 hunks)
  • apps/api/src/routes/v1_ratelimit_limit.ts (4 hunks)
  • apps/dashboard/lib/trpc/routers/workspace/create.ts (1 hunks)
  • deployment/docker-compose.yaml (0 hunks)
  • internal/db/package.json (1 hunks)
  • internal/db/src/index.ts (1 hunks)
  • internal/db/src/schema/audit_logs.ts (1 hunks)
  • internal/db/src/schema/index.ts (1 hunks)
  • internal/db/src/schema/workspaces.ts (2 hunks)
  • internal/id/src/generate.ts (1 hunks)
  • internal/schema/src/auditlog.ts (1 hunks)
  • tools/local/src/db.ts (2 hunks)
  • tools/migrate/auditlog-import.ts (1 hunks)
  • tools/migrate/tinybird-export.ts (1 hunks)
💤 Files not reviewed due to no reviewable changes (2)
  • apps/agent/Taskfile.yml
  • deployment/docker-compose.yaml
🔇 Additional comments not posted (60)
internal/db/src/index.ts (1)

8-8: LGTM: Addition of PlanetScaleTransaction export

The addition of type PlanetScaleTransaction to the exports is consistent with the module's purpose of providing database-related types and functions. This new export will allow consumers of this module to use the PlanetScaleTransaction type, which can be beneficial for type-checking and improved developer experience when working with PlanetScale transactions.

The export syntax is correct, and it's added in the appropriate place alongside the existing PlanetScaleDatabase type.

To ensure this change doesn't introduce any unintended side effects, let's verify its usage across the codebase:

This script will help us understand if there are any existing uses of PlanetScaleTransaction that might be affected by this change, and if there are any places where this new type could be beneficial.

✅ Verification successful

Verification Successful: PlanetScaleTransaction Export Addition

The addition of type PlanetScaleTransaction has been successfully verified. It is consistently used in apps/api/src/pkg/db.ts and internal/db/src/index.ts, aligning with the module's purpose. No conflicts or unintended side effects were found as a result of this addition.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing usage of PlanetScaleTransaction and potential impact

# Search for existing usage of PlanetScaleTransaction
echo "Searching for existing usage of PlanetScaleTransaction:"
rg --type typescript "PlanetScaleTransaction"

# Search for potential places where this new type might be useful
echo "Searching for potential usage of PlanetScaleTransaction:"
rg --type typescript "transaction.*planetscale"

Length of output: 409


Script:

#!/bin/bash
# Description: Check for existing usage of PlanetScaleTransaction and potential impact

# Search for existing usage of PlanetScaleTransaction in TypeScript files
echo "Searching for existing usage of PlanetScaleTransaction:"
rg --type ts "PlanetScaleTransaction"
rg --type tsx "PlanetScaleTransaction"

# Search for potential places where this new type might be useful in TypeScript files
echo "Searching for potential usage of PlanetScaleTransaction:"
rg --type ts "transaction.*planetscale"
rg --type tsx "transaction.*planetscale"

Length of output: 644

apps/agent/pkg/clickhouse/schema/005_create_mv_key_verifications_per_day.sql (3)

11-11: LGTM! Source table is correctly specified.

The FROM clause correctly specifies the source table default.raw_key_verifications_v1. The consistent use of the default schema and versioning convention is good.


12-18: LGTM! GROUP BY clause is correctly structured.

The GROUP BY clause correctly includes all non-aggregated fields from the SELECT clause, including the transformed time field. This ensures proper grouping of the data on a daily basis.


1-19: Overall, excellent implementation of the materialized view.

This SQL file creates a well-structured materialized view for daily key verification aggregations. It follows ClickHouse best practices for materialized views, including the use of countState() for incremental updates and proper time transformations for daily grouping. The consistent naming conventions and use of versioning are commendable.

A few minor suggestions for improvement have been made in previous comments, primarily around adding comments for clarity and considering a down migration. These are not critical but could enhance maintainability.

Great job on this implementation!

internal/db/package.json (1)

26-26: LGTM! Verify usage of the new dependency.

The addition of "@unkey/id": "workspace:^" as a dependency is appropriate and follows the existing pattern for workspace dependencies. This change suggests that @unkey/db now requires functionality from @unkey/id.

To ensure this dependency is being used correctly, please run the following script:

This script will help confirm that the new dependency is actually being used within the @unkey/db package.

✅ Verification successful

LGTM! The @unkey/id dependency is being used in ./internal/db/src/schema/audit_logs.ts.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of @unkey/id in the @unkey/db package

# Test: Search for import statements of @unkey/id
echo "Searching for import statements of @unkey/id:"
rg --type typescript 'from\s+[\'"]@unkey/id[\'"]' ./internal/db/src

# Test: Search for require statements of @unkey/id
echo "Searching for require statements of @unkey/id:"
rg --type typescript 'require\([\'"]@unkey/id[\'"]\)' ./internal/db/src

Length of output: 195


Script:

#!/bin/bash
# Description: Verify the usage of @unkey/id in the @unkey/db package

# Test: Search for import statements of @unkey/id
echo "Searching for import statements of @unkey/id:"
rg --type typescript "from\s+['\"]@unkey/id['\"]" ./internal/db/src

# Test: Search for require statements of @unkey/id
echo "Searching for require statements of @unkey/id:"
rg --type typescript "require\(['\"]@unkey/id['\"]\)" ./internal/db/src

Length of output: 425


Script:

#!/bin/bash
# Description: Verify the usage of @unkey/id in the @unkey/db package

# Test: Search for import statements of @unkey/id
echo "Searching for import statements of @unkey/id:"
rg "from\s+['\"]@unkey/id['\"]" ./internal/db/src

# Test: Search for require statements of @unkey/id
echo "Searching for require statements of @unkey/id:"
rg "require\(['\"]@unkey/id['\"]\)" ./internal/db/src

Length of output: 393

internal/id/src/generate.ts (2)

Line range hint 1-53: LGTM with minor suggestions

The changes to the prefixes object are minimal and don't introduce any breaking modifications to the public API. The functionality of the newId function remains intact.

Please consider addressing the suggestions in the previous comment regarding the clarification of auditLog and auditLogBucket to improve code clarity and maintainability.


28-29: 🛠️ Refactor suggestion

Consider clarifying the distinction between auditLog and auditLogBucket

The addition of auditLogBucket: "buk" is noted. However, there are a couple of points to consider:

  1. The re-addition of auditLog: "log" appears redundant, as it was likely already present in the object. This could lead to confusion.
  2. The distinction between auditLog and auditLogBucket might not be immediately clear to other developers.

Consider the following improvements:

  1. Remove the redundant auditLog entry if it's indeed a duplicate.
  2. Add comments to clarify the specific use cases for auditLog and auditLogBucket.

Example:

const prefixes = {
  // ... other entries ...
  auditLogBucket: "buk", // Prefix for audit log storage buckets
  auditLog: "log",       // Prefix for individual audit log entries
  // ... other entries ...
} as const;

This will enhance code clarity and maintainability.

To ensure auditLog was indeed present before and this is a redundant addition, we can check the git history:

This will show us the commit history for this file, focusing on changes around the auditLog entry.

apps/agent/pkg/clickhouse/schema/000_README.md (3)

39-40: Improved naming convention for aggregation suffixes.

The changes from '_daily' to '_per_day' and '_monthly' to '_per_month' enhance clarity and consistency in the naming convention. This update makes the time-based aggregation more explicit and aligns well with other time-based suffixes like '_per_hour'.


57-57: Example updated to reflect new naming convention.

The materialized view example has been correctly updated to use the new '_per_day' suffix, maintaining consistency with the revised naming convention. This change helps reinforce the new standard through a practical example.


70-70: Example correctly updated to reflect new naming convention.

The example for consistency across related objects has been appropriately updated to use the new '_per_day' suffix, aligning with the revised naming convention.

apps/api/src/pkg/db.ts (2)

2-8: LGTM: Enhanced type safety with new imports

The addition of new imports from "@unkey/db" package, including ExtractTablesWithRelations, PlanetScaleDatabase, PlanetScaleTransaction, and drizzle, enhances type safety and indicates a more structured approach to database operations. This is a positive change that will likely improve code quality and reduce potential runtime errors.


1-17: Overall assessment: Improved type safety and structure

The changes in this file enhance type safety and provide a more structured approach to database operations, particularly for transactions. These modifications align well with the PR objectives and contribute to improving the overall code quality. No issues or concerns were identified in the changes.

apps/api/src/pkg/cache/index.ts (1)

74-76: LGTM! Verify related changes for completeness.

The addition of the auditLogBucketByWorkspaceIdAndName namespace is consistent with the existing code structure and naming conventions. Good job on maintaining consistency!

To ensure completeness:

  1. Confirm that the CacheNamespaces type in the ./namespaces file has been updated to include this new namespace.
  2. Verify that any necessary updates have been made in other parts of the codebase where cache operations are performed.

Let's verify the related changes:

tools/local/src/db.ts (1)

4-4: LGTM: Import statement for newId added.

The addition of the newId import from "@unkey/id" is appropriate for generating unique IDs in the updated code.

internal/db/src/schema/workspaces.ts (3)

13-13: LGTM: Import statement for auditLogBucket

The import statement for auditLogBucket from ./audit_logs is correctly added and necessary for the new relation introduced later in the file.


13-13: Summary: Audit log integration for workspaces

The changes in this file successfully integrate audit log functionality into the workspace schema:

  1. The import of auditLogBucket is added.
  2. A new auditLogBuckets relation is established in workspacesRelations.

These modifications enhance the schema's capability to manage and relate workspace data with audit logs, aligning with the PR objectives. The changes are well-structured and maintain consistency with the existing codebase.

Also applies to: 136-136


136-136: LGTM: New relation for auditLogBuckets

The auditLogBuckets relation is correctly added to the workspacesRelations object. The many-to-one relationship with auditLogBucket is appropriate for the audit logs feature.

To ensure the new relation is properly utilized, please run the following script to check for its usage in related components:

apps/api/src/pkg/middleware/metrics.ts (1)

Line range hint 56-64: Improved error logging for SDK telemetry ingestion.

The addition of detailed error logging for SDK telemetry ingestion is a good practice. It provides comprehensive information for debugging, including the method, path, error message, telemetry data, and event details.

apps/api/src/pkg/key_migration/handler.ts (3)

Line range hint 1-224: Summary: Consistent migration to new analytics system

The changes in this file consistently replace ingestUnkeyAuditLogs with ingestUnkeyAuditLogsTinybird, which appears to be part of a migration to a new analytics system (Tinybird). The modifications maintain the existing functionality while updating the method calls.

Key points:

  1. The changes are consistent across all instances of audit logging.
  2. The data being logged remains unchanged.
  3. The new method supports logging multiple entries, which is utilized for role and permission connections.

Recommendations:

  1. Verify the implementation and performance of the new ingestUnkeyAuditLogsTinybird method.
  2. Consider refactoring the audit logging for roles and permissions to reduce code duplication.
  3. Ensure that the new analytics system (Tinybird) is properly set up and configured to handle these audit logs.

Overall, the changes appear to be a straightforward migration to a new analytics system without introducing any apparent issues.


Line range hint 152-172: LGTM! Consider performance impact of multiple log entries.

The change from ingestUnkeyAuditLogs to ingestUnkeyAuditLogsTinybird is consistent with the previous update. The function now logs multiple audit entries for role connections, which is appropriate for this context.

To ensure the new method handles multiple log entries efficiently, please run the following script:

#!/bin/bash
# Description: Verify the performance impact of logging multiple entries

# Test: Check if the new method supports batch inserts or uses any performance optimization techniques
ast-grep --lang typescript --pattern 'class Analytics {
  $$$
  ingestUnkeyAuditLogsTinybird($_: $_[]) {
    $$$
  }
  $$$
}'

# Test: Look for any performance-related comments or TODOs
rg -i '(performance|optimization).*todo' --type ts

Line range hint 107-128: LGTM! Verify new analytics method implementation.

The change from ingestUnkeyAuditLogs to ingestUnkeyAuditLogsTinybird appears to be part of a migration to a new analytics system. The function signature and usage remain consistent with the previous implementation.

To ensure the new method is correctly implemented, please run the following script:

apps/api/src/routes/v1_identities_updateIdentity.ts (1)

5-5: LGTM: New import for audit log handling.

The new import of insertUnkeyAuditLog from @/pkg/audit is consistent with the existing import style and appears to be related to the changes in audit log handling.

apps/api/src/pkg/audit.ts (1)

85-85: Verify that log.context.location correctly represents remoteIp

The remoteIp field is being set to log.context?.location. Please verify that location contains the client's IP address. If not, consider using the appropriate field from log.context that represents the remote IP.

Run the following script to verify the definition of location:

apps/api/src/routes/v1_permissions_deleteRole.ts (2)

73-100: Good use of transaction to ensure atomicity

Wrapping the role deletion and audit log insertion within a transaction ensures that both operations succeed or fail together, maintaining data consistency.


Line range hint 103-119: Ensure ingestUnkeyAuditLogsTinybird is correctly integrated

Since you've updated the analytics ingestion method to ingestUnkeyAuditLogsTinybird, verify that this function is correctly implemented and that all necessary adjustments have been made in the codebase to support this change.

Run the following script to check for the implementation and usage of ingestUnkeyAuditLogsTinybird in the codebase:

tools/migrate/auditlog-import.ts (1)

59-78: Verify that Date.now() provides the correct timestamp format for updatedAt.

The updatedAt field is set using Date.now(), which returns the number of milliseconds since the Unix epoch. Ensure that this format matches the expected data type of updatedAt in your database schema. If updatedAt expects a DATETIME or TIMESTAMP type, you may need to convert it appropriately.

You can adjust the code if necessary:

-        .onDuplicateKeyUpdate({ set: { updatedAt: Date.now() } });
+        .onDuplicateKeyUpdate({ set: { updatedAt: new Date() } });

Alternatively, verify the expected data type and adjust accordingly.

apps/api/src/routes/v1_permissions_deletePermission.ts (3)

4-4: Import statement is correctly added

The import of insertUnkeyAuditLog from @/pkg/audit is appropriate and allows for the new audit logging functionality.


82-98: Audit logging for permission deletion is properly implemented

The addition of insertUnkeyAuditLog ensures that permission deletions are logged with all the necessary details, enhancing audit traceability.


82-98: Evaluate transaction scope for audit logging

The insertUnkeyAuditLog function is called within the database transaction, which might increase transaction duration and affect performance. Assess whether the audit log insertion needs to be part of the transaction for consistency. If not, consider moving it outside the transaction or deferring it to reduce potential delays.

To verify, check if insertUnkeyAuditLog depends on the transaction context (tx) for data consistency or if it can operate independently.

apps/api/src/routes/v1_apis_createApi.ts (1)

111-112: Verify compliance with privacy policies when capturing userAgent data

Collecting and storing user agent information may have privacy implications. Please ensure that capturing this data complies with your application's privacy policies and relevant regulations.

Also applies to: 128-129

apps/api/src/routes/v1_permissions_createRole.ts (3)

4-4: Approved: Added necessary import statement

The insertUnkeyAuditLog import is correctly added and will be used within the transaction.


87-118: Good use of transaction to ensure atomicity

Wrapping the role creation and audit log insertion within a transaction ensures that both operations succeed or fail together, maintaining data consistency.


Line range hint 121-135: Verify the integration of ingestUnkeyAuditLogsTinybird

Please ensure that the new method analytics.ingestUnkeyAuditLogsTinybird correctly handles audit log ingestion. If this method replaces ingestUnkeyAuditLogs, verify that all instances have been updated accordingly.

Run the following script to identify any remaining usages of ingestUnkeyAuditLogs:

✅ Verification successful

Audit log ingestion has been successfully updated.

All instances of ingestUnkeyAuditLogs have been replaced with ingestUnkeyAuditLogsTinybird. No remaining usages were found in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all occurrences of `ingestUnkeyAuditLogs` in the codebase.

rg --type typescript 'analytics\.ingestUnkeyAuditLogs\('

Length of output: 93


Script:

#!/bin/bash
# Description: Find all occurrences of `ingestUnkeyAuditLogs` in TypeScript files.

rg --glob '**/*.ts' --glob '**/*.tsx' 'analytics\.ingestUnkeyAuditLogs\('

Length of output: 75

apps/api/src/routes/v1_permissions_createPermission.ts (1)

86-118: Verify the safety of insertUnkeyAuditLog within the transaction.

By placing the insertUnkeyAuditLog operation inside the database transaction, you ensure atomicity between the permission creation and audit log insertion. However, it's important to verify that insertUnkeyAuditLog does not perform any operations that could potentially cause the transaction to fail or introduce side effects, such as interacting with external services or causing deadlocks.

Run the following script to confirm that insertUnkeyAuditLog only interacts with the transaction object and doesn't make external calls:

apps/api/src/routes/v1_keys_deleteKey.ts (2)

126-127: Good use of transaction for atomic operations

Wrapping the key deletion within a transaction ensures that the deletion and related audit log insertion occur atomically, enhancing data integrity and consistency.


144-145: Verify the availability of context properties

Ensure that c.get("location") and c.get("userAgent") return defined values before using them in the context object. If these properties might be undefined, consider providing default values or handling the absence to prevent potential errors.

Run the following script to confirm that location and userAgent are properly set:

✅ Verification successful

Context Properties are Properly Initialized

The c.get("location") and c.get("userAgent") properties are consistently set in the following files:

  • apps/semantic-cache/src/pkg/hono/app.ts
  • apps/api/src/pkg/hono/app.ts

All usages within the context object are preceded by these initializations, ensuring they are defined and preventing potential errors.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check where 'location' and 'userAgent' are set in the codebase.

# Test: Search for the assignments of 'location' and 'userAgent'. Expect: Methods that set these values.
rg --type typescript 'c\.set\("location"|c\.set\("userAgent"'

Length of output: 98


Script:

#!/bin/bash
# Description: Recheck where 'location' and 'userAgent' are set in the TypeScript codebase.

# Test: Search for the assignments of 'location' and 'userAgent' within .ts and .tsx files.
rg --type ts 'c\.set\("location"|c\.set\("userAgent"'

Length of output: 365

apps/api/src/routes/v1_identities_deleteIdentity.ts (1)

90-137: Ensure logging failures do not affect the main operation

If insertUnkeyAuditLog or analytics.ingestUnkeyAuditLogsTinybird fail, it might impact the transaction or the overall operation. Consider adding error handling to ensure that failures in logging do not prevent the identity deletion from completing successfully.

You can verify the impact of logging failures by simulating an error in the logging functions and observing the transaction behavior.

apps/api/src/routes/v1_keys_removeRoles.ts (2)

Line range hint 164-191: Confirm the use of waitUntil for background tasks

Using c.executionCtx.waitUntil schedules the analytics ingestion to run asynchronously. Ensure that this behavior is acceptable and that any failure in the background task does not affect the response to the client.

[approve]

You may want to verify that any errors in analytics.ingestUnkeyAuditLogsTinybird are properly logged and monitored.


122-161: Ensure transaction consistency and error handling

While wrapping the deletion and audit log insertion in a transaction ensures atomicity, consider what happens in case of a transaction failure. Additionally, verify that the analytics ingestion reflects the committed state.

Run the following script to check for proper error handling and transaction consistency:

Review the transaction implementations to ensure they handle errors appropriately.

apps/api/src/routes/v1_keys_removePermissions.ts (3)

4-4: Approved import of 'insertUnkeyAuditLog'

The import of insertUnkeyAuditLog from "@/pkg/audit" is necessary for the audit logging functionality introduced in this module.


122-162: Good use of transaction for atomic operations

Wrapping the deletion of permissions and the insertion of audit logs within a transaction ensures atomicity and data consistency. This approach prevents partial updates in case of failures, which is a best practice.


Line range hint 165-192: Approved asynchronous audit log ingestion

Using c.executionCtx.waitUntil with analytics.ingestUnkeyAuditLogsTinybird allows for non-blocking ingestion of audit logs, improving response times while ensuring logs are processed.

apps/api/src/routes/v1_apis_deleteApi.ts (3)

4-4: Added import for insertUnkeyAuditLog

The import statement correctly adds insertUnkeyAuditLog from "@/pkg/audit", which is used for logging audit events.


100-116: Consistent usage of context properties

Ensure that the context object being passed to the logging functions includes all necessary properties. For example, if userAgent or location can be undefined, consider providing default values or handling potential undefined values to prevent errors in the logging functions.

Confirm that c.get("location") and c.get("userAgent") always return valid values.


147-171: Optimize the mapping of keyIds

In lines 150-171, keyIds.map is used to create an array of audit log entries, which is then passed to insertUnkeyAuditLog. If insertUnkeyAuditLog can accept multiple entries at once, this is fine. However, ensure that the function handles arrays of entries correctly.

Run the following script to verify that insertUnkeyAuditLog supports an array of entries:

apps/api/src/routes/v1_keys_updateRemaining.ts (2)

4-4: Importing 'insertUnkeyAuditLog' Function

The insertUnkeyAuditLog function is correctly imported from "@/pkg/audit", ensuring that audit logs can be inserted as intended.


Line range hint 199-213: Verify Asynchronous Logging Execution

Ensure that analytics.ingestUnkeyAuditLogsTinybird is compatible with c.executionCtx.waitUntil. If the function returns a promise, it's suitable for use here. Otherwise, you might need to adjust the implementation to prevent any issues with the execution context.

To confirm the compatibility, you can review the function's return type or test its behavior.

apps/api/src/routes/v1_identities_createIdentity.ts (3)

4-4: Import statement is appropriate

The import of insertUnkeyAuditLog from "@/pkg/audit" aligns with the updated functionality.


144-154: Proper handling of optional ratelimits

The code correctly processes ratelimits when provided, ensuring flexibility for the caller.


155-157: Conditional insertion of ratelimits enhances efficiency

Ratelimits are inserted only when they are provided, which optimizes database operations.

apps/api/src/routes/legacy_keys_createKey.ts (1)

4-4: Import statement for 'insertUnkeyAuditLog' added

The import of insertUnkeyAuditLog from "@/pkg/audit" is appropriate and aligns with its usage later in the code.

apps/api/src/routes/v1_keys_addRoles.ts (1)

237-238: Ensure location and userAgent are safely retrieved

In the context object, you're accessing c.get("location") and c.get("userAgent"). Verify that these values are always defined, or consider providing default values to handle cases where they might be undefined. This will prevent potential runtime errors or incomplete audit logs.

Also applies to: 261-262

apps/api/src/routes/v1_keys_setPermissions.ts (1)

365-388: ⚠️ Potential issue

Duplicate audit log entries for addPermissions

It appears that addPermissions is being mapped over twice when constructing the auditLogs array. This will result in duplicate audit log entries for the same event, which could lead to confusion and inaccurate logging data.

Apply this diff to remove the redundant mapping over addPermissions:

     // Existing mappings over disconnectPermissions and addPermissions
     // ...

     // Mapping over createPermissions
     ...createPermissions.map((r) => ({
       workspaceId: auth.authorizedWorkspaceId,
       event: "permission.create" as const,
       actor: {
         type: "key" as const,
         id: auth.key.id,
       },
       description: `Created ${r.id}`,
       resources: [
         {
           type: "permission" as const,
           id: r.id,
           meta: {
             name: r.name,
           },
         },
       ],
       context: {
         location: c.get("location"),
         userAgent: c.get("userAgent"),
       },
     })),
-    // Duplicate mapping over addPermissions
-    ...addPermissions.map((r) => ({
-      workspaceId: auth.authorizedWorkspaceId,
-      event: "authorization.connect_permission_and_key" as const,
-      actor: {
-        type: "key" as const,
-        id: auth.key.id,
-      },
-      description: `Connected ${r.id} and ${keyId}`,
-      resources: [
-        {
-          type: "permission" as const,
-          id: r.id,
-        },
-        {
-          type: "key" as const,
-          id: keyId,
-        },
-      ],
-      context: {
-        location: c.get("location"),
-        userAgent: c.get("userAgent"),
-      },
-    })),
   ];

Likely invalid or redundant comment.

apps/api/src/routes/v1_ratelimit_limit.ts (1)

50-52: Ensure proper formatting of multi-line string in description

The description field spanning lines 50 to 52 is using a template literal for a multi-line string. Ensure that this formatting is intentional and that the backticks are correctly placed to avoid syntax errors.

Check that the multi-line string is correctly interpreted and does not introduce unintended whitespace or formatting issues in the API documentation.

apps/api/src/routes/v1_keys_updateKey.ts (2)

4-4: Import statement verified

The insertUnkeyAuditLog function is correctly imported from "@/pkg/audit".


385-413: Verify potential duplication of audit logging

Both insertUnkeyAuditLog and analytics.ingestUnkeyAuditLogsTinybird are called with similar parameters. Verify if both calls are necessary, or if this may result in duplicate audit log entries.

Run the following script to check for other usages of these functions:

apps/api/src/routes/v1_keys_createKey.ts (3)

5-5: Import statement is appropriate

The import of insertUnkeyAuditLog from @/pkg/audit is correct and used appropriately in the code.


435-459: Audit log creation logic is sound

The construction of the initial audit log for the key creation event is correctly implemented.


480-482: Verify TypeScript version compatibility with 'satisfies' operator

The satisfies operator is used to ensure that the objects conform to the UnkeyAuditLog type. This feature was introduced in TypeScript 4.9. Please verify that your project's TypeScript version supports it.

You can check the TypeScript version in your project by running the following script:

Also applies to: 503-505

✅ Verification successful

TypeScript version compatibility verified

The project's TypeScript version is 5.5.3, which supports the satisfies operator introduced in TypeScript 4.9.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the TypeScript version specified in package.json.

# Test: Output the TypeScript version. Expect: Version 4.9 or later.

jq -r '.devDependencies.typescript' package.json

Length of output: 55

apps/api/src/routes/v1_migrations_createKey.ts (1)

Line range hint 506-506: Ensure non-null assertion on k.keyAuthId! is safe

In lines 506 and 543, k.keyAuthId! uses the non-null assertion operator !. While previous checks suggest that k.keyAuthId should not be null, it's important to guarantee this to prevent potential runtime errors.

To confirm that k.keyAuthId is always defined:

This script searches for any instance where keyAuthId is assigned a potentially null or undefined value in the routes directory.

Also applies to: 543-543

✅ Verification successful

Verified: All instances of keyAuthId! are confirmed to be non-null, ensuring the safety of the non-null assertion.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that all keys have a non-null keyAuthId

# Test: Check for any keys where keyAuthId might be null or undefined.

rg --type typescript 'keyAuthId\s*:\s*[^;]+;' -A 5 apps/api/src/routes/ | gawk '/keyAuthId/ && /undefined|null/ { print FILENAME ": " $0 }'

Length of output: 176


Script:

#!/bin/bash
# Description: Verify that all keys have a non-null keyAuthId

# Test: Check for any keys where keyAuthId might be null or undefined.

rg 'keyAuthId\s*=\s*(null|undefined)' -A 5 apps/api/src/routes/ --type-add 'typescript:*.ts,*.tsx' --type typescript | gawk '/keyAuthId\s*=\s*(null|undefined)/ { print FILENAME ": " $0 }'

Length of output: 189

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post

🛑 Comments failed to post (86)
apps/dashboard/lib/trpc/routers/key/createRootKey.ts (1)

3-3: 💡 Codebase verification

Audit Logging Functions Incomplete Refactoring Detected

The old ingestAuditLogs function is still being used in several files, which may lead to inconsistent audit logging behavior. Please update all instances to use the new ingestAuditLogsTinybird or insertAuditLogs functions.

Affected files:

  • apps/workflows/lib/tinybird.ts
  • apps/workflows/jobs/refill-monthly.ts
  • apps/workflows/jobs/refill-daily.ts
  • apps/workflows/jobs/end-trials.ts
  • apps/workflows/jobs/downgrade-requests.ts
🔗 Analysis chain

Verify the impact of audit logging changes.

The changes in imports suggest a shift in the audit logging mechanism:

  1. ingestAuditLogs has been renamed to ingestAuditLogsTinybird, indicating a possible move to a Tinybird-specific implementation.
  2. A new insertAuditLogs function has been introduced from @/lib/audit.

These changes may affect the audit logging process throughout the application.

To ensure these changes are consistently applied, run the following script:

Also applies to: 11-11

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of new audit logging functions across the codebase.

# Test 1: Check for any remaining usage of the old 'ingestAuditLogs' function
echo "Checking for old 'ingestAuditLogs' usage:"
rg --type typescript 'ingestAuditLogs\('

# Test 2: Verify the usage of the new 'ingestAuditLogsTinybird' function
echo "Checking 'ingestAuditLogsTinybird' usage:"
rg --type typescript 'ingestAuditLogsTinybird\('

# Test 3: Verify the usage of the new 'insertAuditLogs' function
echo "Checking 'insertAuditLogs' usage:"
rg --type typescript 'insertAuditLogs\('

Length of output: 522


Script:

#!/bin/bash
# Description: Verify the usage of new audit logging functions across the codebase using specific file extensions.

# Test 1: Check for any remaining usage of the old 'ingestAuditLogs' function
echo "Checking for old 'ingestAuditLogs' usage in .ts and .tsx files:"
rg 'ingestAuditLogs\(' --glob '*.ts' --glob '*.tsx'

# Test 2: Verify the usage of the new 'ingestAuditLogsTinybird' function
echo "Checking 'ingestAuditLogsTinybird' usage in .ts and .tsx files:"
rg 'ingestAuditLogsTinybird\(' --glob '*.ts' --glob '*.tsx'

# Test 3: Verify the usage of the new 'insertAuditLogs' function
echo "Checking 'insertAuditLogs' usage in .ts and .tsx files:"
rg 'insertAuditLogs\(' --glob '*.ts' --glob '*.tsx'

Length of output: 12674

apps/dashboard/lib/tinybird.ts (1)

507-507: 💡 Codebase verification

Remaining References to ingestAuditLogs Found

Several files still reference the old function name ingestAuditLogs. Please update these references to ingestAuditLogsTinybird to ensure consistency and prevent potential errors.

  • apps/workflows/jobs/refill-daily.ts
  • apps/workflows/jobs/refill-monthly.ts
  • apps/workflows/lib/tinybird.ts
  • apps/workflows/jobs/end-trials.ts
  • apps/workflows/jobs/downgrade-requests.ts
🔗 Analysis chain

Approved: Function renamed for clarity

The function ingestAuditLogs has been renamed to ingestAuditLogsTinybird, which more accurately reflects its purpose and the underlying technology used. This change improves code readability and maintainability.

To ensure this change doesn't break existing code, please run the following script to check for any remaining references to the old function name:

If any results are found, they should be updated to use the new function name ingestAuditLogsTinybird.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining references to 'ingestAuditLogs'
rg 'ingestAuditLogs\s*\(' --type ts

Length of output: 384

apps/dashboard/lib/audit/index.ts (2)

58-58: 🛠️ Refactor suggestion

Consider using the event's timestamp instead of the current time

Currently, the time field is set using Date.now(), which records the insertion time. If the log object includes the actual event time (e.g., log.time or log.timestamp), consider using that value to accurately reflect when the event occurred.


30-46: 🛠️ Refactor suggestion

Extract bucket retrieval and creation logic into a helper function

The code for retrieving or creating a bucket is used within the loop and could be abstracted into a separate function. This enhances readability and promotes code reuse.

apps/dashboard/lib/trpc/routers/llmGateway/delete.ts (2)

39-69: ⚠️ Potential issue

Potential duplication in audit logging

The code logs audit events both inside the transaction using insertAuditLogs and after the transaction using ingestAuditLogsTinybird. This could lead to duplicate audit entries or inconsistencies if one operation succeeds and the other fails.

Consider consolidating the audit logging to ensure consistency. If both functions are intended for different purposes, clarify their roles and handle potential failures appropriately. You might want to perform both logging operations within the transaction or ensure that they are idempotent.


43-48: ⚠️ Potential issue

Error handling may obscure underlying issues

In the catch block for the deletion operation, the original error _err is caught but not logged or passed along. Throwing a generic INTERNAL_SERVER_ERROR without logging the original error could make debugging more difficult.

Consider logging the original error or including additional context to aid in troubleshooting. Ensure that sensitive information is not exposed in error messages.

Apply this diff to log the error:

 await tx
   .delete(schema.llmGateways)
   .where(eq(schema.llmGateways.id, input.gatewayId))
   .catch((_err) => {
+    console.error("Error deleting LLM gateway:", _err);
     throw new TRPCError({
       code: "INTERNAL_SERVER_ERROR",
       message:
         "We are unable to delete the LLM gateway. Please contact support using support@unkey.dev",
     });
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        .catch((_err) => {
          console.error("Error deleting LLM gateway:", _err);
          throw new TRPCError({
            code: "INTERNAL_SERVER_ERROR",
            message:
              "We are unable to delete the LLM gateway. Please contact support using support@unkey.dev",
          });
apps/dashboard/lib/trpc/routers/key/updateEnabled.ts (1)

46-51: ⚠️ Potential issue

Ensure proper error propagation within the transaction

Catching and rethrowing errors within the transaction's update operation may prevent the transaction from rolling back automatically. The .catch() block at lines 46-51 handles errors locally, which could interfere with rollback mechanisms.

To ensure the transaction rolls back correctly on errors, consider removing the .catch() block and letting the error propagate:

         .where(eq(schema.keys.id, key.id))
-        .catch((_err) => {
-          throw new TRPCError({
-            code: "INTERNAL_SERVER_ERROR",
-            message:
-              "We were unable to update enabled on this key. Please contact support using support@unkey.dev",
-          });
-        });

Alternatively, handle the error at the transaction level to maintain proper rollback behavior.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        .where(eq(schema.keys.id, key.id))
apps/dashboard/lib/trpc/routers/workspace/changeName.ts (1)

46-62: ⚠️ Potential issue

Ensure consistent audit logging by aligning transaction context.

Currently, insertAuditLogs is called within the transaction tx, while ingestAuditLogsTinybird is called afterward. If the transaction is rolled back due to an error after insertAuditLogs executes, the audit log in the database will be reverted, but the log sent via ingestAuditLogsTinybird may have already been processed. This can lead to inconsistencies between your internal audit logs and the external logging system. Consider moving ingestAuditLogsTinybird inside the transaction to ensure both logging actions are atomic, or implement logic to handle potential inconsistencies.

apps/dashboard/lib/trpc/routers/key/updateName.ts (2)

40-74: ⚠️ Potential issue

Ensure atomicity of audit logging within the transaction.

While the database update and insertAuditLogs are wrapped inside a transaction, the call to ingestAuditLogsTinybird occurs outside of it. If the transaction fails after insertAuditLogs but before ingestAuditLogsTinybird, this could lead to inconsistencies between your database and external logging system. Consider including ingestAuditLogsTinybird within the transaction or handling failures to maintain data integrity.


62-67: 🛠️ Refactor suggestion

Enhance audit log detail with previous and new key names.

Currently, the audit log description mentions the key ID and the new name. Including the previous name can provide more context and improve traceability in audit logs.

Update the description field:

- description: `Changed name of ${key.id} to ${input.name}`,
+ description: `Changed name of key ${key.id} from '${key.name}' to '${input.name}'`,

Committable suggestion was skipped due to low confidence.

apps/dashboard/lib/trpc/routers/key/updateOwnerId.ts (1)

40-73: ⚠️ Potential issue

Potential inconsistency between database transaction and audit log ingestion

Within the transaction block (lines 40-73), you call insertAuditLogs(tx, {...}) to log audit data. However, the subsequent call to ingestAuditLogsTinybird({...}) (line 75) occurs outside the transaction. If the transaction fails or rolls back, the audit logs inserted into the database will not correspond with the data ingested into Tinybird, leading to inconsistency.

Consider moving the ingestAuditLogsTinybird call inside the transaction to ensure atomicity. This way, both the database update and the audit log ingestion either succeed or fail together.

apps/dashboard/lib/trpc/routers/api/updateName.ts (2)

54-73: 🛠️ Refactor suggestion

Consolidate audit logging to reduce duplication

Both insertAuditLogs and ingestAuditLogsTinybird are logging audit information with similar data. Consider creating a common abstraction or helper function to handle audit logging in one place. This will reduce code duplication and improve maintainability.


40-73: ⚠️ Potential issue

Ensure error handling for insertAuditLogs within the transaction

Within the transaction block, only the update operation includes error handling with a catch block. If insertAuditLogs fails, the error might not be properly handled, potentially leaving the transaction in an inconsistent state. Consider adding error handling for insertAuditLogs to manage possible errors effectively.

Apply this diff to add error handling:

      await insertAuditLogs(tx, {
        // parameters
      })
+       .catch((_err) => {
+         throw new TRPCError({
+           code: "INTERNAL_SERVER_ERROR",
+           message:
+             "We were unable to log the audit entry. Please contact support using support@unkey.dev.",
+         });
+       });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    await db.transaction(async (tx) => {
      await tx
        .update(schema.apis)
        .set({
          name: input.name,
        })
        .where(eq(schema.apis.id, input.apiId))
        .catch((_err) => {
          throw new TRPCError({
            code: "INTERNAL_SERVER_ERROR",
            message:
              "We were unable to update the API name. Please contact support using support@unkey.dev.",
          });
        });
      await insertAuditLogs(tx, {
        workspaceId: api.workspace.id,
        actor: {
          type: "user",
          id: ctx.user.id,
        },
        event: "api.update",
        description: `Changed ${api.id} name from ${api.name} to ${input.name}`,
        resources: [
          {
            type: "api",
            id: api.id,
          },
        ],
        context: {
          location: ctx.audit.location,
          userAgent: ctx.audit.userAgent,
        },
      })
        .catch((_err) => {
          throw new TRPCError({
            code: "INTERNAL_SERVER_ERROR",
            message:
              "We were unable to log the audit entry. Please contact support using support@unkey.dev.",
          });
        });
    });
apps/dashboard/lib/trpc/routers/workspace/optIntoBeta.ts (3)

1-3: ⚠️ Potential issue

Ensure consistent audit logging mechanism

Both insertAuditLogs (imported from "@/lib/audit") and ingestAuditLogsTinybird (imported from "@/lib/tinybird") are used in this file. This could lead to duplication of audit logs or inconsistency in how logs are handled. Verify if both functions are necessary or if you can consolidate them to use a single audit logging method to maintain consistency.


57-72: ⚠️ Potential issue

Potential duplication of audit logs within transaction

Within the transaction, insertAuditLogs is called to log the audit event. Later, outside the transaction, ingestAuditLogsTinybird is also called to ingest audit logs. This may result in the same audit event being logged twice. Consider unifying these calls to prevent duplication.


74-75: 🛠️ Refactor suggestion

Use a structured logging framework instead of console.error

Using console.error(err); directly may not be optimal for error tracking in production environments. It's advisable to utilize a structured logging framework or service that can capture, format, and transport logs appropriately. This enhances log management and aids in monitoring and debugging.

apps/dashboard/lib/trpc/routers/api/updateDeleteProtection.ts (2)

54-76: 🛠️ Refactor suggestion

Avoid code duplication by abstracting common audit log parameters.

The parameters passed to both insertAuditLogs and ingestAuditLogsTinybird are nearly identical. To enhance maintainability and reduce duplication, consider creating a shared variable or function to generate these parameters.

You can define a common auditLogData object and reuse it in both function calls:

const auditLogData = {
  workspaceId: api.workspace.id,
  actor: {
    type: "user",
    id: ctx.user.id,
  },
  event: "api.update",
  description: `API ${api.name} delete protection is now ${
    input.enabled ? "enabled" : "disabled"
  }`,
  resources: [
    {
      type: "api",
      id: api.id,
      meta: {
        deleteProtection: input.enabled,
      },
    },
  ],
  context: {
    location: ctx.audit.location,
    userAgent: ctx.audit.userAgent,
  },
};

await insertAuditLogs(tx, auditLogData);
// ...
await ingestAuditLogsTinybird(auditLogData);

Also applies to: 79-99


61-63: ⚠️ Potential issue

Remove the extra closing brace in the description string.

There's an extra closing brace } at the end of the template literal in the description field, which can cause a syntax error.

Apply this diff to fix the issue:

 description: `API ${api.name} delete protection is now ${
   input.enabled ? "enabled" : "disabled"
- }.}`,
+ }`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        description: `API ${api.name} delete protection is now ${
          input.enabled ? "enabled" : "disabled"
        }`,
apps/dashboard/lib/trpc/routers/rbac/createPermission.ts (1)

47-73: 🛠️ Refactor suggestion

Consider consolidating audit logging to prevent duplication

Within the transaction, you're inserting audit logs using insertAuditLogs(tx, {...}). After the transaction, you're calling ingestAuditLogsTinybird({...}) with the same audit log data. This may result in duplicate logging or unnecessary redundancy. If both functions serve different purposes (e.g., one logs to your database and the other sends logs to Tinybird), consider combining them into a single function or module to handle both operations simultaneously, or document the reasoning for separate calls.

apps/dashboard/lib/trpc/routers/rbac/disconnectRoleFromKey.ts (1)

46-65: ⚠️ Potential issue

Ensure consistency between database and Tinybird audit logs

Currently, insertAuditLogs is called within the transaction, and ingestAuditLogsTinybird is called after the transaction commits. If ingestAuditLogsTinybird fails, the audit logs in Tinybird will be out of sync with your database logs.

Consider handling failures from ingestAuditLogsTinybird by implementing a retry mechanism or logging the failure for later processing. This will help maintain consistency between your internal database logs and Tinybird audit logs.

Also applies to: 76-92

apps/dashboard/lib/trpc/routers/rbac/updateRole.ts (3)

35-36: ⚠️ Potential issue

Use a proper logging mechanism instead of console.error

Using console.error(err); for error logging is not recommended in production code. Consider using a dedicated logging library or service that allows for configurable logging levels and better management of log outputs.


78-79: ⚠️ Potential issue

Use a proper logging mechanism in transaction error handling

Similar to the previous catch block, replace console.error(err); with a proper logging mechanism to ensure consistent and effective error logging across the application.


61-76: 🛠️ Refactor suggestion

Refactor to avoid duplication of audit log data construction

The audit log data constructed in the transaction (lines 61-76) is very similar to the data used in ingestAuditLogsTinybird (lines 87-93). Consider creating the audit log data object once and reusing it for both functions to reduce code duplication and improve maintainability.

Apply the following changes to extract the audit log data into a single variable:

+        const auditLogData = {
+          workspaceId: workspace.id,
+          actor: { type: "user", id: ctx.user.id },
+          event: "role.update",
+          description: `Updated role ${input.id}`,
+          resources: [
+            {
+              type: "role",
+              id: input.id,
+            },
+          ],
+          context: {
+            location: ctx.audit.location,
+            userAgent: ctx.audit.userAgent,
+          },
+        };
+        await insertAuditLogs(tx, auditLogData);

And later:

-    await ingestAuditLogsTinybird({
-      workspaceId: workspace.id,
-      actor: { type: "user", id: ctx.user.id },
-      event: "role.update",
-      description: `Updated role ${input.id}`,
-      resources: [
-        {
-          type: "role",
-          id: input.id,
-        },
-      ],
-      context: {
-        location: ctx.audit.location,
-        userAgent: ctx.audit.userAgent,
-      },
-    });
+    await ingestAuditLogsTinybird(auditLogData);

Also applies to: 87-93

apps/dashboard/lib/trpc/routers/rbac/deletePermission.ts (1)

57-72: 🛠️ Refactor suggestion

Refactor to eliminate duplication of audit log data

The audit log data used in both insertAuditLogs and ingestAuditLogsTinybird functions is identical. To adhere to the DRY (Don't Repeat Yourself) principle and enhance maintainability, consider defining the audit log data once and reusing it in both function calls.

Apply this diff to refactor the code:

       await db
         .transaction(async (tx) => {
           await tx
             .delete(schema.permissions)
             .where(
               and(
                 eq(schema.permissions.id, input.permissionId),
                 eq(schema.permissions.workspaceId, workspace.id),
               ),
             );
+          const auditLogData = {
+            workspaceId: workspace.id,
+            actor: { type: "user", id: ctx.user.id },
+            event: "permission.delete",
+            description: `Deleted permission ${input.permissionId}`,
+            resources: [
+              {
+                type: "permission",
+                id: input.permissionId,
+              },
+            ],
+            context: {
+              location: ctx.audit.location,
+              userAgent: ctx.audit.userAgent,
+            },
+          };
-          await insertAuditLogs(tx, {
-            workspaceId: workspace.id,
-            actor: { type: "user", id: ctx.user.id },
-            event: "permission.delete",
-            description: `Deleted permission ${input.permissionId}`,
-            resources: [
-              {
-                type: "permission",
-                id: input.permissionId,
-              },
-            ],
-            context: {
-              location: ctx.audit.location,
-              userAgent: ctx.audit.userAgent,
-            },
-          });
+          await insertAuditLogs(tx, auditLogData);
         })
         .catch((err) => {
           console.error(err);
           throw new TRPCError({
             code: "INTERNAL_SERVER_ERROR",
             message:
               "We are unable to delete the permission. Please contact support using support@unkey.dev",
           });
         });

-      await ingestAuditLogsTinybird({
-        workspaceId: workspace.id,
-        actor: { type: "user", id: ctx.user.id },
-        event: "permission.delete",
-        description: `Deleted permission ${input.permissionId}`,
-        resources: [
-          {
-            type: "permission",
-            id: input.permissionId,
-          },
-        ],
-        context: {
-          location: ctx.audit.location,
-          userAgent: ctx.audit.userAgent,
-        },
-      });
+      await ingestAuditLogsTinybird(auditLogData);

Also applies to: 83-95

apps/dashboard/lib/trpc/routers/key/delete.ts (2)

60-75: 🛠️ Refactor suggestion

Refactor to eliminate duplicate code when creating audit logs

The mapping over workspace.keys to create audit log entries is duplicated in lines 60-75 and again in lines 87-102. Extracting this data into a shared auditLogs variable enhances maintainability and reduces code duplication.

Apply this refactor to define a shared auditLogs variable and reuse it:

+ const auditLogs = workspace.keys.map((key) => ({
+   workspaceId: workspace.id,
+   actor: { type: "user", id: ctx.user.id },
+   event: "key.delete",
+   description: `Deleted ${key.id}`,
+   resources: [
+     {
+       type: "key",
+       id: key.id,
+     },
+   ],
+   context: {
+     location: ctx.audit.location,
+     userAgent: ctx.audit.userAgent,
+   },
+ }));

// Inside the transaction
- insertAuditLogs(
-   tx,
-   workspace.keys.map((key) => ({ ... }))
- );
+ insertAuditLogs(tx, auditLogs);

// After the transaction
- await ingestAuditLogsTinybird(
-   workspace.keys.map((key) => ({ ... }))
- );
+ await ingestAuditLogsTinybird(auditLogs);

Also applies to: 87-102


58-76: 💡 Codebase verification

⚠️ Potential issue

Duplicate audit logging confirmed: Both insertAuditLogs and ingestAuditLogsTinybird write to separate audit log destinations, leading to redundant entries.

  • Consider consolidating audit logging to a single destination to prevent unnecessary duplication.
🔗 Analysis chain

Duplicate audit logging: potential for redundant entries

Both insertAuditLogs (lines 58-76) and ingestAuditLogsTinybird (lines 86-103) are called to log the same audit events. This might result in duplicate audit log entries. Please verify whether both functions are required, or if logging can be streamlined to prevent duplication.

To verify if both functions log to the same destination, you can run the following script:

Also applies to: 86-103

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if 'insertAuditLogs' and 'ingestAuditLogsTinybird' write to the same audit log destination.

# Search for the implementation details of both functions.
rg --type ts 'function insertAuditLogs' -A 20
rg --type ts 'function ingestAuditLogsTinybird' -A 20

Length of output: 2858

apps/dashboard/lib/trpc/routers/key/deleteRootKey.ts (1)

60-78: 🛠️ Refactor suggestion

Reduce code duplication by reusing audit log data.

Both insertAuditLogs and ingestAuditLogsTinybird use nearly identical audit log entries. To enhance maintainability and reduce duplication, consider creating the audit log entries once and reusing them for both functions.

Here's how you might refactor the code:

+       const auditLogEntries = rootKeys.map((key) => ({
+         workspaceId: workspace.id,
+         actor: { type: "user", id: ctx.user.id },
+         event: "key.delete",
+         description: `Deleted ${key.id}`,
+         resources: [
+           {
+             type: "key",
+             id: key.id,
+           },
+         ],
+         context: {
+           location: ctx.audit.location,
+           userAgent: ctx.audit.userAgent,
+         },
+       }));

        await db
          .transaction(async (tx) => {
            await tx
              .update(schema.keys)
              .set({ deletedAt: new Date() })
              .where(
                inArray(
                  schema.keys.id,
                  rootKeys.map((k) => k.id),
                ),
              );
-           await insertAuditLogs(
-             tx,
-             rootKeys.map((key) => ({
-               workspaceId: workspace.id,
-               actor: { type: "user", id: ctx.user.id },
-               event: "key.delete",
-               description: `Deleted ${key.id}`,
-               resources: [
-                 {
-                   type: "key",
-                   id: key.id,
-                 },
-               ],
-               context: {
-                 location: ctx.audit.location,
-                 userAgent: ctx.audit.userAgent,
-               },
-             })),
-           );
+           await insertAuditLogs(tx, auditLogEntries);
          })
          .catch((_err) => {
            throw new TRPCError({
              code: "INTERNAL_SERVER_ERROR",
              message:
                "We are unable to delete the root key. Please contact support using support@unkey.dev",
            });
          });

        await ingestAuditLogsTinybird(auditLogEntries);

Also applies to: 88-100

apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts (2)

69-74: 🛠️ Refactor suggestion

Use error codes instead of parsing error messages

Parsing error messages to handle duplicate entries may not be reliable, as error messages can change. Consider using specific error codes or exceptions provided by the database library to detect duplicate entry errors.


78-78: ⚠️ Potential issue

Typo in error message: 'namspace' should be 'namespace'

There's a typo in the error message; 'namspace' should be corrected to 'namespace' to ensure clarity.

Apply this diff to fix the typo:

-            "We are unable to create namspace. Please contact support using support@unkey.dev",
+            "We are unable to create namespace. Please contact support using support@unkey.dev",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            "We are unable to create namespace. Please contact support using support@unkey.dev",
apps/dashboard/lib/trpc/routers/rbac/connectPermissionToRole.ts (1)

71-76: 🛠️ Refactor suggestion

Consider Logging the Original Error for Debugging

In the .catch block, the original error _err is being discarded when throwing a new TRPCError. While it's important not to expose internal errors to the user, logging the original error internally can aid in debugging without revealing sensitive information to the user.

You might modify the code to log the error:

.catch((_err) => {
+   // Log the original error for internal debugging
+   console.error('Error inserting into rolesPermissions:', _err);

    throw new TRPCError({
        code: "INTERNAL_SERVER_ERROR",
        message:
            "We are unable to connect the permission to the role. Please contact support using support@unkey.dev.",
    });
});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        .catch((_err) => {
          // Log the original error for internal debugging
          console.error('Error inserting into rolesPermissions:', _err);

          throw new TRPCError({
            code: "INTERNAL_SERVER_ERROR",
            message:
              "We are unable to connect the permission to the role. Please contact support using support@unkey.dev.",
          });
apps/dashboard/lib/trpc/routers/rbac/disconnectPermissionFromRole.ts (2)

46-65: ⚠️ Potential issue

Potential duplication in audit logging

It appears that audit logs are being recorded twice: once within the transaction using insertAuditLogs (lines 46-65), and again after the transaction using ingestAuditLogsTinybird (lines 76-88). Verify if both calls are necessary to prevent duplicate audit entries. If both are required for different purposes (e.g., logging to different destinations), consider adding comments to clarify their distinct roles.

Also applies to: 76-88


68-68: 🛠️ Refactor suggestion

Use a centralized logging mechanism instead of console.error

Using console.error(err); may not be ideal for error logging in a production environment. Consider utilizing a centralized logging service or framework to handle errors consistently across the application.

apps/dashboard/lib/trpc/routers/llmGateway/create.ts (1)

1-3: ⚠️ Potential issue

Review the necessity of importing both insertAuditLogs and ingestAuditLogsTinybird

You're importing both insertAuditLogs from @/lib/audit and ingestAuditLogsTinybird from @/lib/tinybird. Since both functions relate to audit logging, consider whether both are necessary or if their functionalities overlap. Consolidating them could simplify the code and prevent potential duplication in audit logs.

apps/dashboard/lib/trpc/routers/ratelimit/deleteOverride.ts (2)

63-84: 🛠️ Refactor suggestion

Consolidate audit logging to reduce duplication

Both insertAuditLogs and ingestAuditLogsTinybird are being called with the same data, resulting in redundant code and potential maintenance overhead.

Consider creating a single function that handles both inserting and ingesting audit logs, or ensure that both functions are necessary and serve distinct purposes. This will improve maintainability and reduce the risk of inconsistencies.


51-61: ⚠️ Potential issue

Ensure transaction errors properly trigger rollback

Currently, errors in the update operation within the transaction are handled using .catch, and a TRPCError is thrown. However, throwing an error inside the .catch block may not correctly propagate the error to rollback the transaction.

Consider using a try...catch block inside the transaction callback to handle errors properly, ensuring that any error will cause the transaction to rollback.

Here's a suggested refactor:

await db.transaction(async (tx) => {
+  try {
    await tx
      .update(schema.ratelimitOverrides)
      .set({ deletedAt: new Date() })
      .where(eq(schema.ratelimitOverrides.id, override.id));

    await insertAuditLogs(tx, {
      workspaceId: override.namespace.workspace.id,
      // ...remaining code...
    });
+  } catch (_err) {
+    throw new TRPCError({
+      message:
+        "We are unable to delete the override. Please contact support using support@unkey.dev",
+      code: "INTERNAL_SERVER_ERROR",
+    });
+  }
});

Committable suggestion was skipped due to low confidence.

apps/dashboard/lib/trpc/routers/api/create.ts (3)

74-92: ⚠️ Potential issue

Handle errors from 'insertAuditLogs' within the transaction

If insertAuditLogs fails, the transaction will roll back, but the error isn't explicitly handled. This could result in a generic error being returned to the client.

Consider adding error handling for insertAuditLogs to provide more informative feedback:

await insertAuditLogs(tx, {
  // existing parameters
})
+ .catch((_err) => {
+   throw new TRPCError({
+     code: "INTERNAL_SERVER_ERROR",
+     message:
+       "We encountered an issue while logging the audit event. Please contact support at support@unkey.dev",
+   });
+ });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      await insertAuditLogs(tx, {
        workspaceId: ws.id,
        actor: {
          type: "user",
          id: ctx.user.id,
        },
        event: "api.create",
        description: `Created ${apiId}`,
        resources: [
          {
            type: "api",
            id: apiId,
          },
        ],
        context: {
          location: ctx.audit.location,
          userAgent: ctx.audit.userAgent,
        },
      })
      .catch((_err) => {
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
          message:
            "We encountered an issue while logging the audit event. Please contact support at support@unkey.dev",
        });
      });

93-111: ⚠️ Potential issue

Potential inconsistency with 'ingestAuditLogsTinybird' outside the transaction

Calling ingestAuditLogsTinybird within the transaction without being part of it may lead to data inconsistency if the transaction rolls back after this function executes.

To ensure consistency, consider moving ingestAuditLogsTinybird after the transaction completes successfully:

    });
+   // Move this outside the transaction
+   await ingestAuditLogsTinybird({
+     workspaceId: ws.id,
+     actor: {
+       type: "user",
+       id: ctx.user.id,
+     },
+     event: "api.create",
+     description: `Created ${apiId}`,
+     resources: [
+       {
+         type: "api",
+         id: apiId,
+       },
+     ],
+     context: {
+       location: ctx.audit.location,
+       userAgent: ctx.audit.userAgent,
+     },
+   });

This ensures that ingestAuditLogsTinybird is only called if the transaction commits successfully.

Committable suggestion was skipped due to low confidence.


4-4: 💡 Codebase verification

Missing 'insertAuditLogs' export in '@/lib/audit'

It appears that insertAuditLogs is not exported from @/lib/audit. Instead, ingestAuditLogsTinybird is exported in several tinybird.ts files. Please verify if insertAuditLogs has been renamed or moved, and update the import path or function name accordingly.

🔗 Analysis chain

Verify the import path and exported function

Ensure that insertAuditLogs is correctly exported from @/lib/audit and that the import path is accurate.

Run the following script to confirm the export:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that 'insertAuditLogs' is exported from '@/lib/audit'

# Test: Search for the export statement. Expect: 'insertAuditLogs' should be exported.
rg 'export.*insertAuditLogs' $(fd 'audit.*\.ts$')

Length of output: 363

apps/dashboard/lib/trpc/routers/rbac/updatePermission.ts (1)

86-87: ⚠️ Potential issue

Avoid logging errors directly to the console

Logging errors directly to the console may expose sensitive information in production environments. Consider using a secure logging mechanism that properly handles sensitive data.

Apply this diff to address the issue:

-      console.error(err);
+      // Log the error using a secure logging service
+      logger.error('Error updating permission:', err);

Committable suggestion was skipped due to low confidence.

apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts (2)

64-69: 🛠️ Refactor suggestion

Simplify error handling within the transaction

The catch block inside the transaction catches errors from the update operation and throws a new TRPCError. Since any error within the transaction will propagate and cause it to roll back, you may not need to catch and rethrow the error here. Letting the error propagate naturally can simplify your code.

Consider removing the catch block to allow errors to be handled by the outer error handler:

          await tx
            .update(schema.apis)
            .set({
              ipWhitelist: newIpWhitelist,
            })
            .where(eq(schema.apis.id, input.apiId))
-            .catch((_err) => {
-              throw new TRPCError({
-                code: "INTERNAL_SERVER_ERROR",
-                message:
-                  "We are unable to update the API whitelist. Please contact support using support@unkey.dev",
-              });
-            });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        await tx
          .update(schema.apis)
          .set({
            ipWhitelist: newIpWhitelist,
          })
          .where(eq(schema.apis.id, input.apiId))

78-78: ⚠️ Potential issue

Handle potential null values in audit log description

If api.ipWhitelist or newIpWhitelist is null, the description in the audit log may contain the string 'null', which could be confusing in the logs.

Consider updating the description to handle null values gracefully:

          description: `Changed ${api.id} IP whitelist from ${api.ipWhitelist} to ${newIpWhitelist}`,
+         description: \`Changed ${api.id} IP whitelist from \${api.ipWhitelist ?? 'none'} to \${newIpWhitelist ?? 'none'}\`,

This ensures that the audit log is clear even when the IP whitelist is empty.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        description: `Changed ${api.id} IP whitelist from ${api.ipWhitelist ?? 'none'} to ${newIpWhitelist ?? 'none'}`,
apps/dashboard/lib/trpc/routers/key/updateExpiration.ts (1)

73-94: 🛠️ Refactor suggestion

Avoid code duplication by reusing the audit log data object

The audit log data is constructed twice for insertAuditLogs and ingestAuditLogsTinybird with identical content. To enhance maintainability and adhere to the DRY (Don't Repeat Yourself) principle, consider creating a variable to hold the audit log data and reuse it in both calls.

Apply this diff to refactor:

+        const auditLogData = {
+          workspaceId: key.workspace.id,
+          actor: {
+            type: "user",
+            id: ctx.user.id,
+          },
+          event: "key.update",
+          description: `${
+            input.expiration
+              ? `Changed expiration of ${key.id} to ${input.expiration.toUTCString()}`
+              : `Disabled expiration for ${key.id}`
+          }`,
+          resources: [
+            {
+              type: "key",
+              id: key.id,
+            },
+          ],
+          context: {
+            location: ctx.audit.location,
+            userAgent: ctx.audit.userAgent,
+          },
+        };
+
         await insertAuditLogs(tx, {
-          workspaceId: key.workspace.id,
-          actor: {
-            type: "user",
-            id: ctx.user.id,
-          },
-          event: "key.update",
-          description: `${
-            input.expiration
-              ? `Changed expiration of ${key.id} to ${input.expiration.toUTCString()}`
-              : `Disabled expiration for ${key.id}`
-          }`,
-          resources: [
-            {
-              type: "key",
-              id: key.id,
-            },
-          ],
-          context: {
-            location: ctx.audit.location,
-            userAgent: ctx.audit.userAgent,
-          },
-        });
+          ...auditLogData,
+        });

         await ingestAuditLogsTinybird({
-          workspaceId: key.workspace.id,
-          actor: {
-            type: "user",
-            id: ctx.user.id,
-          },
-          event: "key.update",
-          description: `${
-            input.expiration
-              ? `Changed expiration of ${key.id} to ${input.expiration.toUTCString()}`
-              : `Disabled expiration for ${key.id}`
-          }`,
-          resources: [
-            {
-              type: "key",
-              id: key.id,
-            },
-          ],
-          context: {
-            location: ctx.audit.location,
-            userAgent: ctx.audit.userAgent,
-          },
-        });
+          ...auditLogData,
+        });

Also applies to: 98-117

apps/dashboard/lib/trpc/routers/rbac/connectRoleToKey.ts (1)

65-99: ⚠️ Potential issue

Ensure atomicity by including all audit logs within the transaction.

While you've correctly wrapped the database insert and the insertAuditLogs function within a transaction, the subsequent call to ingestAuditLogsTinybird occurs outside the transaction. This might lead to inconsistencies if the transaction fails but the audit log is still ingested by Tinybird. Consider moving the ingestAuditLogsTinybird call inside the transaction to maintain atomicity and ensure that audit logging is only performed upon successful transaction completion.

apps/dashboard/lib/trpc/routers/rbac/addPermissionToRootKey.ts (3)

109-109: ⚠️ Potential issue

Handle potential errors from ingestAuditLogsTinybird

The ingestAuditLogsTinybird function is called after the transaction commits. If this function fails, there is currently no error handling, which could result in audit logs not being ingested without any notification or retry mechanism.

Consider wrapping the ingestAuditLogsTinybird call in a try...catch block to handle potential errors gracefully.

Apply this diff to add error handling:

-await ingestAuditLogsTinybird([
+try {
+  await ingestAuditLogsTinybird([
     ...auditLogs,
     newAuditLogEntry,
   ]);
+} catch (error) {
+  // Handle the error, e.g., log it or send to an error tracking service
+  console.error('Failed to ingest audit logs to Tinybird:', error);
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    try {
      await ingestAuditLogsTinybird([
        ...auditLogs,
        newAuditLogEntry,
      ]);
    } catch (error) {
      // Handle the error, e.g., log it or send to an error tracking service
      console.error('Failed to ingest audit logs to Tinybird:', error);
    }

69-84: 🛠️ Refactor suggestion

Refactor error handling within the transaction

Currently, the error handling for the insert operation uses a .catch method chained to the query. In an async/await context, it's more idiomatic and clear to use a try...catch block for error handling. This enhances readability and maintainability.

Apply this diff to refactor the error handling:

-await tx
-  .insert(schema.keysPermissions)
-  .values({
-    keyId: rootKey.id,
-    permissionId: p.id,
-    workspaceId: p.workspaceId,
-  })
-  .onDuplicateKeyUpdate({ set: { permissionId: p.id } })
-  .catch((_err) => {
-    throw new TRPCError({
-      code: "INTERNAL_SERVER_ERROR",
-      message:
-        "We are unable to add permission to the root key. Please contact support using support@unkey.dev.",
-    });
-  });
+try {
+  await tx
+    .insert(schema.keysPermissions)
+    .values({
+      keyId: rootKey.id,
+      permissionId: p.id,
+      workspaceId: p.workspaceId,
+    })
+    .onDuplicateKeyUpdate({ set: { permissionId: p.id } });
+} catch (_err) {
+  throw new TRPCError({
+    code: "INTERNAL_SERVER_ERROR",
+    message:
+      "We are unable to add permission to the root key. Please contact support using support@unkey.dev.",
+  });
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    await db.transaction(async (tx) => {
      try {
        await tx
          .insert(schema.keysPermissions)
          .values({
            keyId: rootKey.id,
            permissionId: p.id,
            workspaceId: p.workspaceId,
          })
          .onDuplicateKeyUpdate({ set: { permissionId: p.id } });
      } catch (_err) {
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
          message:
            "We are unable to add permission to the root key. Please contact support using support@unkey.dev.",
        });
      }
    });

85-109: 🛠️ Refactor suggestion

Consolidate audit log entries to avoid duplication

The audit log entry added to insertAuditLogs and the one passed to ingestAuditLogsTinybird are identical. To improve maintainability and reduce the risk of inconsistencies, consider defining the audit log entry once and reusing it in both places.

Apply this diff to refactor the code:

+const newAuditLogEntry = {
+  workspaceId: workspace.id,
+  actor: { type: "user", id: ctx.user.id },
+  event: "authorization.connect_permission_and_key",
+  description: `Attached ${p.id} to ${rootKey.id}`,
+  resources: [
+    {
+      type: "key",
+      id: rootKey.id,
+    },
+    {
+      type: "permission",
+      id: p.id,
+    },
+  ],
+  context: {
+    location: ctx.audit.location,
+    userAgent: ctx.audit.userAgent,
+  },
+};

 await insertAuditLogs(tx, [
   ...auditLogs,
-  {
-    workspaceId: workspace.id,
-    actor: { type: "user", id: ctx.user.id },
-    event: "authorization.connect_permission_and_key",
-    description: `Attached ${p.id} to ${rootKey.id}`,
-    resources: [
-      {
-        type: "key",
-        id: rootKey.id,
-      },
-      {
-        type: "permission",
-        id: p.id,
-      },
-    ],
-    context: {
-      location: ctx.audit.location,
-      userAgent: ctx.audit.userAgent,
-    },
-  },
+  newAuditLogEntry,
 ]);

 await ingestAuditLogsTinybird([
   ...auditLogs,
-  {
-    workspaceId: workspace.id,
-    actor: { type: "user", id: ctx.user.id },
-    event: "authorization.connect_permission_and_key",
-    description: `Attached ${p.id} to ${rootKey.id}`,
-    resources: [
-      {
-        type: "key",
-        id: rootKey.id,
-      },
-      {
-        type: "permission",
-        id: p.id,
-      },
-    ],
-    context: {
-      location: ctx.audit.location,
-      userAgent: ctx.audit.userAgent,
-    },
-  },
+  newAuditLogEntry,
 ]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      const newAuditLogEntry = {
        workspaceId: workspace.id,
        actor: { type: "user", id: ctx.user.id },
        event: "authorization.connect_permission_and_key",
        description: `Attached ${p.id} to ${rootKey.id}`,
        resources: [
          {
            type: "key",
            id: rootKey.id,
          },
          {
            type: "permission",
            id: p.id,
          },
        ],
        context: {
          location: ctx.audit.location,
          userAgent: ctx.audit.userAgent,
        },
      };

      await insertAuditLogs(tx, [
        ...auditLogs,
        newAuditLogEntry,
      ]);
    });
    await ingestAuditLogsTinybird([
      ...auditLogs,
      newAuditLogEntry,
    ]);
apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts (3)

4-4: 🛠️ Refactor suggestion

Inconsistent naming conventions for audit log functions

The functions insertAuditLogs and ingestAuditLogsTinybird use different verbs (insert vs. ingest) and naming patterns. For clarity and consistency, consider standardizing the naming convention to improve readability and maintainability.

Also applies to: 6-6


84-106: 🛠️ Refactor suggestion

Duplicate audit log data structures

The audit log data constructed at lines 85-106 and 117-128 is identical. To adhere to the DRY (Don't Repeat Yourself) principle, consider extracting this data into a shared variable or helper function.

Refactored code example:

+ const auditLogData = {
+   workspaceId: namespace.workspace.id,
+   actor: {
+     type: "user",
+     id: ctx.user.id,
+   },
+   event: "ratelimitOverride.create",
+   description: `Created ${input.identifier}`,
+   resources: [
+     {
+       type: "ratelimitNamespace",
+       id: input.namespaceId,
+     },
+     {
+       type: "ratelimitOverride",
+       id,
+     },
+   ],
+   context: {
+     location: ctx.audit.location,
+     userAgent: ctx.audit.userAgent,
+   },
+ };
  
  await db
    .transaction(async (tx) => {
-     await insertAuditLogs(tx, {
-       workspaceId: namespace.workspace.id,
-       actor: {
-         type: "user",
-         id: ctx.user.id,
-       },
-       event: "ratelimitOverride.create",
-       description: `Created ${input.identifier}`,
-       resources: [
-         {
-           type: "ratelimitNamespace",
-           id: input.namespaceId,
-         },
-         {
-           type: "ratelimitOverride",
-           id,
-         },
-       ],
-       context: {
-         location: ctx.audit.location,
-         userAgent: ctx.audit.userAgent,
-       },
-     });
+     await insertAuditLogs(tx, auditLogData);
    })
    .catch((_err) => {
      // Error handling code
    });
  
- await ingestAuditLogsTinybird({
-   workspaceId: namespace.workspace.id,
-   actor: {
-     type: "user",
-     id: ctx.user.id,
-   },
-   event: "ratelimitOverride.create",
-   description: `Created ${input.identifier}`,
-   resources: [
-     {
-       type: "ratelimitNamespace",
-       id: input.namespaceId,
-     },
-     {
-       type: "ratelimitOverride",
-       id,
-     },
-   ],
-   context: {
-     location: ctx.audit.location,
-     userAgent: ctx.audit.userAgent,
-   },
- });
+ await ingestAuditLogsTinybird(auditLogData);

Also applies to: 116-129


84-106: ⚠️ Potential issue

Lack of error handling for insertAuditLogs

The insertAuditLogs function within the transaction does not have specific error handling. If this function throws an error, it could cause the entire transaction to fail silently.

Consider adding error handling to manage potential exceptions:

  await db
    .transaction(async (tx) => {
      // Previous code...
+     try {
+       await insertAuditLogs(tx, auditLogData);
+     } catch (error) {
+       throw new TRPCError({
+         code: "INTERNAL_SERVER_ERROR",
+         message: "Failed to insert audit logs.",
+       });
+     }
    })
    .catch((_err) => {
      // Existing error handling
    });

Committable suggestion was skipped due to low confidence.

apps/dashboard/lib/trpc/routers/rbac/removePermissionFromRootKey.ts (3)

103-107: 🛠️ Refactor suggestion

Handle transaction errors more gracefully

In the catch block, you are throwing a generic INTERNAL_SERVER_ERROR. Consider providing more context or logging additional details to aid in debugging, while still avoiding exposing sensitive information to the end-user.


62-64: ⚠️ Potential issue

Ensure key.permissions is defined before accessing

When accessing key.permissions.find(...), there's a potential risk if key.permissions is undefined or null. To prevent runtime errors, ensure that key.permissions is always defined.

Consider adding a null check:

-        const permissionRelation = key.permissions.find(
+        const permissionRelation = key.permissions?.find(
            (kp) => kp.permission.name === input.permissionName,
        );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        const permissionRelation = key.permissions?.find(
          (kp) => kp.permission.name === input.permissionName,
        );

103-103: 🛠️ Refactor suggestion

Use a structured logger instead of console.error

In the catch block of the transaction, you're using console.error(err); to log errors. In a production environment, it's recommended to use a structured logging library or utility for better log management, filtering, and monitoring.

Apply this diff to replace console.error with a structured logger:

-        console.error(err);
+        logger.error('Transaction failed in removePermissionFromRootKey', { error: err });

Assuming you have a logger utility configured in your application.

Committable suggestion was skipped due to low confidence.

apps/dashboard/lib/trpc/routers/key/updateRemaining.ts (2)

67-89: 🛠️ Refactor suggestion

Consolidate audit log data to reduce duplication

The data passed to insertAuditLogs and ingestAuditLogsTinybird is nearly identical. To adhere to the DRY (Don't Repeat Yourself) principle, consider creating a shared audit log object and reusing it in both function calls. This makes the code more maintainable and reduces the risk of inconsistencies.

Here's how you might implement this:

        const auditLog = {
          workspaceId: key.workspace.id,
          actor: {
            type: "user",
            id: ctx.user.id,
          },
          event: "key.update",
          description: input.limitEnabled
            ? `Changed remaining for ${key.id} to remaining=${input.remaining}, refill=${
                input.refill ? `${input.refill.amount}@${input.refill.interval}` : "none"
              }`
            : `Disabled limit for ${key.id}`,
          resources: [
            {
              type: "key",
              id: key.id,
            },
          ],
          context: {
            location: ctx.audit.location,
            userAgent: ctx.audit.userAgent,
          },
        };

        await insertAuditLogs(tx, auditLog);
      })
      .catch((err) => {
        // ... existing error handling ...
      });
+     await ingestAuditLogsTinybird(auditLog);

Also applies to: 90-112


90-112: ⚠️ Potential issue

Move ingestAuditLogsTinybird call outside the database transaction

Calling external services like ingestAuditLogsTinybird within a database transaction can lead to prolonged transaction times and potential locking issues, as the transaction remains open during the external operation. This can negatively impact database performance and scalability. Consider moving the call to ingestAuditLogsTinybird outside the transaction block to avoid holding the transaction open during external operations.

To implement this change, you can modify the code as follows:

  • Declare the key variable outside the transaction scope.
  • Assign key inside the transaction.
  • After the transaction completes, call ingestAuditLogsTinybird with the required data.

Apply this diff:

+ let key;
  await db
    .transaction(async (tx) => {
-     const key = await tx.query.keys.findFirst({
+     key = await tx.query.keys.findFirst({
        // ... existing code ...
      });

      // ... existing code ...

      await insertAuditLogs(tx, {
        // ... existing code ...
      });
    })
    .catch((err) => {
      // ... existing error handling ...
    });
+ await ingestAuditLogsTinybird({
+   workspaceId: key.workspace.id,
+   actor: {
+     type: "user",
+     id: ctx.user.id,
+   },
+   event: "key.update",
+   description: input.limitEnabled
+     ? `Changed remaining for ${key.id} to remaining=${input.remaining}, refill=${
+         input.refill ? `${input.refill.amount}@${input.refill.interval}` : "none"
+       }`
+     : `Disabled limit for ${key.id}`,
+   resources: [
+     {
+       type: "key",
+       id: key.id,
+     },
+   ],
+   context: {
+     location: ctx.audit.location,
+     userAgent: ctx.audit.userAgent,
+   },
+ });

Committable suggestion was skipped due to low confidence.

apps/dashboard/lib/trpc/routers/api/delete.ts (1)

50-68: 🛠️ Refactor suggestion

Refactor duplicated audit logging code

The blocks of code for insertAuditLogs and ingestAuditLogsTinybird in lines 50-68 and 99-124 are nearly identical, differing mainly in the event type and resources.

Consider refactoring to reduce duplication by creating a helper function that accepts parameters for the differing parts. This will enhance maintainability and reduce the risk of inconsistencies.

Also applies to: 99-124

apps/dashboard/lib/trpc/routers/ratelimit/deleteNamespace.ts (3)

52-52: ⚠️ Potential issue

Possible inconsistency in accessing workspaceId

At line 52, workspaceId is accessed as namespace.workspaceId, whereas later (line 110), it's accessed as namespace.workspace.id. Verify that namespace.workspaceId and namespace.workspace.id refer to the same value. Using consistent property access improves code readability and reduces the risk of bugs.


70-70: ⚠️ Potential issue

Inconsistent error handling for ingestAuditLogsTinybird

The call to ingestAuditLogsTinybird at line 70 lacks error handling, while a similar call at line 133 includes a .catch block that rolls back the transaction upon failure. This inconsistency could lead to unhandled exceptions or partial data updates if the ingest operation fails at line 70. Consider adding error handling to ensure consistent behavior and data integrity.


110-110: ⚠️ Potential issue

Consistency in accessing workspaceId

At line 110, workspaceId is accessed as namespace.workspace.id, whereas earlier (line 52), it's namespace.workspaceId. Ensure that both references are correct and refer to the same value. Consistent property access enhances clarity and helps prevent potential bugs.

apps/dashboard/lib/trpc/routers/key/create.ts (1)

112-127: ⚠️ Potential issue

Potential Duplication of Audit Logs

The function insertAuditLogs is called within the transaction at lines 112-127, and ingestAuditLogsTinybird is called after the transaction at lines 137-149 with the same audit log data. This may result in duplicate audit entries. Consider consolidating the audit logging to prevent redundancy.

Also applies to: 137-149

apps/dashboard/lib/trpc/routers/rbac/createRole.ts (3)

55-60: 🛠️ Refactor suggestion

Simplify error handling within the transaction

Currently, the error handling for the tx.insert(schema.roles) operation uses a .catch() block to throw a TRPCError. Since any unhandled exception within the transaction will automatically trigger a rollback, you might consider allowing errors to propagate naturally without the .catch() block. This simplifies the code and ensures that database errors are properly reported.

Alternatively, if you need custom error handling, you could wrap the entire transaction in a try-catch block to manage exceptions at a higher level.


82-102: 🛠️ Refactor suggestion

Avoid external network calls inside database transactions to improve performance

Calling ingestAuditLogsTinybird within the database transaction may prolong the transaction duration and potentially block database resources, especially if the external call experiences latency or fails. This can negatively impact database performance and concurrency.

Consider moving the ingestAuditLogsTinybird call outside of the transaction block to prevent external dependencies from affecting your database operations.


135-157: 🛠️ Refactor suggestion

Avoid external network calls inside database transactions to improve performance

Similarly, the ingestAuditLogsTinybird calls within the permissions handling are placed inside the transaction. To enhance performance and reduce the risk of long-running transactions, it's advisable to move these external calls outside of the transaction block.

apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts (3)

51-53: ⚠️ Potential issue

Provide more descriptive error messages

The error message "Invalid input." is generic and may not be helpful for users to correct their input. Consider providing more specific information about which input is invalid or what is expected.


64-88: 🛠️ Refactor suggestion

Refactor duplicate audit log data construction

The audit log data constructed for insertAuditLogs and ingestAuditLogsTinybird is duplicated in both if and else blocks. To enhance maintainability and reduce the risk of inconsistent data, consider constructing the audit log data once and reusing it for both functions.

Here's how you might refactor the code:

+const auditLogData = {
+  workspaceId: key.workspace.id,
+  actor: {
+    type: "user",
+    id: ctx.user.id,
+  },
+  event: "key.update",
+  description: input.enabled
+    ? `Changed ratelimit of ${key.id}`
+    : `Disabled ratelimit of ${key.id}`,
+  resources: [
+    {
+      type: "key",
+      id: key.id,
+      ...(input.enabled && {
+        meta: {
+          "ratelimit.async": input.ratelimitAsync,
+          "ratelimit.limit": input.ratelimitLimit,
+          "ratelimit.duration": input.ratelimitDuration,
+        },
+      }),
+    },
+  ],
+  context: {
+    location: ctx.audit.location,
+    userAgent: ctx.audit.userAgent,
+  },
+};

 if (input.enabled) {
   // validation logic

-  await db.transaction(async (tx) => {
+  await db.transaction(async (tx) => {
     await tx
       .update(schema.keys)
       .set({
         ratelimitAsync,
         ratelimitLimit,
         ratelimitDuration,
       })
       .where(eq(schema.keys.id, key.id));

-    await insertAuditLogs(tx, {
-      // audit log data
-    });
+    await insertAuditLogs(tx, auditLogData);
-  });
+  }).catch((err) => {
+    // error handling
+  });

-  await ingestAuditLogsTinybird({
-    // audit log data
-  }).catch((err) => {
+  await ingestAuditLogsTinybird(auditLogData).catch((err) => {
     // error handling
   });
 } else {
   await db
     .transaction(async (tx) => {
       await tx
         .update(schema.keys)
         .set({
           ratelimitAsync: null,
           ratelimitLimit: null,
           ratelimitDuration: null,
         })
         .where(eq(schema.keys.id, key.id));

-      await insertAuditLogs(tx, {
-        // audit log data
-      });
+      await insertAuditLogs(tx, auditLogData);
     })
     .catch((err) => {
       // error handling
     });

-  await ingestAuditLogsTinybird({
-    // audit log data
-  });
+  await ingestAuditLogsTinybird(auditLogData).catch((err) => {
+    // error handling
+  });
 }

Also applies to: 133-151, 90-119, 162-175


55-88: ⚠️ Potential issue

Add error handling to the transaction

The transaction block starting at line 55 does not have error handling attached to it. In contrast, the transaction in the else block includes a .catch to handle errors. For consistency and to ensure that any errors during the transaction are properly caught and handled, consider adding a .catch block.

Apply this diff to add error handling:

 await db.transaction(async (tx) => {
   // transaction operations
-});
+}).catch((err) => {
+  console.error(err);
+  throw new TRPCError({
+    code: "INTERNAL_SERVER_ERROR",
+    message:
+      "We were unable to update ratelimit on this key. Please contact support using support@unkey.dev",
+  });
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      await db.transaction(async (tx) => {
        await tx
          .update(schema.keys)
          .set({
            ratelimitAsync,
            ratelimitLimit,
            ratelimitDuration,
          })
          .where(eq(schema.keys.id, key.id));
        await insertAuditLogs(tx, {
          workspaceId: key.workspace.id,
          actor: {
            type: "user",
            id: ctx.user.id,
          },
          event: "key.update",
          description: `Changed ratelimit of ${key.id}`,
          resources: [
            {
              type: "key",
              id: key.id,
              meta: {
                "ratelimit.async": ratelimitAsync,
                "ratelimit.limit": ratelimitLimit,
                "ratelimit.duration": ratelimitDuration,
              },
            },
          ],
          context: {
            location: ctx.audit.location,
            userAgent: ctx.audit.userAgent,
          },
        });
      }).catch((err) => {
        console.error(err);
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
          message:
            "We were unable to update ratelimit on this key. Please contact support using support@unkey.dev",
        });
      });
apps/dashboard/app/new/page.tsx (3)

220-253: ⚠️ Potential issue

Potential inconsistency: ingestAuditLogsTinybird is called outside the transaction

The workspace creation and database audit log insertion are within a transaction, but ingestAuditLogsTinybird is called outside the transaction. If the transaction fails, ingestAuditLogsTinybird will still execute, possibly resulting in audit logs without a corresponding workspace. Consider moving ingestAuditLogsTinybird inside the transaction to ensure atomicity.


233-254: 🛠️ Refactor suggestion

DRY Principle: Reuse audit log data to avoid duplication

The audit log data passed to both insertAuditLogs and ingestAuditLogsTinybird is identical. To adhere to the DRY principle, consider preparing the audit log data once, assigning it to a variable, and reusing it in both calls.


250-250: ⚠️ Potential issue

Security concern: Validate x-forwarded-for header to prevent spoofing

The x-forwarded-for header can be manipulated by clients, potentially leading to incorrect location data. Consider using a trusted source for the client's IP address, such as middleware or a service that reliably provides this information.

apps/dashboard/lib/trpc/routers/vercel.ts (6)

78-97: 🛠️ Refactor suggestion

Consider encapsulating audit logging to reduce code duplication

The code block between lines 78-97 and similar blocks throughout the file repeat the same logic for logging audit events using insertAuditLogs and ingestAuditLogsTinybird. To improve maintainability and adhere to the DRY (Don't Repeat Yourself) principle, consider creating a helper function that handles both logging mechanisms.

Apply this diff to introduce a helper function:

+// Define a helper function for audit logging
+async function logAuditEvent(tx, params) {
+  await insertAuditLogs(tx, params);
+  await ingestAuditLogsTinybird(params);
+}

-              await insertAuditLogs(tx, {
-                workspaceId: integration.workspace.id,
-                actor: { type: "user", id: ctx.user.id },
-                event: "key.create",
-                description: `Created ${keyId}`,
-                resources: [
-                  {
-                    type: "key",
-                    id: keyId,
-                  },
-                  {
-                    type: "vercelIntegration",
-                    id: integration.id,
-                  },
-                ],
-                context: {
-                  location: ctx.audit.location,
-                  userAgent: ctx.audit.userAgent,
-                },
-              });
-              await ingestAuditLogsTinybird({
-                workspaceId: integration.workspace.id,
-                actor: { type: "user", id: ctx.user.id },
-                event: "key.create",
-                description: `Created ${keyId}`,
-                resources: [
-                  {
-                    type: "key",
-                    id: keyId,
-                  },
-                  {
-                    type: "vercelIntegration",
-                    id: integration.id,
-                  },
-                ],
-                context: {
-                  location: ctx.audit.location,
-                  userAgent: ctx.audit.userAgent,
-                },
-              });
+              await logAuditEvent(tx, {
+                workspaceId: integration.workspace.id,
+                actor: { type: "user", id: ctx.user.id },
+                event: "key.create",
+                description: `Created ${keyId}`,
+                resources: [
+                  {
+                    type: "key",
+                    id: keyId,
+                  },
+                  {
+                    type: "vercelIntegration",
+                    id: integration.id,
+                  },
+                ],
+                context: {
+                  location: ctx.audit.location,
+                  userAgent: ctx.audit.userAgent,
+                },
+              });

Committable suggestion was skipped due to low confidence.


299-322: 🛠️ Refactor suggestion

Refactor audit logging to use the helper function

The code between lines 299-322 replicates audit logging logic. Applying the logAuditEvent helper function enhances code maintainability.

Apply this diff:

-              await insertAuditLogs(tx, {
-                workspaceId: integration.workspace.id,
-                actor: { type: "user", id: ctx.user.id },
-                event: "vercelBinding.update",
-                description: `Updated ${existingBinding.id}`,
-                resources: [
-                  {
-                    type: "vercelBinding",
-                    id: existingBinding.id,
-                    meta: {
-                      vercelEnvironment: res.val.created.id,
-                    },
-                  },
-                  {
-                    type: "api",
-                    id: input.apiId,
-                  },
-                ],
-                context: {
-                  location: ctx.audit.location,
-                  userAgent: ctx.audit.userAgent,
-                },
-              });
-              await ingestAuditLogsTinybird({
-                workspaceId: integration.workspace.id,
-                actor: { type: "user", id: ctx.user.id },
-                event: "vercelBinding.update",
-                description: `Updated ${existingBinding.id}`,
-                resources: [
-                  {
-                    type: "vercelBinding",
-                    id: existingBinding.id,
-                    meta: {
-                      vercelEnvironment: res.val.created.id,
-                    },
-                  },
-                  {
-                    type: "api",
-                    id: input.apiId,
-                  },
-                ],
-                context: {
-                  location: ctx.audit.location,
-                  userAgent: ctx.audit.userAgent,
-                },
-              });
+              await logAuditEvent(tx, {
+                workspaceId: integration.workspace.id,
+                actor: { type: "user", id: ctx.user.id },
+                event: "vercelBinding.update",
+                description: `Updated ${existingBinding.id}`,
+                resources: [
+                  {
+                    type: "vercelBinding",
+                    id: existingBinding.id,
+                    meta: {
+                      vercelEnvironment: res.val.created.id,
+                    },
+                  },
+                  {
+                    type: "api",
+                    id: input.apiId,
+                  },
+                ],
+                context: {
+                  location: ctx.audit.location,
+                  userAgent: ctx.audit.userAgent,
+                },
+              });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

          await logAuditEvent(tx, {
            workspaceId: integration.workspace.id,
            actor: { type: "user", id: ctx.user.id },
            event: "vercelBinding.update",
            description: `Updated ${existingBinding.id}`,
            resources: [
              {
                type: "vercelBinding",
                id: existingBinding.id,
                meta: {
                  vercelEnvironment: res.val.created.id,
                },
              },
              {
                type: "api",
                id: input.apiId,
              },
            ],
            context: {
              location: ctx.audit.location,
              userAgent: ctx.audit.userAgent,
            },
          });

567-595: 🛠️ Refactor suggestion

Incorporate the helper function for audit logging

Lines 567-595 contain duplicate audit logging code. Refactoring with the logAuditEvent function enhances code quality.

Apply this diff:

-              await insertAuditLogs(tx, {
-                workspaceId: integration.workspace.id,
-                actor: { type: "user", id: ctx.user.id },
-                event: "vercelBinding.create",
-                description: `Created ${vercelBindingId} for ${keyId}`,
-                resources: [
-                  {
-                    type: "vercelIntegration",
-                    id: integration.id,
-                  },
-                  {
-                    type: "vercelBinding",
-                    id: vercelBindingId,
-                    meta: {
-                      environment: input.environment,
-                      projectId: input.projectId,
-                    },
-                  },
-                  {
-                    type: "key",
-                    id: keyId,
-                  },
-                ],
-                context: {
-                  location: ctx.audit.location,
-                  userAgent: ctx.audit.userAgent,
-                },
-              });
-              await ingestAuditLogsTinybird({
-                workspaceId: integration.workspace.id,
-                actor: { type: "user", id: ctx.user.id },
-                event: "vercelBinding.create",
-                description: `Created ${vercelBindingId} for ${keyId}`,
-                resources: [
-                  {
-                    type: "vercelIntegration",
-                    id: integration.id,
-                  },
-                  {
-                    type: "vercelBinding",
-                    id: vercelBindingId,
-                    meta: {
-                      environment: input.environment,
-                      projectId: input.projectId,
-                    },
-                  },
-                  {
-                    type: "key",
-                    id: keyId,
-                  },
-                ],
-                context: {
-                  location: ctx.audit.location,
-                  userAgent: ctx.audit.userAgent,
-                },
-              });
+              await logAuditEvent(tx, {
+                workspaceId: integration.workspace.id,
+                actor: { type: "user", id: ctx.user.id },
+                event: "vercelBinding.create",
+                description: `Created ${vercelBindingId} for ${keyId}`,
+                resources: [
+                  {
+                    type: "vercelIntegration",
+                    id: integration.id,
+                  },
+                  {
+                    type: "vercelBinding",
+                    id: vercelBindingId,
+                    meta: {
+                      environment: input.environment,
+                      projectId: input.projectId,
+                    },
+                  },
+                  {
+                    type: "key",
+                    id: keyId,
+                  },
+                ],
+                context: {
+                  location: ctx.audit.location,
+                  userAgent: ctx.audit.userAgent,
+                },
+              });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

          await logAuditEvent(tx, {
            workspaceId: integration.workspace.id,
            actor: { type: "user", id: ctx.user.id },
            event: "vercelBinding.create",
            description: `Created ${vercelBindingId} for ${keyId}`,
            resources: [
              {
                type: "vercelIntegration",
                id: integration.id,
              },
              {
                type: "vercelBinding",
                id: vercelBindingId,
                meta: {
                  environment: input.environment,
                  projectId: input.projectId,
                },
              },
              {
                type: "key",
                id: keyId,
              },
            ],
            context: {
              location: ctx.audit.location,
              userAgent: ctx.audit.userAgent,
            },
          });

442-458: 🛠️ Refactor suggestion

Refactor audit logging using the helper function

Between lines 442-458, audit logging code is repeated. Utilize the logAuditEvent helper function to streamline the logic.

Apply this diff:

-            await insertAuditLogs(tx, {
-              workspaceId: integration.workspace.id,
-              actor: { type: "user", id: ctx.user.id },
-              event: "key.create",
-              description: `Created ${keyId}`,
-              resources: [
-                {
-                  type: "key",
-                  id: keyId,
-                },
-              ],
-              context: {
-                location: ctx.audit.location,
-                userAgent: ctx.audit.userAgent,
-              },
-            });
-            await ingestAuditLogsTinybird({
-              workspaceId: integration.workspace.id,
-              actor: { type: "user", id: ctx.user.id },
-              event: "key.create",
-              description: `Created ${keyId}`,
-              resources: [
-                {
-                  type: "key",
-                  id: keyId,
-                },
-              ],
-              context: {
-                location: ctx.audit.location,
-                userAgent: ctx.audit.userAgent,
-              },
-            });
+            await logAuditEvent(tx, {
+              workspaceId: integration.workspace.id,
+              actor: { type: "user", id: ctx.user.id },
+              event: "key.create",
+              description: `Created ${keyId}`,
+              resources: [
+                {
+                  type: "key",
+                  id: keyId,
+                },
+              ],
+              context: {
+                location: ctx.audit.location,
+                userAgent: ctx.audit.userAgent,
+              },
+            });

Committable suggestion was skipped due to low confidence.


503-526: 🛠️ Refactor suggestion

Ensure consistency in audit logging by refactoring

Lines 503-526 show repeated audit logging code. Use the logAuditEvent helper function for consistency and to reduce duplication.

Apply this diff:

-              await insertAuditLogs(tx, {
-                workspaceId: integration.workspace.id,
-                actor: { type: "user", id: ctx.user.id },
-                event: "vercelBinding.update",
-                description: `Updated ${existingBinding.id}`,
-                resources: [
-                  {
-                    type: "vercelBinding",
-                    id: existingBinding.id,
-                    meta: {
-                      vercelEnvironment: res.val.created.id,
-                    },
-                  },
-                  {
-                    type: "key",
-                    id: keyId,
-                  },
-                ],
-                context: {
-                  location: ctx.audit.location,
-                  userAgent: ctx.audit.userAgent,
-                },
-              });
-              await ingestAuditLogsTinybird({
-                workspaceId: integration.workspace.id,
-                actor: { type: "user", id: ctx.user.id },
-                event: "vercelBinding.update",
-                description: `Updated ${existingBinding.id}`,
-                resources: [
-                  {
-                    type: "vercelBinding",
-                    id: existingBinding.id,
-                    meta: {
-                      vercelEnvironment: res.val.created.id,
-                    },
-                  },
-                  {
-                    type: "key",
-                    id: keyId,
-                  },
-                ],
-                context: {
-                  location: ctx.audit.location,
-                  userAgent: ctx.audit.userAgent,
-                },
-              });
+              await logAuditEvent(tx, {
+                workspaceId: integration.workspace.id,
+                actor: { type: "user", id: ctx.user.id },
+                event: "vercelBinding.update",
+                description: `Updated ${existingBinding.id}`,
+                resources: [
+                  {
+                    type: "vercelBinding",
+                    id: existingBinding.id,
+                    meta: {
+                      vercelEnvironment: res.val.created.id,
+                    },
+                  },
+                  {
+                    type: "key",
+                    id: keyId,
+                  },
+                ],
+                context: {
+                  location: ctx.audit.location,
+                  userAgent: ctx.audit.userAgent,
+                },
+              });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

          await logAuditEvent(tx, {
            workspaceId: integration.workspace.id,
            actor: { type: "user", id: ctx.user.id },
            event: "vercelBinding.update",
            description: `Updated ${existingBinding.id}`,
            resources: [
              {
                type: "vercelBinding",
                id: existingBinding.id,
                meta: {
                  vercelEnvironment: res.val.created.id,
                },
              },
              {
                type: "key",
                id: keyId,
              },
            ],
            context: {
              location: ctx.audit.location,
              userAgent: ctx.audit.userAgent,
            },
          });

148-168: 🛠️ Refactor suggestion

Refactor repeated audit logging code using the helper function

Similar to the previous suggestion, the code between lines 148-168 duplicates audit logging logic. Utilize the proposed logAuditEvent helper function to streamline the code and enhance readability.

Apply this diff to use the helper function:

-              await insertAuditLogs(tx, {
-                workspaceId: integration.workspace.id,
-                actor: { type: "user", id: ctx.user.id },
-                event: "vercelBinding.create",
-                description: `Created ${vercelBindingId} for ${keyId}`,
-                resources: [
-                  {
-                    type: "vercelBinding",
-                    id: vercelBindingId,
-                  },
-                  {
-                    type: "key",
-                    id: keyId,
-                  },
-                ],
-                context: {
-                  location: ctx.audit.location,
-                  userAgent: ctx.audit.userAgent,
-                },
-              });
-              await ingestAuditLogsTinybird({
-                workspaceId: integration.workspace.id,
-                actor: { type: "user", id: ctx.user.id },
-                event: "vercelBinding.create",
-                description: `Created ${vercelBindingId} for ${keyId}`,
-                resources: [
-                  {
-                    type: "vercelBinding",
-                    id: vercelBindingId,
-                  },
-                  {
-                    type: "key",
-                    id: keyId,
-                  },
-                ],
-                context: {
-                  location: ctx.audit.location,
-                  userAgent: ctx.audit.userAgent,
-                },
-              });
+              await logAuditEvent(tx, {
+                workspaceId: integration.workspace.id,
+                actor: { type: "user", id: ctx.user.id },
+                event: "vercelBinding.create",
+                description: `Created ${vercelBindingId} for ${keyId}`,
+                resources: [
+                  {
+                    type: "vercelBinding",
+                    id: vercelBindingId,
+                  },
+                  {
+                    type: "key",
+                    id: keyId,
+                  },
+                ],
+                context: {
+                  location: ctx.audit.location,
+                  userAgent: ctx.audit.userAgent,
+                },
+              });

Committable suggestion was skipped due to low confidence.

apps/dashboard/lib/trpc/routers/rbac.ts (9)

783-812: ⚠️ Potential issue

Missing Tinybird ingestion after deleting a permission

In the deletePermission method, audit logs are inserted but not ingested into Tinybird, which could lead to missing audit data.

Recommendation:

Add ingestAuditLogsTinybird after the transaction to ensure the audit logs are properly ingested.

Committable suggestion was skipped due to low confidence.


209-236: ⚠️ Potential issue

Ensure audit logs are ingested into Tinybird

In the connectPermissionToRole method, the audit logs are inserted into the database, but ingestAuditLogsTinybird is not called to ingest them into Tinybird. This could lead to missing audit entries in your analytics.

Recommendation:

After the transaction, call ingestAuditLogsTinybird to ensure the audit logs are properly ingested.

Committable suggestion was skipped due to low confidence.


256-286: ⚠️ Potential issue

Missing Tinybird ingestion after disconnecting permission from role

In the disconnectPermissionToRole method, audit logs are inserted into the database, but there's no call to ingestAuditLogsTinybird. This could result in the audit logs not being reflected in Tinybird.

Recommendation:

Add ingestAuditLogsTinybird after the transaction to ingest the audit logs into Tinybird.

Committable suggestion was skipped due to low confidence.


131-161: ⚠️ Potential issue

Missing ingestion of audit logs into Tinybird

In the removePermissionFromRootKey method, after inserting audit logs into the database using insertAuditLogs, there is no corresponding call to ingestAuditLogsTinybird to ingest these logs into Tinybird. This omission may result in incomplete audit data in Tinybird.

Recommendation:

Add a call to ingestAuditLogsTinybird after the transaction to ensure audit logs are ingested into Tinybird.

Committable suggestion was skipped due to low confidence.


334-361: ⚠️ Potential issue

Ensure audit logs are ingested into Tinybird

In the connectRoleToKey method, after inserting audit logs into the database, ingestAuditLogsTinybird is not called. This omission may cause incomplete audit data in Tinybird.

Recommendation:

Include a call to ingestAuditLogsTinybird after the transaction.

Committable suggestion was skipped due to low confidence.


71-82: ⚠️ Potential issue

Ensure atomicity by moving Tinybird ingestion inside the transaction

In the addPermissionToRootKey method, the call to ingestAuditLogsTinybird(auditLogs); is placed outside the transaction block. If the transaction fails but the audit logs are still ingested into Tinybird, it could lead to inconsistencies between your database and audit logs.

Recommendation:

Move the ingestAuditLogsTinybird call inside the transaction to ensure that audit logging is atomic with the database operations.

    ...

Committable suggestion was skipped due to low confidence.


550-567: ⚠️ Potential issue

Missing Tinybird ingestion after updating a role

In the updateRole method, the audit logs are inserted into the database, but they are not ingested into Tinybird. This could lead to inconsistencies in your audit data.

Recommendation:

Add a call to ingestAuditLogsTinybird after the transaction to ingest the audit logs.

Committable suggestion was skipped due to low confidence.


723-752: ⚠️ Potential issue

Missing Tinybird ingestion after updating a permission

The updatePermission method inserts audit logs into the database without ingesting them into Tinybird. This may result in incomplete audit records in Tinybird.

Recommendation:

Call ingestAuditLogsTinybird after the transaction to ingest the audit logs.

Committable suggestion was skipped due to low confidence.


598-619: ⚠️ Potential issue

Missing Tinybird ingestion after deleting a role

In the deleteRole method, audit logs are inserted into the database but not ingested into Tinybird, potentially causing missing entries in your audit analytics.

Recommendation:

Include ingestAuditLogsTinybird after the transaction to ensure audit logs are ingested.

Committable suggestion was skipped due to low confidence.

apps/dashboard/lib/trpc/routers/key/updateRootKeyName.ts (1)

73-73: ⚠️ Potential issue

Handle potential null values in audit log description

If input.name is null or undefined, the description in the audit logs will read Changed name of ${key.id} to null, which may be confusing. Consider adjusting the description to handle null or undefined values appropriately.

Suggested change:

- description: `Changed name of ${key.id} to ${input.name}`,
+ description: input.name
+   ? `Changed name of ${key.id} to ${input.name}`
+   : `Removed name of ${key.id}`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        description: input.name
          ? `Changed name of ${key.id} to ${input.name}`
          : `Removed name of ${key.id}`,
apps/dashboard/lib/trpc/routers/workspace/changePlan.ts (2)

141-156: 🛠️ Refactor suggestion

DRY Principle: Extract Repeated Audit Logging Code

The code for inserting audit logs is repeated multiple times. To adhere to the DRY (Don't Repeat Yourself) principle, consider creating a helper function to handle audit logging.

Create a utility function to handle audit log insertion:

function logWorkspaceUpdate(tx, ctx, workspaceId, description) {
  return insertAuditLogs(tx, {
    workspaceId,
    actor: { type: "user", id: ctx.user.id },
    event: "workspace.update",
    description,
    resources: [{ type: "workspace", id: workspaceId }],
    context: {
      location: ctx.audit.location,
      userAgent: ctx.audit.userAgent,
    },
  });
}

Then replace the audit logging calls with:

- await insertAuditLogs(tx, {
-   // parameters
- });
+ await logWorkspaceUpdate(tx, ctx, workspace.id, "Requested downgrade to 'free'");

114-115: ⚠️ Potential issue

Avoid Logging Sensitive Information

Logging the entire error object may inadvertently expose sensitive information. Consider logging only the necessary details or using a logging utility that sanitizes sensitive data.

Apply this diff to log only the error message:

- console.error(err);
+ console.error('Transaction error in changeWorkspacePlan:', err.message);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

          .catch((err) => {
            console.error('Transaction error in changeWorkspacePlan:', err.message);

harshsbhat pushed a commit to harshsbhat/unkey that referenced this pull request Sep 28, 2024
…keyed#2134)

* feat: mostly just duplicate audit logs and other clickhouse stuff

* chore: audit logs for trpc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants