Skip to content

Conversation

@FelixTJDietrich
Copy link
Collaborator

@FelixTJDietrich FelixTJDietrich commented Dec 13, 2025

Description

  • Introduced Slack OAuth callback handling in the SDK.
  • Added new API types for Slack connection management including disconnect, sync, and installation.
  • Implemented AdminSlackSettings component to manage Slack connection status.
  • Enhanced AdminSettingsPage to include Slack settings and notifications based on Slack connection status.
  • Created SlackConnectionSection for user settings, allowing users to link and unlink their Slack accounts.
  • Updated settings page to handle Slack connection state and provide feedback on connection status.
  • Added storybook stories for Slack connection components to demonstrate various states (connected, disconnected, loading, disabled).
  • Updated routing to handle Slack connection status in admin settings.

How to test

Screenshots

Summary by CodeRabbit

  • New Features

    • Added Slack integration enabling users to link Slack accounts for identity synchronization and bot installation.
    • Introduced Slack connection management in user settings with link, sync, and disconnect options.
    • Added workspace-level Slack bot installation for leaderboard notifications and user mentions.
  • Documentation

    • Added comprehensive Slack integration setup guides for production and local development environments.
    • Added code style and architectural guidelines documentation.
    • Updated production setup documentation with Slack configuration details.
  • Chores

    • Added Slack-related environment variables and configuration across deployment and development stacks.
    • Updated database schema to support Slack user identification.

✏️ Tip: You can customize this high-level summary in your review settings.

- Introduced Slack OAuth callback handling in the SDK.
- Added new API types for Slack connection management including disconnect, sync, and installation.
- Implemented AdminSlackSettings component to manage Slack connection status.
- Enhanced AdminSettingsPage to include Slack settings and notifications based on Slack connection status.
- Created SlackConnectionSection for user settings, allowing users to link and unlink their Slack accounts.
- Updated settings page to handle Slack connection state and provide feedback on connection status.
- Added storybook stories for Slack connection components to demonstrate various states (connected, disconnected, loading, disabled).
- Updated routing to handle Slack connection status in admin settings.
Copilot AI review requested due to automatic review settings December 13, 2025 22:57
@FelixTJDietrich FelixTJDietrich requested a review from a team as a code owner December 13, 2025 22:57
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 13, 2025

Walkthrough

Adds comprehensive Slack integration spanning OAuth identity linking via Keycloak, workspace bot installation, user account management endpoints, React UI components, API contract updates, configuration across Docker and Spring Boot, database schema extension, and documentation guides with full test coverage.

Changes

Cohort / File(s) Summary
Documentation
AGENTS.md, STYLE_GUIDE.md (new), docs/admin/production-setup.mdx, docs/admin/slack-integration.mdx (new), docs/contributor/local-development.mdx, docs/sidebars.admin.ts
Added comprehensive Slack integration guides, updated production setup documentation with Slack environment variables, and expanded admin sidebar navigation.
Configuration & Environment
docker/.env.example, docker/compose.app.yaml, docker/compose.core.yaml, docker/preview/compose.app.yaml, server/application-server/.env.example, server/application-server/compose.yaml
Introduced Slack-related environment variables (KEYCLOAK_SLACK_ENABLED, KEYCLOAK_SLACK_CLIENT_ID/SECRET, SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET, SLACK_LINKING_ENABLED) across Docker and Keycloak services with supporting documentation.
Spring Configuration & Build
server/application-server/application.yml, server/application-server/application-prod.yml, server/application-server/pom.xml
Added Slack integration configuration block, increased Spring Boot plugin wait time, and expanded JVM arguments for OpenAPI spec generation with JMX support.
Backend Services & DTOs
server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/SlackConnectionService.java (new), SlackConnectionDTO.java (new), AccountController.java, server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/user/User.java, UserInfoDTO.java
Implemented SlackConnectionService for user Slack identity management, added SlackConnectionDTO for status representation, extended User and UserInfoDTO with slackUserId field, and integrated Slack endpoints into AccountController.
Workspace Slack Integration
server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/SlackCallbackController.java (new), WorkspaceSlackController.java (new), WorkspaceSlackIntegrationService.java (new)
Introduced workspace-scoped Slack bot installation flow with OAuth callback handling, state validation with HMAC-SHA256 signing, and token exchange integration with Slack API.
Leaderboard & API Spec
server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/tasks/SlackWeeklyLeaderboardTask.java, server/application-server/openapi.yaml
Updated Slack user mapping to prioritize linked Slack IDs over email-based matching, and expanded OpenAPI spec with Slack-related endpoints, user endpoints, workspace endpoints, and SlackConnection data model.
Database Migration
server/application-server/src/main/resources/db/changelog/1765321080884_changelog.xml (new), db/master.xml
Added Liquibase migration to create nullable slack_user_id column on user table with pre-condition checks.
API Code Generation
webapp/src/api/@tanstack/react-query.gen.ts, webapp/src/api/sdk.gen.ts, webapp/src/api/types.gen.ts
Generated React Query hooks, SDK functions, and TypeScript types for Slack-related endpoints (callback, connection status, sync, disconnect, bot install).
Frontend Components
webapp/src/components/admin/AdminSlackSettings.tsx (new), AdminSettingsPage.tsx, AdminSettingsPage.stories.tsx
Created new AdminSlackSettings component for workspace-level Slack bot connection UI and integrated into AdminSettingsPage with toast notifications for OAuth status feedback.
User Settings Components
webapp/src/components/settings/SlackConnectionSection.tsx (new), SlackConnectionSection.stories.tsx (new), SettingsPage.tsx, SettingsPage.stories.tsx
Introduced SlackConnectionSection component for user account Slack linking/unlinking with confirm dialogs, integrated into user SettingsPage with loading and sync state management.
Routes & Integration
webapp/src/routes/_authenticated/settings.tsx, webapp/src/routes/_authenticated/w/$workspaceSlug/admin/_admin/settings.tsx
Wired Slack queries and mutations into settings routes with useQuery for connection status, syncSlackConnectionMutation and disconnectSlackMutation for user account management, and zod validation for admin settings search parameters (slack status).
Test Coverage
server/application-server/src/test/java/de/tum/in/www1/hephaestus/account/SlackConnectionServiceTest.java (new), server/application-server/src/test/java/de/tum/in/www1/hephaestus/leaderboard/tasks/SlackWeeklyLeaderboardTaskMatchingTest.java (new), server/application-server/src/test/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationServiceTest.java (new)
Added unit tests for SlackConnectionService (connection status and sync), SlackWeeklyLeaderboardTask user matching (linked ID vs. email), and WorkspaceSlackIntegrationService (OAuth flow and state validation).
Keycloak Configuration
server/application-server/keycloak-hephaestus-realm-example-config.json
Added Slack identity provider configuration with OpenID Connect settings (tokenUrl, authorizationUrl, userInfoUrl, jwksUrl, issuer, defaultScope) and linking-only behavior.

Sequence Diagram(s)

sequenceDiagram
    participant User as User/<br/>Browser
    participant FE as Frontend
    participant WC as WorkspaceSlack<br/>Controller
    participant WIS as Workspace<br/>SlackIntegration<br/>Service
    participant SlackAPI as Slack<br/>OAuth API
    participant SC as SlackCallback<br/>Controller
    participant DB as Database

    User->>FE: Click "Connect Workspace"
    FE->>WC: GET /slack/install
    WC->>WIS: generateInstallUrl(workspace)
    WIS->>WIS: generateSignedState(slug)
    Note over WIS: HMAC-SHA256(slug+ts)
    WIS-->>WC: install_url + signed_state
    WC-->>FE: 302 Redirect
    FE->>SlackAPI: Navigate to Slack OAuth
    User->>SlackAPI: Authorize app
    SlackAPI->>SC: Callback + code + state
    SC->>WIS: completeInstallation(code, state)
    WIS->>WIS: validateAndParseState()
    Note over WIS: Verify signature & expiry
    WIS->>SlackAPI: POST oauth.v2.access
    SlackAPI-->>WIS: access_token
    WIS->>DB: Update workspace.slack_token
    DB-->>WIS: ✓ Persisted
    WIS-->>SC: token_saved
    SC-->>User: 302 → /admin/settings?slack=success
    User->>FE: Settings page loads
    FE-->>User: Show "Connected" state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • OAuth flow complexity: WorkspaceSlackIntegrationService implements state validation with HMAC-SHA256 signing and expiry checks; requires careful review of constant-time comparisons and token exchange logic.
  • Cross-layer integration: Changes span Keycloak configuration, Spring services, controllers, API contracts, React Query hooks, and UI components; consistency across layers must be verified.
  • API surface expansion: Six new public endpoints (OAuth callback, user connection status/sync/disconnect, workspace install) and related DTOs; verify path validation, response codes, and error handling.
  • Frontend state management: Multiple new mutations and queries with cache invalidation patterns; check proper prop threading through settings pages and loading state aggregation.
  • Security-sensitive areas: User Slack ID synchronization from Keycloak federated identities, state token generation/validation, and workspace token persistence.
  • Leaderboard integration change: Priority shift from fuzzy name matching to linked Slack ID + email matching; impacts message mentions but improves accuracy.

Areas requiring extra attention:

  • WorkspaceSlackIntegrationService.java: Constant-time comparison, state token validation logic, and OAuth flow completeness
  • SlackConnectionService.java: Keycloak federated identity fetching and Slack user ID extraction
  • openapi.yaml: Verify all endpoint definitions, parameter validation, and response models match implementation
  • webapp/src/routes/_authenticated/settings.tsx & admin/_admin/settings.tsx: Verify cache key consistency between query and mutation invalidations
  • SlackConnectionSection.tsx & AdminSlackSettings.tsx: Confirm link URL construction and redirect behavior
  • Database migration: Confirm slack_user_id column is nullable and pre-condition correctly prevents duplicates

Poem

🐰 Hop, hop! The Slack flows freely now,
OAuth tokens dance and bow,
Keycloak links each user true,
Workspace installs—a shiny new brew!
From backend hops to frontend paws,
Integration applause! No pause!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.73% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add Slack integration features and settings' clearly and concisely describes the main change—adding Slack integration throughout the codebase with new endpoints, UI components, and configuration.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/slack-integration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added documentation Improvements or additions to documentation application-server Spring Boot server: APIs, business logic, database feature New feature or enhancement ci GitHub Actions, workflows, build pipeline changes infrastructure Docker, containers, and deployment infrastructure dependencies Package updates, version bumps, lock file changes webapp React app: UI components, routes, state management size:XXL This PR changes 1000+ lines, ignoring generated files. labels Dec 13, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces comprehensive Slack integration features including OAuth-based workspace bot installation, user account linking via Keycloak, and enhanced leaderboard notifications with @-mentions. The implementation follows security best practices with HMAC-signed state tokens, CSRF protection, and proper OAuth flows.

Key Changes:

  • Added dual OAuth flows: workspace bot installation and user account linking
  • Implemented HMAC-SHA256 signed state tokens with 10-minute expiry for CSRF protection
  • Replaced fuzzy name matching with deterministic Slack user matching (linked ID → email)
  • Added comprehensive test coverage for security-critical components

Reviewed changes

