Add connections API over identity providers#3536
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds a new OAuth2-secured Connections API specification, backend route registration, shared connection service and mapping helpers, vendor-specific request/response handling for Google, GitHub, and OIDC, and tests covering the new flows. ChangesConnections API
Sequence Diagram(s)sequenceDiagram
participant Client
participant http.ServeMux
participant connection.handler
participant connection.service
participant idpService
Client->>http.ServeMux: POST /connections/google
http.ServeMux->>connection.handler: handleGoogleCreate
connection.handler->>connection.service: createConnection(googleToIDPDTO)
connection.service->>idpService: CreateIdentityProvider
idpService-->>connection.service: created IDPDTO
connection.service-->>connection.handler: created response DTO
connection.handler-->>Client: 201 Created
Client->>http.ServeMux: PUT /connections/google/{id}
http.ServeMux->>connection.handler: handleGoogleUpdate
connection.handler->>connection.service: updateConnection(googleToIDPDTO)
connection.service->>idpService: GetIdentityProvider
connection.service->>connection.service: mergeStoredSecrets(masked secret)
connection.service->>idpService: UpdateIdentityProvider
idpService-->>connection.service: updated IDPDTO
connection.service-->>connection.handler: updated response DTO
connection.handler-->>Client: 200 OK
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Bundle ReportBundle size has no change ✅ Affected Assets, Files, and Routes:view changes for bundle: console-esmAssets Changed:
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
api/connections.yaml (1)
306-319: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueInconsistent
typecasing across the contract.
ConnectionTypeSummary.typeenumerates lowercase values (github,oidc), while the per-vendor resource responses exampletypeas uppercase (GITHUB,OIDC). Consumers/SDKs reading both surfaces will see two different casings for the same concept. Consider normalizing to one representation (or documenting that one is the connection-type identifier and the other is the underlying IdP type).Also applies to: 344-344
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/connections.yaml` around lines 306 - 319, Normalize the `type` casing across the API contract by making `ConnectionTypeSummary.type` and the per-vendor resource response examples/schemas use the same representation. Update the relevant OpenAPI definitions in `api/connections.yaml` around `ConnectionTypeSummary` and the vendor response schema(s) so `type` consistently uses either lowercase or uppercase, and if both forms must remain, clearly distinguish them in the schema names/descriptions to avoid ambiguity for SDK consumers.backend/internal/connection/mapping.go (1)
104-110: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueOptional: trim whitespace when splitting scopes.
splitScopessplits on,without trimming, so a stored value likeopenid, emailyields a scope with a leading space. Low impact sincejoinScopeswrites without spaces, but defensive trimming guards against externally-seeded properties.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/internal/connection/mapping.go` around lines 104 - 110, The splitScopes helper currently returns raw comma-separated entries, so values like “openid, email” keep leading spaces. Update splitScopes to trim whitespace from each scope after strings.Split while keeping the empty-input behavior, and use the existing splitScopes symbol to make the parsing defensive against externally seeded values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@api/connections.yaml`:
- Around line 1-426: The `/connections` API change is missing the required docs
updates, including the new REST routes, vendor-specific CRUD behavior, secret
masking with “******”, and 409 duplicate-name handling. Update the relevant API
reference in docs/content/apis.mdx to cover the connection endpoints and the
ConnectionListResponse, GoogleConnectionRequest/Response,
GitHubConnectionRequest/Response, OIDCConnectionRequest/Response, Error, and
I18nMessage shapes. Also add or update a guide under docs/content/ or
docs/content/guides/ explaining the new connection resource model and how it
maps to underlying identity providers, and update any SDK docs under
docs/content/sdks/ if the system scope or client-facing schemas are exposed
there.
In `@backend/internal/connection/listing.go`:
- Around line 43-45: Add the missing documentation for the new Connections API
surfaced by handleListConnections and the related /connections/{vendor} routes:
update docs/content/apis.mdx to describe the GET /connections endpoint, its
summary fields (configured and instanceCount), the per-vendor CRUD routes, and
the required authentication. Also add a new guide under docs/content/guides/
explaining the Connections feature and how to use it.
---
Nitpick comments:
In `@api/connections.yaml`:
- Around line 306-319: Normalize the `type` casing across the API contract by
making `ConnectionTypeSummary.type` and the per-vendor resource response
examples/schemas use the same representation. Update the relevant OpenAPI
definitions in `api/connections.yaml` around `ConnectionTypeSummary` and the
vendor response schema(s) so `type` consistently uses either lowercase or
uppercase, and if both forms must remain, clearly distinguish them in the schema
names/descriptions to avoid ambiguity for SDK consumers.
In `@backend/internal/connection/mapping.go`:
- Around line 104-110: The splitScopes helper currently returns raw
comma-separated entries, so values like “openid, email” keep leading spaces.
Update splitScopes to trim whitespace from each scope after strings.Split while
keeping the empty-input behavior, and use the existing splitScopes symbol to
make the parsing defensive against externally seeded values.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 29731724-19fe-425e-a765-e898551c313c
📒 Files selected for processing (12)
api/connections.yamlbackend/cmd/server/servicemanager.gobackend/internal/connection/connection_test.gobackend/internal/connection/github.gobackend/internal/connection/google.gobackend/internal/connection/handler.gobackend/internal/connection/init.gobackend/internal/connection/listing.gobackend/internal/connection/mapping.gobackend/internal/connection/models.gobackend/internal/connection/oidc.gobackend/internal/connection/service.go
| openapi: 3.0.3 | ||
|
|
||
| info: | ||
| title: Connections API | ||
| description: >- | ||
| Configure the external services ThunderID connects to. This is a thin layer over the | ||
| identity-provider service: each connection type has a strict, vendor-specific schema and | ||
| is delegated to the underlying provider, so a configured connection is a real identity | ||
| provider. Multiple instances per type are supported. | ||
| version: "1.0" | ||
| license: | ||
| name: Apache 2.0 | ||
| url: https://www.apache.org/licenses/LICENSE-2.0.html | ||
|
|
||
| servers: | ||
| - url: https://{host}:{port} | ||
| variables: | ||
| host: | ||
| default: "localhost" | ||
| port: | ||
| default: "8090" | ||
|
|
||
| tags: | ||
| - name: Connections | ||
| description: Discover available connection types and configure their instances. | ||
|
|
||
| security: | ||
| - OAuth2: [system] | ||
|
|
||
| paths: | ||
| /connections: | ||
| get: | ||
| tags: [Connections] | ||
| summary: List available connection types | ||
| description: Returns every connection type with its configured status and instance count. | ||
| responses: | ||
| "200": | ||
| description: The available connection types | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: '#/components/schemas/ConnectionListResponse' | ||
| example: | ||
| connections: | ||
| - type: "google" | ||
| configured: true | ||
| instanceCount: 1 | ||
| - type: "github" | ||
| configured: false | ||
| instanceCount: 0 | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
|
|
||
| /connections/google: | ||
| get: | ||
| tags: [Connections] | ||
| summary: List configured Google connections | ||
| responses: | ||
| "200": { $ref: '#/components/responses/InstanceList' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
| post: | ||
| tags: [Connections] | ||
| summary: Create a Google connection | ||
| requestBody: | ||
| required: true | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/GoogleConnectionRequest' } | ||
| responses: | ||
| "201": | ||
| description: Connection created | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/GoogleConnectionResponse' } | ||
| "400": { $ref: '#/components/responses/BadRequest' } | ||
| "409": { $ref: '#/components/responses/Conflict' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
| /connections/google/{id}: | ||
| parameters: | ||
| - { $ref: '#/components/parameters/ConnectionID' } | ||
| get: | ||
| tags: [Connections] | ||
| summary: Get a Google connection | ||
| responses: | ||
| "200": | ||
| description: Connection details (secrets masked) | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/GoogleConnectionResponse' } | ||
| "404": { $ref: '#/components/responses/NotFound' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
| put: | ||
| tags: [Connections] | ||
| summary: Update a Google connection | ||
| description: A secret sent as "******" keeps the stored value. | ||
| requestBody: | ||
| required: true | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/GoogleConnectionRequest' } | ||
| responses: | ||
| "200": | ||
| description: Connection updated | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/GoogleConnectionResponse' } | ||
| "400": { $ref: '#/components/responses/BadRequest' } | ||
| "404": { $ref: '#/components/responses/NotFound' } | ||
| "409": { $ref: '#/components/responses/Conflict' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
| delete: | ||
| tags: [Connections] | ||
| summary: Delete a Google connection | ||
| responses: | ||
| "204": { description: Connection deleted } | ||
| "404": { $ref: '#/components/responses/NotFound' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
|
|
||
| /connections/github: | ||
| get: | ||
| tags: [Connections] | ||
| summary: List configured GitHub connections | ||
| responses: | ||
| "200": { $ref: '#/components/responses/InstanceList' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
| post: | ||
| tags: [Connections] | ||
| summary: Create a GitHub connection | ||
| requestBody: | ||
| required: true | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/GitHubConnectionRequest' } | ||
| responses: | ||
| "201": | ||
| description: Connection created | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/GitHubConnectionResponse' } | ||
| "400": { $ref: '#/components/responses/BadRequest' } | ||
| "409": { $ref: '#/components/responses/Conflict' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
| /connections/github/{id}: | ||
| parameters: | ||
| - { $ref: '#/components/parameters/ConnectionID' } | ||
| get: | ||
| tags: [Connections] | ||
| summary: Get a GitHub connection | ||
| responses: | ||
| "200": | ||
| description: Connection details (secrets masked) | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/GitHubConnectionResponse' } | ||
| "404": { $ref: '#/components/responses/NotFound' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
| put: | ||
| tags: [Connections] | ||
| summary: Update a GitHub connection | ||
| requestBody: | ||
| required: true | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/GitHubConnectionRequest' } | ||
| responses: | ||
| "200": | ||
| description: Connection updated | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/GitHubConnectionResponse' } | ||
| "400": { $ref: '#/components/responses/BadRequest' } | ||
| "404": { $ref: '#/components/responses/NotFound' } | ||
| "409": { $ref: '#/components/responses/Conflict' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
| delete: | ||
| tags: [Connections] | ||
| summary: Delete a GitHub connection | ||
| responses: | ||
| "204": { description: Connection deleted } | ||
| "404": { $ref: '#/components/responses/NotFound' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
|
|
||
| /connections/oidc: | ||
| get: | ||
| tags: [Connections] | ||
| summary: List configured OIDC connections | ||
| responses: | ||
| "200": { $ref: '#/components/responses/InstanceList' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
| post: | ||
| tags: [Connections] | ||
| summary: Create an OIDC connection | ||
| requestBody: | ||
| required: true | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/OIDCConnectionRequest' } | ||
| responses: | ||
| "201": | ||
| description: Connection created | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/OIDCConnectionResponse' } | ||
| "400": { $ref: '#/components/responses/BadRequest' } | ||
| "409": { $ref: '#/components/responses/Conflict' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
| /connections/oidc/{id}: | ||
| parameters: | ||
| - { $ref: '#/components/parameters/ConnectionID' } | ||
| get: | ||
| tags: [Connections] | ||
| summary: Get an OIDC connection | ||
| responses: | ||
| "200": | ||
| description: Connection details (secrets masked) | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/OIDCConnectionResponse' } | ||
| "404": { $ref: '#/components/responses/NotFound' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
| put: | ||
| tags: [Connections] | ||
| summary: Update an OIDC connection | ||
| requestBody: | ||
| required: true | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/OIDCConnectionRequest' } | ||
| responses: | ||
| "200": | ||
| description: Connection updated | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/OIDCConnectionResponse' } | ||
| "400": { $ref: '#/components/responses/BadRequest' } | ||
| "404": { $ref: '#/components/responses/NotFound' } | ||
| "409": { $ref: '#/components/responses/Conflict' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
| delete: | ||
| tags: [Connections] | ||
| summary: Delete an OIDC connection | ||
| responses: | ||
| "204": { description: Connection deleted } | ||
| "404": { $ref: '#/components/responses/NotFound' } | ||
| "500": { $ref: '#/components/responses/InternalServerError' } | ||
|
|
||
| components: | ||
| securitySchemes: | ||
| OAuth2: | ||
| type: oauth2 | ||
| flows: | ||
| authorizationCode: | ||
| authorizationUrl: https://localhost:8090/oauth2/authorize | ||
| tokenUrl: https://localhost:8090/oauth2/token | ||
| scopes: | ||
| system: Access to system management APIs | ||
| clientCredentials: | ||
| tokenUrl: https://localhost:8090/oauth2/token | ||
| scopes: | ||
| system: Access to system management APIs | ||
|
|
||
| parameters: | ||
| ConnectionID: | ||
| in: path | ||
| name: id | ||
| required: true | ||
| schema: | ||
| type: string | ||
| format: uuid | ||
|
|
||
| responses: | ||
| InstanceList: | ||
| description: Configured connection instances | ||
| content: | ||
| application/json: | ||
| schema: | ||
| type: array | ||
| items: | ||
| $ref: '#/components/schemas/ConnectionInstanceSummary' | ||
| BadRequest: | ||
| description: Bad request | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/Error' } | ||
| NotFound: | ||
| description: Connection not found | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/Error' } | ||
| Conflict: | ||
| description: A connection with the same name already exists | ||
| content: | ||
| application/json: | ||
| schema: { $ref: '#/components/schemas/Error' } | ||
| InternalServerError: | ||
| description: Internal server error | ||
|
|
||
| schemas: | ||
| ConnectionListResponse: | ||
| type: object | ||
| required: [connections] | ||
| properties: | ||
| connections: | ||
| type: array | ||
| items: { $ref: '#/components/schemas/ConnectionTypeSummary' } | ||
|
|
||
| ConnectionTypeSummary: | ||
| type: object | ||
| required: [type, configured, instanceCount] | ||
| properties: | ||
| type: | ||
| type: string | ||
| enum: [google, github, oidc] | ||
| example: "google" | ||
| configured: | ||
| type: boolean | ||
| example: true | ||
| instanceCount: | ||
| type: integer | ||
| example: 1 | ||
|
|
||
| ConnectionInstanceSummary: | ||
| type: object | ||
| required: [id, name] | ||
| properties: | ||
| id: { type: string, format: uuid } | ||
| name: { type: string, example: "My Google" } | ||
| description: { type: string } | ||
|
|
||
| GoogleConnectionRequest: | ||
| type: object | ||
| required: [name, clientId, clientSecret, redirectUri] | ||
| properties: | ||
| name: { type: string, example: "My Google" } | ||
| clientId: { type: string } | ||
| clientSecret: { type: string, description: "Write-only. Send \"******\" to keep the stored value on update." } | ||
| redirectUri: { type: string } | ||
| scopes: { type: array, items: { type: string } } | ||
| prompt: { type: string } | ||
| GoogleConnectionResponse: | ||
| type: object | ||
| properties: | ||
| id: { type: string, format: uuid } | ||
| name: { type: string } | ||
| type: { type: string, example: "GOOGLE" } | ||
| clientId: { type: string } | ||
| clientSecret: { type: string, description: "Masked as \"******\".", example: "******" } | ||
| redirectUri: { type: string } | ||
| scopes: { type: array, items: { type: string } } | ||
| prompt: { type: string } | ||
|
|
||
| GitHubConnectionRequest: | ||
| type: object | ||
| required: [name, clientId, clientSecret, redirectUri] | ||
| properties: | ||
| name: { type: string, example: "My GitHub" } | ||
| clientId: { type: string } | ||
| clientSecret: { type: string, description: "Write-only. Send \"******\" to keep the stored value on update." } | ||
| redirectUri: { type: string } | ||
| scopes: { type: array, items: { type: string } } | ||
| prompt: { type: string } | ||
| GitHubConnectionResponse: | ||
| type: object | ||
| properties: | ||
| id: { type: string, format: uuid } | ||
| name: { type: string } | ||
| type: { type: string, example: "GITHUB" } | ||
| clientId: { type: string } | ||
| clientSecret: { type: string, example: "******" } | ||
| redirectUri: { type: string } | ||
| scopes: { type: array, items: { type: string } } | ||
| prompt: { type: string } | ||
|
|
||
| OIDCConnectionRequest: | ||
| type: object | ||
| required: [name, clientId, clientSecret, redirectUri, authorizationEndpoint, tokenEndpoint] | ||
| properties: | ||
| name: { type: string } | ||
| clientId: { type: string } | ||
| clientSecret: { type: string, description: "Write-only. Send \"******\" to keep the stored value on update." } | ||
| redirectUri: { type: string } | ||
| authorizationEndpoint: { type: string } | ||
| tokenEndpoint: { type: string } | ||
| userInfoEndpoint: { type: string } | ||
| jwksEndpoint: { type: string } | ||
| logoutEndpoint: { type: string } | ||
| issuer: { type: string } | ||
| scopes: { type: array, items: { type: string } } | ||
| prompt: { type: string } | ||
| tokenExchangeEnabled: { type: boolean } | ||
| OIDCConnectionResponse: | ||
| type: object | ||
| properties: | ||
| id: { type: string, format: uuid } | ||
| name: { type: string } | ||
| type: { type: string, example: "OIDC" } | ||
| clientId: { type: string } | ||
| clientSecret: { type: string, example: "******" } | ||
| redirectUri: { type: string } | ||
| authorizationEndpoint: { type: string } | ||
| tokenEndpoint: { type: string } | ||
| userInfoEndpoint: { type: string } | ||
| jwksEndpoint: { type: string } | ||
| logoutEndpoint: { type: string } | ||
| issuer: { type: string } | ||
| scopes: { type: array, items: { type: string } } | ||
| prompt: { type: string } | ||
| tokenExchangeEnabled: { type: boolean } | ||
|
|
||
| Error: | ||
| type: object | ||
| required: [code, message] | ||
| properties: | ||
| code: | ||
| type: string | ||
| example: "IDP-1001" | ||
| message: | ||
| $ref: '#/components/schemas/I18nMessage' | ||
| description: | ||
| $ref: '#/components/schemas/I18nMessage' | ||
|
|
||
| I18nMessage: | ||
| type: object | ||
| required: [key, defaultValue] | ||
| properties: | ||
| key: { type: string } | ||
| defaultValue: { type: string } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🔴 Documentation Required
This PR introduces user-facing changes that are not covered by documentation updates under docs/.
Please update the relevant documentation before merging.
Missing documentation:
- New
/connectionsREST API (list types + per-vendorgoogle/github/oidcCRUD): document the routes, request/response schemas, secret-masking (******) semantics, and409duplicate-name behavior in the API reference (docs/content/apis.mdx). - New connection resource concept and per-type configuration model: add or update a content/guide page describing connection types vs. underlying identity providers (
docs/content/ordocs/content/guides/). - SDK-impacting client-facing schemas/scopes (
systemscope, vendor request/response shapes): update SDK docs if applicable (docs/content/sdks/).
As per path instructions: "If ANY of the above are detected and the PR does NOT include corresponding updates under docs/ ... post a single consolidated PR-level comment."
🧰 Tools
🪛 Checkov (3.3.1)
[medium] 276-280: Ensure that arrays have a maximum number of items
(CKV_OPENAPI_21)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@api/connections.yaml` around lines 1 - 426, The `/connections` API change is
missing the required docs updates, including the new REST routes,
vendor-specific CRUD behavior, secret masking with “******”, and 409
duplicate-name handling. Update the relevant API reference in
docs/content/apis.mdx to cover the connection endpoints and the
ConnectionListResponse, GoogleConnectionRequest/Response,
GitHubConnectionRequest/Response, OIDCConnectionRequest/Response, Error, and
I18nMessage shapes. Also add or update a guide under docs/content/ or
docs/content/guides/ explaining the new connection resource model and how it
maps to underlying identity providers, and update any SDK docs under
docs/content/sdks/ if the system scope or client-facing schemas are exposed
there.
Source: Path instructions
| // handleListConnections handles GET /connections, returning the available connection types | ||
| // with their configured status and instance count. | ||
| func (h *handler) handleListConnections(w http.ResponseWriter, r *http.Request) { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check whether docs reference the new /connections API.
fd -t f . docs 2>/dev/null | head -50
echo "--- references to /connections in docs ---"
rg -n "connections" docs/ 2>/dev/null | rg -i "connection" | head -50Repository: thunder-id/thunderid
Length of output: 6351
🔴 Documentation Required
This PR introduces user-facing changes that are not covered by documentation updates under docs/.
Please update the relevant documentation before merging.
Missing documentation:
- New
GET /connectionsAPI endpoint (and the per-vendor/connections/{vendor}CRUD routes wired alongside it): document the route, its summary response (configured,instanceCountper connection type), and authentication requirements in the API reference atdocs/content/apis.mdx, plus a guide page underdocs/content/guides/describing the Connections feature.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/internal/connection/listing.go` around lines 43 - 45, Add the missing
documentation for the new Connections API surfaced by handleListConnections and
the related /connections/{vendor} routes: update docs/content/apis.mdx to
describe the GET /connections endpoint, its summary fields (configured and
instanceCount), the per-vendor CRUD routes, and the required authentication.
Also add a new guide under docs/content/guides/ explaining the Connections
feature and how to use it.
Source: Path instructions
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
cb26d93 to
983280c
Compare
Add a /connections HTTP layer that presents external login providers as typed, per-vendor connection resources (Google, GitHub, Custom OIDC) and delegates to the identity-provider service. It owns no storage, so a configured connection is a real identity provider and federated login keeps working. Includes GET /connections (available types with configured status and instance count), per-type CRUD with strict per-vendor request/response schemas, secret masking on read with masked-secret preservation on update, per-type integrity checks, multi-instance support, and the OpenAPI spec. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
983280c to
0b5cd84
Compare
|
Thanks @coderabbitai. Addressed:
Deferred / skipped:
|
|
Tip For best results, initiate chat on the files or code changes.
Deferring the docs work to the documentation sub-task of 🐇👍 ✏️ Learnings added
|
Purpose
Adds a new
/connectionsbackend API — the foundation of the console Connections area (issue #1157). It presents the external login providers ThunderID connects to as typed, per-vendor connection resources (Google, GitHub, Custom OIDC) and is a thin layer over the existing identity-provider service: it owns no storage, so a configured connection is a real identity provider and federated login keeps working.This is the first backend slice (IdP-backed types). Message-sender connection types (Twilio/Vonage/Custom), the frontend, and docs follow in subsequent PRs.
Endpoints
GET /connections— available connection types with configured status and instance count (structural data only; presentation/branding such as logo, display name and categories lives in the frontend).google/github/oidc:GET /connections/{type}— list configured instances (typed; secrets masked)POST /connections/{type}— create (strict per-vendor body)GET /connections/{type}/{id}— instance detail (edit view)PUT /connections/{type}/{id}— update (a secret sent as******keeps the stored value)DELETE /connections/{type}/{id}— deleteExample:
{ "connections": [ { "type": "google", "configured": true, "instanceCount": 1 }, { "type": "github", "configured": false, "instanceCount": 0 }, { "type": "oidc", "configured": false, "instanceCount": 0 } ] }Approach
backend/internal/connection/package. Each vendor file (google.go,github.go,oidc.go) defines its own strict typed request/response structs andtoDTO/fromDTOmappers; thin handler methods delegate the request plumbing to shared generic helpers (createConnection/getConnection/updateConnection). Type-only operations (list, delete) are shared.{name,value,isSecret}identity-provider property model and delegates toidp.IDPServiceInterface— no new store, no executor wiring. Wired inservicemanager.goviaconnection.Initialize(mux, idpService).******on read; on update, a field echoed back as******is replaced with the stored value before delegating (the connection layer does this — the underlying IdP API does not).GET/PUT/DELETE /connections/{type}/{id}404 if the stored provider's type doesn't match the path, so a vendor endpoint can only act on its own instances.api/connections.yamlwith strict per-vendor schemas.Related Issues
Related PRs
Checklist
breaking changelabel added.Security checks
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes