thinkwork-cli 0.13.1 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/{api-client-4DZ266J5.js → api-client-JYZXYQJR.js} +3 -1
  2. package/dist/bowser-6Z6JFAIR.js +2822 -0
  3. package/dist/chunk-DV3MRBR5.js +43 -0
  4. package/dist/chunk-DVPBI5HM.js +1728 -0
  5. package/dist/chunk-EI5ZX7CN.js +522 -0
  6. package/dist/chunk-EUJ7X6YD.js +126 -0
  7. package/dist/chunk-GG5WKBOX.js +436 -0
  8. package/dist/chunk-I64EZSU5.js +358 -0
  9. package/dist/chunk-ILZKG5WV.js +6151 -0
  10. package/dist/chunk-KMIJ5KGT.js +128 -0
  11. package/dist/chunk-OCPHU34C.js +996 -0
  12. package/dist/chunk-QOERT44A.js +3286 -0
  13. package/dist/chunk-XYM7PE2A.js +2264 -0
  14. package/dist/{chunk-Y2WHJECR.js → chunk-Z2F6RJFR.js} +1 -39
  15. package/dist/cli.js +43080 -13150
  16. package/dist/dist-es-CRV46D5B.js +315 -0
  17. package/dist/dist-es-DCY4IC3Y.js +2000 -0
  18. package/dist/dist-es-ENBUC3JP.js +14726 -0
  19. package/dist/dist-es-G54LBMRN.js +48 -0
  20. package/dist/dist-es-G7PTG75M.js +176 -0
  21. package/dist/dist-es-GKDBE7E6.js +88 -0
  22. package/dist/dist-es-HY3535OI.js +70 -0
  23. package/dist/dist-es-KL7Y5NB2.js +377 -0
  24. package/dist/dist-es-LDE437AM.js +170 -0
  25. package/dist/dist-es-P26FWFLT.js +485 -0
  26. package/dist/dist-es-Q57SZXZ7.js +70 -0
  27. package/dist/dist-es-R7FGL2IB.js +88 -0
  28. package/dist/dist-es-USORBXZ5.js +318 -0
  29. package/dist/dist-es-ZKC2QKD2.js +377 -0
  30. package/dist/drizzle/0076_scheduled_jobs_marco_backfill.sql +2 -1
  31. package/dist/drizzle/0079_seed_tenant_customize_catalog.sql +1 -0
  32. package/dist/drizzle/0090_brain_schema_extraction.sql +10 -0
  33. package/dist/drizzle/0132_drop_computer_tables.sql +3 -0
  34. package/dist/drizzle/0160_compliance_event_types_plugins.sql +2 -0
  35. package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +1 -0
  36. package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +1 -0
  37. package/dist/drizzle/0227_analyst_reader_role.sql +6 -0
  38. package/dist/drizzle/0230_analyst_rls.sql +9 -0
  39. package/dist/drizzle/0250_kg_schema_extraction.sql +27 -0
  40. package/dist/drizzle/0251_brain_stragglers.sql +21 -0
  41. package/dist/drizzle/0253_drop_brain_substrate.sql +2 -0
  42. package/dist/drizzle/0257_agents_runtime_allow_harness.sql +24 -0
  43. package/dist/drizzle/0261_managed_harness_multiplayer.sql +313 -0
  44. package/dist/drizzle/0261_native_auth_control_plane.sql +328 -0
  45. package/dist/drizzle/0262_auth_subscription_tickets.sql +59 -0
  46. package/dist/drizzle/0262_parallel_harness_threads.sql +9 -0
  47. package/dist/drizzle/0263_agentcore_runtime_identifier.sql +69 -0
  48. package/dist/drizzle/0263_drop_workos_auth_runtime.sql +236 -0
  49. package/dist/drizzle/0264_auth_enrollment_attempt_limits.sql +15 -0
  50. package/dist/drizzle/0264_harness_tool_execution_ledger.sql +113 -0
  51. package/dist/drizzle/0265_eval_runs_requester_identity.sql +28 -0
  52. package/dist/drizzle/0265_require_fully_valid_auth_publication.sql +27 -0
  53. package/dist/drizzle/0266_auth_identity_recovery_grants.sql +9 -0
  54. package/dist/drizzle/0266_eval_profiles_runtime_type.sql +12 -0
  55. package/dist/drizzle/0267_ontology_candidate_rejections.sql +53 -0
  56. package/dist/drizzle/0268_identity_crosswalk_routing.sql +182 -0
  57. package/dist/drizzle/0269_ontology_system_map.sql +31 -0
  58. package/dist/event-streams-7JNLJEEL.js +894 -0
  59. package/dist/event-streams-KDF5RWR6.js +1515 -0
  60. package/dist/loadSso-DJVB7BXE.js +576 -0
  61. package/dist/loadSso-JAMNA3WY.js +583 -0
  62. package/dist/plugins/catalog/package.json +1 -2
  63. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
  64. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +0 -69
  65. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
  66. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
  67. package/dist/plugins/catalog/src/contracts.ts +2 -179
  68. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
  69. package/dist/signin-UGDWTGS4.js +821 -0
  70. package/dist/sso-oidc-Q4VXANXY.js +837 -0
  71. package/dist/sts-CWLSE3GH.js +1241 -0
  72. package/dist/sts-DNZLNVOV.js +1245 -0
  73. package/dist/terraform/examples/greenfield/main.tf +223 -66
  74. package/dist/terraform/examples/greenfield/terraform.tfvars.example +15 -1
  75. package/dist/terraform/modules/app/agentcore-gateway/main.tf +305 -0
  76. package/dist/terraform/modules/app/agentcore-gateway/outputs.tf +44 -0
  77. package/dist/terraform/modules/app/agentcore-gateway/scripts/delete_gateway.sh +93 -0
  78. package/dist/terraform/modules/app/agentcore-gateway/scripts/read_gateway.sh +33 -0
  79. package/dist/terraform/modules/app/agentcore-gateway/scripts/reconcile_gateway.sh +543 -0
  80. package/dist/terraform/modules/app/agentcore-gateway/variables.tf +70 -0
  81. package/dist/terraform/modules/app/agentcore-harness/main.tf +237 -8
  82. package/dist/terraform/modules/app/agentcore-harness/outputs.tf +63 -0
  83. package/dist/terraform/modules/app/agentcore-harness/scripts/delete_harness.sh +5 -0
  84. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-lifecycle.mjs +430 -0
  85. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-readback.mjs +180 -0
  86. package/dist/terraform/modules/app/agentcore-harness/scripts/harness-tool-contract.mjs +193 -0
  87. package/dist/terraform/modules/app/agentcore-harness/scripts/prune_harness.sh +5 -0
  88. package/dist/terraform/modules/app/agentcore-harness/scripts/read_harness.sh +5 -0
  89. package/dist/terraform/modules/app/agentcore-harness/scripts/reconcile_harness.sh +5 -0
  90. package/dist/terraform/modules/app/agentcore-harness/variables.tf +72 -1
  91. package/dist/terraform/modules/app/agentcore-identity/main.tf +173 -0
  92. package/dist/terraform/modules/app/agentcore-identity/outputs.tf +51 -0
  93. package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh +122 -0
  94. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_identity.sh +25 -0
  95. package/dist/terraform/modules/app/agentcore-identity/scripts/delete_twenty_identity.sh +16 -0
  96. package/dist/terraform/modules/app/agentcore-identity/scripts/read_identity.sh +40 -0
  97. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_identity.sh +73 -0
  98. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_identity.sh +74 -0
  99. package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_provider.mjs +61 -0
  100. package/dist/terraform/modules/app/agentcore-identity/variables.tf +54 -0
  101. package/dist/terraform/modules/app/appsync-subscriptions/main.tf +127 -21
  102. package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +0 -6
  103. package/dist/terraform/modules/app/appsync-subscriptions/variables.tf +5 -0
  104. package/dist/terraform/modules/app/deployment-control-plane/runner.py +966 -27
  105. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +581 -22
  106. package/dist/terraform/modules/app/lambda-api/agentcore-harness-alarms.tf +88 -0
  107. package/dist/terraform/modules/app/lambda-api/agentcore-identity-alarms.tf +91 -0
  108. package/dist/terraform/modules/app/lambda-api/handlers.tf +298 -74
  109. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +99 -54
  110. package/dist/terraform/modules/app/lambda-api/main.tf +24 -0
  111. package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +181 -4
  112. package/dist/terraform/modules/app/lambda-api/outputs.tf +35 -0
  113. package/dist/terraform/modules/app/lambda-api/runtime-config.tf +3 -21
  114. package/dist/terraform/modules/app/lambda-api/variables.tf +109 -16
  115. package/dist/terraform/modules/foundation/cognito/main.tf +294 -72
  116. package/dist/terraform/modules/foundation/cognito/outputs.tf +42 -0
  117. package/dist/terraform/modules/foundation/cognito/variables.tf +114 -4
  118. package/dist/terraform/modules/thinkwork/main.tf +269 -46
  119. package/dist/terraform/modules/thinkwork/outputs.tf +136 -8
  120. package/dist/terraform/modules/thinkwork/variables.tf +124 -17
  121. package/dist/terraform/schema.graphql +69 -36
  122. package/dist/workspace-defaults/files/USER.md +1 -0
  123. package/package.json +3 -2
  124. package/dist/drizzle/0261_thread_turns_origin_turn_index.sql +0 -11
  125. package/dist/plugins/workos-auth/package.json +0 -22
  126. package/dist/plugins/workos-auth/src/index.ts +0 -30
  127. package/dist/plugins/workos-auth/src/manifest.ts +0 -40
  128. package/dist/plugins/workos-auth/src/provider-contract.ts +0 -39
  129. package/dist/plugins/workos-auth/test/manifest.test.ts +0 -94
  130. package/dist/plugins/workos-auth/tsconfig.json +0 -8
@@ -13,6 +13,30 @@ variable "region" {
13
13
  type = string
14
14
  }
15
15
 
16
+ variable "auth_retirement_phase" {
17
+ description = "Native-auth migration phase. Fresh deployments default to retired; WorkOS callback/bridge routes exist only for explicitly configured upgrade rollback windows."
18
+ type = string
19
+ default = "retired"
20
+
21
+ validation {
22
+ condition = contains(["coexistence", "cutover", "retired"], var.auth_retirement_phase)
23
+ error_message = "auth_retirement_phase must be coexistence, cutover, or retired."
24
+ }
25
+ }
26
+
27
+ variable "auth_migration_recovery_deadline" {
28
+ description = "RFC3339 deadline for completing legacy-session identity migration. Required while auth_retirement_phase is coexistence."
29
+ type = string
30
+ default = ""
31
+
32
+ validation {
33
+ condition = trimspace(var.auth_migration_recovery_deadline) == "" || (
34
+ can(timecmp(var.auth_migration_recovery_deadline, var.auth_migration_recovery_deadline))
35
+ )
36
+ error_message = "auth_migration_recovery_deadline must be empty or an RFC3339 timestamp."
37
+ }
38
+ }
39
+
16
40
  variable "lambda_artifact_bucket" {
17
41
  description = "S3 bucket containing Lambda deployment artifacts"
18
42
  type = string
@@ -135,10 +159,9 @@ variable "appsync_api_url" {
135
159
  type = string
136
160
  }
137
161
 
138
- variable "appsync_api_key" {
139
- description = "AppSync API key"
162
+ variable "appsync_api_id" {
163
+ description = "AppSync subscriptions API ID used as the subscription-ticket audience"
140
164
  type = string
141
- sensitive = true
142
165
  }
143
166
 
144
167
  variable "kb_service_role_arn" {
@@ -147,7 +170,65 @@ variable "kb_service_role_arn" {
147
170
  }
148
171
 
149
172
  variable "agentcore_harness_execution_role_arn" {
150
- description = "AgentCore Harness execution role ARN (THINK-311 U4). Empty string (harness module disabled) omits the Harness invoker grants from the grouped ai policy."
173
+ description = "Legacy Harness execution-role input retained for module compatibility. Request-path PassRole/control-plane grants were removed by THINK-316 U2."
174
+ type = string
175
+ default = ""
176
+ }
177
+
178
+ variable "enable_agentcore_harness_invocation" {
179
+ description = "Grant only the Harness data-plane invocation actions to the request path. Harness control-plane permissions remain outside Lambda."
180
+ type = bool
181
+ default = false
182
+ }
183
+
184
+ variable "enable_agentcore_multiplayer_proof" {
185
+ description = "Enable the non-production THINK-316 AgentCore Identity/Gateway/Harness proof surfaces. Defaults off and never changes ordinary Pi routing."
186
+ type = bool
187
+ default = false
188
+ }
189
+
190
+ variable "agentcore_turn_assertion_key_versions" {
191
+ description = "One or two published AgentCore assertion KMS key versions. Use two only during the bounded rotation overlap."
192
+ type = list(string)
193
+ default = ["v1"]
194
+
195
+ validation {
196
+ condition = (
197
+ length(var.agentcore_turn_assertion_key_versions) >= 1 &&
198
+ length(var.agentcore_turn_assertion_key_versions) <= 2 &&
199
+ length(distinct(var.agentcore_turn_assertion_key_versions)) == length(var.agentcore_turn_assertion_key_versions) &&
200
+ alltrue([for version in var.agentcore_turn_assertion_key_versions : can(regex("^[a-z0-9][a-z0-9-]{0,15}$", version))])
201
+ )
202
+ error_message = "agentcore_turn_assertion_key_versions must contain one or two unique lowercase version labels."
203
+ }
204
+ }
205
+
206
+ variable "agentcore_turn_assertion_active_key_version" {
207
+ description = "Published AgentCore assertion key version used for new signatures."
208
+ type = string
209
+ default = "v1"
210
+
211
+ validation {
212
+ condition = contains(var.agentcore_turn_assertion_key_versions, var.agentcore_turn_assertion_active_key_version)
213
+ error_message = "agentcore_turn_assertion_active_key_version must be present in agentcore_turn_assertion_key_versions."
214
+ }
215
+ }
216
+
217
+ variable "agentcore_proof_oauth_client_id" {
218
+ description = "Synthetic OAuth client id used only by the THINK-316 proof provider."
219
+ type = string
220
+ default = ""
221
+ }
222
+
223
+ variable "agentcore_proof_oauth_client_secret" {
224
+ description = "Synthetic OAuth client secret used only by the THINK-316 proof provider and boundary target."
225
+ type = string
226
+ sensitive = true
227
+ default = ""
228
+ }
229
+
230
+ variable "agentcore_proof_owner_allowlist" {
231
+ description = "Comma-separated synthetic subjects used by the THINK-316 manual authorization-code and mixed-disclosure fixtures. Signed OBO turn participants do not require registration here."
151
232
  type = string
152
233
  default = ""
153
234
  }
@@ -244,18 +325,6 @@ variable "brain_dream_state_schedule_expression" {
244
325
  default = "cron(0 5 * * ? *)"
245
326
  }
246
327
 
247
- variable "retry_dispatcher_enabled" {
248
- description = "Enable the recurring retry-dispatcher schedule (THINK-307). Never enable on a stage before THINK-305, THINK-308, and THINK-309 are live there (parent plan deploy ordering); manual invokes work regardless."
249
- type = bool
250
- default = false
251
- }
252
-
253
- variable "stall_threshold_minutes" {
254
- description = "Minutes of turn inactivity before the stall monitor marks a running turn timed_out; the retry dispatcher reads the same knob as its origin-freshness guard (THINK-306/THINK-307 — one knob, two consumers)."
255
- type = number
256
- default = 5
257
- }
258
-
259
328
  variable "extension_proxy_signing_secret" {
260
329
  description = "Shared HMAC secret used by the generic Admin extension proxy to sign actor context for extension backends."
261
330
  type = string
@@ -549,6 +618,12 @@ variable "knowledge_graph_observations_ingest_enabled" {
549
618
  default = false
550
619
  }
551
620
 
621
+ variable "ontology_scan_sweep_enabled" {
622
+ description = "Enable the recurring per-tenant ontology suggestion scan sweep (THINK-320 U4/KTD-3). Ships disabled; enabled per stage once the Living Map review surfaces land."
623
+ type = bool
624
+ default = false
625
+ }
626
+
552
627
  variable "wiki_source" {
553
628
  description = "Wiki pipeline source dispatch (plan 2026-06-09-004 U10). 'planner' (default) runs the original LLM compile path; 'graph' runs the deterministic graph→wiki materializer over the knowledge-graph mirror and makes successful observation-ingest runs the compile trigger. Variable-ized (not hardcoded) per the wiki-compile env precedent so unrelated deploys don't reset the flag; the Lambda reads it verbatim from env and treats any value other than 'graph' as 'planner'."
554
629
  type = string
@@ -800,6 +875,24 @@ variable "capability_signing_private_key_secret" {
800
875
  default = ""
801
876
  }
802
877
 
878
+ variable "subscription_ticket_signing_key_id" {
879
+ description = "Active Ed25519 subscription-ticket signing key ID."
880
+ type = string
881
+ default = ""
882
+ }
883
+
884
+ variable "subscription_ticket_public_keys" {
885
+ description = "JSON verifier keyring for AppSync subscription tickets; contains public material only."
886
+ type = string
887
+ default = "[]"
888
+ }
889
+
890
+ variable "subscription_ticket_private_key_secret" {
891
+ description = "Secrets Manager name holding the isolated subscription-ticket Ed25519 private key."
892
+ type = string
893
+ default = ""
894
+ }
895
+
803
896
  # ---------------------------------------------------------------------------
804
897
  # THINK-228 U3 — analyst query broker runtime config
805
898
  # ---------------------------------------------------------------------------
@@ -9,15 +9,21 @@
9
9
  locals {
10
10
  create = var.create_cognito
11
11
  create_pre_signup = local.create && var.pre_signup_lambda_zip != ""
12
+ create_pre_token_generation = local.create && trimspace(var.pre_token_generation_lambda_s3_bucket) != "" && trimspace(var.pre_token_generation_lambda_s3_key) != ""
13
+ workos_rollback_enabled = var.auth_retirement_phase != "retired"
12
14
  use_local_custom_auth_artifact = trimspace(var.custom_auth_lambda_zip) != ""
13
15
  use_remote_custom_auth_artifact = trimspace(var.custom_auth_lambda_s3_bucket) != ""
14
16
  custom_auth_artifact_count = (local.use_local_custom_auth_artifact ? 1 : 0) + (local.use_remote_custom_auth_artifact ? 1 : 0)
15
- create_custom_auth = local.create && local.custom_auth_artifact_count == 1
16
-
17
- user_pool_id = local.create ? aws_cognito_user_pool.main[0].id : var.existing_user_pool_id
18
- user_pool_arn = local.create ? aws_cognito_user_pool.main[0].arn : var.existing_user_pool_arn
19
- admin_client_id = local.create ? aws_cognito_user_pool_client.admin[0].id : var.existing_admin_client_id
20
- mobile_client_id = local.create ? aws_cognito_user_pool_client.mobile[0].id : var.existing_mobile_client_id
17
+ create_custom_auth = local.create && local.workos_rollback_enabled && local.custom_auth_artifact_count == 1
18
+
19
+ user_pool_id = local.create ? aws_cognito_user_pool.main[0].id : var.existing_user_pool_id
20
+ user_pool_arn = local.create ? aws_cognito_user_pool.main[0].arn : var.existing_user_pool_arn
21
+ admin_client_id = local.create ? (
22
+ local.workos_rollback_enabled ? aws_cognito_user_pool_client.admin[0].id : aws_cognito_user_pool_client.auth_route["web:local"].id
23
+ ) : var.existing_admin_client_id
24
+ mobile_client_id = local.create ? (
25
+ local.workos_rollback_enabled ? aws_cognito_user_pool_client.mobile[0].id : aws_cognito_user_pool_client.auth_route["mobile:local"].id
26
+ ) : var.existing_mobile_client_id
21
27
  identity_pool_id = local.create ? aws_cognito_identity_pool.main[0].id : var.existing_identity_pool_id
22
28
  oidc_identity_providers = {
23
29
  for provider in var.oidc_identity_providers : provider.provider_name => provider
@@ -25,10 +31,84 @@ locals {
25
31
  saml_identity_providers = {
26
32
  for provider in var.saml_identity_providers : provider.provider_name => provider
27
33
  }
34
+ auth_client_families = {
35
+ web = {
36
+ callback_urls = [for url in var.admin_callback_urls : url if !strcontains(url, ":42010/")]
37
+ logout_urls = var.admin_logout_urls
38
+ refresh_token_days = 30
39
+ }
40
+ mobile = {
41
+ callback_urls = var.mobile_callback_urls
42
+ logout_urls = var.mobile_logout_urls
43
+ refresh_token_days = 90
44
+ }
45
+ desktop = {
46
+ callback_urls = var.desktop_callback_urls
47
+ logout_urls = var.desktop_callback_urls
48
+ refresh_token_days = 30
49
+ }
50
+ cli = {
51
+ callback_urls = var.cli_callback_urls
52
+ logout_urls = var.cli_logout_urls
53
+ refresh_token_days = 30
54
+ }
55
+ }
56
+ static_auth_routes = merge(
57
+ {
58
+ for family, config in local.auth_client_families : "${family}:local" => {
59
+ route_key = "local"
60
+ client_family = family
61
+ provider_names = ["COGNITO"]
62
+ explicit_auth_flows = ["ALLOW_USER_PASSWORD_AUTH", "ALLOW_USER_SRP_AUTH", "ALLOW_REFRESH_TOKEN_AUTH"]
63
+ callback_urls = config.callback_urls
64
+ logout_urls = config.logout_urls
65
+ refresh_token_days = config.refresh_token_days
66
+ }
67
+ },
68
+ var.google_oauth_client_id != "" ? {
69
+ for family, config in local.auth_client_families : "${family}:google" => {
70
+ route_key = "google"
71
+ client_family = family
72
+ provider_names = ["Google"]
73
+ explicit_auth_flows = ["ALLOW_REFRESH_TOKEN_AUTH"]
74
+ callback_urls = config.callback_urls
75
+ logout_urls = config.logout_urls
76
+ refresh_token_days = config.refresh_token_days
77
+ }
78
+ } : {},
79
+ var.microsoft_oauth_client_id != "" ? {
80
+ for family, config in local.auth_client_families : "${family}:microsoft" => {
81
+ route_key = "microsoft"
82
+ client_family = family
83
+ provider_names = ["MicrosoftOrganizations"]
84
+ explicit_auth_flows = ["ALLOW_REFRESH_TOKEN_AUTH"]
85
+ callback_urls = config.callback_urls
86
+ logout_urls = config.logout_urls
87
+ refresh_token_days = config.refresh_token_days
88
+ }
89
+ } : {},
90
+ )
91
+ tenant_auth_routes = merge({}, [
92
+ for connection in var.tenant_entra_connections : {
93
+ for family, config in local.auth_client_families : "${family}:entra:${lower(connection.tenant_id)}" => {
94
+ route_key = "entra-${replace(lower(connection.tenant_id), "-", "")}"
95
+ client_family = family
96
+ provider_names = [connection.provider_name]
97
+ explicit_auth_flows = ["ALLOW_REFRESH_TOKEN_AUTH"]
98
+ callback_urls = config.callback_urls
99
+ logout_urls = config.logout_urls
100
+ refresh_token_days = config.refresh_token_days
101
+ }
102
+ }
103
+ ]...)
104
+ auth_routes = merge(local.static_auth_routes, local.tenant_auth_routes)
28
105
  }
29
106
 
30
107
  resource "terraform_data" "custom_auth_artifact_validation" {
31
- input = local.custom_auth_artifact_count
108
+ input = {
109
+ phase = var.auth_retirement_phase
110
+ artifact_count = local.custom_auth_artifact_count
111
+ }
32
112
 
33
113
  lifecycle {
34
114
  precondition {
@@ -40,6 +120,16 @@ resource "terraform_data" "custom_auth_artifact_validation" {
40
120
  condition = !local.use_remote_custom_auth_artifact || trimspace(var.custom_auth_lambda_s3_key) != ""
41
121
  error_message = "custom_auth_lambda_s3_key must be set when custom_auth_lambda_s3_bucket is set."
42
122
  }
123
+
124
+ precondition {
125
+ condition = !local.create_custom_auth || var.api_auth_secret != ""
126
+ error_message = "The WorkOS rollback custom-auth Lambda requires api_auth_secret until auth_retirement_phase is retired."
127
+ }
128
+
129
+ precondition {
130
+ condition = var.microsoft_oauth_client_id == "" || var.microsoft_oauth_tenant != ""
131
+ error_message = "microsoft_oauth_tenant is required when microsoft_oauth_client_id is configured because Cognito requires an exact tenant issuer."
132
+ }
43
133
  }
44
134
  }
45
135
 
@@ -69,26 +159,6 @@ resource "aws_iam_role_policy_attachment" "pre_signup_basic" {
69
159
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
70
160
  }
71
161
 
72
- resource "aws_iam_role_policy" "pre_signup_cognito" {
73
- count = local.create_pre_signup ? 1 : 0
74
- name = "cognito-access"
75
- role = aws_iam_role.pre_signup[0].id
76
-
77
- policy = jsonencode({
78
- Version = "2012-10-17"
79
- Statement = [{
80
- Effect = "Allow"
81
- Action = [
82
- "cognito-idp:ListUsers",
83
- "cognito-idp:AdminLinkProviderForUser",
84
- "cognito-idp:AdminCreateUser",
85
- "cognito-idp:AdminSetUserPassword"
86
- ]
87
- Resource = aws_cognito_user_pool.main[0].arn
88
- }]
89
- })
90
- }
91
-
92
162
  resource "aws_lambda_function" "pre_signup" {
93
163
  count = local.create_pre_signup ? 1 : 0
94
164
  function_name = "thinkwork-${var.stage}-cognito-pre-signup"
@@ -111,15 +181,62 @@ resource "aws_lambda_permission" "cognito_pre_signup" {
111
181
  }
112
182
 
113
183
  ################################################################################
114
- # Custom Auth Challenge Lambda
184
+ # Pre Token Generation Client Cutoff Lambda
185
+ ################################################################################
186
+
187
+ resource "aws_iam_role" "pre_token_generation" {
188
+ count = local.create_pre_token_generation ? 1 : 0
189
+ name = "thinkwork-${var.stage}-cognito-pre-token-role"
190
+
191
+ assume_role_policy = jsonencode({
192
+ Version = "2012-10-17"
193
+ Statement = [{
194
+ Effect = "Allow"
195
+ Principal = { Service = "lambda.amazonaws.com" }
196
+ Action = "sts:AssumeRole"
197
+ }]
198
+ })
199
+ }
200
+
201
+ resource "aws_iam_role_policy_attachment" "pre_token_generation_basic" {
202
+ count = local.create_pre_token_generation ? 1 : 0
203
+ role = aws_iam_role.pre_token_generation[0].name
204
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
205
+ }
206
+
207
+ resource "aws_lambda_function" "pre_token_generation" {
208
+ count = local.create_pre_token_generation ? 1 : 0
209
+ function_name = "thinkwork-${var.stage}-cognito-pre-token-client-deny"
210
+ s3_bucket = var.pre_token_generation_lambda_s3_bucket
211
+ s3_key = var.pre_token_generation_lambda_s3_key
212
+ handler = "index.handler"
213
+ runtime = "nodejs20.x"
214
+ timeout = 5
215
+ role = aws_iam_role.pre_token_generation[0].arn
216
+
217
+ environment {
218
+ variables = {
219
+ COGNITO_DENIED_APP_CLIENT_IDS = join(",", var.denied_app_client_ids)
220
+ }
221
+ }
222
+ }
223
+
224
+ resource "aws_lambda_permission" "cognito_pre_token_generation" {
225
+ count = local.create_pre_token_generation ? 1 : 0
226
+ statement_id = "AllowCognitoInvoke"
227
+ action = "lambda:InvokeFunction"
228
+ function_name = aws_lambda_function.pre_token_generation[0].function_name
229
+ principal = "cognito-idp.amazonaws.com"
230
+ source_arn = aws_cognito_user_pool.main[0].arn
231
+ }
232
+
233
+ ################################################################################
234
+ # Legacy WorkOS Custom Auth Challenge Lambda (rollback runtime only)
115
235
  ################################################################################
116
236
 
117
237
  resource "aws_iam_role" "custom_auth" {
118
238
  count = local.create_custom_auth ? 1 : 0
119
- # Release deployments use a distinct managed name so the first convergent
120
- # apply does not collide with custom-auth Lambdas created during the legacy
121
- # manual rollout. Local source deployments retain the original name.
122
- name = local.use_remote_custom_auth_artifact ? "thinkwork-${var.stage}-cognito-custom-auth-release-role" : "thinkwork-${var.stage}-cognito-custom-auth-role"
239
+ name = local.use_remote_custom_auth_artifact ? "thinkwork-${var.stage}-cognito-custom-auth-release-role" : "thinkwork-${var.stage}-cognito-custom-auth-role"
123
240
 
124
241
  assume_role_policy = jsonencode({
125
242
  Version = "2012-10-17"
@@ -155,13 +272,6 @@ resource "aws_lambda_function" "custom_auth" {
155
272
  }
156
273
 
157
274
  source_code_hash = local.use_local_custom_auth_artifact ? filebase64sha256(var.custom_auth_lambda_zip) : null
158
-
159
- lifecycle {
160
- precondition {
161
- condition = var.api_auth_secret != ""
162
- error_message = "A Cognito custom-auth Lambda artifact requires api_auth_secret so custom-auth challenges can be verified."
163
- }
164
- }
165
275
  }
166
276
 
167
277
  resource "aws_lambda_permission" "cognito_custom_auth" {
@@ -213,6 +323,30 @@ resource "aws_cognito_user_pool" "main" {
213
323
  }
214
324
  }
215
325
 
326
+ schema {
327
+ name = "entra_tenant_id"
328
+ attribute_data_type = "String"
329
+ required = false
330
+ mutable = true
331
+
332
+ string_attribute_constraints {
333
+ min_length = 0
334
+ max_length = 36
335
+ }
336
+ }
337
+
338
+ schema {
339
+ name = "entra_object_id"
340
+ attribute_data_type = "String"
341
+ required = false
342
+ mutable = true
343
+
344
+ string_attribute_constraints {
345
+ min_length = 0
346
+ max_length = 36
347
+ }
348
+ }
349
+
216
350
  schema {
217
351
  name = "tenant_id"
218
352
  attribute_data_type = "String"
@@ -242,12 +376,19 @@ resource "aws_cognito_user_pool" "main" {
242
376
  }
243
377
 
244
378
  dynamic "lambda_config" {
245
- for_each = local.create_pre_signup || local.create_custom_auth ? [1] : []
379
+ for_each = local.create_pre_signup || local.create_pre_token_generation || local.create_custom_auth ? [1] : []
246
380
  content {
247
381
  pre_sign_up = local.create_pre_signup ? aws_lambda_function.pre_signup[0].arn : null
248
382
  define_auth_challenge = local.create_custom_auth ? aws_lambda_function.custom_auth[0].arn : null
249
383
  create_auth_challenge = local.create_custom_auth ? aws_lambda_function.custom_auth[0].arn : null
250
384
  verify_auth_challenge_response = local.create_custom_auth ? aws_lambda_function.custom_auth[0].arn : null
385
+ dynamic "pre_token_generation_config" {
386
+ for_each = local.create_pre_token_generation ? [1] : []
387
+ content {
388
+ lambda_arn = aws_lambda_function.pre_token_generation[0].arn
389
+ lambda_version = "V2_0"
390
+ }
391
+ }
251
392
  }
252
393
  }
253
394
 
@@ -298,21 +439,108 @@ resource "aws_cognito_identity_provider" "google" {
298
439
  }
299
440
 
300
441
  attribute_mapping = {
301
- email = "email"
302
- name = "name"
303
- username = "sub"
442
+ email = "email"
443
+ email_verified = "email_verified"
444
+ name = "name"
445
+ username = "sub"
446
+ }
447
+ }
448
+
449
+ resource "aws_cognito_identity_provider" "microsoft_organizations" {
450
+ count = local.create && var.microsoft_oauth_client_id != "" ? 1 : 0
451
+ user_pool_id = aws_cognito_user_pool.main[0].id
452
+ provider_name = "MicrosoftOrganizations"
453
+ provider_type = "OIDC"
454
+
455
+ provider_details = {
456
+ client_id = var.microsoft_oauth_client_id
457
+ client_secret = var.microsoft_oauth_client_secret
458
+ authorize_scopes = "openid email profile"
459
+ oidc_issuer = "https://login.microsoftonline.com/${lower(var.microsoft_oauth_tenant)}/v2.0"
460
+ attributes_request_method = "GET"
461
+ }
462
+
463
+ attribute_mapping = {
464
+ # Entra v2 does not guarantee an `email` claim for every work/school
465
+ # account. `preferred_username` is the supported sign-in hint and keeps
466
+ # Cognito's required email attribute populated as a stable normalized
467
+ # correlation value. ThinkWork only consults it after
468
+ # Cognito validates this exact Entra route; ongoing admission uses the
469
+ # immutable Cognito issuer/subject binding created by that first login.
470
+ email = "preferred_username"
471
+ name = "name"
472
+ username = "sub"
473
+ "custom:entra_tenant_id" = "tid"
474
+ "custom:entra_object_id" = "oid"
304
475
  }
305
476
  }
306
477
 
307
478
  locals {
308
479
  identity_providers = concat(
309
480
  var.google_oauth_client_id != "" ? ["Google"] : [],
481
+ var.microsoft_oauth_client_id != "" ? ["MicrosoftOrganizations"] : [],
310
482
  keys(local.oidc_identity_providers),
311
483
  keys(local.saml_identity_providers),
312
484
  ["COGNITO"]
313
485
  )
314
486
  }
315
487
 
488
+ ################################################################################
489
+ # Route-specific public app clients
490
+ ################################################################################
491
+
492
+ resource "aws_cognito_user_pool_client" "auth_route" {
493
+ for_each = local.create ? local.auth_routes : {}
494
+
495
+ name = "Thinkwork-${title(each.value.client_family)}-${title(each.value.route_key)}"
496
+ user_pool_id = aws_cognito_user_pool.main[0].id
497
+
498
+ generate_secret = false
499
+ allowed_oauth_flows_user_pool_client = true
500
+ allowed_oauth_flows = ["code"]
501
+ allowed_oauth_scopes = ["openid", "email", "profile"]
502
+ explicit_auth_flows = each.value.explicit_auth_flows
503
+ supported_identity_providers = each.value.provider_names
504
+ callback_urls = distinct(each.value.callback_urls)
505
+ logout_urls = distinct(each.value.logout_urls)
506
+ enable_token_revocation = true
507
+ prevent_user_existence_errors = "ENABLED"
508
+ access_token_validity = 1
509
+ id_token_validity = 1
510
+ refresh_token_validity = each.value.refresh_token_days
511
+
512
+ token_validity_units {
513
+ access_token = "hours"
514
+ id_token = "hours"
515
+ refresh_token = "days"
516
+ }
517
+
518
+ read_attributes = [
519
+ "email",
520
+ "email_verified",
521
+ "name",
522
+ "custom:tenant_id",
523
+ "custom:entra_tenant_id",
524
+ "custom:entra_object_id",
525
+ ]
526
+
527
+ write_attributes = [
528
+ "email",
529
+ "email_verified",
530
+ "name",
531
+ "custom:tenant_id",
532
+ "custom:entra_tenant_id",
533
+ "custom:entra_object_id",
534
+ ]
535
+
536
+ depends_on = [
537
+ aws_cognito_identity_provider.google,
538
+ aws_cognito_identity_provider.microsoft_organizations,
539
+ aws_cognito_identity_provider.oidc,
540
+ aws_cognito_identity_provider.saml,
541
+ ]
542
+ }
543
+
316
544
  resource "aws_cognito_identity_provider" "oidc" {
317
545
  for_each = local.create ? local.oidc_identity_providers : {}
318
546
 
@@ -363,8 +591,8 @@ resource "aws_cognito_identity_provider" "saml" {
363
591
  ################################################################################
364
592
 
365
593
  resource "aws_cognito_user_pool_client" "admin" {
366
- count = local.create ? 1 : 0
367
- name = "ThinkworkAdmin"
594
+ count = local.create && local.workos_rollback_enabled ? 1 : 0
595
+ name = "ThinkworkAdminLegacy"
368
596
  user_pool_id = aws_cognito_user_pool.main[0].id
369
597
 
370
598
  allowed_oauth_flows_user_pool_client = true
@@ -380,8 +608,8 @@ resource "aws_cognito_user_pool_client" "admin" {
380
608
 
381
609
  supported_identity_providers = local.identity_providers
382
610
 
383
- callback_urls = distinct(concat(var.admin_callback_urls, var.desktop_callback_urls))
384
- logout_urls = distinct(concat(var.admin_logout_urls, var.desktop_callback_urls))
611
+ callback_urls = distinct(concat(var.admin_callback_urls, var.desktop_callback_urls, var.cli_callback_urls))
612
+ logout_urls = distinct(concat(var.admin_logout_urls, var.desktop_callback_urls, var.cli_logout_urls))
385
613
 
386
614
  access_token_validity = 1
387
615
  id_token_validity = 1
@@ -418,8 +646,8 @@ resource "aws_cognito_user_pool_client" "admin" {
418
646
  ################################################################################
419
647
 
420
648
  resource "aws_cognito_user_pool_client" "mobile" {
421
- count = local.create ? 1 : 0
422
- name = "ThinkworkMobile"
649
+ count = local.create && local.workos_rollback_enabled ? 1 : 0
650
+ name = "ThinkworkMobileLegacy"
423
651
  user_pool_id = aws_cognito_user_pool.main[0].id
424
652
 
425
653
  explicit_auth_flows = [
@@ -477,16 +705,25 @@ resource "aws_cognito_identity_pool" "main" {
477
705
  identity_pool_name = var.identity_pool_name != "" ? var.identity_pool_name : "thinkwork-${var.stage}-identity-pool"
478
706
  allow_unauthenticated_identities = false
479
707
 
480
- cognito_identity_providers {
481
- client_id = aws_cognito_user_pool_client.admin[0].id
482
- provider_name = "cognito-idp.${var.region}.amazonaws.com/${aws_cognito_user_pool.main[0].id}"
483
- server_side_token_check = false
708
+ dynamic "cognito_identity_providers" {
709
+ for_each = local.workos_rollback_enabled ? {
710
+ admin = aws_cognito_user_pool_client.admin[0].id
711
+ mobile = aws_cognito_user_pool_client.mobile[0].id
712
+ } : {}
713
+ content {
714
+ client_id = cognito_identity_providers.value
715
+ provider_name = "cognito-idp.${var.region}.amazonaws.com/${aws_cognito_user_pool.main[0].id}"
716
+ server_side_token_check = false
717
+ }
484
718
  }
485
719
 
486
- cognito_identity_providers {
487
- client_id = aws_cognito_user_pool_client.mobile[0].id
488
- provider_name = "cognito-idp.${var.region}.amazonaws.com/${aws_cognito_user_pool.main[0].id}"
489
- server_side_token_check = false
720
+ dynamic "cognito_identity_providers" {
721
+ for_each = aws_cognito_user_pool_client.auth_route
722
+ content {
723
+ client_id = cognito_identity_providers.value.id
724
+ provider_name = "cognito-idp.${var.region}.amazonaws.com/${aws_cognito_user_pool.main[0].id}"
725
+ server_side_token_check = true
726
+ }
490
727
  }
491
728
 
492
729
  tags = {
@@ -520,21 +757,6 @@ resource "aws_iam_role" "authenticated" {
520
757
  })
521
758
  }
522
759
 
523
- resource "aws_iam_role_policy" "authenticated_appsync" {
524
- count = local.create ? 1 : 0
525
- name = "appsync-access"
526
- role = aws_iam_role.authenticated[0].id
527
-
528
- policy = jsonencode({
529
- Version = "2012-10-17"
530
- Statement = [{
531
- Effect = "Allow"
532
- Action = "appsync:GraphQL"
533
- Resource = "*"
534
- }]
535
- })
536
- }
537
-
538
760
  resource "aws_cognito_identity_pool_roles_attachment" "main" {
539
761
  count = local.create ? 1 : 0
540
762
  identity_pool_id = aws_cognito_identity_pool.main[0].id