Skip to content

Commit f08e92a

Browse files
committed
feat(all): add OAuth re-authentication for MCP installations
Allow users to recover from expired OAuth tokens by re-authenticating existing installations without reinstalling. Backend: - Add database migration for installation_id field in oauthPendingFlows table - Add POST /teams/:teamId/mcp/installations/:installationId/reauth endpoint - Modify OAuth callback to detect re-auth flows and update tokens in-place - Fix non-existent permission check (use mcp.installations.view) Frontend: - Add re-authenticate button on installation detail page (requires_reauth status) - Add OAuth popup flow with postMessage handling and status updates - Add McpInstallationService.startReAuth() method - Add i18n translations for re-auth UI - Fix button visibility for all team members (OAuth is per-user) - Fix props usage for team/installation IDs When OAuth tokens fail to refresh, users can now click "Re-authenticate" to restart OAuth flow and update credentials without losing their installation configuration.
1 parent 69ace9d commit f08e92a

File tree

13 files changed

+7396
-9
lines changed

13 files changed

+7396
-9
lines changed

services/backend/api-spec.json

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27861,6 +27861,215 @@
2786127861
}
2786227862
}
2786327863
},
27864+
"/api/teams/{teamId}/mcp/installations/{installationId}/reauth": {
27865+
"post": {
27866+
"summary": "Re-authenticate MCP server installation",
27867+
"tags": [
27868+
"MCP Installations",
27869+
"OAuth"
27870+
],
27871+
"description": "Initiates OAuth re-authorization for existing installation with expired or invalid tokens. Supports both cookie-based authentication (for web users) and OAuth2 Bearer token authentication (for CLI users). Requires mcp:read scope for OAuth2 access.",
27872+
"parameters": [
27873+
{
27874+
"schema": {
27875+
"type": "string",
27876+
"minLength": 1
27877+
},
27878+
"in": "path",
27879+
"name": "teamId",
27880+
"required": true,
27881+
"description": "Team ID that owns the installation"
27882+
},
27883+
{
27884+
"schema": {
27885+
"type": "string",
27886+
"minLength": 1
27887+
},
27888+
"in": "path",
27889+
"name": "installationId",
27890+
"required": true,
27891+
"description": "Installation ID"
27892+
}
27893+
],
27894+
"security": [
27895+
{
27896+
"cookieAuth": []
27897+
},
27898+
{
27899+
"bearerAuth": []
27900+
}
27901+
],
27902+
"responses": {
27903+
"200": {
27904+
"description": "Re-authentication OAuth authorization URL generated successfully",
27905+
"content": {
27906+
"application/json": {
27907+
"schema": {
27908+
"type": "object",
27909+
"properties": {
27910+
"flow_id": {
27911+
"type": "string",
27912+
"description": "Unique flow ID for the pending OAuth flow"
27913+
},
27914+
"authorization_url": {
27915+
"type": "string",
27916+
"format": "uri",
27917+
"description": "OAuth authorization URL to redirect user to for authentication"
27918+
},
27919+
"requires_authorization": {
27920+
"type": "boolean",
27921+
"description": "Indicates that OAuth authorization is required (always true for this endpoint)"
27922+
},
27923+
"expires_at": {
27924+
"type": "string",
27925+
"format": "date-time",
27926+
"description": "ISO 8601 timestamp when the OAuth state expires"
27927+
}
27928+
},
27929+
"required": [
27930+
"flow_id",
27931+
"authorization_url",
27932+
"requires_authorization",
27933+
"expires_at"
27934+
],
27935+
"description": "Re-authentication OAuth authorization URL generated successfully"
27936+
}
27937+
}
27938+
}
27939+
},
27940+
"400": {
27941+
"description": "Installation does not require re-authorization or does not use OAuth",
27942+
"content": {
27943+
"application/json": {
27944+
"schema": {
27945+
"type": "object",
27946+
"properties": {
27947+
"success": {
27948+
"type": "boolean",
27949+
"default": false,
27950+
"description": "Indicates the operation failed"
27951+
},
27952+
"error": {
27953+
"type": "string",
27954+
"description": "Error message describing what went wrong"
27955+
}
27956+
},
27957+
"required": [
27958+
"success",
27959+
"error"
27960+
],
27961+
"description": "Installation does not require re-authorization or does not use OAuth"
27962+
}
27963+
}
27964+
}
27965+
},
27966+
"401": {
27967+
"description": "Unauthorized - Authentication required or invalid token",
27968+
"content": {
27969+
"application/json": {
27970+
"schema": {
27971+
"type": "object",
27972+
"properties": {
27973+
"success": {
27974+
"type": "boolean",
27975+
"default": false,
27976+
"description": "Indicates the operation failed"
27977+
},
27978+
"error": {
27979+
"type": "string",
27980+
"description": "Error message describing what went wrong"
27981+
}
27982+
},
27983+
"required": [
27984+
"success",
27985+
"error"
27986+
],
27987+
"description": "Unauthorized - Authentication required or invalid token"
27988+
}
27989+
}
27990+
}
27991+
},
27992+
"403": {
27993+
"description": "Forbidden - Insufficient permissions or scope",
27994+
"content": {
27995+
"application/json": {
27996+
"schema": {
27997+
"type": "object",
27998+
"properties": {
27999+
"success": {
28000+
"type": "boolean",
28001+
"default": false,
28002+
"description": "Indicates the operation failed"
28003+
},
28004+
"error": {
28005+
"type": "string",
28006+
"description": "Error message describing what went wrong"
28007+
}
28008+
},
28009+
"required": [
28010+
"success",
28011+
"error"
28012+
],
28013+
"description": "Forbidden - Insufficient permissions or scope"
28014+
}
28015+
}
28016+
}
28017+
},
28018+
"404": {
28019+
"description": "Installation not found or does not belong to team",
28020+
"content": {
28021+
"application/json": {
28022+
"schema": {
28023+
"type": "object",
28024+
"properties": {
28025+
"success": {
28026+
"type": "boolean",
28027+
"default": false,
28028+
"description": "Indicates the operation failed"
28029+
},
28030+
"error": {
28031+
"type": "string",
28032+
"description": "Error message describing what went wrong"
28033+
}
28034+
},
28035+
"required": [
28036+
"success",
28037+
"error"
28038+
],
28039+
"description": "Installation not found or does not belong to team"
28040+
}
28041+
}
28042+
}
28043+
},
28044+
"500": {
28045+
"description": "Internal Server Error",
28046+
"content": {
28047+
"application/json": {
28048+
"schema": {
28049+
"type": "object",
28050+
"properties": {
28051+
"success": {
28052+
"type": "boolean",
28053+
"default": false,
28054+
"description": "Indicates the operation failed"
28055+
},
28056+
"error": {
28057+
"type": "string",
28058+
"description": "Error message describing what went wrong"
28059+
}
28060+
},
28061+
"required": [
28062+
"success",
28063+
"error"
28064+
],
28065+
"description": "Internal Server Error"
28066+
}
28067+
}
28068+
}
28069+
}
28070+
}
28071+
}
28072+
},
2786428073
"/api/teams/{teamId}/mcp/oauth/callback/{flowId}": {
2786528074
"get": {
2786628075
"tags": [

services/backend/api-spec.yaml

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19751,6 +19751,154 @@ paths:
1975119751
- success
1975219752
- error
1975319753
description: Internal Server Error
19754+
/api/teams/{teamId}/mcp/installations/{installationId}/reauth:
19755+
post:
19756+
summary: Re-authenticate MCP server installation
19757+
tags:
19758+
- MCP Installations
19759+
- OAuth
19760+
description: Initiates OAuth re-authorization for existing installation with
19761+
expired or invalid tokens. Supports both cookie-based authentication
19762+
(for web users) and OAuth2 Bearer token authentication (for CLI users).
19763+
Requires mcp:read scope for OAuth2 access.
19764+
parameters:
19765+
- schema:
19766+
type: string
19767+
minLength: 1
19768+
in: path
19769+
name: teamId
19770+
required: true
19771+
description: Team ID that owns the installation
19772+
- schema:
19773+
type: string
19774+
minLength: 1
19775+
in: path
19776+
name: installationId
19777+
required: true
19778+
description: Installation ID
19779+
security:
19780+
- cookieAuth: []
19781+
- bearerAuth: []
19782+
responses:
19783+
"200":
19784+
description: Re-authentication OAuth authorization URL generated successfully
19785+
content:
19786+
application/json:
19787+
schema:
19788+
type: object
19789+
properties:
19790+
flow_id:
19791+
type: string
19792+
description: Unique flow ID for the pending OAuth flow
19793+
authorization_url:
19794+
type: string
19795+
format: uri
19796+
description: OAuth authorization URL to redirect user to for authentication
19797+
requires_authorization:
19798+
type: boolean
19799+
description: Indicates that OAuth authorization is required (always true for
19800+
this endpoint)
19801+
expires_at:
19802+
type: string
19803+
format: date-time
19804+
description: ISO 8601 timestamp when the OAuth state expires
19805+
required:
19806+
- flow_id
19807+
- authorization_url
19808+
- requires_authorization
19809+
- expires_at
19810+
description: Re-authentication OAuth authorization URL generated successfully
19811+
"400":
19812+
description: Installation does not require re-authorization or does not use OAuth
19813+
content:
19814+
application/json:
19815+
schema:
19816+
type: object
19817+
properties:
19818+
success:
19819+
type: boolean
19820+
default: false
19821+
description: Indicates the operation failed
19822+
error:
19823+
type: string
19824+
description: Error message describing what went wrong
19825+
required:
19826+
- success
19827+
- error
19828+
description: Installation does not require re-authorization or does not use
19829+
OAuth
19830+
"401":
19831+
description: Unauthorized - Authentication required or invalid token
19832+
content:
19833+
application/json:
19834+
schema:
19835+
type: object
19836+
properties:
19837+
success:
19838+
type: boolean
19839+
default: false
19840+
description: Indicates the operation failed
19841+
error:
19842+
type: string
19843+
description: Error message describing what went wrong
19844+
required:
19845+
- success
19846+
- error
19847+
description: Unauthorized - Authentication required or invalid token
19848+
"403":
19849+
description: Forbidden - Insufficient permissions or scope
19850+
content:
19851+
application/json:
19852+
schema:
19853+
type: object
19854+
properties:
19855+
success:
19856+
type: boolean
19857+
default: false
19858+
description: Indicates the operation failed
19859+
error:
19860+
type: string
19861+
description: Error message describing what went wrong
19862+
required:
19863+
- success
19864+
- error
19865+
description: Forbidden - Insufficient permissions or scope
19866+
"404":
19867+
description: Installation not found or does not belong to team
19868+
content:
19869+
application/json:
19870+
schema:
19871+
type: object
19872+
properties:
19873+
success:
19874+
type: boolean
19875+
default: false
19876+
description: Indicates the operation failed
19877+
error:
19878+
type: string
19879+
description: Error message describing what went wrong
19880+
required:
19881+
- success
19882+
- error
19883+
description: Installation not found or does not belong to team
19884+
"500":
19885+
description: Internal Server Error
19886+
content:
19887+
application/json:
19888+
schema:
19889+
type: object
19890+
properties:
19891+
success:
19892+
type: boolean
19893+
default: false
19894+
description: Indicates the operation failed
19895+
error:
19896+
type: string
19897+
description: Error message describing what went wrong
19898+
required:
19899+
- success
19900+
- error
19901+
description: Internal Server Error
1975419902
/api/teams/{teamId}/mcp/oauth/callback/{flowId}:
1975519903
get:
1975619904
tags:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ALTER TABLE "oauthPendingFlows" ADD COLUMN "installation_id" text;--> statement-breakpoint
2+
ALTER TABLE "oauthPendingFlows" ADD CONSTRAINT "oauthPendingFlows_installation_id_mcpServerInstallations_id_fk" FOREIGN KEY ("installation_id") REFERENCES "public"."mcpServerInstallations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
3+
CREATE INDEX "oauth_pending_flows_installation_id_idx" ON "oauthPendingFlows" USING btree ("installation_id");

0 commit comments

Comments
 (0)