From 277269f97effc8978dae9ced82d85d652a213ddb Mon Sep 17 00:00:00 2001 From: Asher Date: Fri, 19 Jan 2024 12:17:53 -0900 Subject: [PATCH] feat: add endpoints to oauth2 provider applications These will show up when configuring the application along with the client ID and everything else. Should make it easier to configure the application, otherwise you will have to go look up the URLs in the docs (which are not yet written). Co-authored-by: Steven Masley --- coderd/apidoc/docs.go | 23 +++++++++ coderd/apidoc/swagger.json | 23 +++++++++ coderd/database/db2sdk/db2sdk.go | 17 +++++-- codersdk/oauth2.go | 12 +++++ docs/api/enterprise.md | 38 ++++++++++++--- docs/api/schemas.md | 36 +++++++++++--- enterprise/coderd/oauth2.go | 10 ++-- site/src/api/typesGenerated.ts | 8 ++++ .../EditOAuth2AppPageView.tsx | 47 ++++++++++++++----- site/src/testHelpers/entities.ts | 5 ++ 10 files changed, 187 insertions(+), 32 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 06ed3e19dfe1c..e17e2d8081180 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -9688,6 +9688,21 @@ const docTemplate = `{ } } }, + "codersdk.OAuth2AppEndpoints": { + "type": "object", + "properties": { + "authorization": { + "type": "string" + }, + "device_authorization": { + "description": "DeviceAuth is optional.", + "type": "string" + }, + "token": { + "type": "string" + } + } + }, "codersdk.OAuth2Config": { "type": "object", "properties": { @@ -9734,6 +9749,14 @@ const docTemplate = `{ "callback_url": { "type": "string" }, + "endpoints": { + "description": "Endpoints are included in the app response for easier discovery. The OAuth2\nspec does not have a defined place to find these (for comparison, OIDC has\na '/.well-known/openid-configuration' endpoint).", + "allOf": [ + { + "$ref": "#/definitions/codersdk.OAuth2AppEndpoints" + } + ] + }, "icon": { "type": "string" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 8982d4a4a781f..34b4bd36df2a3 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -8683,6 +8683,21 @@ } } }, + "codersdk.OAuth2AppEndpoints": { + "type": "object", + "properties": { + "authorization": { + "type": "string" + }, + "device_authorization": { + "description": "DeviceAuth is optional.", + "type": "string" + }, + "token": { + "type": "string" + } + } + }, "codersdk.OAuth2Config": { "type": "object", "properties": { @@ -8729,6 +8744,14 @@ "callback_url": { "type": "string" }, + "endpoints": { + "description": "Endpoints are included in the app response for easier discovery. The OAuth2\nspec does not have a defined place to find these (for comparison, OIDC has\na '/.well-known/openid-configuration' endpoint).", + "allOf": [ + { + "$ref": "#/definitions/codersdk.OAuth2AppEndpoints" + } + ] + }, "icon": { "type": "string" }, diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index c88b8d5c8a685..3d9953dd874b1 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -4,6 +4,7 @@ package db2sdk import ( "encoding/json" "fmt" + "net/url" "strconv" "strings" "time" @@ -226,19 +227,29 @@ func templateVersionParameterOptions(rawOptions json.RawMessage) ([]codersdk.Tem return options, nil } -func OAuth2ProviderApp(dbApp database.OAuth2ProviderApp) codersdk.OAuth2ProviderApp { +func OAuth2ProviderApp(accessURL *url.URL, dbApp database.OAuth2ProviderApp) codersdk.OAuth2ProviderApp { return codersdk.OAuth2ProviderApp{ ID: dbApp.ID, Name: dbApp.Name, CallbackURL: dbApp.CallbackURL, Icon: dbApp.Icon, + Endpoints: codersdk.OAuth2AppEndpoints{ + Authorization: accessURL.ResolveReference(&url.URL{ + Path: "/login/oauth2/authorize", + }).String(), + Token: accessURL.ResolveReference(&url.URL{ + Path: "/login/oauth2/tokens", + }).String(), + // We do not currently support DeviceAuth. + DeviceAuth: "", + }, } } -func OAuth2ProviderApps(dbApps []database.OAuth2ProviderApp) []codersdk.OAuth2ProviderApp { +func OAuth2ProviderApps(accessURL *url.URL, dbApps []database.OAuth2ProviderApp) []codersdk.OAuth2ProviderApp { apps := []codersdk.OAuth2ProviderApp{} for _, dbApp := range dbApps { - apps = append(apps, OAuth2ProviderApp(dbApp)) + apps = append(apps, OAuth2ProviderApp(accessURL, dbApp)) } return apps } diff --git a/codersdk/oauth2.go b/codersdk/oauth2.go index acebc9c718ac7..318743959d5dc 100644 --- a/codersdk/oauth2.go +++ b/codersdk/oauth2.go @@ -14,6 +14,18 @@ type OAuth2ProviderApp struct { Name string `json:"name"` CallbackURL string `json:"callback_url"` Icon string `json:"icon"` + + // Endpoints are included in the app response for easier discovery. The OAuth2 + // spec does not have a defined place to find these (for comparison, OIDC has + // a '/.well-known/openid-configuration' endpoint). + Endpoints OAuth2AppEndpoints `json:"endpoints"` +} + +type OAuth2AppEndpoints struct { + Authorization string `json:"authorization"` + Token string `json:"token"` + // DeviceAuth is optional. + DeviceAuth string `json:"device_authorization"` } // OAuth2ProviderApps returns the applications configured to authenticate using diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index 956bb75653dca..1ae77d4b7edbb 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -454,6 +454,11 @@ curl -X GET http://coder-server:8080/api/v2/oauth2-provider/apps \ [ { "callback_url": "string", + "endpoints": { + "authorization": "string", + "device_authorization": "string", + "token": "string" + }, "icon": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" @@ -471,13 +476,17 @@ curl -X GET http://coder-server:8080/api/v2/oauth2-provider/apps \ Status Code **200** -| Name | Type | Required | Restrictions | Description | -| ---------------- | ------------ | -------- | ------------ | ----------- | -| `[array item]` | array | false | | | -| `» callback_url` | string | false | | | -| `» icon` | string | false | | | -| `» id` | string(uuid) | false | | | -| `» name` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------------- | -------------------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `[array item]` | array | false | | | +| `» callback_url` | string | false | | | +| `» endpoints` | [codersdk.OAuth2AppEndpoints](schemas.md#codersdkoauth2appendpoints) | false | | Endpoints are included in the app response for easier discovery. The OAuth2 spec does not have a defined place to find these (for comparison, OIDC has a '/.well-known/openid-configuration' endpoint). | +| `»» authorization` | string | false | | | +| `»» device_authorization` | string | false | | Device authorization is optional. | +| `»» token` | string | false | | | +| `» icon` | string | false | | | +| `» id` | string(uuid) | false | | | +| `» name` | string | false | | | To perform this operation, you must be authenticated. [Learn more](authentication.md). @@ -518,6 +527,11 @@ curl -X POST http://coder-server:8080/api/v2/oauth2-provider/apps \ ```json { "callback_url": "string", + "endpoints": { + "authorization": "string", + "device_authorization": "string", + "token": "string" + }, "icon": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" @@ -558,6 +572,11 @@ curl -X GET http://coder-server:8080/api/v2/oauth2-provider/apps/{app} \ ```json { "callback_url": "string", + "endpoints": { + "authorization": "string", + "device_authorization": "string", + "token": "string" + }, "icon": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" @@ -610,6 +629,11 @@ curl -X PUT http://coder-server:8080/api/v2/oauth2-provider/apps/{app} \ ```json { "callback_url": "string", + "endpoints": { + "authorization": "string", + "device_authorization": "string", + "token": "string" + }, "icon": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" diff --git a/docs/api/schemas.md b/docs/api/schemas.md index a51b3bcdfd3df..3ec2e2ede886d 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -3519,6 +3519,24 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `id` | string | true | | | | `username` | string | true | | | +## codersdk.OAuth2AppEndpoints + +```json +{ + "authorization": "string", + "device_authorization": "string", + "token": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ---------------------- | ------ | -------- | ------------ | --------------------------------- | +| `authorization` | string | false | | | +| `device_authorization` | string | false | | Device authorization is optional. | +| `token` | string | false | | | + ## codersdk.OAuth2Config ```json @@ -3572,6 +3590,11 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { "callback_url": "string", + "endpoints": { + "authorization": "string", + "device_authorization": "string", + "token": "string" + }, "icon": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string" @@ -3580,12 +3603,13 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------ | -------- | ------------ | ----------- | -| `callback_url` | string | false | | | -| `icon` | string | false | | | -| `id` | string | false | | | -| `name` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | ---------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `callback_url` | string | false | | | +| `endpoints` | [codersdk.OAuth2AppEndpoints](#codersdkoauth2appendpoints) | false | | Endpoints are included in the app response for easier discovery. The OAuth2 spec does not have a defined place to find these (for comparison, OIDC has a '/.well-known/openid-configuration' endpoint). | +| `icon` | string | false | | | +| `id` | string | false | | | +| `name` | string | false | | | ## codersdk.OAuth2ProviderAppSecret diff --git a/enterprise/coderd/oauth2.go b/enterprise/coderd/oauth2.go index 3d4c822131f1e..3ebb39aaee887 100644 --- a/enterprise/coderd/oauth2.go +++ b/enterprise/coderd/oauth2.go @@ -54,7 +54,7 @@ func (api *API) oAuth2ProviderApps(rw http.ResponseWriter, r *http.Request) { httpapi.InternalServerError(rw, err) return } - httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApps(dbApps)) + httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApps(api.AccessURL, dbApps)) } // @Summary Get OAuth2 application. @@ -65,10 +65,10 @@ func (api *API) oAuth2ProviderApps(rw http.ResponseWriter, r *http.Request) { // @Param app path string true "App ID" // @Success 200 {object} codersdk.OAuth2ProviderApp // @Router /oauth2-provider/apps/{app} [get] -func (*API) oAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) { +func (api *API) oAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() app := httpmw.OAuth2ProviderApp(r) - httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApp(app)) + httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApp(api.AccessURL, app)) } // @Summary Create OAuth2 application. @@ -101,7 +101,7 @@ func (api *API) postOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) { }) return } - httpapi.Write(ctx, rw, http.StatusCreated, db2sdk.OAuth2ProviderApp(app)) + httpapi.Write(ctx, rw, http.StatusCreated, db2sdk.OAuth2ProviderApp(api.AccessURL, app)) } // @Summary Update OAuth2 application. @@ -135,7 +135,7 @@ func (api *API) putOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) { }) return } - httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApp(app)) + httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApp(api.AccessURL, app)) } // @Summary Delete OAuth2 application. diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 7127b5f72c114..48d05be9d9e73 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -656,6 +656,13 @@ export interface MinimalUser { readonly avatar_url: string; } +// From codersdk/oauth2.go +export interface OAuth2AppEndpoints { + readonly authorization: string; + readonly token: string; + readonly device_authorization: string; +} + // From codersdk/deployment.go export interface OAuth2Config { readonly github: OAuth2GithubConfig; @@ -678,6 +685,7 @@ export interface OAuth2ProviderApp { readonly name: string; readonly callback_url: string; readonly icon: string; + readonly endpoints: OAuth2AppEndpoints; } // From codersdk/oauth2.go diff --git a/site/src/pages/DeploySettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx b/site/src/pages/DeploySettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx index dc0629e10b61b..2a28ad668d340 100644 --- a/site/src/pages/DeploySettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx +++ b/site/src/pages/DeploySettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx @@ -1,4 +1,4 @@ -import { useTheme } from "@emotion/react"; +import { type Interpolation, type Theme, useTheme } from "@emotion/react"; import CopyIcon from "@mui/icons-material/FileCopyOutlined"; import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft"; import Divider from "@mui/material/Divider"; @@ -139,16 +139,28 @@ export const EditOAuth2AppPageView: FC = ({ onCancel={() => setShowDelete(false)} /> -

Client ID

- - {app.id}{" "} - - +
+
Client ID
+
+ + {app.id} + +
+
Authorization URL
+
+ + {app.endpoints.authorization}{" "} + + +
+
Token URL
+
+ + {app.endpoints.token}{" "} + + +
+
@@ -303,3 +315,16 @@ const OAuth2SecretRow: FC = ({ ); }; + +const styles = { + dataList: { + display: "grid", + gridTemplateColumns: "max-content auto", + "& > dt": { + fontWeight: "bold", + }, + "& > dd": { + marginLeft: 10, + }, + }, +} satisfies Record>; diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 591536334694c..b4f6db93d9bad 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -3371,6 +3371,11 @@ export const MockOAuth2ProviderApps: TypesGen.OAuth2ProviderApp[] = [ name: "foo", callback_url: "http://localhost:3001", icon: "/icon/github.svg", + endpoints: { + authorization: "http://localhost:3001/login/oauth2/authorize", + token: "http://localhost:3001/login/oauth2/token", + device_authorization: "", + }, }, ];