Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dynamic token refresh and authentication error handling #911

Merged
merged 18 commits into from
Nov 7, 2024

Conversation

chacha912
Copy link
Contributor

@chacha912 chacha912 commented Oct 17, 2024

What this PR does / why we need it?

  • Modify client option to accept an authTokenInjector function instead of a static token
  • Implement retry logic after token update
  • Event notification on authentication error (in syncLoop or watchLoop)

Any background context you want to provide?

image

1. Token Refresh Support

Added a new authTokenInjector interface that enables token refresh based on authentication error messages.
When a 401(Unauthenticated) error occurs, the SDK calls the authTokenInjector with the error message to obtain a new authToken and retries the request.

// as-is
const client = new yorkie.Client(YorkieAPIAddr, {
  apiKey,
  token
});

// to-be
const client = new yorkie.Client(YorkieAPIAddr, {
  apiKey,
  authTokenInjector: async (reason) => {
    // Handle token refresh logic according to the error message in the webhook response
    if (reason === 'token expired') {
      return await refreshAccessToken();
    }
    return accessToken;
  },
});

2. Updated Webhook Response Format

Added webhook response status codes in HTTP header: yorkie-team/yorkie#1037

  • 200: OK (Allowed)
  • 401: Unauthorized (Invalid or missing token)
  • 403: Forbidden (Permission denied)

403 errors and invalid webhook response formats will throw errors immediately without attempting token refresh.

3. Authentication Error Event Notifications

Added support for monitoring authentication errors through event subscriptions:

doc.subscribe('auth-error', (event) => {
  console.log(event.value); 
  // event.value contains:
  // - reason: string
  // - method: 'PushPull' | 'WatchDocuments'
});
  • Notifies when a 401 response is received from the webhook during syncLoop(PushPull) or watchLoop(WatchDocuments)

4. Retry Logic for Authentication Errors

  • Automatic Retry
    • syncLoop(PushPull), watchLoop(WatchDocuments)
    • Automatically handle token refresh and retry on auth errors
    • Error monitoring available through auth-error event subscription
  • Manual Retry Required
    • activate, deactivate, attach, detach, manual sync
    • When an auth error occurs, the token refresh will be triggered but the operation will not be retried automatically
    • Users need to catch the thrown auth error and manually retry the operation

What are the relevant tickets?

Fixes yorkie-team/yorkie#996

Checklist

  • Added relevant tests or not required
  • Didn't break anything

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced error handling capabilities, including new authentication error events and improved token management.
    • Introduced new dependencies to bolster SDK functionality.
    • Added integration tests for authentication webhook scenarios.
  • Bug Fixes

    • Improved error handling for authentication issues and connection errors.
  • Documentation

    • Updated event handling documentation to include new authentication error events.
  • Chores

    • Updated Docker configurations for better host communication.

@chacha912 chacha912 marked this pull request as ready for review October 23, 2024 23:34
Copy link

coderabbitai bot commented Oct 23, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The pull request introduces several changes across multiple files, primarily focusing on enhancing error handling, authentication mechanisms, and dependency management within the yorkie-js-sdk. Key modifications include adding new dependencies in package.json, improving error handling functions in converter.ts, updating the Client class for dynamic token management, and enhancing event handling in the Document class. Additionally, new integration tests for the authentication webhook are introduced, along with updates to Docker configurations to facilitate communication with the host.

Changes

File Path Change Summary
packages/sdk/package.json Added dependencies: @types/express, axios, express; updated vite-tsconfig-paths version from ^4.2.1 to ^4.3.1.
packages/sdk/src/api/converter.ts Replaced errorCodeOf function with errorMetadataOf for improved error metadata retrieval; updated control flow to integrate new function.
packages/sdk/src/client/client.ts Updated ClientOptions to replace token with authTokenInjector method; refactored methods for asynchronous error handling and token refresh logic.
packages/sdk/src/document/document.ts Added AuthError event type and AuthErrorEvent interface; updated event handling to support new authentication error events.
packages/sdk/src/util/error.ts Added new error codes: ErrPermissionDenied and ErrUnauthenticated.
packages/sdk/test/helper/helper.ts Updated assertThrowsAsync function to accept RegExp or string for error message validation.
packages/sdk/test/integration/integration_helper.ts Introduced constants testAPIID, testAPIPW, and webhookAddr based on environment variables and checks for Docker container status.
packages/sdk/test/integration/webhook_test.ts Added integration tests for the authentication webhook, covering various token scenarios and ensuring proper error handling and token refresh logic.
docker/docker-compose-ci.yml Added extra_hosts entry for yorkie service to resolve host machine's address.
docker/docker-compose.yml Added extra_hosts entry for yorkie service to allow communication with the host system.
packages/sdk/src/client/auth_interceptor.ts Modified createAuthInterceptor function to return an object with authInterceptor and setToken for dynamic token updates.
packages/sdk/src/document/change/change_pack.ts Renamed getKey method to getDocumentKey for clarity.
packages/sdk/src/yorkie.ts Renamed constants: DocumentSyncStatus to DocSyncStatus and DocumentStatus to DocStatus for consistency.
packages/sdk/test/bench/document.bench.ts Updated import statement for DocumentStatus to DocStatus in benchmark tests.
packages/sdk/test/integration/client_test.ts Renamed DocumentSyncStatus to DocSyncStatus in various test cases for consistency.
packages/sdk/test/integration/document_test.ts Renamed DocumentStatus to DocStatus in multiple test scenarios for clarity.

Assessment against linked issues

