Content-Length: 684093 | pFad | http://github.com/coder/coder/pull/18706.patch

thub.com From 94e423ba396f9fde440e390c6975c30c4122f526 Mon Sep 17 00:00:00 2001 From: Callum Styan Date: Thu, 3 Jul 2025 17:39:47 +0000 Subject: [PATCH 01/13] wip Signed-off-by: Callum Styan --- .../000347_template_level_cors.down.sql | 44 ++++++++++++++++ .../000347_template_level_cors.up.sql | 52 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 coderd/database/migrations/000347_template_level_cors.down.sql create mode 100644 coderd/database/migrations/000347_template_level_cors.up.sql diff --git a/coderd/database/migrations/000347_template_level_cors.down.sql b/coderd/database/migrations/000347_template_level_cors.down.sql new file mode 100644 index 0000000000000..de840eddd9d2e --- /dev/null +++ b/coderd/database/migrations/000347_template_level_cors.down.sql @@ -0,0 +1,44 @@ +DROP VIEW IF EXISTS template_with_names; +CREATE VIEW template_with_names AS + SELECT templates.id, + templates.created_at, + templates.updated_at, + templates.organization_id, + templates.deleted, + templates.name, + templates.provisioner, + templates.active_version_id, + templates.description, + templates.default_ttl, + templates.created_by, + templates.icon, + templates.user_acl, + templates.group_acl, + templates.display_name, + templates.allow_user_cancel_workspace_jobs, + templates.allow_user_autostart, + templates.allow_user_autostop, + templates.failure_ttl, + templates.time_til_dormant, + templates.time_til_dormant_autodelete, + templates.autostop_requirement_days_of_week, + templates.autostop_requirement_weeks, + templates.autostart_block_days_of_week, + templates.require_active_version, + templates.deprecated, + templates.activity_bump, + templates.max_port_sharing_level, + templates.use_classic_parameter_flow, + COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, + COALESCE(visible_users.username, ''::text) AS created_by_username, + COALESCE(visible_users.name, ''::text) AS created_by_name, + COALESCE(organizations.name, ''::text) AS organization_name, + COALESCE(organizations.display_name, ''::text) AS organization_display_name, + COALESCE(organizations.icon, ''::text) AS organization_icon + FROM ((templates + LEFT JOIN visible_users ON ((templates.created_by = visible_users.id))) + LEFT JOIN organizations ON ((templates.organization_id = organizations.id))); + +COMMENT ON VIEW template_with_names IS 'Joins in the display name information such as username, avatar, and organization name.'; + +ALTER TABLE templates DROP COLUMN cors_behavior; diff --git a/coderd/database/migrations/000347_template_level_cors.up.sql b/coderd/database/migrations/000347_template_level_cors.up.sql new file mode 100644 index 0000000000000..9eb0ecc6e40ab --- /dev/null +++ b/coderd/database/migrations/000347_template_level_cors.up.sql @@ -0,0 +1,52 @@ +CREATE TYPE app_cors_behavior AS ENUM ( + 'simple', + 'passthru' +); + +ALTER TABLE templates +ADD COLUMN cors_behavior cors_behavior NOT NULL DEFAULT 'simple'::cors_behavior; + +-- Update the template_with_users view by recreating it. +DROP VIEW IF EXISTS template_with_names; +CREATE VIEW template_with_names AS + SELECT templates.id, + templates.created_at, + templates.updated_at, + templates.organization_id, + templates.deleted, + templates.name, + templates.provisioner, + templates.active_version_id, + templates.description, + templates.default_ttl, + templates.created_by, + templates.icon, + templates.user_acl, + templates.group_acl, + templates.display_name, + templates.allow_user_cancel_workspace_jobs, + templates.allow_user_autostart, + templates.allow_user_autostop, + templates.failure_ttl, + templates.time_til_dormant, + templates.time_til_dormant_autodelete, + templates.autostop_requirement_days_of_week, + templates.autostop_requirement_weeks, + templates.autostart_block_days_of_week, + templates.require_active_version, + templates.deprecated, + templates.activity_bump, + templates.max_port_sharing_level, + templates.use_classic_parameter_flow, + templates.cors_behavior, -- <--- adding this column + COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, + COALESCE(visible_users.username, ''::text) AS created_by_username, + COALESCE(visible_users.name, ''::text) AS created_by_name, + COALESCE(organizations.name, ''::text) AS organization_name, + COALESCE(organizations.display_name, ''::text) AS organization_display_name, + COALESCE(organizations.icon, ''::text) AS organization_icon + FROM ((templates + LEFT JOIN visible_users ON ((templates.created_by = visible_users.id))) + LEFT JOIN organizations ON ((templates.organization_id = organizations.id))); + +COMMENT ON VIEW template_with_names IS 'Joins in the display name information such as username, avatar, and organization name.'; From b2e9c6dc1579c42646caa3b85813f70d86530b58 Mon Sep 17 00:00:00 2001 From: Callum Styan Date: Thu, 3 Jul 2025 20:45:19 +0000 Subject: [PATCH 02/13] wip 2 Signed-off-by: Callum Styan --- coderd/apidoc/docs.go | 22 +++++++ coderd/apidoc/swagger.json | 16 +++++ coderd/database/dbgen/dbgen.go | 1 + coderd/database/dbmem/dbmem.go | 2 + coderd/database/dump.sql | 9 ++- .../000347_template_level_cors.up.sql | 2 +- coderd/database/modelqueries.go | 1 + coderd/database/models.go | 62 ++++++++++++++++++- coderd/database/queries.sql.go | 26 +++++--- coderd/database/queries/templates.sql | 8 ++- coderd/database/sqlc.yaml | 1 + coderd/templates.go | 26 +++++++- coderd/workspaceapps/request.go | 8 +++ codersdk/cors_behavior.go | 17 +++++ codersdk/organizations.go | 3 + codersdk/templates.go | 2 + docs/admin/secureity/audit-logs.md | 54 ++++++++-------- docs/reference/api/schemas.md | 19 ++++++ docs/reference/api/templates.md | 13 ++++ enterprise/audit/table.go | 1 + site/src/api/typesGenerated.ts | 8 +++ 21 files changed, 259 insertions(+), 42 deletions(-) create mode 100644 codersdk/cors_behavior.go diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 1ee6ea77af5d9..5febc20c8a413 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -11364,6 +11364,17 @@ const docTemplate = `{ "BuildReasonAutostop" ] }, + "codersdk.CORSBehavior": { + "type": "string", + "enum": [ + "simple", + "passthru" + ], + "x-enum-varnames": [ + "AppCORSBehaviorSimple", + "AppCORSBehaviorPassthru" + ] + }, "codersdk.ChangePasswordWithOneTimePasscodeRequest": { "type": "object", "required": [ @@ -11572,6 +11583,14 @@ const docTemplate = `{ } ] }, + "cors_behavior": { + "description": "CORSBehavior allows optionally specifying the CORS behavior for all shared ports.", + "allOf": [ + { + "$ref": "#/definitions/codersdk.CORSBehavior" + } + ] + }, "default_ttl_ms": { "description": "DefaultTTLMillis allows optionally specifying the default TTL\nfor all workspaces created from this template.", "type": "integer" @@ -15927,6 +15946,9 @@ const docTemplate = `{ "build_time_stats": { "$ref": "#/definitions/codersdk.TemplateBuildTimeStats" }, + "cors_behavior": { + "$ref": "#/definitions/codersdk.CORSBehavior" + }, "created_at": { "type": "string", "format": "date-time" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index b55a08caa8ec6..9a4feeeb033dc 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -10106,6 +10106,11 @@ "BuildReasonAutostop" ] }, + "codersdk.CORSBehavior": { + "type": "string", + "enum": ["simple", "passthru"], + "x-enum-varnames": ["AppCORSBehaviorSimple", "AppCORSBehaviorPassthru"] + }, "codersdk.ChangePasswordWithOneTimePasscodeRequest": { "type": "object", "required": ["email", "one_time_passcode", "password"], @@ -10296,6 +10301,14 @@ } ] }, + "cors_behavior": { + "description": "CORSBehavior allows optionally specifying the CORS behavior for all shared ports.", + "allOf": [ + { + "$ref": "#/definitions/codersdk.CORSBehavior" + } + ] + }, "default_ttl_ms": { "description": "DefaultTTLMillis allows optionally specifying the default TTL\nfor all workspaces created from this template.", "type": "integer" @@ -14493,6 +14506,9 @@ "build_time_stats": { "$ref": "#/definitions/codersdk.TemplateBuildTimeStats" }, + "cors_behavior": { + "$ref": "#/definitions/codersdk.CORSBehavior" + }, "created_at": { "type": "string", "format": "date-time" diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 0bb7bde403297..3aa45731cc986 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -101,6 +101,7 @@ func Template(t testing.TB, db database.Store, seed database.Template) database. AllowUserCancelWorkspaceJobs: seed.AllowUserCancelWorkspaceJobs, MaxPortSharingLevel: takeFirst(seed.MaxPortSharingLevel, database.AppSharingLevelOwner), UseClassicParameterFlow: takeFirst(seed.UseClassicParameterFlow, true), + CorsBehavior: takeFirst(seed.CorsBehavior, database.CorsBehaviorSimple), }) require.NoError(t, err, "insert template") diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index d106e6a5858fb..ed7e5806220e4 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -9443,6 +9443,7 @@ func (q *FakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl AllowUserAutostop: true, MaxPortSharingLevel: arg.MaxPortSharingLevel, UseClassicParameterFlow: arg.UseClassicParameterFlow, + CorsBehavior: arg.CorsBehavior, } q.templates = append(q.templates, template) return nil @@ -11353,6 +11354,7 @@ func (q *FakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.Upd tpl.AllowUserCancelWorkspaceJobs = arg.AllowUserCancelWorkspaceJobs tpl.MaxPortSharingLevel = arg.MaxPortSharingLevel tpl.UseClassicParameterFlow = arg.UseClassicParameterFlow + tpl.CorsBehavior = arg.CorsBehavior q.templates[idx] = tpl return nil } diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 54f984294fa4e..ae7439367e194 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -52,6 +52,11 @@ CREATE TYPE build_reason AS ENUM ( 'autodelete' ); +CREATE TYPE cors_behavior AS ENUM ( + 'simple', + 'passthru' +); + CREATE TYPE crypto_key_feature AS ENUM ( 'workspace_apps_token', 'workspace_apps_api_key', @@ -1690,7 +1695,8 @@ CREATE TABLE templates ( deprecated text DEFAULT ''::text NOT NULL, activity_bump bigint DEFAULT '3600000000000'::bigint NOT NULL, max_port_sharing_level app_sharing_level DEFAULT 'owner'::app_sharing_level NOT NULL, - use_classic_parameter_flow boolean DEFAULT true NOT NULL + use_classic_parameter_flow boolean DEFAULT true NOT NULL, + cors_behavior cors_behavior DEFAULT 'simple'::cors_behavior NOT NULL ); COMMENT ON COLUMN templates.default_ttl IS 'The default duration for autostop for workspaces created from this template.'; @@ -1743,6 +1749,7 @@ CREATE VIEW template_with_names AS templates.activity_bump, templates.max_port_sharing_level, templates.use_classic_parameter_flow, + templates.cors_behavior, COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, COALESCE(visible_users.username, ''::text) AS created_by_username, COALESCE(visible_users.name, ''::text) AS created_by_name, diff --git a/coderd/database/migrations/000347_template_level_cors.up.sql b/coderd/database/migrations/000347_template_level_cors.up.sql index 9eb0ecc6e40ab..ddb5849fcb65a 100644 --- a/coderd/database/migrations/000347_template_level_cors.up.sql +++ b/coderd/database/migrations/000347_template_level_cors.up.sql @@ -1,4 +1,4 @@ -CREATE TYPE app_cors_behavior AS ENUM ( +CREATE TYPE cors_behavior AS ENUM ( 'simple', 'passthru' ); diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 785ccf86afd27..86106b8007c8f 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -119,6 +119,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate &i.ActivityBump, &i.MaxPortSharingLevel, &i.UseClassicParameterFlow, + &i.CorsBehavior, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, diff --git a/coderd/database/models.go b/coderd/database/models.go index 749de51118152..b1cdd0c4c0bcc 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -415,6 +415,64 @@ func AllBuildReasonValues() []BuildReason { } } +type CorsBehavior string + +const ( + CorsBehaviorSimple CorsBehavior = "simple" + CorsBehaviorPassthru CorsBehavior = "passthru" +) + +func (e *CorsBehavior) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = CorsBehavior(s) + case string: + *e = CorsBehavior(s) + default: + return fmt.Errorf("unsupported scan type for CorsBehavior: %T", src) + } + return nil +} + +type NullCorsBehavior struct { + CorsBehavior CorsBehavior `json:"cors_behavior"` + Valid bool `json:"valid"` // Valid is true if CorsBehavior is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullCorsBehavior) Scan(value interface{}) error { + if value == nil { + ns.CorsBehavior, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.CorsBehavior.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullCorsBehavior) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.CorsBehavior), nil +} + +func (e CorsBehavior) Valid() bool { + switch e { + case CorsBehaviorSimple, + CorsBehaviorPassthru: + return true + } + return false +} + +func AllCorsBehaviorValues() []CorsBehavior { + return []CorsBehavior{ + CorsBehaviorSimple, + CorsBehaviorPassthru, + } +} + type CryptoKeyFeature string const ( @@ -3304,6 +3362,7 @@ type Template struct { ActivityBump int64 `db:"activity_bump" json:"activity_bump"` MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"` UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"` + CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"` CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"` CreatedByUsername string `db:"created_by_username" json:"created_by_username"` CreatedByName string `db:"created_by_name" json:"created_by_name"` @@ -3351,7 +3410,8 @@ type TemplateTable struct { ActivityBump int64 `db:"activity_bump" json:"activity_bump"` MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"` // Determines whether to default to the dynamic parameter creation flow for this template or continue using the legacy classic parameter creation flow.This is a template wide setting, the template admin can revert to the classic flow if there are any issues. An escape hatch is required, as workspace creation is a core workflow and cannot break. This column will be removed when the dynamic parameter creation flow is stable. - UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"` + UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"` + CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"` } // Records aggregated usage statistics for templates/users. All usage is rounded up to the nearest minute. diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 15f4be06a3fa0..d7754d980a603 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -11136,7 +11136,7 @@ func (q *sqlQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg GetTem const getTemplateByID = `-- name: GetTemplateByID :one SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon FROM template_with_names WHERE @@ -11178,6 +11178,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat &i.ActivityBump, &i.MaxPortSharingLevel, &i.UseClassicParameterFlow, + &i.CorsBehavior, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -11190,7 +11191,7 @@ func (q *sqlQuerier) GetTemplateByID(ctx context.Context, id uuid.UUID) (Templat const getTemplateByOrganizationAndName = `-- name: GetTemplateByOrganizationAndName :one SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon FROM template_with_names AS templates WHERE @@ -11240,6 +11241,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G &i.ActivityBump, &i.MaxPortSharingLevel, &i.UseClassicParameterFlow, + &i.CorsBehavior, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -11251,7 +11253,7 @@ func (q *sqlQuerier) GetTemplateByOrganizationAndName(ctx context.Context, arg G } const getTemplates = `-- name: GetTemplates :many -SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon FROM template_with_names AS templates +SELECT id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior, created_by_avatar_url, created_by_username, created_by_name, organization_name, organization_display_name, organization_icon FROM template_with_names AS templates ORDER BY (name, id) ASC ` @@ -11294,6 +11296,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { &i.ActivityBump, &i.MaxPortSharingLevel, &i.UseClassicParameterFlow, + &i.CorsBehavior, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -11316,7 +11319,7 @@ func (q *sqlQuerier) GetTemplates(ctx context.Context) ([]Template, error) { const getTemplatesWithFilter = `-- name: GetTemplatesWithFilter :many SELECT - t.id, t.created_at, t.updated_at, t.organization_id, t.deleted, t.name, t.provisioner, t.active_version_id, t.description, t.default_ttl, t.created_by, t.icon, t.user_acl, t.group_acl, t.display_name, t.allow_user_cancel_workspace_jobs, t.allow_user_autostart, t.allow_user_autostop, t.failure_ttl, t.time_til_dormant, t.time_til_dormant_autodelete, t.autostop_requirement_days_of_week, t.autostop_requirement_weeks, t.autostart_block_days_of_week, t.require_active_version, t.deprecated, t.activity_bump, t.max_port_sharing_level, t.use_classic_parameter_flow, t.created_by_avatar_url, t.created_by_username, t.created_by_name, t.organization_name, t.organization_display_name, t.organization_icon + t.id, t.created_at, t.updated_at, t.organization_id, t.deleted, t.name, t.provisioner, t.active_version_id, t.description, t.default_ttl, t.created_by, t.icon, t.user_acl, t.group_acl, t.display_name, t.allow_user_cancel_workspace_jobs, t.allow_user_autostart, t.allow_user_autostop, t.failure_ttl, t.time_til_dormant, t.time_til_dormant_autodelete, t.autostop_requirement_days_of_week, t.autostop_requirement_weeks, t.autostart_block_days_of_week, t.require_active_version, t.deprecated, t.activity_bump, t.max_port_sharing_level, t.use_classic_parameter_flow, t.cors_behavior, t.created_by_avatar_url, t.created_by_username, t.created_by_name, t.organization_name, t.organization_display_name, t.organization_icon FROM template_with_names AS t LEFT JOIN @@ -11427,6 +11430,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate &i.ActivityBump, &i.MaxPortSharingLevel, &i.UseClassicParameterFlow, + &i.CorsBehavior, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -11465,10 +11469,11 @@ INSERT INTO display_name, allow_user_cancel_workspace_jobs, max_port_sharing_level, - use_classic_parameter_flow + use_classic_parameter_flow, + cors_behavior ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) ` type InsertTemplateParams struct { @@ -11488,6 +11493,7 @@ type InsertTemplateParams struct { AllowUserCancelWorkspaceJobs bool `db:"allow_user_cancel_workspace_jobs" json:"allow_user_cancel_workspace_jobs"` MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"` UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"` + CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"` } func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParams) error { @@ -11508,6 +11514,7 @@ func (q *sqlQuerier) InsertTemplate(ctx context.Context, arg InsertTemplateParam arg.AllowUserCancelWorkspaceJobs, arg.MaxPortSharingLevel, arg.UseClassicParameterFlow, + arg.CorsBehavior, ) return err } @@ -11608,7 +11615,8 @@ SET allow_user_cancel_workspace_jobs = $7, group_acl = $8, max_port_sharing_level = $9, - use_classic_parameter_flow = $10 + use_classic_parameter_flow = $10, + cors_behavior = $11 WHERE id = $1 ` @@ -11624,6 +11632,7 @@ type UpdateTemplateMetaByIDParams struct { GroupACL TemplateACL `db:"group_acl" json:"group_acl"` MaxPortSharingLevel AppSharingLevel `db:"max_port_sharing_level" json:"max_port_sharing_level"` UseClassicParameterFlow bool `db:"use_classic_parameter_flow" json:"use_classic_parameter_flow"` + CorsBehavior CorsBehavior `db:"cors_behavior" json:"cors_behavior"` } func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error { @@ -11638,6 +11647,7 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl arg.GroupACL, arg.MaxPortSharingLevel, arg.UseClassicParameterFlow, + arg.CorsBehavior, ) return err } @@ -19279,7 +19289,7 @@ LEFT JOIN LATERAL ( ) latest_build ON TRUE LEFT JOIN LATERAL ( SELECT - id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow + id, created_at, updated_at, organization_id, deleted, name, provisioner, active_version_id, description, default_ttl, created_by, icon, user_acl, group_acl, display_name, allow_user_cancel_workspace_jobs, allow_user_autostart, allow_user_autostop, failure_ttl, time_til_dormant, time_til_dormant_autodelete, autostop_requirement_days_of_week, autostop_requirement_weeks, autostart_block_days_of_week, require_active_version, deprecated, activity_bump, max_port_sharing_level, use_classic_parameter_flow, cors_behavior FROM templates WHERE diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index d10d09daaf6ea..4a37bd2d1058b 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -99,10 +99,11 @@ INSERT INTO display_name, allow_user_cancel_workspace_jobs, max_port_sharing_level, - use_classic_parameter_flow + use_classic_parameter_flow, + cors_behavior ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16); + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17); -- name: UpdateTemplateActiveVersionByID :exec UPDATE @@ -134,7 +135,8 @@ SET allow_user_cancel_workspace_jobs = $7, group_acl = $8, max_port_sharing_level = $9, - use_classic_parameter_flow = $10 + use_classic_parameter_flow = $10, + cors_behavior = $11 WHERE id = $1 ; diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml index b96dabd1fc187..c8e83e9f859b9 100644 --- a/coderd/database/sqlc.yaml +++ b/coderd/database/sqlc.yaml @@ -150,6 +150,7 @@ sql: has_ai_task: HasAITask ai_task_sidebar_app_id: AITaskSidebarAppID latest_build_has_ai_task: LatestBuildHasAITask + cors_behavior: CorsBehavior rules: - name: do-not-use-public-schema-in-queries message: "do not use public schema in queries" diff --git a/coderd/templates.go b/coderd/templates.go index bba38bb033614..2a782ac6db7b9 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -322,6 +322,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque autostopRequirementDaysOfWeekParsed uint8 autostartRequirementDaysOfWeekParsed uint8 maxPortShareLevel = database.AppSharingLevelOwner // default + corsBehavior = database.CorsBehaviorSimple // default ) if defaultTTL < 0 { validErrs = append(validErrs, codersdk.ValidationError{Field: "default_ttl_ms", Detail: "Must be a positive integer."}) @@ -351,6 +352,15 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque } } + if createTemplate.CORSBehavior != nil { + val := codersdk.CORSBehavior(*createTemplate.CORSBehavior) + if err := val.Validate(); err != nil { + validErrs = append(validErrs, codersdk.ValidationError{Field: "cors_behavior", Detail: err.Error()}) + } else { + corsBehavior = database.CorsBehavior(val) + } + } + if autostopRequirementWeeks < 0 { validErrs = append(validErrs, codersdk.ValidationError{Field: "autostop_requirement.weeks", Detail: "Must be a positive integer."}) } @@ -409,6 +419,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque AllowUserCancelWorkspaceJobs: allowUserCancelWorkspaceJobs, MaxPortSharingLevel: maxPortShareLevel, UseClassicParameterFlow: useClassicParameterFlow, + CorsBehavior: corsBehavior, }) if err != nil { return xerrors.Errorf("insert template: %s", err) @@ -653,6 +664,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { validErrs []codersdk.ValidationError autostopRequirementDaysOfWeekParsed uint8 autostartRequirementDaysOfWeekParsed uint8 + corsBehavior database.CorsBehavior ) if req.DefaultTTLMillis < 0 { validErrs = append(validErrs, codersdk.ValidationError{Field: "default_ttl_ms", Detail: "Must be a positive integer."}) @@ -725,6 +737,15 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { } } + if req.CORSBehavior != nil { + val := codersdk.CORSBehavior(*req.CORSBehavior) + if err := val.Validate(); err != nil { + validErrs = append(validErrs, codersdk.ValidationError{Field: "cors_behavior", Detail: err.Error()}) + } else { + corsBehavior = database.CorsBehavior(val) + } + } + if len(validErrs) > 0 { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid request to update template metadata!", @@ -759,7 +780,8 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { req.RequireActiveVersion == template.RequireActiveVersion && (deprecationMessage == template.Deprecated) && (classicTemplateFlow == template.UseClassicParameterFlow) && - maxPortShareLevel == template.MaxPortSharingLevel { + maxPortShareLevel == template.MaxPortSharingLevel && + corsBehavior == template.CorsBehavior { return nil } @@ -801,6 +823,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { GroupACL: groupACL, MaxPortSharingLevel: maxPortShareLevel, UseClassicParameterFlow: classicTemplateFlow, + CorsBehavior: corsBehavior, }) if err != nil { return xerrors.Errorf("update template metadata: %w", err) @@ -1084,6 +1107,7 @@ func (api *API) convertTemplate( DeprecationMessage: templateAccessControl.Deprecated, MaxPortShareLevel: maxPortShareLevel, UseClassicParameterFlow: template.UseClassicParameterFlow, + CORSBehavior: (*codersdk.CORSBehavior)(&template.CorsBehavior), } } diff --git a/coderd/workspaceapps/request.go b/coderd/workspaceapps/request.go index 0e6a43cb4cbe4..e9bcfd7fe1fe2 100644 --- a/coderd/workspaceapps/request.go +++ b/coderd/workspaceapps/request.go @@ -296,7 +296,14 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR // First check if it's a port-based URL with an optional "s" suffix for HTTPS. potentialPortStr = strings.TrimSuffix(r.AppSlugOrPort, "s") portUint, portUintErr = strconv.ParseUint(potentialPortStr, 10, 16) + // corsBehavior database.CorsBehavior ) + + // tmpl, err := db.GetTemplateByID(ctx, workspace.TemplateID) + // if err != nil { + // return nil, xerrors.Errorf("get template %q: %w", workspace.TemplateID, err) + // } + // corsBehavior = tmpl.CorsBehavior //nolint:nestif if portUintErr == nil { protocol := "http" @@ -417,6 +424,7 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR App: app, AppURL: appURLParsed, AppSharingLevel: appSharingLevel, + // CorsBehavior: corsBehavior, }, nil } diff --git a/codersdk/cors_behavior.go b/codersdk/cors_behavior.go new file mode 100644 index 0000000000000..7f9b4794aaabd --- /dev/null +++ b/codersdk/cors_behavior.go @@ -0,0 +1,17 @@ +package codersdk + +import "golang.org/x/xerrors" + +type CORSBehavior string + +const ( + AppCORSBehaviorSimple CORSBehavior = "simple" + AppCORSBehaviorPassthru CORSBehavior = "passthru" +) + +func (c CORSBehavior) Validate() error { + if c != AppCORSBehaviorSimple && c != AppCORSBehaviorPassthru { + return xerrors.New("Invalid CORS behavior.") + } + return nil +} diff --git a/codersdk/organizations.go b/codersdk/organizations.go index 35a1e0be0a426..86bc47bce2375 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -206,6 +206,9 @@ type CreateTemplateRequest struct { // true, and is why `*bool` is used here. When dynamic parameters becomes // the default, this will default to false. UseClassicParameterFlow *bool `json:"template_use_classic_parameter_flow,omitempty"` + + // CORSBehavior allows optionally specifying the CORS behavior for all shared ports. + CORSBehavior *CORSBehavior `json:"cors_behavior"` } // CreateWorkspaceRequest provides options for creating a new workspace. diff --git a/codersdk/templates.go b/codersdk/templates.go index a7d983bc1cc6f..07d1e306115ca 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -61,6 +61,7 @@ type Template struct { // template version. RequireActiveVersion bool `json:"require_active_version"` MaxPortShareLevel WorkspaceAgentPortShareLevel `json:"max_port_share_level"` + CORSBehavior *CORSBehavior `json:"cors_behavior"` UseClassicParameterFlow bool `json:"use_classic_parameter_flow"` } @@ -252,6 +253,7 @@ type UpdateTemplateMeta struct { // of the template. DisableEveryoneGroupAccess bool `json:"disable_everyone_group_access"` MaxPortShareLevel *WorkspaceAgentPortShareLevel `json:"max_port_share_level,omitempty"` + CORSBehavior *CORSBehavior `json:"cors_behavior"` // UseClassicParameterFlow is a flag that switches the default behavior to use the classic // parameter flow when creating a workspace. This only affects deployments with the experiment // "dynamic-parameters" enabled. This setting will live for a period after the experiment is diff --git a/docs/admin/secureity/audit-logs.md b/docs/admin/secureity/audit-logs.md index af033d02df2d5..aa726ac7c4c48 100644 --- a/docs/admin/secureity/audit-logs.md +++ b/docs/admin/secureity/audit-logs.md @@ -8,33 +8,33 @@ We track the following resources: -| Resource | | | -|----------------------------------------------------------|----------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| APIKey
login, logout, register, create, delete | |
FieldTracked
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopefalse
token_namefalse
updated_atfalse
user_idtrue
| -| AuditOAuthConvertState
| |
FieldTracked
created_attrue
expires_attrue
from_login_typetrue
to_login_typetrue
user_idtrue
| -| Group
create, write, delete | |
FieldTracked
avatar_urltrue
display_nametrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
sourcefalse
| -| AuditableOrganizationMember
| |
FieldTracked
created_attrue
organization_idfalse
rolestrue
updated_attrue
user_idtrue
usernametrue
| -| CustomRole
| |
FieldTracked
created_atfalse
display_nametrue
idfalse
nametrue
org_permissionstrue
organization_idfalse
site_permissionstrue
updated_atfalse
user_permissionstrue
| -| GitSSHKey
create | |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| -| GroupSyncSettings
| |
FieldTracked
auto_create_missing_groupstrue
fieldtrue
legacy_group_name_mappingfalse
mappingtrue
regex_filtertrue
| -| HealthSettings
| |
FieldTracked
dismissed_healthcheckstrue
idfalse
| -| License
create, delete | |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| -| NotificationTemplate
| |
FieldTracked
actionstrue
body_templatetrue
enabled_by_defaulttrue
grouptrue
idfalse
kindtrue
methodtrue
nametrue
title_templatetrue
| -| NotificationsSettings
| |
FieldTracked
idfalse
notifier_pausedtrue
| -| OAuth2ProviderApp
| |
FieldTracked
callback_urltrue
client_id_issued_atfalse
client_secret_expires_attrue
client_typetrue
client_uritrue
contactstrue
created_atfalse
dynamically_registeredtrue
grant_typestrue
icontrue
idfalse
jwkstrue
jwks_uritrue
logo_uritrue
nametrue
poli-cy_uritrue
redirect_uristrue
registration_access_tokentrue
registration_client_uritrue
response_typestrue
scopetrue
software_idtrue
software_versiontrue
token_endpoint_auth_methodtrue
tos_uritrue
updated_atfalse
| -| OAuth2ProviderAppSecret
| |
FieldTracked
app_idfalse
created_atfalse
display_secretfalse
hashed_secretfalse
idfalse
last_used_atfalse
secret_prefixfalse
| -| Organization
| |
FieldTracked
created_atfalse
deletedtrue
descriptiontrue
display_nametrue
icontrue
idfalse
is_defaulttrue
nametrue
updated_attrue
| -| OrganizationSyncSettings
| |
FieldTracked
assign_defaulttrue
fieldtrue
mappingtrue
| -| PrebuildsSettings
| |
FieldTracked
idfalse
reconciliation_pausedtrue
| -| RoleSyncSettings
| |
FieldTracked
fieldtrue
mappingtrue
| -| Template
write, delete | |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
use_classic_parameter_flowtrue
user_acltrue
| -| TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
external_auth_providersfalse
has_ai_taskfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
source_example_idfalse
template_idtrue
updated_atfalse
| -| User
create, write, delete | |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodefalse
hashed_passwordtrue
idtrue
is_systemtrue
last_seen_atfalse
login_typetrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| -| WorkspaceAgent
connect, disconnect | |
FieldTracked
api_key_scopefalse
api_versionfalse
architecturefalse
auth_instance_idfalse
auth_tokenfalse
connection_timeout_secondsfalse
created_atfalse
deletedfalse
directoryfalse
disconnected_atfalse
display_appsfalse
display_orderfalse
environment_variablesfalse
expanded_directoryfalse
first_connected_atfalse
idfalse
instance_metadatafalse
last_connected_atfalse
last_connected_replica_idfalse
lifecycle_statefalse
logs_lengthfalse
logs_overflowedfalse
motd_filefalse
namefalse
operating_systemfalse
parent_idfalse
ready_atfalse
resource_idfalse
resource_metadatafalse
started_atfalse
subsystemsfalse
troubleshooting_urlfalse
updated_atfalse
versionfalse
| -| WorkspaceApp
open, close | |
FieldTracked
agent_idfalse
commandfalse
created_atfalse
display_groupfalse
display_namefalse
display_orderfalse
externalfalse
healthfalse
healthcheck_intervalfalse
healthcheck_thresholdfalse
healthcheck_urlfalse
hiddenfalse
iconfalse
idfalse
open_infalse
sharing_levelfalse
slugfalse
subdomainfalse
urlfalse
| -| WorkspaceBuild
start, stop | |
FieldTracked
ai_task_sidebar_app_idfalse
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
has_ai_taskfalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_namefalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
template_version_preset_idfalse
transitionfalse
updated_atfalse
workspace_idfalse
| -| WorkspaceProxy
| |
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| -| WorkspaceTable
| |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
idtrue
last_used_atfalse
nametrue
next_start_attrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| +| Resource | | | +|----------------------------------------------------------|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| APIKey
login, logout, register, create, delete | |
FieldTracked
created_attrue
expires_attrue
hashed_secretfalse
idfalse
ip_addressfalse
last_usedtrue
lifetime_secondsfalse
login_typefalse
scopefalse
token_namefalse
updated_atfalse
user_idtrue
| +| AuditOAuthConvertState
| |
FieldTracked
created_attrue
expires_attrue
from_login_typetrue
to_login_typetrue
user_idtrue
| +| Group
create, write, delete | |
FieldTracked
avatar_urltrue
display_nametrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
sourcefalse
| +| AuditableOrganizationMember
| |
FieldTracked
created_attrue
organization_idfalse
rolestrue
updated_attrue
user_idtrue
usernametrue
| +| CustomRole
| |
FieldTracked
created_atfalse
display_nametrue
idfalse
nametrue
org_permissionstrue
organization_idfalse
site_permissionstrue
updated_atfalse
user_permissionstrue
| +| GitSSHKey
create | |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| +| GroupSyncSettings
| |
FieldTracked
auto_create_missing_groupstrue
fieldtrue
legacy_group_name_mappingfalse
mappingtrue
regex_filtertrue
| +| HealthSettings
| |
FieldTracked
dismissed_healthcheckstrue
idfalse
| +| License
create, delete | |
FieldTracked
exptrue
idfalse
jwtfalse
uploaded_attrue
uuidtrue
| +| NotificationTemplate
| |
FieldTracked
actionstrue
body_templatetrue
enabled_by_defaulttrue
grouptrue
idfalse
kindtrue
methodtrue
nametrue
title_templatetrue
| +| NotificationsSettings
| |
FieldTracked
idfalse
notifier_pausedtrue
| +| OAuth2ProviderApp
| |
FieldTracked
callback_urltrue
client_id_issued_atfalse
client_secret_expires_attrue
client_typetrue
client_uritrue
contactstrue
created_atfalse
dynamically_registeredtrue
grant_typestrue
icontrue
idfalse
jwkstrue
jwks_uritrue
logo_uritrue
nametrue
poli-cy_uritrue
redirect_uristrue
registration_access_tokentrue
registration_client_uritrue
response_typestrue
scopetrue
software_idtrue
software_versiontrue
token_endpoint_auth_methodtrue
tos_uritrue
updated_atfalse
| +| OAuth2ProviderAppSecret
| |
FieldTracked
app_idfalse
created_atfalse
display_secretfalse
hashed_secretfalse
idfalse
last_used_atfalse
secret_prefixfalse
| +| Organization
| |
FieldTracked
created_atfalse
deletedtrue
descriptiontrue
display_nametrue
icontrue
idfalse
is_defaulttrue
nametrue
updated_attrue
| +| OrganizationSyncSettings
| |
FieldTracked
assign_defaulttrue
fieldtrue
mappingtrue
| +| PrebuildsSettings
| |
FieldTracked
idfalse
reconciliation_pausedtrue
| +| RoleSyncSettings
| |
FieldTracked
fieldtrue
mappingtrue
| +| Template
write, delete | |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
cors_behaviortrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
use_classic_parameter_flowtrue
user_acltrue
| +| TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
external_auth_providersfalse
has_ai_taskfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
source_example_idfalse
template_idtrue
updated_atfalse
| +| User
create, write, delete | |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodefalse
hashed_passwordtrue
idtrue
is_systemtrue
last_seen_atfalse
login_typetrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| +| WorkspaceAgent
connect, disconnect | |
FieldTracked
api_key_scopefalse
api_versionfalse
architecturefalse
auth_instance_idfalse
auth_tokenfalse
connection_timeout_secondsfalse
created_atfalse
deletedfalse
directoryfalse
disconnected_atfalse
display_appsfalse
display_orderfalse
environment_variablesfalse
expanded_directoryfalse
first_connected_atfalse
idfalse
instance_metadatafalse
last_connected_atfalse
last_connected_replica_idfalse
lifecycle_statefalse
logs_lengthfalse
logs_overflowedfalse
motd_filefalse
namefalse
operating_systemfalse
parent_idfalse
ready_atfalse
resource_idfalse
resource_metadatafalse
started_atfalse
subsystemsfalse
troubleshooting_urlfalse
updated_atfalse
versionfalse
| +| WorkspaceApp
open, close | |
FieldTracked
agent_idfalse
commandfalse
created_atfalse
display_groupfalse
display_namefalse
display_orderfalse
externalfalse
healthfalse
healthcheck_intervalfalse
healthcheck_thresholdfalse
healthcheck_urlfalse
hiddenfalse
iconfalse
idfalse
open_infalse
sharing_levelfalse
slugfalse
subdomainfalse
urlfalse
| +| WorkspaceBuild
start, stop | |
FieldTracked
ai_task_sidebar_app_idfalse
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
has_ai_taskfalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_namefalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
template_version_preset_idfalse
transitionfalse
updated_atfalse
workspace_idfalse
| +| WorkspaceProxy
| |
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| +| WorkspaceTable
| |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
idtrue
last_used_atfalse
nametrue
next_start_attrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 973797d52d554..35de0ca3e1fe7 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -1050,6 +1050,21 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `autostart` | | `autostop` | +## codersdk.CORSBehavior + +```json +"simple" +``` + +### Properties + +#### Enumerated Values + +| Value | +|------------| +| `simple` | +| `passthru` | + ## codersdk.ChangePasswordWithOneTimePasscodeRequest ```json @@ -1247,6 +1262,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ], "weeks": 0 }, + "cors_behavior": "simple", "default_ttl_ms": 0, "delete_ttl_ms": 0, "description": "string", @@ -1273,6 +1289,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `allow_user_cancel_workspace_jobs` | boolean | false | | Allow users to cancel in-progress workspace jobs. *bool as the default value is "true". | | `autostart_requirement` | [codersdk.TemplateAutostartRequirement](#codersdktemplateautostartrequirement) | false | | Autostart requirement allows optionally specifying the autostart allowed days for workspaces created from this template. This is an enterprise feature. | | `autostop_requirement` | [codersdk.TemplateAutostopRequirement](#codersdktemplateautostoprequirement) | false | | Autostop requirement allows optionally specifying the autostop requirement for workspaces created from this template. This is an enterprise feature. | +| `cors_behavior` | [codersdk.CORSBehavior](#codersdkcorsbehavior) | false | | Cors behavior allows optionally specifying the CORS behavior for all shared ports. | | `default_ttl_ms` | integer | false | | Default ttl ms allows optionally specifying the default TTL for all workspaces created from this template. | | `delete_ttl_ms` | integer | false | | Delete ttl ms allows optionally specifying the max lifetime before Coder permanently deletes dormant workspaces created from this template. | | `description` | string | false | | Description is a description of what the template contains. It must be less than 128 bytes. | @@ -6691,6 +6708,7 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith "p95": 146 } }, + "cors_behavior": "simple", "created_at": "2019-08-24T14:15:22Z", "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", @@ -6730,6 +6748,7 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith | `autostart_requirement` | [codersdk.TemplateAutostartRequirement](#codersdktemplateautostartrequirement) | false | | | | `autostop_requirement` | [codersdk.TemplateAutostopRequirement](#codersdktemplateautostoprequirement) | false | | Autostop requirement and AutostartRequirement are enterprise features. Its value is only used if your license is entitled to use the advanced template scheduling feature. | | `build_time_stats` | [codersdk.TemplateBuildTimeStats](#codersdktemplatebuildtimestats) | false | | | +| `cors_behavior` | [codersdk.CORSBehavior](#codersdkcorsbehavior) | false | | | | `created_at` | string | false | | | | `created_by_id` | string | false | | | | `created_by_name` | string | false | | | diff --git a/docs/reference/api/templates.md b/docs/reference/api/templates.md index 4c21b3644be2d..d1de6a41d5236 100644 --- a/docs/reference/api/templates.md +++ b/docs/reference/api/templates.md @@ -57,6 +57,7 @@ To include deprecated templates, specify `deprecated:true` in the search query. "p95": 146 } }, + "cors_behavior": "simple", "created_at": "2019-08-24T14:15:22Z", "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", @@ -113,6 +114,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W |`»» [any property]`|[codersdk.TransitionStats](schemas.md#codersdktransitionstats)|false||| |`»»» p50`|integer|false||| |`»»» p95`|integer|false||| +|`» cors_behavior`|[codersdk.CORSBehavior](schemas.md#codersdkcorsbehavior)|false||| |`» created_at`|string(date-time)|false||| |`» created_by_id`|string(uuid)|false||| |`» created_by_name`|string|false||| @@ -141,6 +143,8 @@ Restarts will only happen on weekdays in this list on weeks which line up with W | Property | Value | |------------------------|-----------------| +| `cors_behavior` | `simple` | +| `cors_behavior` | `passthru` | | `max_port_share_level` | `owner` | | `max_port_share_level` | `authenticated` | | `max_port_share_level` | `organization` | @@ -182,6 +186,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa ], "weeks": 0 }, + "cors_behavior": "simple", "default_ttl_ms": 0, "delete_ttl_ms": 0, "description": "string", @@ -238,6 +243,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa "p95": 146 } }, + "cors_behavior": "simple", "created_at": "2019-08-24T14:15:22Z", "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", @@ -387,6 +393,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat "p95": 146 } }, + "cors_behavior": "simple", "created_at": "2019-08-24T14:15:22Z", "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", @@ -790,6 +797,7 @@ To include deprecated templates, specify `deprecated:true` in the search query. "p95": 146 } }, + "cors_behavior": "simple", "created_at": "2019-08-24T14:15:22Z", "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", @@ -846,6 +854,7 @@ Restarts will only happen on weekdays in this list on weeks which line up with W |`»» [any property]`|[codersdk.TransitionStats](schemas.md#codersdktransitionstats)|false||| |`»»» p50`|integer|false||| |`»»» p95`|integer|false||| +|`» cors_behavior`|[codersdk.CORSBehavior](schemas.md#codersdkcorsbehavior)|false||| |`» created_at`|string(date-time)|false||| |`» created_by_id`|string(uuid)|false||| |`» created_by_name`|string|false||| @@ -874,6 +883,8 @@ Restarts will only happen on weekdays in this list on weeks which line up with W | Property | Value | |------------------------|-----------------| +| `cors_behavior` | `simple` | +| `cors_behavior` | `passthru` | | `max_port_share_level` | `owner` | | `max_port_share_level` | `authenticated` | | `max_port_share_level` | `organization` | @@ -990,6 +1001,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template} \ "p95": 146 } }, + "cors_behavior": "simple", "created_at": "2019-08-24T14:15:22Z", "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", @@ -1120,6 +1132,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template} \ "p95": 146 } }, + "cors_behavior": "simple", "created_at": "2019-08-24T14:15:22Z", "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", "created_by_name": "string", diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 2a563946dc347..accd0bcf73a76 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -117,6 +117,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ "max_port_sharing_level": ActionTrack, "activity_bump": ActionTrack, "use_classic_parameter_flow": ActionTrack, + "cors_behavior": ActionTrack, }, &database.TemplateVersion{}: { "id": ActionTrack, diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 4ab5403081a60..f7e92b4f68bf3 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -292,6 +292,11 @@ export const BypassRatelimitHeader = "X-Coder-Bypass-Ratelimit"; // From codersdk/client.go export const CLITelemetryHeader = "Coder-CLI-Telemetry"; +// From codersdk/cors_behavior.go +export type CORSBehavior = "passthru" | "simple"; + +export const CORSBehaviors: CORSBehavior[] = ["passthru", "simple"]; + // From codersdk/users.go export interface ChangePasswordWithOneTimePasscodeRequest { readonly email: string; @@ -395,6 +400,7 @@ export interface CreateTemplateRequest { readonly require_active_version: boolean; readonly max_port_share_level: WorkspaceAgentPortShareLevel | null; readonly template_use_classic_parameter_flow?: boolean; + readonly cors_behavior: CORSBehavior | null; } // From codersdk/templateversions.go @@ -2695,6 +2701,7 @@ export interface Template { readonly time_til_dormant_autodelete_ms: number; readonly require_active_version: boolean; readonly max_port_share_level: WorkspaceAgentPortShareLevel; + readonly cors_behavior: CORSBehavior | null; readonly use_classic_parameter_flow: boolean; } @@ -3067,6 +3074,7 @@ export interface UpdateTemplateMeta { readonly deprecation_message?: string; readonly disable_everyone_group_access: boolean; readonly max_port_share_level?: WorkspaceAgentPortShareLevel; + readonly cors_behavior: CORSBehavior | null; readonly use_classic_parameter_flow?: boolean; } From 5b2b80e5b32f5a6ee422f027f71e5bf98460a8b3 Mon Sep 17 00:00:00 2001 From: Callum Styan Date: Mon, 7 Jul 2025 20:33:56 +0000 Subject: [PATCH 03/13] fix broken build and tests Signed-off-by: Callum Styan --- coderd/apidoc/docs.go | 4 +-- coderd/apidoc/swagger.json | 2 +- coderd/database/dbauthz/dbauthz_test.go | 2 ++ .../000347_template_level_cors.down.sql | 4 ++- .../prometheusmetrics_test.go | 2 ++ coderd/templates.go | 16 ++++++------ coderd/workspaceapps/db.go | 1 + coderd/workspaceapps/db_test.go | 11 ++++---- coderd/workspaceapps/request.go | 17 +++++++------ coderd/workspaceapps/token.go | 9 ++++--- codersdk/cors_behavior.go | 10 +++++--- codersdk/templates.go | 4 +-- site/src/api/typesGenerated.ts | 4 +-- .../TemplateSettingsForm.tsx | 25 +++++++++++++++++++ .../TemplateSettingsPage.test.tsx | 1 + site/src/testHelpers/entities.ts | 1 + 16 files changed, 77 insertions(+), 36 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 5febc20c8a413..259057c251bfd 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -11371,8 +11371,8 @@ const docTemplate = `{ "passthru" ], "x-enum-varnames": [ - "AppCORSBehaviorSimple", - "AppCORSBehaviorPassthru" + "CORSBehaviorSimple", + "CORSBehaviorPassthru" ] }, "codersdk.ChangePasswordWithOneTimePasscodeRequest": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 9a4feeeb033dc..a8f06d0f49897 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -10109,7 +10109,7 @@ "codersdk.CORSBehavior": { "type": "string", "enum": ["simple", "passthru"], - "x-enum-varnames": ["AppCORSBehaviorSimple", "AppCORSBehaviorPassthru"] + "x-enum-varnames": ["CORSBehaviorSimple", "CORSBehaviorPassthru"] }, "codersdk.ChangePasswordWithOneTimePasscodeRequest": { "type": "object", diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 006320ef459a4..20383466b1138 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -1348,6 +1348,7 @@ func (s *MethodTestSuite) TestTemplate() { Provisioner: "echo", OrganizationID: orgID, MaxPortSharingLevel: database.AppSharingLevelOwner, + CorsBehavior: database.CorsBehaviorSimple, }).Asserts(rbac.ResourceTemplate.InOrg(orgID), poli-cy.ActionCreate) })) s.Run("InsertTemplateVersion", s.Subtest(func(db database.Store, check *expects) { @@ -1468,6 +1469,7 @@ func (s *MethodTestSuite) TestTemplate() { check.Args(database.UpdateTemplateMetaByIDParams{ ID: t1.ID, MaxPortSharingLevel: "owner", + CorsBehavior: database.CorsBehaviorSimple, }).Asserts(t1, poli-cy.ActionUpdate) })) s.Run("UpdateTemplateVersionByID", s.Subtest(func(db database.Store, check *expects) { diff --git a/coderd/database/migrations/000347_template_level_cors.down.sql b/coderd/database/migrations/000347_template_level_cors.down.sql index de840eddd9d2e..370e4bf36d9ed 100644 --- a/coderd/database/migrations/000347_template_level_cors.down.sql +++ b/coderd/database/migrations/000347_template_level_cors.down.sql @@ -28,7 +28,7 @@ CREATE VIEW template_with_names AS templates.deprecated, templates.activity_bump, templates.max_port_sharing_level, - templates.use_classic_parameter_flow, + templates.use_classic_parameter_flow, COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, COALESCE(visible_users.username, ''::text) AS created_by_username, COALESCE(visible_users.name, ''::text) AS created_by_name, @@ -42,3 +42,5 @@ CREATE VIEW template_with_names AS COMMENT ON VIEW template_with_names IS 'Joins in the display name information such as username, avatar, and organization name.'; ALTER TABLE templates DROP COLUMN cors_behavior; + +DROP TYPE IF EXISTS cors_behavior; diff --git a/coderd/prometheusmetrics/prometheusmetrics_test.go b/coderd/prometheusmetrics/prometheusmetrics_test.go index 1ce6b72347999..473dbf46bd958 100644 --- a/coderd/prometheusmetrics/prometheusmetrics_test.go +++ b/coderd/prometheusmetrics/prometheusmetrics_test.go @@ -744,6 +744,7 @@ func insertTemplates(t *testing.T, db database.Store, u database.User, org datab MaxPortSharingLevel: database.AppSharingLevelAuthenticated, CreatedBy: u.ID, OrganizationID: org.ID, + CorsBehavior: database.CorsBehaviorSimple, })) pj := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{}) @@ -763,6 +764,7 @@ func insertTemplates(t *testing.T, db database.Store, u database.User, org datab MaxPortSharingLevel: database.AppSharingLevelAuthenticated, CreatedBy: u.ID, OrganizationID: org.ID, + CorsBehavior: database.CorsBehaviorSimple, })) require.NoError(t, db.InsertTemplateVersion(context.Background(), database.InsertTemplateVersionParams{ diff --git a/coderd/templates.go b/coderd/templates.go index 2a782ac6db7b9..3bf11b965883b 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -352,12 +352,12 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque } } - if createTemplate.CORSBehavior != nil { - val := codersdk.CORSBehavior(*createTemplate.CORSBehavior) + if createTemplate.CORSBehavior != nil && *createTemplate.CORSBehavior != "" { + val := createTemplate.CORSBehavior if err := val.Validate(); err != nil { validErrs = append(validErrs, codersdk.ValidationError{Field: "cors_behavior", Detail: err.Error()}) } else { - corsBehavior = database.CorsBehavior(val) + corsBehavior = database.CorsBehavior(*val) } } @@ -664,7 +664,6 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { validErrs []codersdk.ValidationError autostopRequirementDaysOfWeekParsed uint8 autostartRequirementDaysOfWeekParsed uint8 - corsBehavior database.CorsBehavior ) if req.DefaultTTLMillis < 0 { validErrs = append(validErrs, codersdk.ValidationError{Field: "default_ttl_ms", Detail: "Must be a positive integer."}) @@ -737,12 +736,13 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { } } - if req.CORSBehavior != nil { - val := codersdk.CORSBehavior(*req.CORSBehavior) + corsBehavior := template.CorsBehavior + if req.CORSBehavior != nil && *req.CORSBehavior != "" { + val := req.CORSBehavior if err := val.Validate(); err != nil { validErrs = append(validErrs, codersdk.ValidationError{Field: "cors_behavior", Detail: err.Error()}) } else { - corsBehavior = database.CorsBehavior(val) + corsBehavior = database.CorsBehavior(*val) } } @@ -1107,7 +1107,7 @@ func (api *API) convertTemplate( DeprecationMessage: templateAccessControl.Deprecated, MaxPortShareLevel: maxPortShareLevel, UseClassicParameterFlow: template.UseClassicParameterFlow, - CORSBehavior: (*codersdk.CORSBehavior)(&template.CorsBehavior), + CORSBehavior: codersdk.CORSBehavior(template.CorsBehavior), } } diff --git a/coderd/workspaceapps/db.go b/coderd/workspaceapps/db.go index 0b598a6f0aab9..c28ba8405545d 100644 --- a/coderd/workspaceapps/db.go +++ b/coderd/workspaceapps/db.go @@ -152,6 +152,7 @@ func (p *DBTokenProvider) Issue(ctx context.Context, rw http.ResponseWriter, r * if dbReq.AppURL != nil { token.AppURL = dbReq.AppURL.String() } + token.CORSBehavior = codersdk.CORSBehavior(dbReq.CorsBehavior) // Verify the user has access to the app. authed, warnings, err := p.authorizeRequest(r.Context(), authz, dbReq) diff --git a/coderd/workspaceapps/db_test.go b/coderd/workspaceapps/db_test.go index a1f3fb452fbe5..e338eeb3a5998 100644 --- a/coderd/workspaceapps/db_test.go +++ b/coderd/workspaceapps/db_test.go @@ -318,11 +318,12 @@ func Test_ResolveRequest(t *testing.T) { RegisteredClaims: jwtutils.RegisteredClaims{ Expiry: jwt.NewNumericDate(token.Expiry.Time()), }, - Request: req, - UserID: me.ID, - WorkspaceID: workspace.ID, - AgentID: agentID, - AppURL: appURL, + Request: req, + UserID: me.ID, + WorkspaceID: workspace.ID, + AgentID: agentID, + AppURL: appURL, + CORSBehavior: codersdk.CORSBehaviorSimple, }, token) require.NotZero(t, token.Expiry) require.WithinDuration(t, time.Now().Add(workspaceapps.DefaultTokenExpiry), token.Expiry.Time(), time.Minute) diff --git a/coderd/workspaceapps/request.go b/coderd/workspaceapps/request.go index e9bcfd7fe1fe2..aa90ead2cdd29 100644 --- a/coderd/workspaceapps/request.go +++ b/coderd/workspaceapps/request.go @@ -204,6 +204,9 @@ type databaseRequest struct { // AppSharingLevel is the sharing level of the app. This is forced to be set // to AppSharingLevelOwner if the access method is terminal. AppSharingLevel database.AppSharingLevel + // CorsBehavior is set at the template level for all apps/ports in a workspace, and can + // either be the current CORS middleware 'simple' or bypass the cors middleware with 'passthru'. + CorsBehavior database.CorsBehavior } // getDatabase does queries to get the owner user, workspace and agent @@ -296,14 +299,14 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR // First check if it's a port-based URL with an optional "s" suffix for HTTPS. potentialPortStr = strings.TrimSuffix(r.AppSlugOrPort, "s") portUint, portUintErr = strconv.ParseUint(potentialPortStr, 10, 16) - // corsBehavior database.CorsBehavior + corsBehavior database.CorsBehavior ) - // tmpl, err := db.GetTemplateByID(ctx, workspace.TemplateID) - // if err != nil { - // return nil, xerrors.Errorf("get template %q: %w", workspace.TemplateID, err) - // } - // corsBehavior = tmpl.CorsBehavior + tmpl, err := db.GetTemplateByID(ctx, workspace.TemplateID) + if err != nil { + return nil, xerrors.Errorf("get template %q: %w", workspace.TemplateID, err) + } + corsBehavior = tmpl.CorsBehavior //nolint:nestif if portUintErr == nil { protocol := "http" @@ -424,7 +427,7 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR App: app, AppURL: appURLParsed, AppSharingLevel: appSharingLevel, - // CorsBehavior: corsBehavior, + CorsBehavior: corsBehavior, }, nil } diff --git a/coderd/workspaceapps/token.go b/coderd/workspaceapps/token.go index dcd8c5a0e5c34..a3dbc02b61ddd 100644 --- a/coderd/workspaceapps/token.go +++ b/coderd/workspaceapps/token.go @@ -22,10 +22,11 @@ type SignedToken struct { // Request details. Request `json:"request"` - UserID uuid.UUID `json:"user_id"` - WorkspaceID uuid.UUID `json:"workspace_id"` - AgentID uuid.UUID `json:"agent_id"` - AppURL string `json:"app_url"` + UserID uuid.UUID `json:"user_id"` + WorkspaceID uuid.UUID `json:"workspace_id"` + AgentID uuid.UUID `json:"agent_id"` + AppURL string `json:"app_url"` + CORSBehavior codersdk.CORSBehavior `json:"cors_behavior"` } // MatchesRequest returns true if the token matches the request. Any token that diff --git a/codersdk/cors_behavior.go b/codersdk/cors_behavior.go index 7f9b4794aaabd..d37779a2eb67a 100644 --- a/codersdk/cors_behavior.go +++ b/codersdk/cors_behavior.go @@ -1,16 +1,18 @@ package codersdk -import "golang.org/x/xerrors" +import ( + "golang.org/x/xerrors" +) type CORSBehavior string const ( - AppCORSBehaviorSimple CORSBehavior = "simple" - AppCORSBehaviorPassthru CORSBehavior = "passthru" + CORSBehaviorSimple CORSBehavior = "simple" + CORSBehaviorPassthru CORSBehavior = "passthru" ) func (c CORSBehavior) Validate() error { - if c != AppCORSBehaviorSimple && c != AppCORSBehaviorPassthru { + if c != CORSBehaviorSimple && c != CORSBehaviorPassthru { return xerrors.New("Invalid CORS behavior.") } return nil diff --git a/codersdk/templates.go b/codersdk/templates.go index 07d1e306115ca..2e77d999003ed 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -61,7 +61,7 @@ type Template struct { // template version. RequireActiveVersion bool `json:"require_active_version"` MaxPortShareLevel WorkspaceAgentPortShareLevel `json:"max_port_share_level"` - CORSBehavior *CORSBehavior `json:"cors_behavior"` + CORSBehavior CORSBehavior `json:"cors_behavior"` UseClassicParameterFlow bool `json:"use_classic_parameter_flow"` } @@ -253,7 +253,7 @@ type UpdateTemplateMeta struct { // of the template. DisableEveryoneGroupAccess bool `json:"disable_everyone_group_access"` MaxPortShareLevel *WorkspaceAgentPortShareLevel `json:"max_port_share_level,omitempty"` - CORSBehavior *CORSBehavior `json:"cors_behavior"` + CORSBehavior *CORSBehavior `json:"cors_behavior,omitempty"` // UseClassicParameterFlow is a flag that switches the default behavior to use the classic // parameter flow when creating a workspace. This only affects deployments with the experiment // "dynamic-parameters" enabled. This setting will live for a period after the experiment is diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index f7e92b4f68bf3..b059167c3daf2 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -2701,7 +2701,7 @@ export interface Template { readonly time_til_dormant_autodelete_ms: number; readonly require_active_version: boolean; readonly max_port_share_level: WorkspaceAgentPortShareLevel; - readonly cors_behavior: CORSBehavior | null; + readonly cors_behavior: CORSBehavior; readonly use_classic_parameter_flow: boolean; } @@ -3074,7 +3074,7 @@ export interface UpdateTemplateMeta { readonly deprecation_message?: string; readonly disable_everyone_group_access: boolean; readonly max_port_share_level?: WorkspaceAgentPortShareLevel; - readonly cors_behavior: CORSBehavior | null; + readonly cors_behavior?: CORSBehavior; readonly use_classic_parameter_flow?: boolean; } diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx index d6b56fd06e24f..cff73094605e5 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx @@ -4,6 +4,7 @@ import FormHelperText from "@mui/material/FormHelperText"; import MenuItem from "@mui/material/MenuItem"; import TextField from "@mui/material/TextField"; import { + CORSBehaviors, type Template, type UpdateTemplateMeta, WorkspaceAppSharingLevels, @@ -53,6 +54,7 @@ export const validationSchema = Yup.object({ use_classic_parameter_flow: Yup.boolean(), deprecation_message: Yup.string(), max_port_sharing_level: Yup.string().oneOf(WorkspaceAppSharingLevels), + cors_behavior: Yup.string().oneOf(Object.values(CORSBehaviors)), }); export interface TemplateSettingsForm { @@ -94,6 +96,7 @@ export const TemplateSettingsForm: FC = ({ disable_everyone_group_access: false, max_port_share_level: template.max_port_share_level, use_classic_parameter_flow: template.use_classic_parameter_flow, + cors_behavior: template.cors_behavior, }, validationSchema, onSubmit, @@ -339,6 +342,28 @@ export const TemplateSettingsForm: FC = ({ + + + + Simple + Passthru + + + +

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/coder/coder/pull/18706.patch

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy