Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env-cmdrc-template
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"SWITCHER_API_KEY": "[SWITCHER_API_KEY]",
"SWITCHER_API_DOMAIN": "Switcher API",
"SWITCHER_API_ENVIRONMENT": "default",
"SWITCHER_MANAGEMENT_URL": "http://localhost:4200",

"SWITCHER_SLACK_JWT_SECRET": "[SWITCHER_SLACK_JWT_SECRET]",
"SWITCHER_GITOPS_JWT_SECRET": "[SWITCHER_GITOPS_JWT_SECRET]",
Expand Down Expand Up @@ -71,6 +72,7 @@
"SWITCHER_API_LOGGER_LEVEL": "debug",
"SWITCHER_API_ENABLE": false,
"SWITCHER_API_THROTTLE": 5000,
"SWITCHER_MANAGEMENT_URL": "http://localhost:4200",
"SWITCHER_SLACK_JWT_SECRET": "[SWITCHER_SLACK_JWT_SECRET]",
"SWITCHER_GITOPS_JWT_SECRET": "[SWITCHER_GITOPS_JWT_SECRET]",
"SWITCHER_GITOPS_URL": "http://localhost:8000"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
SWITCHER_API_LOGGER: false

- name: SonarCloud Scan
uses: sonarsource/sonarqube-scan-action@v8.2.1
uses: sonarsource/sonarqube-scan-action@v8.3.0
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
if: env.SONAR_TOKEN != ''
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sonar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ jobs:
SWITCHER_API_LOGGER: false

- name: SonarCloud Scan
uses: sonarsource/sonarqube-scan-action@v8.2.1
uses: sonarsource/sonarqube-scan-action@v8.3.0
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
if: env.SONAR_TOKEN != ''
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ Switcher API supports multiple auth providers such as email/password-based authe
3. Enable SAML authentication in Switcher Management by setting the environment variable SAML_ENABLE=true

* `service_provider` refers to Switcher API

#### Dynamic OAuth clients (e.g. MCP server) consent screen

OAuth clients created via `POST /oauth/register` (such as switcher-mcp-server) are redirected to a
browser-based consent screen hosted by Switcher Management at `/oauth/consent`, rather than directly
to this API. Set `SWITCHER_MANAGEMENT_URL` to your Switcher Management base URL so the
`authorization_endpoint` advertised at `/.well-known/oauth-authorization-server` points there
(if unset, it falls back to `SAML_REDIRECT_ENDPOINT_URL`, then to this API's own `/oauth/authorize`,
which has no consent UI).
* `web_app` refers to Switcher Management

### Running Switcher API from Docker Composer manifest file
Expand Down
1 change: 1 addition & 0 deletions config/.env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ HISTORY_ACTIVATED=true
METRICS_MAX_PAGE=50
GOOGLE_SKIP_AUTH=true
SWITCHER_API_LOGGER=true
SWITCHER_MANAGEMENT_URL=http://localhost:4200

### SAML Configuration
SAML_ENTRY_POINT=
Expand Down
99 changes: 99 additions & 0 deletions src/api-docs/paths/path-oauth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { pathParameter, queryParameter } from '../schemas/common.js';
import { commonSchemaContent } from './common.js';

export default {
'/oauth/register': {
post: {
tags: ['OAuth'],
description: 'Register a public OAuth client using dynamic client registration',
requestBody: {
content: commonSchemaContent('OAuthClientRegistrationRequest')
},
responses: {
201: {
description: 'OAuth client registered',
content: commonSchemaContent('OAuthClientRegistrationResponse')
}
}
}
},
'/oauth/authorize': {
get: {
tags: ['OAuth'],
description: 'Create an authorization code for a logged-in admin',
security: [{ bearerAuth: [] }],
parameters: [
queryParameter('response_type', 'OAuth response type', true),
queryParameter('client_id', 'OAuth client id', true),
queryParameter('redirect_uri', 'Registered redirect uri', true),
queryParameter('scope', 'Requested OAuth scope', false),
queryParameter('state', 'Client-provided state', false),
queryParameter('code_challenge', 'PKCE SHA-256 code challenge', true),
queryParameter('code_challenge_method', 'PKCE code challenge method', true),
queryParameter('consent', 'Consent flag', false, 'boolean')
],
responses: {
302: {
description: 'Redirect back to the client with authorization result'
}
}
}
},
'/oauth/token': {
post: {
tags: ['OAuth'],
description: 'Exchange an authorization code or refresh token for an access token',
requestBody: {
content: commonSchemaContent('OAuthTokenRequest')
},
responses: {
200: {
description: 'OAuth token response',
content: commonSchemaContent('OAuthTokenResponse')
}
}
}
},
'/.well-known/oauth-authorization-server': {
get: {
tags: ['OAuth'],
description: 'OAuth authorization server metadata',
responses: {
200: {
description: 'Authorization server metadata',
content: commonSchemaContent('OAuthAuthorizationServerMetadata')
}
}
}
},
'/.well-known/oauth-protected-resource': {
get: {
tags: ['OAuth'],
description: 'OAuth protected resource metadata',
responses: {
200: {
description: 'Protected resource metadata',
content: commonSchemaContent('OAuthProtectedResourceMetadata')
}
}
}
},
'/config/key/{key}': {
get: {
tags: ['Config'],
description: 'Get Config by key and domain name',
security: [{ bearerAuth: [] }],
parameters: [
pathParameter('key', 'Config key', true),
queryParameter('domain', 'Domain name', true),
queryParameter('environment', 'Environment name', false)
],
responses: {
200: {
description: 'Config',
content: commonSchemaContent('ConfigByKeyResponse')
}
}
}
}
};
209 changes: 209 additions & 0 deletions src/api-docs/schemas/oauth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { config } from './config.js';

const oauthMetadata = {
type: 'object',
properties: {
issuer: {
type: 'string'
},
authorization_endpoint: {
type: 'string'
},
token_endpoint: {
type: 'string'
},
registration_endpoint: {
type: 'string'
},
scopes_supported: {
type: 'array',
items: {
type: 'string'
}
},
response_types_supported: {
type: 'array',
items: {
type: 'string'
}
},
grant_types_supported: {
type: 'array',
items: {
type: 'string'
}
},
code_challenge_methods_supported: {
type: 'array',
items: {
type: 'string'
}
},
token_endpoint_auth_methods_supported: {
type: 'array',
items: {
type: 'string'
}
}
}
};

export default {
OAuthClientRegistrationRequest: {
type: 'object',
required: ['redirect_uris'],
properties: {
client_name: {
type: 'string'
},
redirect_uris: {
type: 'array',
items: {
type: 'string'
}
},
grant_types: {
type: 'array',
items: {
type: 'string'
}
},
response_types: {
type: 'array',
items: {
type: 'string'
}
},
token_endpoint_auth_method: {
type: 'string'
},
scope: {
type: 'string'
}
}
},
OAuthClientRegistrationResponse: {
type: 'object',
properties: {
client_id: {
type: 'string'
},
client_id_issued_at: {
type: 'integer'
},
client_name: {
type: 'string'
},
redirect_uris: {
type: 'array',
items: {
type: 'string'
}
},
grant_types: {
type: 'array',
items: {
type: 'string'
}
},
response_types: {
type: 'array',
items: {
type: 'string'
}
},
token_endpoint_auth_method: {
type: 'string'
},
scope: {
type: 'string'
}
}
},
OAuthTokenRequest: {
type: 'object',
properties: {
grant_type: {
type: 'string'
},
code: {
type: 'string'
},
redirect_uri: {
type: 'string'
},
client_id: {
type: 'string'
},
code_verifier: {
type: 'string'
},
refresh_token: {
type: 'string'
}
}
},
OAuthTokenResponse: {
type: 'object',
properties: {
access_token: {
type: 'string'
},
token_type: {
type: 'string'
},
expires_in: {
type: 'integer'
},
refresh_token: {
type: 'string'
},
scope: {
type: 'string'
}
}
},
OAuthAuthorizationServerMetadata: oauthMetadata,
OAuthProtectedResourceMetadata: {
...oauthMetadata,
properties: {
resource: {
type: 'string'
},
authorization_servers: {
type: 'array',
items: {
type: 'string'
}
},
bearer_methods_supported: {
type: 'array',
items: {
type: 'string'
}
},
...oauthMetadata.properties
}
},
ConfigByKeyResponse: {
...config({
type: 'array',
description: 'The component IDs that can use the Swichter',
items: {
type: 'string'
}
}),
properties: {
...config({
type: 'array',
description: 'The component IDs that can use the Swichter',
items: {
type: 'string'
}
}).properties,
enabled: {
type: 'boolean'
}
}
}
};
Loading
Loading