Copilot reviewed 43 out of 43 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
webapp/src/routes/_authenticated/settings.tsx Integrated Slack connection management into user settings with sync/disconnect mutations
webapp/src/components/settings/SlackConnectionSection.tsx New component for user-level Slack account linking UI
webapp/src/components/admin/AdminSlackSettings.tsx Admin component for workspace-level Slack bot installation
server/.../WorkspaceSlackIntegrationService.java Core OAuth flow with HMAC-signed state tokens and constant-time signature validation
server/.../SlackCallbackController.java Handles Slack OAuth callback with security validation
server/.../SlackConnectionService.java Manages user account linking via Keycloak federated identities
server/.../SlackWeeklyLeaderboardTask.java Replaced fuzzy name matching with deterministic ID/email matching
server/.../WorkspaceSlackIntegrationServiceTest.java Comprehensive tests for OAuth flow, state validation, and security
docs/admin/slack-integration.mdx Complete setup guide for production Slack integration
pom.xml Updated Maven plugin wait time and JMX configuration (unrelated to Slack)

function RouteComponent() {
const navigate = useNavigate();
const queryClient = useQueryClient();
const client = useQueryClient();
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

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

Variable renamed from queryClient to client. This inconsistency with common naming conventions makes the code less searchable. Consider keeping queryClient as it's the standard name used throughout the codebase and in TanStack Query documentation.

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +69
const settingsKey = getUserSettingsQueryKey();

// Query for user settings
const { data: settings, isLoading } = useQuery({
...getUserSettingsOptions({}),
retry: 1,
});

// Mutation for updating user settings
const updateSettingsMutation = useMutation<
const updateMutation = useMutation<
UpdateUserSettingsResponse,
DefaultError,
Options<UpdateUserSettingsData>,
{ previousSettings?: UserSettings }
{ previous?: UserSettings }
>({
...updateUserSettingsMutation(),
onMutate: async (variables) => {
await queryClient.cancelQueries({
queryKey: userSettingsQueryKey,
});
const previousSettings =
queryClient.getQueryData<UserSettings>(userSettingsQueryKey);
await client.cancelQueries({ queryKey: settingsKey });
const previous = client.getQueryData<UserSettings>(settingsKey);
if (variables.body) {
queryClient.setQueryData(userSettingsQueryKey, variables.body);
client.setQueryData(settingsKey, variables.body);
}
return { previousSettings };
return { previous };
},
onError: (error, _variables, context) => {
if (context?.previousSettings) {
queryClient.setQueryData(
userSettingsQueryKey,
context.previousSettings,
);
onError: (_error, _variables, context) => {
if (context?.previous) {
client.setQueryData(settingsKey, context.previous);
}
console.error("Failed to update user settings:", error);
toast.error("Failed to update settings. Please try again later.");
},
onSuccess: (data) => {
queryClient.setQueryData(userSettingsQueryKey, data);
},
onSettled: () => {
queryClient.invalidateQueries({
queryKey: userSettingsQueryKey,
});
toast.error("Failed to update settings.");
},
onSuccess: (data) => client.setQueryData(settingsKey, data),
onSettled: () => client.invalidateQueries({ queryKey: settingsKey }),
});

// Mutation for deleting account
const deleteAccountMutation = useMutation({
const deleteMutation = useMutation({
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

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

Variable naming is overly abbreviated. settingsKey, updateMutation, and deleteMutation should maintain their full descriptive names like userSettingsQueryKey, updateSettingsMutation, and deleteAccountMutation for better clarity and consistency with TanStack Query conventions.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +41
const handleConnect = () => {
if (!linkUrl) return;
window.open(linkUrl, "_blank", "noopener,noreferrer");
};
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

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

Missing null check for linkUrl. If linkUrl is null or undefined, the button will be disabled but window.open could still be called if this logic changes. Add an explicit null check before calling window.open.

Copilot uses AI. Check for mistakes.
}: SlackConnectionSectionProps) {
if (!slackEnabled && !isLoading) return null;

const pending = Boolean(isLoading);
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

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

The pending variable is redundant. It's directly derived from isLoading with a Boolean cast that's unnecessary since isLoading is already a boolean. Remove this variable and use isLoading directly.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +54
export const Disconnected: Story = {
args: {
isConnected: false,
slackEnabled: true,
linkUrl: "https://keycloak.example.com/realms/hephaestus/account",
},
};

export const Connected: Story = {
args: {
isConnected: true,
slackUserId: "U12345ABCDE",
slackEnabled: true,
},
};

export const Loading: Story = {
args: {
isConnected: false,
slackEnabled: true,
isLoading: true,
},
};

export const Disabled: Story = {
args: {
isConnected: false,
slackEnabled: false,
},
};
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

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

Missing JSDoc comments for each story. According to the Storybook guidelines, add brief JSDoc comments explaining each story's specific use case with action-oriented descriptions.

Copilot generated this review using guidance from repository custom instructions.
</exclude>
</excludes>
<wait>1000</wait>
<wait>30000</wait>
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

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

The wait time was increased from 1000ms to 30000ms (30 seconds). This is an extremely long wait time for the Spring Boot Maven plugin. This change appears unrelated to Slack integration and should be reverted or explained in a separate commit.

Suggested change
<wait>30000</wait>
<wait>1000</wait>

Copilot uses AI. Check for mistakes.
<!-- Use 'specs' profile for OpenAPI generation - disables WorkspaceStartupListener -->
<profiles>specs</profiles>
<jvmArguments>-Dspring.application.admin.enabled=true -XX:+EnableDynamicAgentLoading --enable-native-access=ALL-UNNAMED -Dspring.devtools.restart.enabled=false</jvmArguments>
<jvmArguments>-Dspring.application.admin.enabled=true -XX:+EnableDynamicAgentLoading --enable-native-access=ALL-UNNAMED -Dspring.devtools.restart.enabled=false -Djava.rmi.server.hostname=127.0.0.1 -Dspring.jmx.enabled=true -Dspring.application.admin.jmx-name=org.springframework.boot:type=Admin,name=SpringApplication</jvmArguments>
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

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

Additional JMX configuration appears unrelated to Slack integration. These changes should be in a separate commit or explained in the PR description.

Suggested change
<jvmArguments>-Dspring.application.admin.enabled=true -XX:+EnableDynamicAgentLoading --enable-native-access=ALL-UNNAMED -Dspring.devtools.restart.enabled=false -Djava.rmi.server.hostname=127.0.0.1 -Dspring.jmx.enabled=true -Dspring.application.admin.jmx-name=org.springframework.boot:type=Admin,name=SpringApplication</jvmArguments>
<jvmArguments>-Dspring.application.admin.enabled=true -XX:+EnableDynamicAgentLoading --enable-native-access=ALL-UNNAMED -Dspring.devtools.restart.enabled=false</jvmArguments>

Copilot uses AI. Check for mistakes.
isLoading?: boolean;
}

export function SlackConnectionSection({
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

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

Missing JSDoc comment for the component. According to the Storybook guidelines (CodingGuidelineID: 1000004), add a concise JSDoc comment above the component explaining its purpose and when to use it.

Copilot generated this review using guidance from repository custom instructions.
import { fn } from "@storybook/test";
import { SlackConnectionSection } from "./SlackConnectionSection";

const meta = {
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

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

Missing JSDoc comment for the Storybook meta object. According to the Storybook guidelines, add a brief comment explaining the component's purpose and key capabilities.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +241 to +251
/**
* Constant-time string comparison to prevent timing attacks.
* <p>
* Uses MessageDigest.isEqual which is specifically designed for cryptographic
* comparisons.
*/
private boolean constantTimeEquals(String a, String b) {
byte[] aBytes = a.getBytes(StandardCharsets.UTF_8);
byte[] bBytes = b.getBytes(StandardCharsets.UTF_8);
return MessageDigest.isEqual(aBytes, bBytes);
}
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

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

The constant-time comparison using MessageDigest.isEqual is good for security. However, consider documenting why this specific method is used instead of a simple string equality check to educate future maintainers about timing attack prevention.

Copilot uses AI. Check for mistakes.
HttpServletResponse response) throws IOException {
// Handle user cancellation or Slack-side errors
if (error != null) {
logger.info("Slack OAuth was cancelled or errored: {}", error);

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.
Comment on lines +135 to +139
logger.info(
"Successfully connected Workspace '{}' to Slack Team '{}' ({})",
workspaceSlug,
response.getTeam().getName(),
response.getTeam().getId());

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.
String payload = encodedSlug + "|" + timestamp;
String expectedSignature = sign(payload);
if (!constantTimeEquals(expectedSignature, providedSignature)) {
logger.warn("State signature mismatch for workspace '{}' - possible CSRF attempt", workspaceSlug);

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.
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: 16

🧹 Nitpick comments (14)
docker/.env.example (1)

182-188: Optional: Consider addressing static analysis hints.

The static analysis tool suggests:

  • Alphabetically ordering the KEYCLOAK_SLACK_* keys
  • Removing the extra blank line at 188

These are style nitpicks and not blocking issues, but addressing them would improve consistency.

webapp/src/components/settings/SlackConnectionSection.tsx (1)

27-27: Remove unused parameter or use it in the UI.

The slackUserId parameter is destructured with an underscore prefix (indicating intentional non-use) but never referenced in the component. If this is intended for future use or debugging, consider either:

  1. Removing it from the props interface if not needed
  2. Displaying it in the connected state UI for user confirmation
  3. Adding a comment explaining why it's present but unused

If you intend to display it:

 <h3 className="text-base font-medium">
   {isConnected ? "Connected" : "Not connected"}
 </h3>
 <p className="text-sm text-muted-foreground leading-relaxed">
   {isConnected
-    ? "Your Slack and GitHub accounts are linked. You will be @-mentioned in leaderboard notifications."
+    ? `Your Slack account (${_slackUserId || 'ID unknown'}) is linked. You will be @-mentioned in leaderboard notifications.`
     : "Connect your Slack account to be @-mentioned in notifications."}
 </p>
server/application-server/src/test/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationServiceTest.java (2)

29-31: Prefer extending BaseUnitTest to avoid repeating Mockito/JUnit plumbing.

You already have de.tum.in.www1.hephaestus.testconfig.BaseUnitTest providing @ExtendWith(MockitoExtension.class) + @Tag("unit"); inheriting it reduces duplication and keeps conventions consistent. Based on learnings, ...


117-125: Duplicate helper extractAndDecodeStateFromUrl—keep one shared implementation.

Both nested classes define the same helper; move it to the outer test class (or a small private static utility) to prevent drift.

Also applies to: 281-288

webapp/src/components/admin/AdminSlackSettings.tsx (1)

36-41: Prefer clsx/tailwind-merge + design tokens over manual string building + raw hex.

This aligns with the project’s TSX guidelines (class composition helpers + tokenized colors). If the Slack purple is intentional, consider centralizing it (e.g., a CSS var / theme token) rather than inline hex.

Also applies to: 61-68

server/application-server/src/test/java/de/tum/in/www1/hephaestus/account/SlackConnectionServiceTest.java (1)

22-24: Avoid ReflectionTestUtils by injecting config (constructor/setter) for testability.

Setting realm/slackEnabled via reflection makes tests more fragile; prefer providing these via constructor or explicit package-private setters (or Spring config in an integration test). As per coding guidelines, ...

Also applies to: 46-51

webapp/src/components/admin/AdminSettingsPage.tsx (1)

1-6: Move Slack toast side-effects to the route/container (keep this component presentational).

AdminSettingsPage now runs useEffect + toast(...), which conflicts with its “presentational component” role and the project’s container/presentation split. Prefer handling slackStatus in webapp/src/routes/_authenticated/w/$workspaceSlug/admin/_admin/settings.tsx (then pass only render-state props down). As per coding guidelines, ...

Also applies to: 12-29, 50-81

server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackController.java (1)

67-76: Consider extracting the callback path to a shared constant.

The path /integrations/slack/callback is duplicated between this controller and SlackCallbackController. While the comment helps maintain consistency, a shared constant would eliminate the risk of drift.

// In a shared location (e.g., SlackConstants.java):
public static final String SLACK_CALLBACK_PATH = "/integrations/slack/callback";
webapp/src/routes/_authenticated/settings.tsx (1)

102-118: Consider invalidating Slack query after mutations for consistency.

The settings mutations use onSettled to invalidate queries, but Slack mutations only use setQueryData. While this works since the server response is authoritative, adding invalidation would ensure consistency if cache state diverges.

   const syncMutation = useMutation({
     ...syncSlackConnectionMutation(),
     onSuccess: (data) => {
       client.setQueryData(getSlackConnectionQueryKey(), data);
       toast.success("Slack synced");
     },
     onError: () => toast.error("Failed to sync Slack."),
+    onSettled: () => client.invalidateQueries({ queryKey: getSlackConnectionQueryKey() }),
   });
server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/SlackConnectionService.java (1)

161-167: Consider URL-encoding the realm name.

The realm value is concatenated directly into the URL without encoding. While realm names are typically alphanumeric, special characters could break the URL.

   private String getLinkUrl(String keycloakBaseUrl) {
       if (!slackEnabled || keycloakBaseUrl == null) {
           return null;
       }
       // Keycloak Account Console v3 - Identity Providers section
-      return keycloakBaseUrl + "/realms/" + realm + "/account/#/security/linked-accounts";
+      return keycloakBaseUrl + "/realms/" + URLEncoder.encode(realm, StandardCharsets.UTF_8) + "/account/#/security/linked-accounts";
   }

Note: Requires importing java.net.URLEncoder and java.nio.charset.StandardCharsets.

server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationService.java (2)

176-224: Harden state parsing: preserve empty parts + reject far-future timestamps.
String.split() drops trailing empty parts; and a far-future timestamp (even though signed) makes debugging harder if clocks drift.

@@
-        String[] parts = decoded.split("\\|");
+        String[] parts = decoded.split("\\|", -1);
         if (parts.length != 3) {
             throw new IllegalArgumentException("Malformed state parameter");
         }
@@
         long now = Instant.now().getEpochSecond();
+        // Allow small clock skew, but reject obviously broken timestamps
+        if (timestamp > now + 60) {
+            throw new IllegalArgumentException("State timestamp is in the future");
+        }
         if (now - timestamp > STATE_EXPIRY_SECONDS) {
             throw new IllegalArgumentException("State token expired");
         }

247-251: Make constantTimeEquals null-safe.
Prevents accidental NPEs if future refactors allow null inputs.

@@
     private boolean constantTimeEquals(String a, String b) {
+        if (a == null || b == null) {
+            return false;
+        }
         byte[] aBytes = a.getBytes(StandardCharsets.UTF_8);
         byte[] bBytes = b.getBytes(StandardCharsets.UTF_8);
         return MessageDigest.isEqual(aBytes, bBytes);
     }
server/application-server/openapi.yaml (1)

150-191: Add auth error responses (or document global security) for Slack connection endpoints.
Right now only 200 is modeled; clients may assume these can’t return 401/403. If security is global, fine—but then keep endpoint-level docs consistent across the spec.

server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/AccountController.java (1)

228-234: Normalize keycloakIssuerUri (blank/null/trailing slash) before deriving base URL.
Returning "" can create malformed linkUrls downstream.

@@
     private String getKeycloakBaseUrl() {
         // Strip /realms/REALM from issuer URI to get base URL
-        if (keycloakIssuerUri != null && keycloakIssuerUri.contains("/realms/")) {
-            return keycloakIssuerUri.substring(0, keycloakIssuerUri.indexOf("/realms/"));
+        if (keycloakIssuerUri == null || keycloakIssuerUri.isBlank()) {
+            return null;
+        }
+        String issuer = keycloakIssuerUri.endsWith("/") ? keycloakIssuerUri.substring(0, keycloakIssuerUri.length() - 1) : keycloakIssuerUri;
+        if (issuer.contains("/realms/")) {
+            return issuer.substring(0, issuer.indexOf("/realms/"));
         }
-        return keycloakIssuerUri;
+        return issuer;
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29396f2 and a91e642.

📒 Files selected for processing (43)
  • AGENTS.md (1 hunks)
  • STYLE_GUIDE.md (1 hunks)
  • docker/.env.example (1 hunks)
  • docker/compose.app.yaml (1 hunks)
  • docker/compose.core.yaml (2 hunks)
  • docker/preview/compose.app.yaml (1 hunks)
  • docs/admin/production-setup.mdx (1 hunks)
  • docs/admin/slack-integration.mdx (1 hunks)
  • docs/contributor/local-development.mdx (1 hunks)
  • docs/sidebars.admin.ts (1 hunks)
  • server/application-server/.env.example (1 hunks)
  • server/application-server/compose.yaml (1 hunks)
  • server/application-server/keycloak-hephaestus-realm-example-config.json (34 hunks)
  • server/application-server/openapi.yaml (7 hunks)
  • server/application-server/pom.xml (2 hunks)
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/AccountController.java (3 hunks)
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/SlackConnectionDTO.java (1 hunks)
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/SlackConnectionService.java (1 hunks)
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/user/User.java (1 hunks)
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/user/UserInfoDTO.java (2 hunks)
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/tasks/SlackWeeklyLeaderboardTask.java (1 hunks)
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/SlackCallbackController.java (1 hunks)
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackController.java (1 hunks)
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationService.java (1 hunks)
  • server/application-server/src/main/resources/application-prod.yml (1 hunks)
  • server/application-server/src/main/resources/application.yml (1 hunks)
  • server/application-server/src/main/resources/db/changelog/1765321080884_changelog.xml (1 hunks)
  • server/application-server/src/main/resources/db/master.xml (1 hunks)
  • server/application-server/src/test/java/de/tum/in/www1/hephaestus/account/SlackConnectionServiceTest.java (1 hunks)
  • server/application-server/src/test/java/de/tum/in/www1/hephaestus/leaderboard/tasks/SlackWeeklyLeaderboardTaskMatchingTest.java (1 hunks)
  • server/application-server/src/test/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationServiceTest.java (1 hunks)
  • webapp/src/api/@tanstack/react-query.gen.ts (4 hunks)
  • webapp/src/api/sdk.gen.ts (4 hunks)
  • webapp/src/api/types.gen.ts (5 hunks)
  • webapp/src/components/admin/AdminSettingsPage.stories.tsx (2 hunks)
  • webapp/src/components/admin/AdminSettingsPage.tsx (4 hunks)
  • webapp/src/components/admin/AdminSlackSettings.tsx (1 hunks)
  • webapp/src/components/settings/SettingsPage.stories.tsx (4 hunks)
  • webapp/src/components/settings/SettingsPage.tsx (2 hunks)
  • webapp/src/components/settings/SlackConnectionSection.stories.tsx (1 hunks)
  • webapp/src/components/settings/SlackConnectionSection.tsx (1 hunks)
  • webapp/src/routes/_authenticated/settings.tsx (3 hunks)
  • webapp/src/routes/_authenticated/w/$workspaceSlug/admin/_admin/settings.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (16)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/general-coding.instructions.md)

**/*.{ts,tsx}: Use PascalCase for component names, interfaces, and type aliases
Use camelCase for variables, functions, and methods
Prefix private class members with underscore (_)
Use ALL_CAPS for constants
Use try/catch blocks for async operations
Implement proper error boundaries in React components
Always log errors with contextual information

**/*.{ts,tsx}: Ship new code as TypeScript. Reach for type aliases when composing shapes and interface when you need declaration merging or extension.
Default to const, mark collections readonly when practical, and avoid mutating function arguments.
Rely on optional chaining (?.) and nullish coalescing (??) when working with generated API responses.
Export prop and return-value types so other modules can reuse them (for example export interface MentorCardProps).
Import with the @/* alias defined in tsconfig.json. Keep relative paths shallow.
Use discriminated unions or zod schemas for request and response guards. Assert intent with the satisfies operator instead of broad casts.
Model async flows with PromiseSettledResult helpers and exhaustively handle states.
Author components as named functions (export function Component(props: ComponentProps)). Avoid React.FC; annotate props explicitly.
Obey the Rules of Hooks. No conditional hook calls or hook invocations inside loops.
Keep render logic pure—never mutate stores, perform navigation, or touch the DOM during render.
Prefer named exports for components and hooks so tree shaking stays predictable.
Fetch data with TanStack Query v5. Spread the generated option helpers from src/api/@tanstack/react-query.gen.ts into useQuery/useMutation so request typing, retries, and query keys stay in sync with the server.
Use the shared QueryClient provided by src/integrations/tanstack-query/root-provider.tsx. Invalidate with the generated ...QueryKey() helpers instead of literal arrays.
Reuse the preconfigured OpenAPI client (set up in `main.t...

Files:

  • webapp/src/components/admin/AdminSlackSettings.tsx
  • webapp/src/components/settings/SlackConnectionSection.tsx
  • webapp/src/components/settings/SettingsPage.stories.tsx
  • webapp/src/components/settings/SlackConnectionSection.stories.tsx
  • webapp/src/routes/_authenticated/w/$workspaceSlug/admin/_admin/settings.tsx
  • webapp/src/api/sdk.gen.ts
  • docs/sidebars.admin.ts
  • webapp/src/components/admin/AdminSettingsPage.stories.tsx
  • webapp/src/components/admin/AdminSettingsPage.tsx
  • webapp/src/api/@tanstack/react-query.gen.ts
  • webapp/src/routes/_authenticated/settings.tsx
  • webapp/src/api/types.gen.ts
  • webapp/src/components/settings/SettingsPage.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use descriptive variable and function names in code

Files:

  • webapp/src/components/admin/AdminSlackSettings.tsx
  • webapp/src/components/settings/SlackConnectionSection.tsx
  • webapp/src/components/settings/SettingsPage.stories.tsx
  • webapp/src/components/settings/SlackConnectionSection.stories.tsx
  • webapp/src/routes/_authenticated/w/$workspaceSlug/admin/_admin/settings.tsx
  • webapp/src/api/sdk.gen.ts
  • docs/sidebars.admin.ts
  • webapp/src/components/admin/AdminSettingsPage.stories.tsx
  • webapp/src/components/admin/AdminSettingsPage.tsx
  • webapp/src/api/@tanstack/react-query.gen.ts
  • webapp/src/routes/_authenticated/settings.tsx
  • webapp/src/api/types.gen.ts
  • webapp/src/components/settings/SettingsPage.tsx
webapp/src/{routes,components}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

webapp/src/{routes,components}/**/*.{ts,tsx}: Follow container/presentation split: route files under src/routes/** fetch data and pass to components under src/components/**. Keep components pure and side-effect free
Fetch data exclusively with TanStack Query v5 and the generated helpers in @/api/@tanstack/react-query.gen.ts. Spread option objects: useQuery(getTeamsOptions({ ... }))

Files:

  • webapp/src/components/admin/AdminSlackSettings.tsx
  • webapp/src/components/settings/SlackConnectionSection.tsx
  • webapp/src/components/settings/SettingsPage.stories.tsx
  • webapp/src/components/settings/SlackConnectionSection.stories.tsx
  • webapp/src/routes/_authenticated/w/$workspaceSlug/admin/_admin/settings.tsx
  • webapp/src/components/admin/AdminSettingsPage.stories.tsx
  • webapp/src/components/admin/AdminSettingsPage.tsx
  • webapp/src/routes/_authenticated/settings.tsx
  • webapp/src/components/settings/SettingsPage.tsx
webapp/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

webapp/src/**/*.{ts,tsx}: Do not call fetch directly; reuse the generated @hey-api client configured in src/api/client.ts and the shared QueryClient from src/integrations/tanstack-query/root-provider.tsx
TypeScript style in webapp: explicit prop interfaces, no React.FC, favour type aliases for composite shapes, prefer discriminated unions or Zod guards when needed, keep imports using @/* alias, avoid deep relative paths

Files:

  • webapp/src/components/admin/AdminSlackSettings.tsx
  • webapp/src/components/settings/SlackConnectionSection.tsx
  • webapp/src/components/settings/SettingsPage.stories.tsx
  • webapp/src/components/settings/SlackConnectionSection.stories.tsx
  • webapp/src/routes/_authenticated/w/$workspaceSlug/admin/_admin/settings.tsx
  • webapp/src/api/sdk.gen.ts
  • webapp/src/components/admin/AdminSettingsPage.stories.tsx
  • webapp/src/components/admin/AdminSettingsPage.tsx
  • webapp/src/api/@tanstack/react-query.gen.ts
  • webapp/src/routes/_authenticated/settings.tsx
  • webapp/src/api/types.gen.ts
  • webapp/src/components/settings/SettingsPage.tsx
webapp/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind utility classes + shadcn primitives in src/components/ui. Compose class names with clsx/tailwind-merge; use tokenized colors (bg-surface, text-muted, etc.)

Files:

  • webapp/src/components/admin/AdminSlackSettings.tsx
  • webapp/src/components/settings/SlackConnectionSection.tsx
  • webapp/src/components/settings/SettingsPage.stories.tsx
  • webapp/src/components/settings/SlackConnectionSection.stories.tsx
  • webapp/src/components/admin/AdminSettingsPage.stories.tsx
  • webapp/src/components/admin/AdminSettingsPage.tsx
  • webapp/src/components/settings/SettingsPage.tsx
webapp/src/components/**/*.{ts,tsx,stories.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Follow shadcn patterns for accessibility, wire up ARIA roles, and manage focus for dialogs/menus. Storybook stories should cover default/variant/edge states

Files:

  • webapp/src/components/admin/AdminSlackSettings.tsx
  • webapp/src/components/settings/SlackConnectionSection.tsx
  • webapp/src/components/settings/SettingsPage.stories.tsx
  • webapp/src/components/settings/SlackConnectionSection.stories.tsx
  • webapp/src/components/admin/AdminSettingsPage.stories.tsx
  • webapp/src/components/admin/AdminSettingsPage.tsx
  • webapp/src/components/settings/SettingsPage.tsx
webapp/src/{components,routes}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

React Compiler (via babel-plugin-react-compiler in vite.config.js) automatically optimizes components. Do not use useMemo, useCallback, or React.memo for new code—the compiler handles memoization automatically

Files:

  • webapp/src/components/admin/AdminSlackSettings.tsx
  • webapp/src/components/settings/SlackConnectionSection.tsx
  • webapp/src/components/settings/SettingsPage.stories.tsx
  • webapp/src/components/settings/SlackConnectionSection.stories.tsx
  • webapp/src/routes/_authenticated/w/$workspaceSlug/admin/_admin/settings.tsx
  • webapp/src/components/admin/AdminSettingsPage.stories.tsx
  • webapp/src/components/admin/AdminSettingsPage.tsx
  • webapp/src/routes/_authenticated/settings.tsx
  • webapp/src/components/settings/SettingsPage.tsx
**/*.stories.{ts,tsx}

📄 CodeRabbit inference engine (.github/instructions/tsx.instructions.md)

**/*.stories.{ts,tsx}: Keep stories colocated and represent the real UI states that Chromatic validates.
Use Storybook play functions for interactive flows instead of end-to-end tests when the surface is presentational.

Files:

  • webapp/src/components/settings/SettingsPage.stories.tsx
  • webapp/src/components/settings/SlackConnectionSection.stories.tsx
  • webapp/src/components/admin/AdminSettingsPage.stories.tsx
server/application-server/src/main/java/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

server/application-server/src/main/java/**/*.java: Keep business logic in services annotated with @Service and use transactional boundaries (@Transactional) where needed. Controllers should be thin (input validation + delegation)
Use Lombok consistently (@Getter, @Setter, etc.) but prefer explicit builders or records when immutability helps

Files:

  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/user/User.java
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/SlackCallbackController.java
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/tasks/SlackWeeklyLeaderboardTask.java
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/SlackConnectionDTO.java
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackController.java
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/SlackConnectionService.java
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/AccountController.java
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationService.java
  • server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/user/UserInfoDTO.java
server/application-server/src/main/resources/db/changelog/*.xml

📄 CodeRabbit inference engine (AGENTS.md)

Liquibase changelog files live under server/application-server/src/main/resources/db/changelog/ and must be included via master.xml. Trim generated changelogs to minimal delta before committing

Files:

  • server/application-server/src/main/resources/db/changelog/1765321080884_changelog.xml
server/application-server/src/main/{java/**/entity/**,resources/db/changelog}/**/*.{java,xml}

📄 CodeRabbit inference engine (AGENTS.md)

Keep Liquibase changelog IDs monotonic and descriptive. Align entity annotations with the generated change sets

Files:

  • server/application-server/src/main/resources/db/changelog/1765321080884_changelog.xml
server/application-server/src/test/**/*.java

📄 CodeRabbit inference engine (.github/instructions/java-tests.instructions.md)

server/application-server/src/test/**/*.java: Each test case should have a single responsibility: one behavior or assertion per test
Test cases should have clear and concise objectives: state the objective simply without ambiguity
Test cases must be independent with no hidden dependencies between tests
Test cases should be traceable: link directly to requirements
Test cases must be repeatable with consistent setup and data
Test cases should be maintainable: easy to update when implementation changes
Focus on risk: cover critical flows and edge cases first when writing tests
Test cases should have minimal setup: avoid unnecessary initialization steps
Test cases must execute fast to fit seamlessly into CI pipelines
Test cases should use realistic data: use representative scenarios that mirror production use cases
Test cases should have concise expected results: one clear outcome per test case
Test cases should follow Arrange-Act-Assert (AAA) structure: keep structure clear with distinct setup, execution, and verification phases
Test cases may run in parallel, so avoid required cleanup steps and assume that there might be data from previous tests in the database

Files:

  • server/application-server/src/test/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationServiceTest.java
  • server/application-server/src/test/java/de/tum/in/www1/hephaestus/account/SlackConnectionServiceTest.java
  • server/application-server/src/test/java/de/tum/in/www1/hephaestus/leaderboard/tasks/SlackWeeklyLeaderboardTaskMatchingTest.java
server/application-server/src/test/java/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Group new Java tests under proper JUnit tag so CI picks them up (@Tag("unit"), @Tag("integration"), or @Tag("live")) and follow AAA structure with single assertion focus

Files:

  • server/application-server/src/test/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationServiceTest.java
  • server/application-server/src/test/java/de/tum/in/www1/hephaestus/account/SlackConnectionServiceTest.java
  • server/application-server/src/test/java/de/tum/in/www1/hephaestus/leaderboard/tasks/SlackWeeklyLeaderboardTaskMatchingTest.java
webapp/src/routes/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Declare new routes via createFileRoute, respect loader/guard conventions, and keep loader side effects out of render paths

Files:

  • webapp/src/routes/_authenticated/w/$workspaceSlug/admin/_admin/settings.tsx
  • webapp/src/routes/_authenticated/settings.tsx
webapp/src/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never hand-edit webapp/src/api/**/*, webapp/src/api/@tanstack/react-query.gen.ts, webapp/src/api/client.gen.ts, webapp/src/api/types.gen.ts — regenerate via npm run generate:api:application-server:client

Files:

  • webapp/src/api/sdk.gen.ts
  • webapp/src/api/@tanstack/react-query.gen.ts
  • webapp/src/api/types.gen.ts
server/application-server/openapi.yaml

📄 CodeRabbit inference engine (AGENTS.md)

Never hand-edit server/application-server/openapi.yaml — regenerate via npm run generate:api:application-server:specs

Files:

  • server/application-server/openapi.yaml
🧬 Code graph analysis (11)
webapp/src/components/admin/AdminSlackSettings.tsx (2)
webapp/src/components/ui/card.tsx (5)
  • Card (85-85)
  • CardHeader (86-86)
  • CardTitle (88-88)
  • CardDescription (90-90)
  • CardContent (91-91)
webapp/src/components/ui/button.tsx (1)
  • Button (61-61)
server/application-server/src/test/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationServiceTest.java (1)
server/application-server/src/test/java/de/tum/in/www1/hephaestus/testconfig/BaseUnitTest.java (1)
  • ExtendWith (11-15)
webapp/src/components/settings/SlackConnectionSection.stories.tsx (2)
webapp/src/components/settings/SlackConnectionSection.tsx (1)
  • SlackConnectionSection (25-103)
webapp/src/components/settings/SettingsPage.stories.tsx (1)
  • Loading (94-115)
webapp/src/api/sdk.gen.ts (1)
webapp/src/api/types.gen.ts (10)
  • SlackCallbackData (1690-1698)
  • SlackCallbackResponses (1700-1705)
  • DisconnectSlackData (1753-1758)
  • DisconnectSlackResponses (1760-1765)
  • GetSlackConnectionData (1769-1774)
  • GetSlackConnectionResponses (1776-1781)
  • SyncSlackConnectionData (1785-1790)
  • SyncSlackConnectionResponses (1792-1797)
  • InstallSlackBotData (2757-2767)
  • InstallSlackBotResponses (2769-2774)
webapp/src/components/admin/AdminSettingsPage.stories.tsx (1)
webapp/src/components/admin/AdminTeamsTable.stories.tsx (1)
  • Story (130-130)
server/application-server/src/test/java/de/tum/in/www1/hephaestus/account/SlackConnectionServiceTest.java (1)
server/application-server/src/test/java/de/tum/in/www1/hephaestus/testconfig/BaseUnitTest.java (1)
  • ExtendWith (11-15)
server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/SlackConnectionService.java (1)
server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationService.java (1)
  • Service (38-259)
webapp/src/api/@tanstack/react-query.gen.ts (2)
webapp/src/api/sdk.gen.ts (6)
  • Options (7-19)
  • slackCallback (36-41)
  • disconnectSlack (84-89)
  • getSlackConnection (95-100)
  • syncSlackConnection (106-111)
  • installSlackBot (533-538)
webapp/src/api/types.gen.ts (7)
  • SlackCallbackData (1690-1698)
  • DisconnectSlackData (1753-1758)
  • DisconnectSlackResponse (1767-1767)
  • GetSlackConnectionData (1769-1774)
  • SyncSlackConnectionData (1785-1790)
  • SyncSlackConnectionResponse (1799-1799)
  • InstallSlackBotData (2757-2767)
webapp/src/routes/_authenticated/settings.tsx (1)
webapp/src/api/@tanstack/react-query.gen.ts (6)
  • getUserSettingsQueryKey (103-103)
  • getUserSettingsOptions (109-122)
  • getSlackConnectionOptions (187-200)
  • syncSlackConnectionMutation (227-239)
  • getSlackConnectionQueryKey (181-181)
  • disconnectSlackMutation (167-179)
server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationService.java (1)
server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/SlackConnectionService.java (1)
  • Service (28-168)
webapp/src/components/settings/SettingsPage.tsx (5)
webapp/src/components/settings/NotificationsSection.tsx (1)
  • NotificationsSectionProps (4-17)
webapp/src/components/settings/ResearchParticipationSection.tsx (2)
  • ResearchParticipationSectionProps (4-17)
  • ResearchParticipationSection (23-67)
webapp/src/components/settings/AccountSection.tsx (2)
  • AccountSectionProps (14-27)
  • AccountSection (33-92)
webapp/src/components/settings/SlackConnectionSection.tsx (2)
  • SlackConnectionSectionProps (15-23)
  • SlackConnectionSection (25-103)
webapp/src/integrations/posthog/config.ts (1)
  • isPosthogEnabled (33-34)
🪛 ast-grep (0.40.0)
server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationService.java

[warning] 230-230: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Mac.getInstance(HMAC_SHA256)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA

(desede-is-deprecated-java)


[warning] 230-230: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Mac.getInstance(HMAC_SHA256)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html

(use-of-aes-ecb-java)

🪛 dotenv-linter (4.0.0)
docker/.env.example

[warning] 182-182: [UnorderedKey] The KEYCLOAK_SLACK_CLIENT_ID key should go before the KEYCLOAK_SLACK_ENABLED key

(UnorderedKey)


[warning] 183-183: [UnorderedKey] The KEYCLOAK_SLACK_CLIENT_SECRET key should go before the KEYCLOAK_SLACK_ENABLED key

(UnorderedKey)


[warning] 188-188: [ExtraBlankLine] Extra blank line detected

(ExtraBlankLine)

server/application-server/.env.example

[warning] 4-4: [UnorderedKey] The KEYCLOAK_GITHUB_ADMIN_USERNAME key should go before the KEYCLOAK_GITHUB_CLIENT_ID key

(UnorderedKey)

🪛 LanguageTool
AGENTS.md

[uncategorized] ~3-~3: The official name of this software platform is spelled with a capital “H”.
Context: ...ails with the scoped instructions under .github/instructions/** (general coding, TSX, ...

(GITHUB)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: docker-build / intelligence-service-build / Build linux/amd64 Docker Image for ls1intum/hephaestus/intelligence-service
  • GitHub Check: docker-build / intelligence-service-build / Build linux/arm64 Docker Image for ls1intum/hephaestus/intelligence-service
  • GitHub Check: docker-build / webapp-build / Build linux/amd64 Docker Image for ls1intum/hephaestus/webapp
  • GitHub Check: docker-build / webapp-build / Build linux/arm64 Docker Image for ls1intum/hephaestus/webapp
  • GitHub Check: quality-gates / database-documentation-validation
  • GitHub Check: quality-gates / docs-quality
  • GitHub Check: quality-gates / database-schema-validation
  • GitHub Check: quality-gates / webapp-quality
  • GitHub Check: security-scan / dependencies
  • GitHub Check: test-suite / webapp-visual
  • GitHub Check: security-scan / sast
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (java)
  • GitHub Check: Docs / Build (quality gate)
🔇 Additional comments (54)
docker/preview/compose.app.yaml (1)

276-277: LGTM — feature-flagged configuration addition.

Default-disabled Slack linking in preview deployments is appropriate; the comment clarifies the setting's purpose.

server/application-server/compose.yaml (1)

26-34: LGTM — Slack IdP and proxy configuration for local development.

The configuration is safe, disabled by default, and the comments guide developers. Keycloak proxy settings (KC_PROXY_HEADERS: xforwarded, KC_HTTP_ENABLED: true) are appropriate for local ngrok/proxy scenarios.

Please confirm that the referenced documentation (docs/dev/slack-account-linking.md) exists and contains clear setup instructions for developers enabling Slack locally.

server/application-server/pom.xml (2)

493-494: Verify necessity of extended wait time and JMX configuration.

The 30-second <wait> timeout is substantially longer than typical Spring Boot defaults (300ms–1000ms). This will slow down mvn spring-boot:run and OpenAPI spec generation during CI pipelines.

Questions:

  • Is the 30-second wait necessary? If so, please document the rationale (e.g., artifact download, slow CI runner).
  • If this is temporary (e.g., for a specific build environment), consider using an environment variable override or Maven property.
  • Is JMX port 9001 exposed intentionally? Confirm it doesn't conflict with other services or create unintended security surface.

Suggest adding a comment explaining the extended timeout rationale:

+                    <!-- Wait longer for slow CI environments or large dependency downloads -->
                     <wait>30000</wait>

Or consider making it conditional:

-                    <wait>30000</wait>
+                    <wait>${spring-boot.wait.timeout:-30000}</wait>

508-508: Verify RMI/JMX configuration for OpenAPI spec generation.

The extended JVM arguments enable RMI and JMX management for the OpenAPI spec generation phase. Confirm that:

  • The hostname binding to 127.0.0.1 and JMX port 9001 don't conflict with other services.
  • Developers are aware of the exposed management port during local builds.
  • The spring.application.admin.jmx-name configuration is necessary for Spring Boot Admin or another monitoring tool.
server/application-server/src/main/resources/db/master.xml (1)

38-38: LGTM — changelog inclusion properly ordered.

The Slack changelog is correctly included in chronological order. Verify that the referenced file (./changelog/1765321080884_changelog.xml) contains only the slack_user_id column addition and no other unrelated schema changes.

Please confirm that the changelog file adds the slack_user_id nullable column to the user table and nothing more.

server/application-server/src/main/resources/application.yml (1)

162-168: LGTM — Slack configuration properly feature-flagged.

The configuration is safe, well-documented, and aligns with environment variables defined in Docker Compose files. Default-disabled behavior and empty credential defaults are appropriate.

STYLE_GUIDE.md (1)

1-78: LGTM — comprehensive style guide for the project.

The guide is well-structured, with clear absolutes, language-specific recommendations, and actionable patterns. The React Compiler guidance and mutation naming examples are particularly helpful.

Adoption note: Verify that this guide is integrated into:

  • CI/CD linting rules (ESLint, Biome, Checkstyle, Spotbugs for Java)
  • Pre-commit hooks or GitHub Actions enforcements
  • Code review checklists

Without tooling enforcement, style guides risk becoming advisory only. Consider flagging high-priority rules (e.g., "no else blocks," "no any type") in lint configuration.

Please confirm that linting and CI rules are updated to enforce high-priority style rules (e.g., no bare else, no any in TypeScript, constructor injection in Java).

AGENTS.md (1)

3-3: LGTM — STYLE_GUIDE.md reference added correctly.

The Markdown link is properly formatted and positioned early in the handbook. This ensures developers encounter the style guide alongside other foundational instructions.

server/application-server/keycloak-hephaestus-realm-example-config.json (2)

1642-1670: LGTM — Slack OIDC identity provider correctly configured.

The Slack IdP is secure and well-designed:

  • linkOnly: true ensures Slack is for account linking, not primary authentication.
  • hideOnLogin: true prevents confusion on the login page.
  • Signature validation and JWKS URL usage follow security best practices.
  • Scope "openid email profile" is minimal and appropriate.
  • Environment variable templating with safe defaults prevents accidental misconfiguration.

Please verify that the Slack OIDC endpoints remain current:

  • https://slack.com/api/openid.connect.token
  • https://slack.com/api/openid.connect.userInfo
  • https://slack.com/openid/connect/keys
  • https://slack.com/openid/connect/authorize

If these endpoints have changed, update the configuration to match Slack's latest OpenID Connect documentation.


66-75: LGTM — JSON formatting changes are cosmetic and safe.

The reflowing of arrays and objects throughout the file improves readability without changing semantics. All role composites, client scopes, and protocol mapper configurations remain functionally identical.

Also applies to: 242-246, 365-367, 435-437

docker/compose.app.yaml (1)

105-109: LGTM! Secure defaults for optional Slack integration.

The environment variables follow secure defaults (disabled by default, empty credentials), and the comments clearly indicate the optional nature and Keycloak dependency. This aligns with defense-in-depth principles.

docker/.env.example (1)

153-187: Excellent documentation for Slack integration.

The comprehensive documentation clearly distinguishes between:

  1. OpenID Connect flow for user identity linking (Keycloak)
  2. OAuth v2 flow for workspace bot installation

The separation of concerns and explicit redirect URLs will help operators configure this correctly.

server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/tasks/SlackWeeklyLeaderboardTask.java (3)

113-136: Excellent Javadoc explaining the deterministic matching strategy.

The documentation clearly articulates why fuzzy matching was removed (non-unique names, false positives) and establishes a clear priority order. This will help future maintainers understand the design rationale.


148-168: Linked Slack ID matching with appropriate error handling.

The priority 1 logic correctly handles:

  • Successful match with debug logging
  • Stale/deactivated users with a warning

The warning message at lines 161-166 provides actionable context for operators investigating why a linked user isn't being matched.


170-203: Email-based fallback with informative guidance.

The fallback logic is clean and the info-level message at lines 195-201 helpfully guides users on how to enable Slack mentions. The method correctly returns null when no match is found, allowing the caller to handle the absence gracefully.

docker/compose.core.yaml (3)

102-104: LGTM! Keycloak Slack configuration properly wired.

The environment variables correctly default to disabled (false) and empty credentials, consistent with the secure-by-default pattern used elsewhere.


379-380: Correct use of linkOnly and hideOnLogin for account linking.

Setting linkOnly: true and hideOnLogin: true is the correct configuration for a "Connect Slack" feature that links accounts rather than providing an alternative login method. This prevents users from attempting to log in with Slack alone.


369-396: The Slack identity provider configuration uses the correct OpenID Connect endpoints as documented in Slack's official OIDC specification. No changes needed.

server/application-server/.env.example (1)

1-19: Well-organized environment variable structure.

The clear separation between GitHub OAuth (required), PAT mode (alternative), and Slack integration (optional) makes it easy for developers to understand the configuration options. The ngrok note is a helpful addition for local Slack testing.

webapp/src/components/settings/SlackConnectionSection.tsx (2)

38-41: Secure window.open usage.

The handleConnect function correctly uses noopener,noreferrer flags when opening the Slack OAuth flow in a new window, preventing potential security issues (tabnapping, referrer leakage).


44-52: Good accessibility practices.

The component uses semantic HTML with proper ARIA labeling (aria-labelledby, heading hierarchy) and descriptive text for screen readers.

server/application-server/src/test/java/de/tum/in/www1/hephaestus/leaderboard/tasks/SlackWeeklyLeaderboardTaskMatchingTest.java (2)

86-227: Excellent test coverage for the deterministic matching strategy.

The nested test structure clearly documents the matching behavior:

  1. Linked Slack ID takes priority (lines 86-146)
  2. Email matching as fallback (lines 148-187)
  3. Explicit verification that fuzzy matching is NOT performed (lines 189-227)

The test at lines 194-209 ("should NOT match 'Khiem Nguyen' to 'Khoa Nguyen'") directly validates the critical bug fix mentioned in the PR context.


229-286: Comprehensive edge case coverage.

The edge case tests properly handle:

  • Null user in entry
  • Missing email with no linked ID
  • Empty Slack user list
  • Null Slack profile

This defensive testing ensures the mapper won't throw NPEs in production.

server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/user/User.java (1)

118-119: Database migration confirmed for slackUserId column.

The Liquibase migration file (1765321080884_changelog.xml) exists and correctly adds the slack_user_id column to the user table with type VARCHAR(255). The migration includes proper preconditions and documentation.

webapp/src/components/admin/AdminSettingsPage.stories.tsx (2)

69-70: LGTM! Slack integration props added to stories.

The new workspaceSlug and hasSlackToken props are properly typed and align with the Slack integration UI changes in AdminSettingsPage.


85-92: Good coverage of Slack connection state.

The ConnectedToSlack story provides visual regression testing for the Slack-connected UI state, which aligns with the Storybook guideline to represent real UI states.

server/application-server/src/main/resources/application-prod.yml (1)

73-79: Well-structured Slack integration configuration.

The new hephaestus.slack block follows the existing pattern for service integrations (like posthog), uses appropriate environment variable placeholders, and defaults to disabled for safe production deployment. The comments clearly explain the relationship to Keycloak's Slack IdP and the bot installation flow.

docs/sidebars.admin.ts (1)

4-7: LGTM! Slack Integration properly added to admin sidebar.

The new entry follows the Docusaurus sidebar config structure and maintains consistency with the existing Production Setup entry.

docs/contributor/local-development.mdx (1)

159-189: Comprehensive Slack integration setup for local development.

The optional Slack integration section clearly explains the HTTPS requirement, provides step-by-step instructions, and correctly notes the need to access Keycloak via the ngrok URL for testing. The scopes and environment variables align with the production setup documentation.

docs/admin/production-setup.mdx (1)

36-48: Good documentation structure for Slack integration.

The optional Slack integration subsection follows the DRY principle by referencing the dedicated guide rather than duplicating setup steps. The environment variable table is complete and matches the configuration in application-prod.yml.

server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/SlackConnectionDTO.java (1)

1-23: Well-designed DTO with clear factory methods.

The record structure is appropriate for an immutable DTO, and the static factory methods (connected() and disconnected()) provide clear construction semantics. The @JsonInclude(NON_EMPTY) annotation correctly omits null/empty fields while preserving primitive boolean values.

server/application-server/src/main/resources/db/changelog/1765321080884_changelog.xml (1)

1-17: Changelog properly included in master.xml.

The changelog has been correctly added to server/application-server/src/main/resources/db/master.xml at line 38. The timestamp-based ID is monotonic, preConditions prevent duplicate execution, and the nullable column is appropriate for optional Slack linking. The structure aligns with Liquibase best practices and coding guidelines.

webapp/src/routes/_authenticated/w/$workspaceSlug/admin/_admin/settings.tsx (1)

15-24: Good: validateSearch + Route.useSearch() makes Slack status handling safer.

Optional improvement: export type SlackStatus = z.infer<typeof searchSchema>["slack"] and thread that type into AdminSettingsPage so the toast switch can be exhaustive.

Also applies to: 36-37, 161-164

webapp/src/components/settings/SettingsPage.stories.tsx (1)

15-32: Nice: stories now cover SettingsPage with Slack props across key states.

Also applies to: 41-63, 68-89, 94-115

webapp/src/components/settings/SettingsPage.tsx (1)

12-15: LGTM: Slack section integrated cleanly and loading is derived consistently.

Also applies to: 17-23, 25-69

server/application-server/src/main/java/de/tum/in/www1/hephaestus/gitprovider/user/UserInfoDTO.java (1)

7-16: Good: slackUserId is added in a null-friendly way and wired through the factory.

Also applies to: 21-31

webapp/src/components/settings/SlackConnectionSection.stories.tsx (1)

1-54: LGTM! Well-structured Storybook stories covering all UI states.

The stories comprehensively cover the four key states (Disconnected, Connected, Loading, Disabled) with appropriate prop combinations. The use of satisfies for type safety and the centered layout decorator with fixed width provide good visual testing coverage.

server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/SlackCallbackController.java (2)

94-99: Verify redirect behavior when hephaestus.webapp.url is not configured.

When webappUrl is blank, redirects become relative paths (e.g., /settings?slack=error). This may fail if the webapp is hosted on a different origin or behind a reverse proxy. Consider logging a warning or throwing an error if the URL is not configured in production.


74-88: LGTM! Proper OAuth callback handling with appropriate error categorization.

The flow correctly validates the state parameter for CSRF protection, exchanges the code for tokens via the service, and provides meaningful redirect states for different failure modes.

webapp/src/routes/_authenticated/settings.tsx (1)

120-150: LGTM! Clean integration of Slack props with proper loading state composition.

The Slack connection state is correctly wired to the SettingsPage component with comprehensive loading state aggregation from query and both mutations.

server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/SlackConnectionService.java (2)

88-130: LGTM! Robust sync logic with proper edge case handling.

The method correctly handles null/empty Keycloak user IDs, clears stale Slack IDs when the identity is unlinked, and gracefully returns current status on errors to avoid breaking the user flow.


67-75: LGTM! Efficient status check prioritizing local cache.

Checking the User entity first before querying Keycloak is a good optimization that reduces external API calls.

webapp/src/api/sdk.gen.ts (3)

80-112: Slack connection endpoints look consistent with the REST surface.
DELETE/GET/POST /user/slack-connection* wrappers are straightforward.


529-538: installSlackBot() should not be called via XHR—this endpoint must redirect the browser to Slack's OAuth authorization URL.

The correct pattern for Slack bot installation is direct browser navigation to the install endpoint (as currently done with window.location.href in AdminSlackSettings.tsx), not a fetch/XHR request. The backend endpoint should return a 302 redirect to Slack's authorize URL with the proper state and scopes pre-configured. The generated SDK function exposes this as a data operation, which is structurally incorrect for a redirect endpoint and should never be invoked.


32-41: This is a generated file that cannot be edited; the actual issue is in the backend OpenAPI spec design.

The /integrations/slack/callback endpoint is documented in openapi.yaml to return 302 Redirect as its primary response. When this endpoint is called via XHR (as the generated slackCallback() wrapper would), the @hey-api client follows the redirect by default (Fetch API behavior) and returns the final response body (HTML), not a typed API response. This is a fundamental mismatch: Slack OAuth callbacks should be browser navigations, not API calls.

However, slackCallback() is not invoked anywhere in the webapp, so this doesn't currently impact functionality. The proper fix is to either:

  1. Handle the callback server-side (don't expose it as an API endpoint), or
  2. If frontend involvement is needed, navigate directly via window.location.href instead of calling via the client

Since this is generated code, the fix belongs in the OpenAPI schema or backend endpoint design.

server/application-server/openapi.yaml (2)

1345-1371: Install endpoint returns 302; confirm the generated client is meant to call it.
If frontend uses the generated SDK, consider returning 200 { url: string } instead of 302 (or keep 302 but ensure UI uses navigation, not fetch).


3666-3668: UserInfo.slackUserId should also be nullable.
Same contract issue as SlackConnection.slackUserId.

@@
     UserInfo:
       type: object
       properties:
@@
         slackUserId:
-          type: string
+          type: [string, "null"]

Likely an incorrect or invalid review comment.

server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/AccountController.java (2)

48-58: Constructor injection looks good; controller stays thin.
Slack-specific logic remains in SlackConnectionService.


173-199: /sync correctly requires auth.
This endpoint is the right place to hit Keycloak.

webapp/src/api/@tanstack/react-query.gen.ts (3)

64-83: Be careful using slackCallbackOptions() as a query (redirect endpoint).
A useQuery() may refetch (window focus/retry), which is a bad fit for “complete OAuth + redirect” flows. Prefer a route loader that triggers navigation, or don’t model this as queryable data.


163-239: Slack connection query/mutations look fine for TanStack Query.
GET status + POST sync + DELETE disconnect map well to query/mutation usage.


1271-1291: installSlackBotOptions() is also a redirect endpoint; avoid useQuery() here.
This should usually be a user-initiated navigation, not a background query.

server/application-server/src/main/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationService.java (1)

50-67: Fail fast on missing Slack credentials (not only in generateInstallUrl).
completeInstallation() can currently run with blank clientId/clientSecret until the Slack call fails; better to validate at entry (or constructor) so failures are deterministic.

@@
     @Transactional
     public String completeInstallation(String code, String state, String redirectUri) {
+        if (clientId == null || clientId.isBlank() || clientSecret == null || clientSecret.isBlank()) {
+            throw new IllegalStateException("Slack client credentials are not configured.");
+        }
+        if (code == null || code.isBlank()) {
+            throw new IllegalArgumentException("Missing code parameter");
+        }
+        if (redirectUri == null || redirectUri.isBlank()) {
+            throw new IllegalArgumentException("Missing redirectUri parameter");
+        }
         String workspaceSlug = validateAndParseState(state);
@@
     }

Likely an incorrect or invalid review comment.

webapp/src/api/types.gen.ts (1)

1-2984: Auto-generated types for Slack integration look correct.

The new Slack-related types (SlackConnection, SlackCallbackData, DisconnectSlackData, GetSlackConnectionData, SyncSlackConnectionData, InstallSlackBotData, and associated response types) follow the established @hey-api/openapi-ts generation patterns. The UserInfo extension with slackUserId is also properly typed as optional.

As per coding guidelines, ensure these changes were regenerated via npm run generate:api:application-server:client rather than hand-edited.

Comment on lines +1 to +81
---
id: slack-integration
sidebar_position: 2
title: Slack Integration
description: Configure Slack for leaderboard notifications and user mentions.
---

# Slack Integration

Enables weekly leaderboard notifications to Slack with @-mentions for linked users.

## Setup

### 1. Create Slack App

Go to [api.slack.com/apps](https://api.slack.com/apps)**Create New App****From scratch** → Name: `Hephaestus`

### 2. Configure OAuth

**Features → OAuth & Permissions → Redirect URLs** (add both):

```
https://YOUR_DOMAIN/keycloak/realms/hephaestus/broker/slack/endpoint
https://YOUR_DOMAIN/api/integrations/slack/callback
```

Replace `YOUR_DOMAIN` with your production domain (e.g., `hephaestus.example.com`).

**Scopes** (Features → OAuth & Permissions):

| Type | Scopes |
|------|--------|
| User Token Scopes | `openid`, `profile`, `email` |
| Bot Token Scopes | `chat:write`, `channels:read`, `users:read`, `team:read` |

### 3. Get Credentials

From **Settings → Basic Information → App Credentials**, copy:

- **Client ID**`KEYCLOAK_SLACK_CLIENT_ID`
- **Client Secret**`KEYCLOAK_SLACK_CLIENT_SECRET`
- **Signing Secret**`SLACK_SIGNING_SECRET`

### 4. Install & Get Bot Token

**Features → OAuth & Permissions****Install to Workspace** → Copy **Bot User OAuth Token** (`xoxb-...`) → `SLACK_BOT_TOKEN`

### 5. Set Environment Variables

Add to `docker/.env` (or your secret manager):

```bash
# Required for Slack integration
KEYCLOAK_SLACK_ENABLED=true
SLACK_LINKING_ENABLED=true
KEYCLOAK_SLACK_CLIENT_ID=...
KEYCLOAK_SLACK_CLIENT_SECRET=...
SLACK_BOT_TOKEN=xoxb-...
SLACK_SIGNING_SECRET=...

# Leaderboard notifications (optional)
LEADERBOARD_NOTIFICATION_ENABLED=true
LEADERBOARD_NOTIFICATION_CHANNEL_ID=C0123456789
```

To find channel ID: right-click channel in Slack → **View channel details** → scroll to bottom.

### 6. Restart & Verify

1. Restart the application server
2. Go to **Workspace Settings → Slack Integration****Connect Workspace**
3. Users link accounts in **Settings → Slack Connection**

## Troubleshooting

| Issue | Solution |
|-------|----------|
| "Slack integration is not enabled" | Verify `KEYCLOAK_SLACK_ENABLED=true` and `SLACK_LINKING_ENABLED=true` |
| "Invalid state" after OAuth | Token expired (10 min). Start the flow again. |
| Bot not posting | Invite bot: `/invite @Hephaestus`. Verify `SLACK_BOT_TOKEN` and channel ID. |
| User not @-mentioned | User must link in Settings AND click **I've Linked My Account**. |
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "slack.*post|slack.*chat|slack.*user|slackClient|webClient" --type java -A 2 -B 2

Repository: ls1intum/Hephaestus

Length of output: 17932


🏁 Script executed:

rg "slackApp\.client\(\)\." --type java -A 1 -B 1

Repository: ls1intum/Hephaestus

Length of output: 1348


🏁 Script executed:

cat -n server/application-server/src/main/java/de/tum/in/www1/hephaestus/leaderboard/SlackMessageService.java

Repository: ls1intum/Hephaestus

Length of output: 3977


🏁 Script executed:

rg "channels\.list|conversations\.list|team\.|teams\." --type java

Repository: ls1intum/Hephaestus

Length of output: 25913


🏁 Script executed:

rg "\.client\(\)\." --type java | grep -i slack | head -20

Repository: ls1intum/Hephaestus

Length of output: 586


Comprehensive Slack integration guide with minor scope documentation update needed.

The documentation is well-structured and covers the complete setup flow, from app creation through troubleshooting. The dual redirect URL requirement (Keycloak IdP and bot callback) is clearly documented, and the scope separation between User Token Scopes (for identity linking) and Bot Token Scopes (for posting) is correct.

However, the Bot Token Scopes section lists channels:read and team:read, but the codebase only uses chat:write and users:read for leaderboard functionality:

  • usersList() requires users:read
  • chatPostMessage() requires chat:write

The channels:read and team:read scopes are not used in the current implementation. Consider removing them from the documented scopes or adding a note explaining they are reserved for future features.

🤖 Prompt for AI Agents
In docs/admin/slack-integration.mdx around lines 1 to 81, the Bot Token Scopes
list includes channels:read and team:read which are not used by the current
implementation; update the Bot Token Scopes to only list the scopes required by
the codebase (chat:write and users:read) and either remove channels:read and
team:read entirely or add a one-line note that those scopes are
optional/reserved for future features and not required for current leaderboard
functionality.

Comment on lines +27 to +37
- description: |-
Controller for managing Slack integration settings within a workspace
context.
<p>
This controller is workspace-scoped, meaning all endpoints are prefixed with
<code>/workspaces/{workspaceSlug</code>}.
name: workspace-slack-controller
- description: |-
Controller for managing workspace memberships and roles.
Handles CRUD operations for workspace members and role assignments.
name: workspace-membership-controller
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix tag typos/inconsistencies (they affect generated docs + SDK grouping).
Examples in this hunk: missing } in /workspaces/{workspaceSlug...}, and tag name mismatch between declared tags and usage later (workspace-slack-controller vs workspace-slack).

🤖 Prompt for AI Agents
In server/application-server/openapi.yaml around lines 27 to 37, the tags and
path text contain typos that break generated docs/SDKs: add the missing closing
brace in the path fragment so it reads /workspaces/{workspaceSlug} and reconcile
the tag names so they are identical between the tag declaration and any usage
(choose one consistent name such as workspace-slack or
workspace-slack-controller and update all occurrences), ensuring the tag entry's
name matches how controllers/operations reference it.

Comment on lines +73 to 107
/integrations/slack/callback:
get:
description: Handles the callback from Slack after bot installation. Validates
the HMAC-signed state parameter and exchanges the authorization code for an
access token.
operationId: slackCallback
parameters:
- description: OAuth authorization code from Slack
in: query
name: code
required: false
schema:
type: string
- description: HMAC-signed state containing workspace slug
in: query
name: state
required: false
schema:
type: string
- description: Error code if user cancelled or Slack returned an error
in: query
name: error
required: false
schema:
type: string
responses:
"302":
description: Redirect to workspace settings on success or error page on
failure
"400":
description: Missing required OAuth parameters
summary: Slack OAuth Callback
tags:
- 'Workspace '
/user:
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Slack callback tags value looks wrong and params are modeled as optional.
tags: - 'Workspace ' (trailing space + odd quoting) is likely accidental; also consider whether code/state should be required in the “success” path (even if error exists).

@@
   /integrations/slack/callback:
     get:
@@
       summary: Slack OAuth Callback
       tags:
-      - 'Workspace '
+      - Workspace Integration

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In server/application-server/openapi.yaml around lines 73 to 107, the Slack
callback operation has an incorrect tags entry and query parameters marked
optional: remove the trailing space and odd quoting so the tag is a normal value
(e.g. Workspace) and normalize quoting (or leave unquoted), and make the code
and state query parameters required: set required: true for both (keep error as
required: false) so the successful OAuth callback path requires code and state;
keep responses unchanged.

Comment on lines +2606 to 2621
SlackConnection:
type: object
description: DTO representing the user's Slack connection status.
properties:
connected:
type: boolean
description: Whether the user has linked their Slack account
linkUrl:
type: string
slackEnabled:
type: boolean
description: Whether Slack integration is enabled for this instance
slackUserId:
type: string
description: "The Slack User ID if connected, null otherwise"
SortObject:
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

SlackConnection.linkUrl / slackUserId should be nullable in the schema.
Backend behavior (and even your own description “null otherwise”) implies these can be null; modeling them as plain string will generate incorrect TS types.

@@
     SlackConnection:
       type: object
       description: DTO representing the user's Slack connection status.
       properties:
         connected:
           type: boolean
           description: Whether the user has linked their Slack account
         linkUrl:
-          type: string
+          type: [string, "null"]
         slackEnabled:
           type: boolean
           description: Whether Slack integration is enabled for this instance
         slackUserId:
-          type: string
+          type: [string, "null"]
           description: "The Slack User ID if connected, null otherwise"
+      required: [connected, linkUrl, slackEnabled, slackUserId]

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In server/application-server/openapi.yaml around lines 2606 to 2621,
SlackConnection.linkUrl and SlackConnection.slackUserId are declared as plain
strings but must allow null; update their schema entries to permit null (e.g.,
add nullable: true or use oneOf: [ { type: string }, { type: "null" } ]) and
adjust the descriptions if needed to state they can be null so generated
TypeScript types become string | null.

Comment on lines +145 to +171
@GetMapping("/slack-connection")
@Operation(
summary = "Get Slack connection status",
description = "Check if the current user has linked their Slack account. " +
"If Slack is enabled, also returns the URL where users can link their Slack account in Keycloak."
)
public ResponseEntity<SlackConnectionDTO> getSlackConnection(@AuthenticationPrincipal JwtAuthenticationToken auth) {
var user = userRepository.getCurrentUser();
if (user.isEmpty()) {
return ResponseEntity.notFound().build();
}

JwtAuthenticationToken token = resolveAuthentication(auth);
String keycloakUserId = null;
if (token != null) {
keycloakUserId = token.getToken().getClaimAsString(StandardClaimNames.SUB);
}

SlackConnectionDTO status;
if (keycloakUserId != null && slackConnectionService.isSlackEnabled()) {
status = slackConnectionService.syncSlackIdentity(user.get(), keycloakUserId, getKeycloakBaseUrl());
} else {
status = slackConnectionService.getConnectionStatus(user.get(), getKeycloakBaseUrl());
}

return ResponseEntity.ok(status);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid turning “unauthenticated” into 404, and consider not auto-syncing on every GET.
Right now, missing auth can fall through to notFound() (via getCurrentUser()), and GET may call Keycloak every time when Slack is enabled.

@@
     public ResponseEntity<SlackConnectionDTO> getSlackConnection(@AuthenticationPrincipal JwtAuthenticationToken auth) {
+        JwtAuthenticationToken token = resolveAuthentication(auth);
+        if (token == null) {
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
+        }
         var user = userRepository.getCurrentUser();
         if (user.isEmpty()) {
             return ResponseEntity.notFound().build();
         }
 
-        JwtAuthenticationToken token = resolveAuthentication(auth);
-        String keycloakUserId = null;
-        if (token != null) {
-            keycloakUserId = token.getToken().getClaimAsString(StandardClaimNames.SUB);
-        }
+        String keycloakUserId = token.getToken().getClaimAsString(StandardClaimNames.SUB);
@@
-        if (keycloakUserId != null && slackConnectionService.isSlackEnabled()) {
-            status = slackConnectionService.syncSlackIdentity(user.get(), keycloakUserId, getKeycloakBaseUrl());
-        } else {
-            status = slackConnectionService.getConnectionStatus(user.get(), getKeycloakBaseUrl());
-        }
+        // Consider: keep GET as a cheap read, and require explicit POST /sync for Keycloak calls.
+        status = slackConnectionService.getConnectionStatus(user.get(), getKeycloakBaseUrl());
🤖 Prompt for AI Agents
server/application-server/src/main/java/de/tum/in/www1/hephaestus/account/AccountController.java
lines 145-171: the current GET handler can return 404 for unauthenticated users
(via getCurrentUser()) and performs an automatic sync with Keycloak on every GET
when Slack is enabled; change the flow to first detect missing authentication
and return 401 Unauthorized instead of 404, resolve the JwtAuthenticationToken
up-front and only use keycloakUserId if token is present; remove or disable the
automatic sync on a plain GET (call
slackConnectionService.getConnectionStatus(...) for idempotent reads) and move
sync logic to a separate POST/PUT endpoint or make it opt-in via a query
parameter so Keycloak is not contacted on every GET.

Comment on lines +1 to +41
package de.tum.in.www1.hephaestus.leaderboard.tasks;

import static org.junit.jupiter.api.Assertions.*;

import com.slack.api.model.User;
import de.tum.in.www1.hephaestus.gitprovider.user.UserInfoDTO;
import de.tum.in.www1.hephaestus.leaderboard.LeaderboardEntryDTO;
import de.tum.in.www1.hephaestus.testconfig.BaseUnitTest;
import java.util.List;
import java.util.function.Function;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;

/**
* Unit tests for Slack user matching logic in SlackWeeklyLeaderboardTask.
*
* <p>
* The matching follows industry best practices for cross-platform identity
* resolution:
* <ol>
* <li><b>Linked Slack User ID</b>: Highest priority - users explicitly link
* their Slack account</li>
* <li><b>Email matching</b>: Fallback for users who haven't linked their
* account</li>
* </ol>
*
* <p>
* Fuzzy name matching is intentionally NOT supported - names are not reliable
* identifiers.
*/
class SlackWeeklyLeaderboardTaskMatchingTest extends BaseUnitTest {

private SlackWeeklyLeaderboardTask task;

@BeforeEach
void setUp() {
task = new SlackWeeklyLeaderboardTask();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check if BaseUnitTest has @Tag annotation
rg -n "@Tag|class BaseUnitTest" server/application-server/src/test/java -A 5 -B 5 | grep -A 10 -B 10 "BaseUnitTest"

Repository: ls1intum/Hephaestus

Length of output: 3773


🏁 Script executed:

# Check the complete SlackWeeklyLeaderboardTaskMatchingTest file for @Tag annotation
cat server/application-server/src/test/java/de/tum/in/www1/hephaestus/leaderboard/tasks/SlackWeeklyLeaderboardTaskMatchingTest.java

Repository: ls1intum/Hephaestus

Length of output: 10143


🏁 Script executed:

# Also find where BaseUnitTest is located
fd -name "BaseUnitTest.java" server/application-server/src/test/java

Repository: ls1intum/Hephaestus

Length of output: 293


Add explicit @Tag("unit") annotation to the test class.

JUnit 5 does not inherit tag annotations from parent classes. Although BaseUnitTest has @Tag("unit"), the test class must declare it explicitly for CI to properly categorize and pick up the test. Add @Tag("unit") above the class declaration.

🤖 Prompt for AI Agents
In
server/application-server/src/test/java/de/tum/in/www1/hephaestus/leaderboard/tasks/SlackWeeklyLeaderboardTaskMatchingTest.java
around lines 1 to 41, the test class relies on BaseUnitTest's @Tag("unit") but
JUnit 5 does not inherit tags; add an explicit @Tag("unit") annotation directly
above the class declaration so CI recognizes and categorizes these tests as unit
tests.

Comment on lines +56 to +105
@Test
@DisplayName("should generate valid Slack OAuth URL with signed state")
void shouldGenerateValidUrl() {
String slug = "test-workspace";
String redirectUri = "https://example.com/callback";

String url = service.generateInstallUrl(slug, redirectUri);

// Verify URL structure
assertThat(url).startsWith("https://slack.com/oauth/v2/authorize?");
assertThat(url).contains("client_id=" + CLIENT_ID);
assertThat(url).contains("scope=chat%3Awrite%2Cchannels%3Aread%2Cusers%3Aread%2Cteam%3Aread");

// Verify redirect_uri is URL encoded
assertThat(url).contains("redirect_uri=https%3A%2F%2Fexample.com%2Fcallback");

// Verify state is present and URL encoded
assertThat(url).contains("state=");

// Extract and verify state contains the workspace slug (double base64 encoded)
String state = extractAndDecodeStateFromUrl(url);
assertThat(state).isNotEmpty();

String decoded = new String(Base64.getUrlDecoder().decode(state), StandardCharsets.UTF_8);
String[] parts = decoded.split("\\|");
assertThat(parts).hasSize(3); // encodedSlug|timestamp|signature

// Decode the inner slug
String decodedSlug = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8);
assertThat(decodedSlug).isEqualTo(slug);
}

@Test
@DisplayName("should handle workspace slugs with special characters")
void shouldHandleSpecialCharacters() {
String slug = "my|workspace|with|pipes";
String redirectUri = "https://example.com/callback";

String url = service.generateInstallUrl(slug, redirectUri);

// Extract and verify the slug survives the round-trip
String state = extractAndDecodeStateFromUrl(url);
String decoded = new String(Base64.getUrlDecoder().decode(state), StandardCharsets.UTF_8);
String[] parts = decoded.split("\\|");

// The first part is the base64-encoded slug
String decodedSlug = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8);
assertThat(decodedSlug).isEqualTo(slug);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Tests mix multiple responsibilities and are brittle to OAuth scope changes.

shouldGenerateValidUrl asserts URL prefix, client_id, scopes, redirect encoding, and state format/slug round-trip all at once; this makes failures noisy and tightly couples tests to scope ordering/encoding. Consider splitting into focused tests (URL contains required params vs state round-trip correctness), per test guidelines (single objective / minimal assertions). As per coding guidelines, ...

🤖 Prompt for AI Agents
In
server/application-server/src/test/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationServiceTest.java
around lines 56-105, the test shouldGenerateValidUrl mixes many concerns (URL
structure, exact scope string, redirect encoding, and state round-trip) making
it brittle; split it into focused tests: one test that only asserts the presence
of required OAuth parameters (startsWith slack URL, contains client_id,
redirect_uri correctly encoded, and that each required scope is present without
relying on exact ordering or full encoded scope string) and a second test that
only verifies the state round-trip (state exists, decode base64 twice, validate
parts count and that the inner slug decodes to the original, including a
separate test for special characters); remove assertions that tie to scope
ordering/encoding and move scope checks to individual contains assertions (or
URL-decoded contains) so failures are narrow and stable.

Comment on lines +252 to +279
@Test
@DisplayName("should reject expired state token (security: 10-minute expiry)")
void shouldRejectExpiredState() {
// Create a state with a timestamp from 11 minutes ago (beyond the 10-minute
// expiry)
String slug = "test-workspace";
String encodedSlug = Base64.getUrlEncoder().withoutPadding()
.encodeToString(slug.getBytes(StandardCharsets.UTF_8));
long expiredTimestamp = java.time.Instant.now().getEpochSecond() - 660; // 11 minutes ago

// We need to create a validly-signed but expired state
// This is tricky because we don't have access to the sign() method
// Instead, we test that the service correctly identifies timestamps in the past

// For this test, we'll create a properly formatted state with an old timestamp
// and wrong signature (since we can't compute the real signature without the
// secret)
String payload = encodedSlug + "|" + expiredTimestamp + "|fake-signature";
String expiredState = Base64.getUrlEncoder()
.encodeToString(payload.getBytes(StandardCharsets.UTF_8));

// This will fail on signature first, which is fine - signature check happens
// before expiry
assertThatThrownBy(() -> service.completeInstallation("code", expiredState, "uri"))
.isInstanceOf(IllegalArgumentException.class);
// Note: In practice, signature validation happens before expiry check,
// so this tests the overall validation pipeline
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

“Expired state” test does not validate expiry behavior (fails earlier on signature).

shouldRejectExpiredState constructs an expired timestamp but intentionally uses a fake signature and then accepts any IllegalArgumentException, so the expiry check could be broken without this test failing. To actually test the 10‑minute expiry branch, generate a validly signed payload with an old timestamp (e.g., by extracting the signing logic into a package-private collaborator or injecting a clock/state-signer that the test can control). As per coding guidelines, ...

🤖 Prompt for AI Agents
In
server/application-server/src/test/java/de/tum/in/www1/hephaestus/workspace/WorkspaceSlackIntegrationServiceTest.java
around lines 252 to 279, the test constructs an expired state but uses a fake
signature and asserts any IllegalArgumentException, so it cannot verify the
specific 10-minute expiry branch; change the test to produce a legitimately
signed state with an old timestamp and assert the expiry-specific failure:
refactor or expose the real signing behavior (make the signer package-private or
inject a StateSigner or Clock into the service), use that signer (or a test
signer) to sign the slug|oldTimestamp payload, or inject a controllable Clock to
create an expired timestamp deterministically, then call completeInstallation
and assert the exact exception/message that indicates expiry (not a generic
IllegalArgumentException).

Comment on lines +24 to 26
/** Status from Slack OAuth callback: 'success' | 'error' | 'cancelled' | 'invalid' | undefined */
slackStatus?: string | null;
onAddRepository: (nameWithOwner: string) => void;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Tighten slackStatus to the validated union to make the switch exhaustive.

Instead of string | null, use "success" | "error" | "cancelled" | "invalid" | undefined (ideally inferred from the zod schema) and add a default branch to avoid silently ignoring unexpected values.

Also applies to: 54-79

🤖 Prompt for AI Agents
In webapp/src/components/admin/AdminSettingsPage.tsx around lines 24-26 (and
similarly update usages around lines 54-79), slackStatus is currently typed as
string | null; tighten it to the validated union "success" | "error" |
"cancelled" | "invalid" | undefined (preferably by importing/inferencing the
union from the zod schema or a shared type) and update any switch/conditional
handling to include a default branch that catches unexpected values (e.g., treat
as undefined or log/handle as unknown). Ensure all props, state, and handlers
use the new union type so the switch is exhaustive and update related type
annotations in the 54-79 range accordingly.

Comment on lines +20 to +22
const handleConnect = () => {
window.location.href = `/api/workspaces/${workspaceSlug}/slack/install`;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Encode workspaceSlug when building the install URL.

Today: /api/workspaces/${workspaceSlug}/slack/install. Safer: /api/workspaces/${encodeURIComponent(workspaceSlug)}/slack/install to avoid path issues if slugs ever contain reserved characters.

🤖 Prompt for AI Agents
In webapp/src/components/admin/AdminSlackSettings.tsx around lines 20 to 22, the
install URL is built using the raw workspaceSlug which can break if the slug
contains reserved URL/path characters; update the handler to encode the slug
with encodeURIComponent when constructing the path (i.e., use
encodeURIComponent(workspaceSlug) in the URL) so the redirect URL is safe and
correctly formed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

application-server Spring Boot server: APIs, business logic, database ci GitHub Actions, workflows, build pipeline changes dependencies Package updates, version bumps, lock file changes documentation Improvements or additions to documentation feature New feature or enhancement infrastructure Docker, containers, and deployment infrastructure size:XXL This PR changes 1000+ lines, ignoring generated files. webapp React app: UI components, routes, state management

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants