feat: add endpoints to list all authed external apps (#10944)

* feat: add endpoints to list all authed external apps

Listing the apps allows users to auth to external apps without going through the create workspace flow.
This commit is contained in:
Steven Masley 2023-12-05 14:03:44 -06:00 committed by GitHub
parent feaa9894a4
commit 81a3b36884
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 565 additions and 10 deletions

75
coderd/apidoc/docs.go generated
View File

@ -748,6 +748,31 @@ const docTemplate = `{
}
}
},
"/external-auth": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Git"
],
"summary": "Get user external auths",
"operationId": "get-user-external-auths",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.ExternalAuthLink"
}
}
}
}
},
"/external-auth/{externalauth}": {
"get": {
"security": [
@ -781,6 +806,33 @@ const docTemplate = `{
}
}
}
},
"delete": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Git"
],
"summary": "Delete external auth user link by ID",
"operationId": "delete-external-auth-user-link-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "Git Provider ID",
"name": "externalauth",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/external-auth/{externalauth}/device": {
@ -8852,6 +8904,29 @@ const docTemplate = `{
}
}
},
"codersdk.ExternalAuthLink": {
"type": "object",
"properties": {
"created_at": {
"type": "string",
"format": "date-time"
},
"expires": {
"type": "string",
"format": "date-time"
},
"has_refresh_token": {
"type": "boolean"
},
"provider_id": {
"type": "string"
},
"updated_at": {
"type": "string",
"format": "date-time"
}
}
},
"codersdk.ExternalAuthUser": {
"type": "object",
"properties": {

View File

@ -638,6 +638,27 @@
}
}
},
"/external-auth": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Git"],
"summary": "Get user external auths",
"operationId": "get-user-external-auths",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.ExternalAuthLink"
}
}
}
}
},
"/external-auth/{externalauth}": {
"get": {
"security": [
@ -667,6 +688,31 @@
}
}
}
},
"delete": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Git"],
"summary": "Delete external auth user link by ID",
"operationId": "delete-external-auth-user-link-by-id",
"parameters": [
{
"type": "string",
"format": "string",
"description": "Git Provider ID",
"name": "externalauth",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/external-auth/{externalauth}/device": {
@ -7944,6 +7990,29 @@
}
}
},
"codersdk.ExternalAuthLink": {
"type": "object",
"properties": {
"created_at": {
"type": "string",
"format": "date-time"
},
"expires": {
"type": "string",
"format": "date-time"
},
"has_refresh_token": {
"type": "boolean"
},
"provider_id": {
"type": "string"
},
"updated_at": {
"type": "string",
"format": "date-time"
}
}
},
"codersdk.ExternalAuthUser": {
"type": "object",
"properties": {

View File

@ -660,14 +660,21 @@ func New(options *Options) *API {
r.Get("/{fileID}", api.fileByID)
r.Post("/", api.postFile)
})
r.Route("/external-auth/{externalauth}", func(r chi.Router) {
r.Route("/external-auth", func(r chi.Router) {
r.Use(
apiKeyMiddleware,
httpmw.ExtractExternalAuthParam(options.ExternalAuthConfigs),
)
r.Get("/", api.externalAuthByID)
r.Post("/device", api.postExternalAuthDeviceByID)
r.Get("/device", api.externalAuthDeviceByID)
// Get without a specific external auth ID will return all external auths.
r.Get("/", api.listUserExternalAuths)
r.Route("/{externalauth}", func(r chi.Router) {
r.Use(
httpmw.ExtractExternalAuthParam(options.ExternalAuthConfigs),
)
r.Delete("/", api.deleteExternalAuthByID)
r.Get("/", api.externalAuthByID)
r.Post("/device", api.postExternalAuthDeviceByID)
r.Get("/device", api.externalAuthDeviceByID)
})
})
r.Route("/organizations", func(r chi.Router) {
r.Use(

View File

@ -16,6 +16,24 @@ import (
"github.com/coder/coder/v2/provisionersdk/proto"
)
func ExternalAuths(auths []database.ExternalAuthLink) []codersdk.ExternalAuthLink {
out := make([]codersdk.ExternalAuthLink, 0, len(auths))
for _, auth := range auths {
out = append(out, ExternalAuth(auth))
}
return out
}
func ExternalAuth(auth database.ExternalAuthLink) codersdk.ExternalAuthLink {
return codersdk.ExternalAuthLink{
ProviderID: auth.ProviderID,
CreatedAt: auth.CreatedAt,
UpdatedAt: auth.UpdatedAt,
HasRefreshToken: auth.OAuthRefreshToken != "",
Expires: auth.OAuthExpiry,
}
}
func WorkspaceBuildParameters(params []database.WorkspaceBuildParameter) []codersdk.WorkspaceBuildParameter {
out := make([]codersdk.WorkspaceBuildParameter, len(params))
for i, p := range params {

View File

@ -754,6 +754,13 @@ func (q *querier) DeleteCoordinator(ctx context.Context, id uuid.UUID) error {
return q.db.DeleteCoordinator(ctx, id)
}
func (q *querier) DeleteExternalAuthLink(ctx context.Context, arg database.DeleteExternalAuthLinkParams) error {
return deleteQ(q.log, q.auth, func(ctx context.Context, arg database.DeleteExternalAuthLinkParams) (database.ExternalAuthLink, error) {
//nolint:gosimple
return q.db.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{UserID: arg.UserID, ProviderID: arg.ProviderID})
}, q.db.DeleteExternalAuthLink)(ctx, arg)
}
func (q *querier) DeleteGitSSHKey(ctx context.Context, userID uuid.UUID) error {
return deleteQ(q.log, q.auth, q.db.GetGitSSHKey, q.db.DeleteGitSSHKey)(ctx, userID)
}
@ -996,10 +1003,7 @@ func (q *querier) GetExternalAuthLink(ctx context.Context, arg database.GetExter
}
func (q *querier) GetExternalAuthLinksByUserID(ctx context.Context, userID uuid.UUID) ([]database.ExternalAuthLink, error) {
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
return nil, err
}
return q.db.GetExternalAuthLinksByUserID(ctx, userID)
return fetchWithPostFilter(q.auth, q.db.GetExternalAuthLinksByUserID)(ctx, userID)
}
func (q *querier) GetFileByHashAndCreator(ctx context.Context, arg database.GetFileByHashAndCreatorParams) (database.File, error) {

View File

@ -1027,6 +1027,29 @@ func (*FakeQuerier) DeleteCoordinator(context.Context, uuid.UUID) error {
return ErrUnimplemented
}
func (q *FakeQuerier) DeleteExternalAuthLink(_ context.Context, arg database.DeleteExternalAuthLinkParams) error {
err := validateDatabaseType(arg)
if err != nil {
return err
}
q.mutex.Lock()
defer q.mutex.Unlock()
for index, key := range q.externalAuthLinks {
if key.UserID != arg.UserID {
continue
}
if key.ProviderID != arg.ProviderID {
continue
}
q.externalAuthLinks[index] = q.externalAuthLinks[len(q.externalAuthLinks)-1]
q.externalAuthLinks = q.externalAuthLinks[:len(q.externalAuthLinks)-1]
return nil
}
return sql.ErrNoRows
}
func (q *FakeQuerier) DeleteGitSSHKey(_ context.Context, userID uuid.UUID) error {
q.mutex.Lock()
defer q.mutex.Unlock()

View File

@ -176,6 +176,13 @@ func (m metricsStore) DeleteCoordinator(ctx context.Context, id uuid.UUID) error
return m.s.DeleteCoordinator(ctx, id)
}
func (m metricsStore) DeleteExternalAuthLink(ctx context.Context, arg database.DeleteExternalAuthLinkParams) error {
start := time.Now()
r0 := m.s.DeleteExternalAuthLink(ctx, arg)
m.queryLatencies.WithLabelValues("DeleteExternalAuthLink").Observe(time.Since(start).Seconds())
return r0
}
func (m metricsStore) DeleteGitSSHKey(ctx context.Context, userID uuid.UUID) error {
start := time.Now()
err := m.s.DeleteGitSSHKey(ctx, userID)

View File

@ -238,6 +238,20 @@ func (mr *MockStoreMockRecorder) DeleteCoordinator(arg0, arg1 interface{}) *gomo
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCoordinator", reflect.TypeOf((*MockStore)(nil).DeleteCoordinator), arg0, arg1)
}
// DeleteExternalAuthLink mocks base method.
func (m *MockStore) DeleteExternalAuthLink(arg0 context.Context, arg1 database.DeleteExternalAuthLinkParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteExternalAuthLink", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteExternalAuthLink indicates an expected call of DeleteExternalAuthLink.
func (mr *MockStoreMockRecorder) DeleteExternalAuthLink(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteExternalAuthLink", reflect.TypeOf((*MockStore)(nil).DeleteExternalAuthLink), arg0, arg1)
}
// DeleteGitSSHKey mocks base method.
func (m *MockStore) DeleteGitSSHKey(arg0 context.Context, arg1 uuid.UUID) error {
m.ctrl.T.Helper()

View File

@ -51,6 +51,7 @@ type sqlcQuerier interface {
DeleteAllTailnetTunnels(ctx context.Context, arg DeleteAllTailnetTunnelsParams) error
DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
DeleteCoordinator(ctx context.Context, id uuid.UUID) error
DeleteExternalAuthLink(ctx context.Context, arg DeleteExternalAuthLinkParams) error
DeleteGitSSHKey(ctx context.Context, userID uuid.UUID) error
DeleteGroupByID(ctx context.Context, id uuid.UUID) error
DeleteGroupMemberFromGroup(ctx context.Context, arg DeleteGroupMemberFromGroupParams) error

View File

@ -788,6 +788,20 @@ func (q *sqlQuerier) RevokeDBCryptKey(ctx context.Context, activeKeyDigest strin
return err
}
const deleteExternalAuthLink = `-- name: DeleteExternalAuthLink :exec
DELETE FROM external_auth_links WHERE provider_id = $1 AND user_id = $2
`
type DeleteExternalAuthLinkParams struct {
ProviderID string `db:"provider_id" json:"provider_id"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
}
func (q *sqlQuerier) DeleteExternalAuthLink(ctx context.Context, arg DeleteExternalAuthLinkParams) error {
_, err := q.db.ExecContext(ctx, deleteExternalAuthLink, arg.ProviderID, arg.UserID)
return err
}
const getExternalAuthLink = `-- name: GetExternalAuthLink :one
SELECT provider_id, user_id, created_at, updated_at, oauth_access_token, oauth_refresh_token, oauth_expiry, oauth_access_token_key_id, oauth_refresh_token_key_id, oauth_extra FROM external_auth_links WHERE provider_id = $1 AND user_id = $2
`

