From 71c84a8bb2c44901cf23d69efcd452ed1949cdeb Mon Sep 17 00:00:00 2001 From: DevCats Date: Mon, 17 Nov 2025 13:28:05 -0500 Subject: [PATCH] fix: jfrog oauth username extraction from oauth jwt token (#539) ## Description Add username extraction from jfrog JWT OAuth token with fallback to coder username. ## Type of Change - [ ] New module - [ ] New template - [X] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information **Path:** `registry/coder/modules/jfrog-oauth` **New version:** `v1.2.2` **Breaking change:** [ ] Yes [X] No ## Template Information **Path:** `registry/[namespace]/templates/[template-name]` ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun fmt`) - [X] Changes tested locally ## Related Issues --- registry/coder/modules/jfrog-oauth/README.md | 15 ++++++++++--- registry/coder/modules/jfrog-oauth/main.tf | 23 ++++++++++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/registry/coder/modules/jfrog-oauth/README.md b/registry/coder/modules/jfrog-oauth/README.md index b2a129d1..50311de7 100644 --- a/registry/coder/modules/jfrog-oauth/README.md +++ b/registry/coder/modules/jfrog-oauth/README.md @@ -16,7 +16,7 @@ Install the JF CLI and authenticate package managers with Artifactory using OAut module "jfrog" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jfrog-oauth/coder" - version = "1.2.1" + version = "1.2.2" agent_id = coder_agent.example.id jfrog_url = "https://example.jfrog.io" username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username" @@ -39,6 +39,15 @@ module "jfrog" { This module is usable by JFrog self-hosted (on-premises) Artifactory as it requires configuring a custom integration. This integration benefits from Coder's [external-auth](https://coder.com/docs/v2/latest/admin/external-auth) feature and allows each user to authenticate with Artifactory using an OAuth flow and issues user-scoped tokens to each user. For configuration instructions, see this [guide](https://coder.com/docs/v2/latest/guides/artifactory-integration#jfrog-oauth) on the Coder documentation. +## Username Handling + +The module automatically extracts your JFrog username directly from the OAuth token's JWT payload. This preserves special characters like dots (`.`), hyphens (`-`), and accented characters that Coder normalizes in usernames. + +**Priority order:** + +1. **JWT extraction** (default) - Extracts username from OAuth token, preserving special characters +2. **Fallback to `username_field`** - If JWT extraction fails, uses Coder username or email + ## Examples Configure the Python pip package manager to fetch packages from Artifactory while mapping the Coder email to the Artifactory username. @@ -47,7 +56,7 @@ Configure the Python pip package manager to fetch packages from Artifactory whil module "jfrog" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jfrog-oauth/coder" - version = "1.2.1" + version = "1.2.2" agent_id = coder_agent.example.id jfrog_url = "https://example.jfrog.io" username_field = "email" @@ -76,7 +85,7 @@ The [JFrog extension](https://open-vsx.org/extension/JFrog/jfrog-vscode-extensio module "jfrog" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/jfrog-oauth/coder" - version = "1.2.1" + version = "1.2.2" agent_id = coder_agent.example.id jfrog_url = "https://example.jfrog.io" username_field = "username" # If you are using GitHub to login to both Coder and Artifactory, use username_field = "username" diff --git a/registry/coder/modules/jfrog-oauth/main.tf b/registry/coder/modules/jfrog-oauth/main.tf index 922f6442..416ca3dc 100644 --- a/registry/coder/modules/jfrog-oauth/main.tf +++ b/registry/coder/modules/jfrog-oauth/main.tf @@ -76,8 +76,27 @@ variable "package_managers" { } locals { - # The username field to use for artifactory - username = var.username_field == "email" ? data.coder_workspace_owner.me.email : data.coder_workspace_owner.me.name + jwt_parts = try(split(".", data.coder_external_auth.jfrog.access_token), []) + jwt_payload = try(local.jwt_parts[1], "") + payload_padding = local.jwt_payload == "" ? "" : ( + length(local.jwt_payload) % 4 == 0 ? "" : + length(local.jwt_payload) % 4 == 2 ? "==" : + length(local.jwt_payload) % 4 == 3 ? "=" : + "" + ) + + jwt_username = try( + regex( + "/users/([^/]+)", + jsondecode(base64decode("${local.jwt_payload}${local.payload_padding}"))["sub"] + )[0], + "" + ) + + username = coalesce( + local.jwt_username != "" ? local.jwt_username : null, + var.username_field == "email" ? data.coder_workspace_owner.me.email : data.coder_workspace_owner.me.name + ) jfrog_host = split("://", var.jfrog_url)[1] common_values = { JFROG_URL = var.jfrog_url