From eed33e3cec8b9cc5855ee90cd5214991303eaa07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Meadows-J=C3=B6nsson?= Date: Wed, 8 Apr 2026 10:58:14 +0200 Subject: [PATCH 1/5] Fix API spec to match hexpm implementation - OAuth2 tokens use Bearer prefix in Authorization header - Short URL endpoint returns 201 with {url}, not 200 with {short_code, short_url} - Delete all keys returns 200 with authing key body, not 204 - Document downloads as alias for total_downloads sort option - Remove body text from 204 response (HTTP spec violation) - Remove duplicate Body blocks in OAuth token request (API Blueprint violation) --- apiary.apib | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/apiary.apib b/apiary.apib index 965146f..0d269d7 100644 --- a/apiary.apib +++ b/apiary.apib @@ -71,10 +71,10 @@ Any endpoint implementation for Hex should support SSL termination by default, t ##### OAuth2 Token ``` -$ curl -H "Authorization: token" https://hex.pm/api +$ curl -H "Authorization: Bearer token" https://hex.pm/api ``` -OAuth2 tokens are obtained via the [Device Authorization Grant (RFC 8628)](https://datatracker.ietf.org/doc/html/rfc8628). See the OAuth section below for details. OAuth tokens have read-only permissions by default; write operations require two-factor authentication via the `x-hex-otp` header. +OAuth2 tokens are obtained via the [Device Authorization Grant (RFC 8628)](https://datatracker.ietf.org/doc/html/rfc8628). See the OAuth section below for details. OAuth2 tokens must be sent with the `Bearer` prefix in the `Authorization` header. OAuth tokens have read-only permissions by default; write operations require two-factor authentication via the `x-hex-otp` header. ##### API Token @@ -717,6 +717,7 @@ This collection is paginated. + name - Package name, ascending + recent_downloads - Number of package downloads in the last 90 days, descending + total_downloads - Total number of package downloads, descending + + downloads - Alias for `total_downloads` + inserted_at - Package insertion time, descending + updated_at - Package last update time, descending + search: phoenix (string, optional) @@ -1444,7 +1445,33 @@ Removes all API keys for the authenticated user. Authorization: e2bfe5e65b9235acebe06df8027905c0 -+ Response 204 ++ Response 200 (application/json) + + Returns the authing key. + + + Attributes (API Key) + + + Body + + { + "name": "my_computer", + "authing_key": true, + "permissions": [ + { + "domain": "api", + "resource": "write" + } + ], + "revoke_at": null, + "last_use": { + "ip": "192.168.1.1", + "user_agent": "Hex/2.0.0 (Elixir/1.14.0) (OTP/25.0)", + "used_at": "2014-04-21T18:00:00Z" + }, + "inserted_at": "2014-04-21T17:20:12Z", + "updated_at": "2014-04-21T17:20:12Z", + "url": "https://hex.pm/api/keys/my_computer" + } ## API Key [/keys/{name}] @@ -1653,17 +1680,15 @@ Creates a shortened URL. "url": "https://hex.pm/packages/plug/1.0.0" } -+ Response 200 (application/json) ++ Response 201 (application/json) + Attributes - + `short_code` (string, required) - The short code - + `short_url` (string, required) - The full shortened URL + + `url` (string, required) - The full shortened URL + Body { - "short_code": "abc123", - "short_url": "https://hex.pm/l/abc123" + "url": "https://hex.pm/l/abc123" } ## Auth [/auth] @@ -1683,8 +1708,6 @@ Verifies that the provided authentication token is valid and has the required pe + Response 204 - Authentication is valid. - + Response 401 (application/json) Authentication failed. @@ -1756,14 +1779,10 @@ Exchanges credentials for access and refresh tokens. Supports multiple grant typ + `device_code` (string, optional) - Required for device_code grant + `refresh_token` (string, optional) - Required for refresh_token grant - + Body (Device Code Grant) + + Body grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS&client_id=78ea6566-89fd-481e-a1d6-7d9d78eacca8 - + Body (Refresh Token Grant) - - grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...&client_id=78ea6566-89fd-481e-a1d6-7d9d78eacca8 - + Response 200 (application/json) Returned when the token request is successful. From e6e1bfabf3ea111b8ef6e0929ce8558718615791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Meadows-J=C3=B6nsson?= Date: Wed, 8 Apr 2026 10:58:18 +0200 Subject: [PATCH 2/5] Add CI to validate API Blueprint syntax Builds drafter from source and runs it against apiary.apib. Fails on both errors and warnings. Pin checkout action to SHA, restrict permissions, and disable credential persistence. --- .github/workflows/ci.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..47f3abd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Build and install drafter + run: | + git clone --branch v5.1.0 --depth 1 --recursive https://github.com/apiaryio/drafter.git /tmp/drafter + cd /tmp/drafter + cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + cmake --build build --target drafter + sudo cp build/drafter /usr/local/bin/ + + - name: Validate API Blueprint + run: | + output=$(drafter -l apiary.apib 2>&1) + echo "$output" + if echo "$output" | grep -q "warning:"; then + echo "::error::API Blueprint has warnings" + exit 1 + fi From 98aa64d886b153d5d4dfcc8229f58a825285db0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Meadows-J=C3=B6nsson?= Date: Wed, 8 Apr 2026 10:59:49 +0200 Subject: [PATCH 3/5] Add CodeQL, zizmor, and Dependabot for GitHub Actions - CodeQL scans Actions workflows for security issues - Zizmor audits workflow security - Dependabot keeps pinned action versions current --- .github/dependabot.yml | 8 ++++++ .github/workflows/codeql.yml | 52 ++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/codeql.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6cc0071 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + cooldown: + default-days: 7 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..6199f03 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,52 @@ +name: "CodeQL Advanced" + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: "29 8 * * 1" + +permissions: + contents: read + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + permissions: + security-events: write + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Initialize CodeQL + uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4 + with: + category: "/language:${{matrix.language}}" + + zizmor: + name: Zizmor + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Run zizmor + uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0 From 26ba1e0ea7dcbacbee65b0e94aedb55e2d7f359c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Meadows-J=C3=B6nsson?= Date: Wed, 8 Apr 2026 12:34:52 +0200 Subject: [PATCH 4/5] Fix drafter build on Ubuntu 24.04 Add missing include for GCC 13+ compatibility. --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47f3abd..b24f03e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,8 @@ jobs: run: | git clone --branch v5.1.0 --depth 1 --recursive https://github.com/apiaryio/drafter.git /tmp/drafter cd /tmp/drafter + # Fix missing include for GCC 13+ + sed -i '1i #include ' packages/drafter/src/utils/so/JsonIo.cc cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --target drafter sudo cp build/drafter /usr/local/bin/ From b46e701cf5dd3850f085ae695a21bf17d38b54a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Meadows-J=C3=B6nsson?= Date: Wed, 8 Apr 2026 12:40:57 +0200 Subject: [PATCH 5/5] Fix drafter binary path in CI The cmake build places the binary in a subdirectory, not directly in the build root. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b24f03e..9a144f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: sed -i '1i #include ' packages/drafter/src/utils/so/JsonIo.cc cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --target drafter - sudo cp build/drafter /usr/local/bin/ + sudo cp $(find build -name drafter -type f -executable) /usr/local/bin/ - name: Validate API Blueprint run: |