Objective Addressed Explanation
Provide a way to refresh token for Auth Webhook (#996)

Possibly related PRs

Suggested reviewers

  • chacha912
  • cloneot

🐇 In the garden, where bunnies play,
New tokens hop in, come what may!
With errors caught and paths so bright,
We dance through code, a joyful sight!
For every hop, a test we weave,
In Yorkie's world, we all believe! 🌼


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

@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: 15

🧹 Outside diff range and nitpick comments (9)
packages/sdk/test/helper/helper.ts (1)

190-190: LGTM! Consider adding JSDoc examples.

The signature change to support both string and RegExp for error message validation is a good improvement. This will enable more flexible error testing patterns, especially useful for the new authentication token update feature.

Consider adding JSDoc examples to demonstrate both string and RegExp usage:

/**
 * Asserts that the async function throws an error matching the expected type and message.
 * @param fn The async function to test
 * @param errType The expected error type
 * @param message Optional string or RegExp to match against the error message
 * @example
 * // Using string message
 * await assertThrowsAsync(
 *   async () => { throw new Error('auth failed') },
 *   Error,
 *   'auth failed'
 * );
 * 
 * // Using RegExp pattern
 * await assertThrowsAsync(
 *   async () => { throw new Error('token expired: ABC123') },
 *   Error,
 *   /token expired: .+/
 * );
 */
packages/sdk/src/api/converter.ts (1)

822-832: Add JSDoc documentation for the new function.

The function lacks documentation about its purpose, parameters, return value, and usage examples. This is especially important as it's part of the authentication error handling mechanism.

Add comprehensive documentation:

+/**
+ * `errorMetadataOf` extracts all metadata from the given ConnectError.
+ * This is particularly useful for handling authentication errors and token refresh scenarios.
+ *
+ * @param error - The ConnectError containing error details
+ * @returns A record of metadata key-value pairs or an empty object if no metadata exists
+ * 
+ * @example
+ * ```typescript
+ * const error = new ConnectError("Auth failed");
+ * const metadata = errorMetadataOf(error);
+ * if (metadata.reason === "token_expired") {
+ *   // Handle token refresh
+ * }
+ * ```
+ */
 export function errorMetadataOf(error: ConnectError): Record<string, string> {
packages/sdk/src/document/document.ts (2)

909-917: Enhance documentation for auth-error subscription.

The method overload is correctly implemented, but the documentation could be more descriptive about when this event occurs and how to handle it.

 /**
  * `subscribe` registers a callback to subscribe to events on the document.
- * The callback will be called when the authentification error occurs.
+ * The callback will be called when authentication errors occur, such as:
+ * - Token expiration
+ * - Invalid token
+ * - Authentication webhook failures
+ * 
+ * @example
+ * ```typescript
+ * doc.subscribe('auth-error', (event) => {
+ *   console.log(`Auth error in ${event.value.method}: ${event.value.errorMessage}`);
+ *   // Handle token refresh
+ * });
+ * ```
  */

Line range hint 202-1101: Consider enhancing the auth error handling architecture.

The current implementation provides a good foundation for auth error handling through events. However, consider these architectural improvements:

  1. Error Recovery Pattern: Consider implementing a standardized error recovery pattern that includes:

    • Automatic retry with exponential backoff
    • Circuit breaker pattern for repeated auth failures
    • Queue for operations during token refresh
  2. Token Lifecycle Management: Consider adding events for the entire token lifecycle:

    • Token refresh initiated
    • Token refresh succeeded
    • Token refresh failed
  3. State Management: Consider tracking auth state to prevent operation attempts during token refresh.

This would make the authentication system more robust and provide better visibility into the auth state.

packages/sdk/test/integration/webhook_test.ts (1)

308-312: Avoid potential test flakiness due to timing dependencies

Using a short token expiration time like 500ms and relying on setTimeout can lead to flaky tests, especially on slower machines or under varying load conditions.

Consider increasing TokenExpirationMs to a higher value or mock the time functions to simulate token expiration without relying on real-time delays.

Also applies to: 328-329

packages/sdk/test/integration/client_test.ts (1)

Line range hint 428-436: Simplify the conditional statements in the broadcast event handler

Both conditions in the if and else if statements perform the same action. You can simplify the code by removing the conditional checks.

Apply this diff to simplify the code:

 const unsubscribe = d2.subscribe('broadcast', (event) => {
   const { topic, payload } = event.value;

-  if (topic === broadcastTopic1) {
-    eventCollector.add([topic, payload]);
-  } else if (topic === broadcastTopic2) {
-    eventCollector.add([topic, payload]);
-  }
+  eventCollector.add([topic, payload]);
 });
packages/sdk/src/client/client.ts (3)

132-138: Clarify the authErrorMessage parameter in authTokenInjector.

The description mentions an authError parameter, but the function signature uses authErrorMessage. Ensure consistency and clarify the purpose of this parameter in the documentation.


Line range hint 913-987: Ensure retryCount increments correctly in runWatchLoop.

In the runWatchLoop, the retryCount variable is initialized but may not increment in the error handling block, potentially leading to infinite retries. Increment retryCount upon each retry attempt and enforce maxRequestRetries.

Apply this diff to increment retryCount:

...
if (await this.handleConnectError(err)) {
  if (
    err instanceof ConnectError &&
    errorCodeOf(err) === Code.ErrUnauthenticated
  ) {
    if (retryCount >= this.maxRequestRetries) {
      logger.error(
        `[WD] c:"${this.getKey()}" max retries (${this.maxRequestRetries}) exceeded`,
      );
      reject(err);
      return;
    }
    attachment.doc.publish([
      {
        type: DocEventType.AuthError,
        value: {
          errorMessage: errorMetadataOf(err).message,
          method: 'WatchDocuments',
        },
      },
    ]);
  }
  onDisconnect();
+ retryCount++;
} else {
  this.conditions[ClientCondition.WatchLoop] = false;
}
...

Line range hint 1147-1186: Expand retry logic to include additional error codes.

Currently, handleConnectError retries on a specific set of error codes. Consider including DeadlineExceeded and Internal errors, which are often transient and may benefit from retrying.

Apply this diff to handle more retryable errors:

...
if (
  err.code === ConnectErrorCode.Canceled ||
  err.code === ConnectErrorCode.Unknown ||
  err.code === ConnectErrorCode.ResourceExhausted ||
  err.code === ConnectErrorCode.Unavailable ||
+ err.code === ConnectErrorCode.DeadlineExceeded ||
+ err.code === ConnectErrorCode.Internal
) {
  return true;
}
...
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 1d8adb3 and 840cf00.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • packages/sdk/package.json (2 hunks)
  • packages/sdk/src/api/converter.ts (1 hunks)
  • packages/sdk/src/client/client.ts (21 hunks)
  • packages/sdk/src/document/document.ts (6 hunks)
  • packages/sdk/src/util/error.ts (1 hunks)
  • packages/sdk/test/helper/helper.ts (1 hunks)
  • packages/sdk/test/integration/client_test.ts (1 hunks)
  • packages/sdk/test/integration/integration_helper.ts (2 hunks)
  • packages/sdk/test/integration/webhook_test.ts (1 hunks)
🔇 Additional comments (10)
packages/sdk/test/integration/integration_helper.ts (2)

31-36: Verify retry logic test coverage.

Setting retryRequestDelay: 0 could potentially mask timing-related issues in the retry mechanism. While this makes tests run faster, it might not catch real-world scenarios where delays between retries are crucial.

#!/bin/bash
# Check for retry-related test coverage
echo "Checking for retry-related tests..."
rg -g '**/*test.ts' -B 5 -A 5 'retryRequestDelay|retry.*logic|retry.*mechanism'

# Look for other test files that might need the retry configuration
echo "Checking for other client initializations that might need updating..."
rg -g '**/*test.ts' 'new yorkie\.Client'

Consider adding:

  1. Tests with non-zero retry delays to validate timing behavior
  2. Tests specifically for the retry mechanism with token updates
  3. Tests for maximum retry attempts and failure scenarios

8-9: Consider security implications of default credentials and add documentation.

While using default credentials in tests is common, having them default to 'admin' could lead to:

  1. Accidentally running tests against production if env vars are misconfigured
  2. Security issues if these defaults are copied to other contexts

Consider these improvements:

  1. Add JSDoc comments explaining the purpose and usage of these constants
  2. Use more obvious test credentials (e.g., 'test-api-id') to prevent confusion
  3. Consider making these required env vars for better security
✅ Verification successful

Let me generate additional verification scripts to check for security implications.

The first script will help us verify that these test credentials are indeed confined to test files. The second script will help us understand the broader authentication testing patterns in the codebase to make a more informed decision about the security implications of these default credentials.


Let me try one more verification with corrected pattern for test files:

This should help us understand the authentication patterns in tests and check if these credentials are mentioned in any documentation, which would be concerning from a security perspective.


Based on the verification results, I can now provide a final response:

Default test credentials are safely confined to test environment

The verification shows that:

  1. The TEST_API_ID and TEST_API_PW environment variables and their default values are only used in test files
  2. The credentials are part of a comprehensive authentication testing suite that includes proper token management, expiration handling, and error scenarios
  3. These credentials are not referenced in any documentation or example files that could lead to misuse

The default 'admin' values are acceptable in this context because:

  1. They are only used in integration tests
  2. The test suite properly tests both environment variable and default scenarios
  3. The authentication system has proper security measures including token expiration and refresh mechanisms
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if these credentials are used in actual implementation code
rg -g '!test/**' -l 'admin.*admin' .

Length of output: 17785


Script:

#!/bin/bash
# Check if these credentials are used in production code or configuration
rg -g '!test/**' 'TEST_API_ID|TEST_API_PW' .

# Check if there are any other test credentials in the test files
rg -g 'test/**' 'apiKey|token|credential|auth' -B 2 -A 2

Length of output: 484


Script:

#!/bin/bash
# Check for authentication patterns in test files
rg -g '**/*test*.ts' 'apiKey|token|credential|auth' -B 2 -A 2

# Check if these test credentials are referenced in any documentation or example files
rg -g '**/*.{md,mdx,txt}' 'TEST_API_ID|TEST_API_PW|testAPIID|testAPIPW'

Length of output: 24484

packages/sdk/package.json (2)

67-67: LGTM: Safe dependency update.

The update of vite-tsconfig-paths from 4.2.1 to 4.3.1 is a minor version bump that should maintain backward compatibility according to semver.


56-56: Verify security implications of axios version.

The current axios version (1.7.7) might have known vulnerabilities. Consider upgrading to the latest patch version.

Let's check for known vulnerabilities:

✅ Verification successful

Let me try a different approach to verify the axios version security implications using GitHub's API to check for security advisories.

This script will help us:

  1. Check for any published security advisories affecting axios
  2. Get the latest version from the main branch to compare with 1.7.7

Let me try one final verification approach using the GitHub API to check for security advisories in a different way.


Axios version 1.7.7 is secure - no action needed

Based on the GitHub Security Advisory data, version 1.7.7 is not affected by any known vulnerabilities:

  • The most recent high-severity vulnerability affects versions <= 1.7.3
  • Other vulnerabilities affect older version ranges that don't include 1.7.7

The current version (1.7.7) is actually newer than the versions affected by known security issues, making it a secure choice.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for known vulnerabilities in axios v1.7.7
# Note: This is a basic check. In production, you should use tools like npm audit

# Get the latest version of axios 1.7.x
LATEST=$(npm view axios versions --json | jq -r '.[]' | grep '^1\.7\.' | sort -V | tail -n 1)
echo "Current version: 1.7.7"
echo "Latest 1.7.x version: $LATEST"

if [ "$LATEST" != "1.7.7" ]; then
  echo "Consider upgrading to $LATEST"
fi

Length of output: 360


Script:

#!/bin/bash
# Check GitHub Security Advisories for axios vulnerabilities
gh api \
  -H "Accept: application/vnd.github+json" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  /repos/axios/axios/security-advisories \
  | jq '.[] | select(.state=="published") | {published_at, summary, severity, references: .references.advisory_url}'

# Also check the latest version from the package.json in axios repo
curl -s https://raw.githubusercontent.com/axios/axios/main/package.json | jq -r '.version'

Length of output: 339


Script:

#!/bin/bash
# Check for vulnerabilities using GitHub's security advisory database
gh api \
  -H "Accept: application/vnd.github+json" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  graphql -f query='
    query {
      securityVulnerabilities(ecosystem: NPM, package: "axios", first: 5, orderBy: {field: UPDATED_AT, direction: DESC}) {
        nodes {
          severity
          vulnerableVersionRange
          advisory {
            publishedAt
            summary
          }
        }
      }
    }
  '

Length of output: 1335

packages/sdk/src/document/document.ts (3)

202-206: LGTM! New auth error event type is well-defined.

The addition of AuthError event type follows consistent naming patterns and is properly documented.


448-448: LGTM! Callback mapping is correctly defined.

The 'auth-error' event callback mapping follows the established pattern and uses the correct type.


1090-1101: LGTM! Auth error event handling is properly implemented.

The implementation follows the established pattern for event handling and is clean and straightforward.

packages/sdk/test/integration/webhook_test.ts (1)

400-405: Ensure proper error handling in asynchronous operations

The client.attach(doc) operation is asynchronous and may throw errors. Without proper error handling or assertions, failures might go unnoticed, potentially leading to silent test failures.

Ensure that all asynchronous operations are wrapped in try-catch blocks or are properly awaited with assertions to handle any potential errors.

await client.attach(doc);
+ // Add assertion or error handling as needed
packages/sdk/test/integration/client_test.ts (2)

Line range hint 332-340: No issues found with the synchronization test

The added code correctly tests the behavior of clients in RealtimePushOnly sync mode and ensures events are handled as expected.


Line range hint 612-614: Test for 'maxRetries' set to zero works as expected

The added test correctly verifies that broadcasting does not retry on network failure when maxRetries is set to zero.

packages/sdk/package.json Outdated Show resolved Hide resolved
packages/sdk/src/util/error.ts Show resolved Hide resolved
packages/sdk/src/api/converter.ts Outdated Show resolved Hide resolved
packages/sdk/src/document/document.ts Outdated Show resolved Hide resolved
packages/sdk/test/integration/webhook_test.ts Show resolved Hide resolved
packages/sdk/test/integration/client_test.ts Outdated Show resolved Hide resolved
packages/sdk/src/client/client.ts Outdated Show resolved Hide resolved
packages/sdk/src/client/client.ts Outdated Show resolved Hide resolved
packages/sdk/src/client/client.ts Outdated Show resolved Hide resolved
packages/sdk/src/client/client.ts Outdated Show resolved Hide resolved
Copy link

@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: 2

🧹 Outside diff range and nitpick comments (2)
packages/sdk/src/client/client.ts (1)

1063-1071: Consider adding a comment explaining the race condition.

The code handles a race condition between syncLoop and syncMode, but the comment could be more explicit about why this check is necessary and what problems it prevents.

-// NOTE(chacha912, hackerwins): If syncLoop already executed with
-// PushPull, ignore the response when the syncMode is PushOnly.
+// NOTE(chacha912, hackerwins): Ignore the response when in PushOnly mode to prevent
+// applying changes twice. This can happen when syncLoop has already executed with
+// PushPull mode and the mode was subsequently changed to PushOnly. Without this
+// check, the same changes could be applied multiple times.
packages/sdk/src/api/converter.ts (1)

807-818: Consider improving type safety and documentation.

The function implementation looks good, but could benefit from these improvements:

  1. Consider using a type for metadata keys to ensure type safety.
  2. Add JSDoc to document the return type structure and possible metadata keys.
+/**
+ * Returns the error metadata from the given ConnectError.
+ * @param error - The ConnectError instance
+ * @returns Record<MetadataKey, string> where MetadataKey can be 'code' or other error-specific keys
+ */
 export function errorMetadataOf(error: ConnectError): Record<string, string> {
   // NOTE(chacha912): Currently, we only use the first detail to represent the
   // error metadata.
   const infos = error.findDetails(ErrorInfo);
   for (const info of infos) {
     return info.metadata;
   }

   return {};
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 840cf00 and 0cbdff2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • packages/sdk/package.json (2 hunks)
  • packages/sdk/src/api/converter.ts (1 hunks)
  • packages/sdk/src/client/client.ts (21 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sdk/package.json
🔇 Additional comments (3)
packages/sdk/src/client/client.ts (2)

132-136: LGTM! Well-documented token injector interface.

The authTokenInjector interface is well-designed with clear documentation explaining its purpose and behavior.


1080-1084: LGTM! Good handling of removed documents.

The code properly handles cleanup of watch streams for removed documents, preventing unnecessary network traffic and potential memory leaks.

packages/sdk/src/api/converter.ts (1)

820-824: LGTM! Good refactoring.

The function has been nicely refactored to use errorMetadataOf, making it more maintainable and reducing code duplication.

@yorkie-team yorkie-team deleted a comment from coderabbitai bot Oct 24, 2024
@yorkie-team yorkie-team deleted a comment from coderabbitai bot Oct 24, 2024
Copy link

@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: 2

🧹 Outside diff range and nitpick comments (1)
packages/sdk/src/client/client.ts (1)

Line range hint 894-978: Reset retry count on successful watch stream connection.

The retry count for watch loop is not reset when the connection is successfully established. This could lead to premature rejection of future reconnection attempts if the connection is lost after several retries.

Add retry count reset after successful connection:

 if (resp.body.case === 'initialization') {
+  retryCount = 0;  // Reset retry count on successful connection
   resolve([stream, ac]);
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 0cbdff2 and acd5ca6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • packages/sdk/package.json (2 hunks)
  • packages/sdk/src/api/converter.ts (1 hunks)
  • packages/sdk/src/client/client.ts (21 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sdk/package.json
🔇 Additional comments (3)
packages/sdk/src/client/client.ts (1)

Line range hint 132-172: LGTM! Well-documented interface changes.

The new options are clearly documented and properly typed:

  • authTokenInjector for dynamic token updates
  • retryRequestDelay and maxRequestRetries for configuring retry behavior
packages/sdk/src/api/converter.ts (2)

809-822: Consider extracting shared error detail logic.

The function implementation looks good, but there's an opportunity to reduce code duplication with errorCodeOf as suggested in the past review.


824-828: LGTM!

The refactored implementation is more concise and properly handles undefined values.

packages/sdk/src/api/converter.ts Outdated Show resolved Hide resolved
Copy link

@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: 0

🧹 Outside diff range and nitpick comments (2)
packages/sdk/src/api/converter.ts (2)

807-809: Enhance function documentation.

The documentation could be more descriptive about the function's behavior, especially regarding error handling and the structure of returned metadata.

 /**
- * `errorMetadataOf` returns the error metadata of the given connect error.
+ * `errorMetadataOf` returns the metadata from the first ErrorInfo detail of the given connect error.
+ * If the input is not a ConnectError or no ErrorInfo details are found, returns an empty object.
+ * @param error - The ConnectError to extract metadata from
+ * @returns A record of string key-value pairs containing error metadata
  */

807-828: Well-aligned with Auth Token Update requirements.

The enhanced error handling implementation aligns well with the PR objectives for the Auth Token Update Interface. The ability to extract detailed error metadata will be particularly useful for handling authentication-related errors and token refresh scenarios.

Consider documenting common error metadata fields that will be used for authentication scenarios, particularly those related to token expiration and refresh requirements.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between acd5ca6 and 95f5faa.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • packages/sdk/package.json (2 hunks)
  • packages/sdk/src/api/converter.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sdk/package.json
🔇 Additional comments (1)
packages/sdk/src/api/converter.ts (1)

824-828: LGTM! Good refactoring.

The function has been effectively refactored to reuse errorMetadataOf, addressing the code duplication issue from previous reviews.

Copy link

@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: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between b05d0be and 8f34257.

📒 Files selected for processing (1)
  • packages/sdk/src/document/document.ts (6 hunks)
🔇 Additional comments (5)
packages/sdk/src/document/document.ts (5)

202-206: LGTM! The new DocEventType enum value is well-defined.

The AuthError enum value follows the established naming pattern and has a clear descriptive comment.


227-228: LGTM! The DocEvent type union is correctly updated.

The AuthErrorEvent is properly added to the DocEvent type union.


451-451: LGTM! The DocEventCallbackMap is correctly updated.

The 'auth-error' mapping is properly added with the correct callback type.


1093-1104: LGTM! The subscribe implementation is well-structured.

The implementation correctly follows the established pattern for event subscriptions.


424-430: 🛠️ Refactor suggestion

Consider enhancing AuthErrorEvent with additional error details.

The current interface could be improved to better handle token expiration scenarios and provide more context for debugging:

The previous review suggestion is still valid. Apply this diff to enhance error information:

 export interface AuthErrorEvent extends BaseDocEvent {
   type: DocEventType.AuthError;
   value: {
     errorMessage: string;
     method: 'AttachDocument' | 'DetachDocument' | 'PushPull' | 'WatchDocuments';
+    code?: string;
+    details?: Record<string, unknown>;
   };
 }

packages/sdk/src/document/document.ts Show resolved Hide resolved
@chacha912 chacha912 marked this pull request as draft November 4, 2024 08:40
Copy link

@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: 2

🧹 Outside diff range and nitpick comments (3)
packages/sdk/test/integration/client_test.ts (1)

Line range hint 1-1: Add test coverage for Auth Token Update Interface.

The PR implements token refresh functionality, but there are no tests verifying:

  1. Token expiration handling
  2. Server notification handling for expired tokens
  3. Token refresh callback execution
  4. Retry behavior with expired tokens

Consider adding the following test cases:

it('Should refresh token when server notifies expiration', async function ({ task }) {
  const mockTokenRefresh = vi.fn().mockResolvedValue('new-token');
  const cli = new yorkie.Client(testRPCAddr, {
    authTokenInjector: mockTokenRefresh
  });
  // Test implementation
});

it('Should retry operations with refreshed token', async function ({ task }) {
  // Test implementation
});

it('Should handle token refresh failures', async function ({ task }) {
  // Test implementation
});

Would you like me to help implement these test cases or create a GitHub issue to track this task?

packages/sdk/src/client/client.ts (1)

Line range hint 1147-1185: Consider caching the RPC client creation.

The current implementation creates a new RPC client on every token refresh. Consider updating the interceptors of the existing client instead of creating a new one.

Modify the token refresh logic:

 if (errorCodeOf(err) === Code.ErrUnauthenticated) {
   const token =
     this.authTokenInjector &&
     (await this.authTokenInjector(errorMetadataOf(err).reason));

-  this.rpcClient = createPromiseClient(
-    YorkieService,
-    createGrpcWebTransport({
-      baseUrl: this.rpcAddr,
-      interceptors: [
-        createAuthInterceptor(this.apiKey, token),
-        createMetricInterceptor(),
-      ],
-    }),
-  );
+  if (this.rpcClient) {
+    // Update interceptors of existing transport
+    const transport = this.rpcClient.transport as any;
+    transport.interceptors = [
+      createAuthInterceptor(this.apiKey, token),
+      createMetricInterceptor(),
+    ];
+  }
   return true;
 }
packages/sdk/src/document/document.ts (1)

202-206: Fix typo in documentation comment.

The word "authentification" should be "authentication".

  /**
-   * `AuthError` means that the authentification error occurs.
+   * `AuthError` means that the authentication error occurs.
   */
  AuthError = 'auth-error',
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8f34257 and dcc6d47.

📒 Files selected for processing (5)
  • packages/sdk/package.json (2 hunks)
  • packages/sdk/src/client/client.ts (21 hunks)
  • packages/sdk/src/document/document.ts (6 hunks)
  • packages/sdk/test/integration/client_test.ts (1 hunks)
  • packages/sdk/test/integration/webhook_test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/sdk/package.json
  • packages/sdk/test/integration/webhook_test.ts
🔇 Additional comments (8)
packages/sdk/test/integration/client_test.ts (1)

166-171: Set minimal retryRequestDelay to prevent tight retry loops.

Setting retryRequestDelay to 0 may cause high CPU usage during network failures.

packages/sdk/src/client/client.ts (3)

132-138: LGTM! Well-documented token injector interface.

The authTokenInjector function is well-documented with clear explanation of its purpose and behavior.


Line range hint 211-251: LGTM! Clean initialization of new functionality.

The initialization of auth and retry-related fields is clean and follows the existing pattern.


702-709: Add retry logic to the remove method.

The remove method lacks retry logic unlike other methods (attach, detach). This inconsistency could lead to reliability issues.

packages/sdk/src/document/document.ts (4)

424-430: LGTM!

The interface follows the established pattern and the past review comment about enhancing error information is still valid.


451-451: LGTM!

The callback mapping is correctly defined and follows the established pattern.


912-920: LGTM!

The overload is well-defined and the past review comment about enhancing the overload is still valid.


1093-1104: LGTM!

The implementation is clean, consistent with other event types, and properly handles the auth-error subscription.

packages/sdk/src/client/client.ts Outdated Show resolved Hide resolved
packages/sdk/src/client/client.ts Outdated Show resolved Hide resolved
@chacha912 chacha912 marked this pull request as ready for review November 5, 2024 02:47
Copy link

@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: 1

🧹 Outside diff range and nitpick comments (3)
docker/docker-compose.yml (1)

12-13: LGTM! Consider adding a comment to document the purpose.

The extra_hosts configuration is correctly implemented, enabling container-to-host communication necessary for Auth Webhook testing.

Consider adding a comment above the configuration to explain its purpose:

+    # Enable communication with host machine for Auth Webhook testing
     extra_hosts:
       - 'host.docker.internal:host-gateway'
docker/docker-compose-ci.yml (1)

14-15: Consider security implications of exposing host gateway.

While enabling host access is necessary for webhook communication, consider:

  1. Restricting this configuration to specific environments where it's needed
  2. Documenting the security implications in the configuration file
  3. Adding network-level restrictions if possible
packages/sdk/test/integration/integration_helper.ts (1)

9-10: Consider using non-admin test credentials by default.

While using 'admin' credentials is convenient for testing, it might mask authentication-related issues. Consider using regular user credentials as defaults to ensure the authentication flow is thoroughly tested under normal conditions.

-export const testAPIID = process.env.TEST_API_ID || 'admin';
-export const testAPIPW = process.env.TEST_API_PW || 'admin';
+export const testAPIID = process.env.TEST_API_ID || 'test_user';
+export const testAPIPW = process.env.TEST_API_PW || 'test_password';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between dcc6d47 and 9454843.

📒 Files selected for processing (4)
  • docker/docker-compose-ci.yml (1 hunks)
  • docker/docker-compose.yml (1 hunks)
  • packages/sdk/test/integration/integration_helper.ts (2 hunks)
  • packages/sdk/test/integration/webhook_test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sdk/test/integration/webhook_test.ts
🔇 Additional comments (4)
docker/docker-compose.yml (1)

12-13: Verify production deployment configuration.

While this configuration is suitable for development and testing, ensure it's not included in production deployments where direct host access should be restricted.

Consider using environment-specific compose files or environment variables to control this configuration. For example:

✅ Verification successful

Docker host access configuration appears to be consistent across environments

The host.docker.internal configuration is present in both docker-compose files (docker-compose.yml and docker-compose-ci.yml), and there are no separate production deployment configurations found in the repository. Since this is a development-focused repository (containing examples, SDK, and devtools), the current configuration is appropriate for its purpose.

  • Both compose files are located in the docker/ directory and are used for development/CI environments
  • No production-specific deployment configurations were found
  • The repository structure suggests this is primarily a development/SDK repository
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if this configuration exists in production-related files
rg -l 'host.docker.internal' deploy/ production/ kubernetes/

Length of output: 212


Script:

#!/bin/bash
# Let's first check what deployment-related directories exist
fd -t d -d 1

# Then search for docker-compose files and their content
fd -e yml -e yaml docker-compose
fd -e yml -e yaml docker-compose --exec cat {}

# Also check for any Kubernetes manifests or deployment configs
fd -e yml -e yaml -e json

Length of output: 1948

docker/docker-compose-ci.yml (1)

14-15: LGTM! Verify if this configuration is required for CI environment.

The addition of host.docker.internal mapping is typically used for local development to enable container-to-host communication. While this change aligns with the auth webhook functionality, please verify if this is necessary in the CI environment.

packages/sdk/test/integration/integration_helper.ts (2)

50-55: Consider implications of zero retry delay in tests.

Setting retryRequestDelay: 0 might make tests run faster but could mask timing-related issues that would occur in production. Consider adding additional test cases with realistic retry delays to ensure robust error handling.

#!/bin/bash
# Check for other test files that might need similar retry delay configurations
rg "retryRequestDelay" -t ts -t js

Add test cases with realistic delays:

// Add to test suite
it('handles token refresh with realistic delays', async () => {
  const client = new yorkie.Client(testRPCAddr, {
    retryRequestDelay: 1000, // 1 second delay
  });
  // ... test implementation
});

11-27: 🛠️ Refactor suggestion

Ensure cross-platform compatibility for Docker checks.

The Docker command might fail on Windows systems due to different shell syntax. Consider using a cross-platform solution or handling platform-specific cases.

 function isYorkieContainerRunning() {
   if (process.env.CI === 'true') {
     return true;
   }

   try {
+    const command = process.platform === 'win32'
+      ? 'docker ps --filter "name=^/yorkie$" --format "{{.Names}}"'
+      : 'docker ps --filter "name=^/yorkie$" --format "{{.Names}}"';
     const result = execSync(
-      'docker ps --filter "name=^/yorkie$" --format "{{.Names}}"',
+      command,
       {
         stdio: 'pipe',
+        shell: true,
       },
     );
     return result && result.toString().trim() !== '';
   } catch (error) {
     return false;
   }
 }

Copy link

@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: 2

🧹 Outside diff range and nitpick comments (5)
packages/sdk/test/integration/client_test.ts (1)

Line range hint 1-1006: Add test coverage for Auth Token Update Interface.

The test suite is missing coverage for the new authentication features mentioned in the PR objectives:

  1. Auth token expiration handling
  2. Token refresh mechanism using authTokenInjector
  3. Authentication error events for document operations (attach, detach, pushpull, watch)

Consider adding the following test scenarios:

  • Token expiration and automatic refresh
  • Failed token refresh scenarios
  • Authentication error event handling
  • Integration with Auth Webhook

Would you like me to help generate test cases for these scenarios?

packages/sdk/src/client/client.ts (1)

Line range hint 1151-1182: Consider defining retryable error codes as constants.

The error codes that determine retry behavior should be defined as named constants for better maintainability and documentation.

+const RETRYABLE_CONNECT_ERROR_CODES = [
+  ConnectErrorCode.Canceled,
+  ConnectErrorCode.Unknown,
+  ConnectErrorCode.ResourceExhausted,
+  ConnectErrorCode.Unavailable,
+] as const;

 private async handleConnectError(err: any): Promise<boolean> {
   if (!(err instanceof ConnectError)) {
     return false;
   }

-  if (
-    err.code === ConnectErrorCode.Canceled ||
-    err.code === ConnectErrorCode.Unknown ||
-    err.code === ConnectErrorCode.ResourceExhausted ||
-    err.code === ConnectErrorCode.Unavailable
-  ) {
+  if (RETRYABLE_CONNECT_ERROR_CODES.includes(err.code)) {
     return true;
   }
packages/sdk/src/client/auth_interceptor.ts (1)

26-29: Update documentation for the updated return type.

The createAuthInterceptor function now returns an object containing authInterceptor and setToken. To improve clarity for users, consider adding documentation or JSDoc comments to explain the new return type and its usage.

packages/sdk/src/document/document.ts (2)

203-205: Fix typo in comment: 'authentification' should be 'authentication'.

The word 'authentification' is misspelled; it should be 'authentication' in the comment.


913-915: Fix typo in documentation: 'authentification' should be 'authentication'.

Replace 'authentification' with 'authentication' in the comment to correct the spelling.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9454843 and 4e6f5b4.

📒 Files selected for processing (5)
  • packages/sdk/src/client/auth_interceptor.ts (1 hunks)
  • packages/sdk/src/client/client.ts (21 hunks)
  • packages/sdk/src/document/document.ts (6 hunks)
  • packages/sdk/test/helper/helper.ts (1 hunks)
  • packages/sdk/test/integration/client_test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sdk/test/helper/helper.ts
🔇 Additional comments (10)
packages/sdk/test/integration/client_test.ts (1)

169-174: Previous comment about minimal retryRequestDelay is still applicable.

packages/sdk/src/client/client.ts (4)

Line range hint 132-172: LGTM! Well-documented interface changes.

The new ClientOptions interface properly implements the token refresh mechanism requirements with clear documentation.


195-196: Previous suggestion still applies.


286-315: Previous suggestion for extracting retry logic still applies.

Also applies to: 329-356, 406-490, 525-582, 1052-1143


706-713: Previous suggestion for adding retry logic to remove method still applies.

packages/sdk/src/client/auth_interceptor.ts (1)

40-41: Ensure currentToken includes any required authorization scheme prefix.

When setting the authorization header, verify that currentToken includes any necessary scheme prefixes such as Bearer . If currentToken is just the token string, you may need to prepend Bearer before setting the header to ensure proper authentication.

packages/sdk/src/document/document.ts (4)

227-228: Approve addition of AuthErrorEvent to DocEvent union type.

The inclusion of AuthErrorEvent in the DocEvent type is appropriate and ensures proper event handling.


424-430: Approve definition of AuthErrorEvent interface.

The AuthErrorEvent interface is well-defined and correctly extends BaseDocEvent.


451-451: Approve addition of 'auth-error' in DocEventCallbackMap.

The mapping of 'auth-error' to NextFn<AuthErrorEvent> in DocEventCallbackMap maintains consistency with other event callbacks.


1093-1104: Approve implementation of 'auth-error' event handling in subscribe method.

The addition of the 'auth-error' case in the subscribe method correctly follows the established pattern.

packages/sdk/src/client/client.ts Outdated Show resolved Hide resolved
packages/sdk/src/client/auth_interceptor.ts Show resolved Hide resolved
Copy link

@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: 3

🧹 Outside diff range and nitpick comments (3)
packages/sdk/src/client/client.ts (3)

159-176: Remove implementation details from comments.

These comments contain implementation details that should be in proper documentation. Consider moving them to API documentation or developer guides.


303-334: Extract common retry logic to reduce duplication.

The retry logic is duplicated between activate and deactivate methods. Consider extracting it into a reusable utility.

Create a utility method:

private async withRetry<T>(
  operation: () => Promise<T>,
  context: string,
): Promise<T> {
  const executeWithRetry = async (retryCount = 0): Promise<T> => {
    try {
      return await operation();
    } catch (err) {
      if (retryCount >= this.maxRequestRetries) {
        logger.error(
          `[${context}] c:"${this.getKey()}" max retries (${this.maxRequestRetries}) exceeded`,
        );
        throw err;
      }

      if (await this.handleConnectError(err)) {
        await delay(this.retryRequestDelay);
        return executeWithRetry(retryCount + 1);
      }

      logger.error(`[${context}] c:"${this.getKey()}" err:`, err);
      throw err;
    }
  };

  return executeWithRetry();
}

Also applies to: 347-376


488-505: Consider simplifying error handling flow.

The error handling in document operations is complex with nested conditions. Consider extracting the auth error publishing logic to a separate method for better readability.

Create a utility method:

private publishAuthError(doc: Document<any, any>, err: ConnectError, method: string): void {
  if (errorCodeOf(err) === Code.ErrUnauthenticated) {
    doc.publish([
      {
        type: DocEventType.AuthError,
        value: {
          reason: errorMetadataOf(err).reason,
          method,
        },
      },
    ]);
  }
}

Also applies to: 581-598

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4e6f5b4 and 5402e93.

📒 Files selected for processing (2)
  • packages/sdk/src/client/client.ts (17 hunks)
  • packages/sdk/test/integration/webhook_test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sdk/test/integration/webhook_test.ts

packages/sdk/src/client/client.ts Outdated Show resolved Hide resolved
packages/sdk/src/client/client.ts Outdated Show resolved Hide resolved
Copy link

@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. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (6)
packages/sdk/test/integration/integration_helper.ts (2)

9-10: Add documentation and CI environment validation for API credentials.

While using default test credentials is acceptable for local development, consider:

  1. Adding JSDoc comments explaining the purpose and usage of these constants
  2. Enforcing environment variables in CI to prevent accidental use of default credentials
+/**
+ * Default API credentials for testing.
+ * NOTE: These should only be used in test environments.
+ * In CI environments, these should be overridden via environment variables.
+ */
 export const testAPIID = process.env.TEST_API_ID || 'admin';
 export const testAPIPW = process.env.TEST_API_PW || 'admin';
+
+if (process.env.CI === 'true' && (!process.env.TEST_API_ID || !process.env.TEST_API_PW)) {
+  throw new Error('TEST_API_ID and TEST_API_PW must be set in CI environment');
+}

11-27: Consider performance optimizations for Docker container check.

The implementation is secure but could benefit from performance improvements:

  1. Cache the result as it's unlikely to change during test execution
  2. Add a timeout to prevent hanging
+let isYorkieContainerRunningCache: boolean | null = null;
+
 function isYorkieContainerRunning() {
   if (process.env.CI === 'true') {
     return true;
   }
 
+  if (isYorkieContainerRunningCache !== null) {
+    return isYorkieContainerRunningCache;
+  }
+
   try {
     const result = execSync(
       'docker ps --filter "name=^/yorkie$" --format "{{.Names}}"',
       {
         stdio: 'pipe',
+        timeout: 5000, // 5 second timeout
       },
     );
-    return result && result.toString().trim() !== '';
+    isYorkieContainerRunningCache = result && result.toString().trim() !== '';
+    return isYorkieContainerRunningCache;
   } catch (error) {
-    return false;
+    isYorkieContainerRunningCache = false;
+    return isYorkieContainerRunningCache;
   }
 }
packages/sdk/src/client/client.ts (4)

192-192: Ensure proper initialization of auth-related properties.

While the properties are well-typed, consider initializing setAuthToken to a no-op function by default to prevent potential undefined calls.

-  private setAuthToken: (token: string) => void;
+  private setAuthToken: (token: string) => void = () => {};

Also applies to: 199-199


228-229: Consider validating tokens before setting them.

Add basic token validation to prevent setting empty or malformed tokens.

   const { authInterceptor, setToken } = createAuthInterceptor(this.apiKey);
-  this.setAuthToken = setToken;
+  this.setAuthToken = (token: string) => {
+    if (!token?.trim()) {
+      logger.warn(`[AC] c:"${this.getKey()}" invalid token provided`);
+      return;
+    }
+    setToken(token);
+  };

Also applies to: 237-237


750-770: Consider consolidating auth error handling.

The auth error handling logic is duplicated across different methods. Consider extracting it to a shared method.

+  private publishAuthError(doc: Document<unknown, any>, reason: string, method: string) {
+    doc.publish([
+      {
+        type: DocEventType.AuthError,
+        value: { reason, method },
+      },
+    ]);
+  }

   syncJobs.push(
     this.syncInternal(attachment, attachment.syncMode).catch(
       async (err) => {
         if (
           err instanceof ConnectError &&
           errorCodeOf(err) === Code.ErrUnauthenticated
         ) {
-          attachment.doc.publish([
-            {
-              type: DocEventType.AuthError,
-              value: {
-                reason: errorMetadataOf(err).reason,
-                method: 'PushPull',
-              },
-            },
-          ]);
+          this.publishAuthError(
+            attachment.doc,
+            errorMetadataOf(err).reason,
+            'PushPull'
+          );
         }
         throw err;
       },
     ),
   );

866-880: Reuse consolidated auth error handling.

Apply the same error handling consolidation here for consistency.

   if (await this.handleConnectError(err)) {
     if (
       err instanceof ConnectError &&
       errorCodeOf(err) === Code.ErrUnauthenticated
     ) {
-      attachment.doc.publish([
-        {
-          type: DocEventType.AuthError,
-          value: {
-            reason: errorMetadataOf(err).reason,
-            method: 'WatchDocuments',
-          },
-        },
-      ]);
+      this.publishAuthError(
+        attachment.doc,
+        errorMetadataOf(err).reason,
+        'WatchDocuments'
+      );
     }
     onDisconnect();
   }
🛑 Comments failed to post (1)
packages/sdk/src/client/client.ts (1)

253-256: ⚠️ Potential issue

Add error handling for initial token injection.

The initial token injection should handle potential errors to prevent activation with invalid tokens.

   if (this.authTokenInjector) {
-    const token = await this.authTokenInjector();
-    this.setAuthToken(token);
+    try {
+      const token = await this.authTokenInjector();
+      if (!token) {
+        throw new Error('No token provided');
+      }
+      this.setAuthToken(token);
+    } catch (err) {
+      logger.error(`[AC] c:"${this.getKey()}" initial token injection failed:`, err);
+      throw new YorkieError(Code.ErrUnauthenticated, 'Failed to inject initial auth token');
+    }
   }
📝 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.

    if (this.authTokenInjector) {
      try {
        const token = await this.authTokenInjector();
        if (!token) {
          throw new Error('No token provided');
        }
        this.setAuthToken(token);
      } catch (err) {
        logger.error(`[AC] c:"${this.getKey()}" initial token injection failed:`, err);
        throw new YorkieError(Code.ErrUnauthenticated, 'Failed to inject initial auth token');
      }
    }

- Keep retry logic only in syncLoop and watchLoop
- Remove retry from other method to allow manual retry by users
Copy link
Member

@hackerwins hackerwins left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution.

@hackerwins hackerwins changed the title Implement Auth Token Update Interface for Auth Webhook Add dynamic token refresh and authentication error handling Nov 7, 2024
@hackerwins hackerwins merged commit cdde945 into main Nov 7, 2024
2 checks passed
@hackerwins hackerwins deleted the refresh-auth-token branch November 7, 2024 07:50
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.

Provide a way to refresh token for Auth Webhook
2 participants