View File

@ -1,6 +1,9 @@
-- name: GetExternalAuthLink :one
SELECT * FROM external_auth_links WHERE provider_id = $1 AND user_id = $2;
-- name: DeleteExternalAuthLink :exec
DELETE FROM external_auth_links WHERE provider_id = $1 AND user_id = $2;
-- name: GetExternalAuthLinksByUserID :many
SELECT * FROM external_auth_links WHERE user_id = $1;

View File

@ -10,6 +10,7 @@ import (
"golang.org/x/sync/errgroup"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/db2sdk"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/externalauth"
"github.com/coder/coder/v2/coderd/httpapi"
@ -20,8 +21,8 @@ import (
// @Summary Get external auth by ID
// @ID get-external-auth-by-id
// @Security CoderSessionToken
// @Produce json
// @Tags Git
// @Produce json
// @Param externalauth path string true "Git Provider ID" format(string)
// @Success 200 {object} codersdk.ExternalAuth
// @Router /external-auth/{externalauth} [get]
@ -77,6 +78,39 @@ func (api *API) externalAuthByID(w http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, w, http.StatusOK, res)
}
// deleteExternalAuthByID only deletes the link on the Coder side, does not revoke the token on the provider side.
//
// @Summary Delete external auth user link by ID
// @ID delete-external-auth-user-link-by-id
// @Security CoderSessionToken
// @Tags Git
// @Success 200
// @Param externalauth path string true "Git Provider ID" format(string)
// @Router /external-auth/{externalauth} [delete]
func (api *API) deleteExternalAuthByID(w http.ResponseWriter, r *http.Request) {
config := httpmw.ExternalAuthParam(r)
apiKey := httpmw.APIKey(r)
ctx := r.Context()
err := api.Database.DeleteExternalAuthLink(ctx, database.DeleteExternalAuthLinkParams{
ProviderID: config.ID,
UserID: apiKey.UserID,
})
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
httpapi.ResourceNotFound(w)
return
}
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to delete external auth link.",
Detail: err.Error(),
})
return
}
httpapi.Write(ctx, w, http.StatusOK, "OK")
}
// @Summary Post external auth device by ID
// @ID post-external-auth-device-by-id
// @Security CoderSessionToken
@ -275,3 +309,64 @@ func (api *API) externalAuthCallback(externalAuthConfig *externalauth.Config) ht
http.Redirect(rw, r, redirect, http.StatusTemporaryRedirect)
}
}
// listUserExternalAuths lists all external auths available to a user and
// their auth links if they exist.
//
// @Summary Get user external auths
// @ID get-user-external-auths
// @Security CoderSessionToken
// @Produce json
// @Tags Git
// @Success 200 {object} codersdk.ExternalAuthLink
// @Router /external-auth [get]
func (api *API) listUserExternalAuths(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
key := httpmw.APIKey(r)
links, err := api.Database.GetExternalAuthLinksByUserID(ctx, key.UserID)
if err != nil {
if httpapi.Is404Error(err) {
httpapi.ResourceNotFound(rw)
return
}
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user's external auths.",
Detail: err.Error(),
})
return
}
// Note: It would be really nice if we could cfg.Validate() the links and
// return their authenticated status. To do this, we would also have to
// refresh expired tokens too. For now, I do not want to cause the excess
// traffic on this request, so the user will have to do this with a separate
// call.
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ListUserExternalAuthResponse{
Providers: ExternalAuthConfigs(api.ExternalAuthConfigs),
Links: db2sdk.ExternalAuths(links),
})
}
func ExternalAuthConfigs(auths []*externalauth.Config) []codersdk.ExternalAuthLinkProvider {
out := make([]codersdk.ExternalAuthLinkProvider, 0, len(auths))
for _, auth := range auths {
if auth == nil {
continue
}
out = append(out, ExternalAuthConfig(auth))
}
return out
}
func ExternalAuthConfig(cfg *externalauth.Config) codersdk.ExternalAuthLinkProvider {
return codersdk.ExternalAuthLinkProvider{
ID: cfg.ID,
Type: cfg.Type,
Device: cfg.DeviceAuth != nil,
DisplayName: cfg.DisplayName,
DisplayIcon: cfg.DisplayIcon,
AllowRefresh: !cfg.NoRefresh,
AllowValidate: cfg.ValidateURL != "",
}
}

View File

@ -145,6 +145,61 @@ func TestExternalAuthByID(t *testing.T) {
})
}
// TestExternalAuthManagement is for testing the apis interacting with
// external auths from the user perspective. We assume the external auth
// will always work, so we can test the managing apis like unlinking and
// listing.
func TestExternalAuthManagement(t *testing.T) {
t.Parallel()
t.Run("ListProviders", func(t *testing.T) {
t.Parallel()
const githubID = "fake-github"
const gitlabID = "fake-gitlab"
github := oidctest.NewFakeIDP(t, oidctest.WithServing())
gitlab := oidctest.NewFakeIDP(t, oidctest.WithServing())
owner := coderdtest.New(t, &coderdtest.Options{
ExternalAuthConfigs: []*externalauth.Config{
github.ExternalAuthConfig(t, githubID, nil, func(cfg *externalauth.Config) {
cfg.Type = codersdk.EnhancedExternalAuthProviderGitHub.String()
}),
gitlab.ExternalAuthConfig(t, gitlabID, nil, func(cfg *externalauth.Config) {
cfg.Type = codersdk.EnhancedExternalAuthProviderGitLab.String()
}),
},
})
ownerUser := coderdtest.CreateFirstUser(t, owner)
// Just a regular user
client, _ := coderdtest.CreateAnotherUser(t, owner, ownerUser.OrganizationID)
ctx := testutil.Context(t, testutil.WaitLong)
// List auths without any links.
list, err := client.ListExternalAuths(ctx)
require.NoError(t, err)
require.Len(t, list.Providers, 2)
require.Len(t, list.Links, 0)
// Log into github
github.ExternalLogin(t, client)
list, err = client.ListExternalAuths(ctx)
require.NoError(t, err)
require.Len(t, list.Providers, 2)
require.Len(t, list.Links, 1)
require.Equal(t, list.Links[0].ProviderID, githubID)
// Unlink
err = client.UnlinkExternalAuthByID(ctx, githubID)
require.NoError(t, err)
list, err = client.ListExternalAuths(ctx)
require.NoError(t, err)
require.Len(t, list.Providers, 2)
require.Len(t, list.Links, 0)
})
}
func TestExternalAuthDevice(t *testing.T) {
t.Parallel()
t.Run("NotSupported", func(t *testing.T) {

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"time"
)
// EnhancedExternalAuthProvider is a constant that represents enhanced
@ -57,6 +58,37 @@ type ExternalAuth struct {
AppInstallURL string `json:"app_install_url"`
}
type ListUserExternalAuthResponse struct {
Providers []ExternalAuthLinkProvider `json:"providers"`
// Links are all the authenticated links for the user.
// If a link has a provider ID that does not exist, then that provider
// is no longer configured, rendering it unusable. It is still valuable
// to include these links so that the user can unlink them.
Links []ExternalAuthLink `json:"links"`
}
// ExternalAuthLink is a link between a user and an external auth provider.
// It excludes information that requires a token to access, so can be statically
// built from the database and configs.
type ExternalAuthLink struct {
ProviderID string `json:"provider_id"`
CreatedAt time.Time `json:"created_at" format:"date-time"`
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
HasRefreshToken bool `json:"has_refresh_token"`
Expires time.Time `json:"expires" format:"date-time"`
}
// ExternalAuthLinkProvider are the static details of a provider.
type ExternalAuthLinkProvider struct {
ID string `json:"id"`
Type string `json:"type"`
Device bool `json:"device"`
DisplayName string `json:"display_name"`
DisplayIcon string `json:"display_icon"`
AllowRefresh bool `json:"allow_refresh"`
AllowValidate bool `json:"allow_validate"`
}
type ExternalAuthAppInstallation struct {
ID int `json:"id"`
Account ExternalAuthUser `json:"account"`
@ -123,3 +155,32 @@ func (c *Client) ExternalAuthByID(ctx context.Context, provider string) (Externa
var extAuth ExternalAuth
return extAuth, json.NewDecoder(res.Body).Decode(&extAuth)
}
// UnlinkExternalAuthByID deletes the external auth for the given provider by ID
// for the user. This does not revoke the token from the IDP.
func (c *Client) UnlinkExternalAuthByID(ctx context.Context, provider string) error {
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/external-auth/%s", provider), nil)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return ReadBodyAsError(res)
}
return nil
}
// ListExternalAuths returns the available external auth providers and the user's
// authenticated links if they exist.
func (c *Client) ListExternalAuths(ctx context.Context) (ListUserExternalAuthResponse, error) {
res, err := c.Request(ctx, http.MethodGet, "/api/v2/external-auth", nil)
if err != nil {
return ListUserExternalAuthResponse{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return ListUserExternalAuthResponse{}, ReadBodyAsError(res)
}
var extAuth ListUserExternalAuthResponse
return extAuth, json.NewDecoder(res.Body).Decode(&extAuth)
}

61
docs/api/git.md generated
View File

@ -1,5 +1,40 @@
# Git
## Get user external auths
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/external-auth \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /external-auth`
### Example responses
> 200 Response
```json
{
"created_at": "2019-08-24T14:15:22Z",
"expires": "2019-08-24T14:15:22Z",
"has_refresh_token": true,
"provider_id": "string",
"updated_at": "2019-08-24T14:15:22Z"
}
```
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------- |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.ExternalAuthLink](schemas.md#codersdkexternalauthlink) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get external auth by ID
### Code samples
@ -59,6 +94,32 @@ curl -X GET http://coder-server:8080/api/v2/external-auth/{externalauth} \
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Delete external auth user link by ID
### Code samples
```shell
# Example request using curl
curl -X DELETE http://coder-server:8080/api/v2/external-auth/{externalauth} \
-H 'Coder-Session-Token: API_KEY'
```
`DELETE /external-auth/{externalauth}`
### Parameters
| Name | In | Type | Required | Description |
| -------------- | ---- | -------------- | -------- | --------------- |
| `externalauth` | path | string(string) | true | Git Provider ID |
### Responses
| Status | Meaning | Description | Schema |
| ------ | ------------------------------------------------------- | ----------- | ------ |
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get external auth device by ID.
### Code samples

22
docs/api/schemas.md generated
View File

@ -3001,6 +3001,28 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
| `user_code` | string | false | | |
| `verification_uri` | string | false | | |
## codersdk.ExternalAuthLink
```json
{
"created_at": "2019-08-24T14:15:22Z",
"expires": "2019-08-24T14:15:22Z",
"has_refresh_token": true,
"provider_id": "string",
"updated_at": "2019-08-24T14:15:22Z"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| ------------------- | ------- | -------- | ------------ | ----------- |
| `created_at` | string | false | | |
| `expires` | string | false | | |
| `has_refresh_token` | boolean | false | | |
| `provider_id` | string | false | | |
| `updated_at` | string | false | | |
## codersdk.ExternalAuthUser
```json

View File

@ -495,6 +495,26 @@ export interface ExternalAuthDeviceExchange {
readonly device_code: string;
}
// From codersdk/externalauth.go
export interface ExternalAuthLink {
readonly provider_id: string;
readonly created_at: string;
readonly updated_at: string;
readonly has_refresh_token: boolean;
readonly expires: string;
}
// From codersdk/externalauth.go
export interface ExternalAuthLinkProvider {
readonly id: string;
readonly type: string;
readonly device: boolean;
readonly display_name: string;
readonly display_icon: string;
readonly allow_refresh: boolean;
readonly allow_validate: boolean;
}
// From codersdk/externalauth.go
export interface ExternalAuthUser {
readonly login: string;
@ -588,6 +608,12 @@ export interface LinkConfig {
readonly icon: string;
}
// From codersdk/externalauth.go
export interface ListUserExternalAuthResponse {
readonly providers: ExternalAuthLinkProvider[];
readonly links: ExternalAuthLink[];
}
// From codersdk/deployment.go
export interface LoggingConfig {
readonly log_filter: string[];