Skip to content

feat: Add support for publishing packages from GitHub organizations#138

Open
meiseayoung wants to merge 5 commits into
vlang:masterfrom
meiseayoung:feat/organization-support
Open

feat: Add support for publishing packages from GitHub organizations#138
meiseayoung wants to merge 5 commits into
vlang:masterfrom
meiseayoung:feat/organization-support

Conversation

@meiseayoung

Copy link
Copy Markdown

Summary

This PR adds support for publishing packages from GitHub organization repositories. Currently, VPM only allows users to publish packages from their personal GitHub accounts. This change enables users who are members of GitHub organizations to publish packages from organization repositories.

Problem

When a user (e.g., meiseayoung) is a member of a GitHub organization (e.g., v-hono) and wants to publish packages from organization repositories, they receive the error: "You must submit a package from your own account".

Solution

  1. Fetch user's organizations during OAuth login - When a user logs in via GitHub OAuth, we now fetch their organization memberships using the GitHub API (/user/orgs)

  2. Store organization memberships - Organizations are stored in a new UserOrganization table

  3. Validate against organizations - When creating a package, the URL is validated against both the user's account AND their organizations

  4. Use organization as package prefix - If the URL belongs to an organization, the package name uses the org name as prefix (e.g., v-hono.hono instead of meiseayoung.hono)

Changes

File Description
src/entity/organization.v New UserOrganization entity
src/repo/organization.v Database operations for organizations
src/auth.v Fetch user's organizations during OAuth login
src/usecase/package/packages.v Support organization URLs and prefixes
src/package.v Pass organization info to create function
src/usecase/package/packages_test.v Unit tests
src/repo/organization_test.v Unit tests

Database Migration

CREATE TABLE IF NOT EXISTS "UserOrganization" (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL,
    org_name VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_user_org_user_id ON "UserOrganization" (user_id);

- Add UserOrganization entity to store user's org memberships
- Fetch user's organizations during OAuth login via GitHub API
- Modify check_vcs() to accept URLs from user's organizations
- Automatically use organization name as package prefix when publishing from org repos

This allows users who are members of GitHub organizations to publish
packages from organization repositories. The package name will use
the organization name as prefix (e.g., 'v-hono.hono' instead of
'meiseayoung.hono' when publishing from v-hono organization).

Closes #XXX
- Add tests for extract_owner_from_url function
- Add tests for check_vcs backward compatibility
- Add tests for check_vcs_with_orgs new functionality
- Add tests for is_valid_mod_name validation
- Add tests for UserOrganization entity and membership logic

All 2 test files pass with 20+ test cases covering:
- Own account publishing (existing behavior)
- Organization member publishing (new feature)
- Non-member organization rejection
- Admin bypass
- Edge cases and error handling
@JalonSolov

JalonSolov commented Jan 13, 2026

Copy link
Copy Markdown
Contributor

If you run v git-fmt-hook install, it will install a pre-commit hook in your clone that will run v fmt -w on your files on every commit.

@meiseayoung

Copy link
Copy Markdown
Author

If you run v git-fmt-hook install, it will install a pre-commit hook in your clone that will run v fmt -w on your files on every commit.

got it,thank you.

@medvednikov

Copy link
Copy Markdown
Member

@codex review

@medvednikov

Copy link
Copy Markdown
Member

Review: vlang/vpm#138

Reviewed head c749c1a: 5 commits, 9 changed files. Verdict: request changes. The feature direction is sensible, but there are three blocking correctness/security issues.

##([GitHub Docs]1)h tokens cannot call /user/orgs**

The callback now calls /user/orgs, but login_link() still requests no OAuth scope. GitHub requires an OAuth token with at least user or read:org; an insufficiently scoped token receives 403 Forbidden. Because the callback only handles status 200 and otherwise continues logging the user in, fresh users will have no organizations stored and the main feature will not work. ([GitHub Docs]1)

Request the minimal read:org scope in the authorization URL, handle a rejected or missing scope explicitly, and account for existing users needing to reauthorize.

2. [P1] Prefix matching authorizes repositories owned by a different account

format_url() produces a value such as https://github.com/acme without a terminating slash. The new organization check uses starts_with, so membership in acme authorizes a URL such as:

https://github.com/acme-inc/some-repository

The subsequent owner extraction returns acme-inc, which is not in user_orgs, so the package can then be registered under the submitting user’s namespace. This is an authorization bypass. The personal-account check has the same prefix problem. se the URL once, extract the first path segment, normalize its case, and compare the complete owner value:

owner == username || owner in user_orgs

At minimum, add a regression test proving that membership in acme does not authorize acme-inc.

3. [P1] Organization packages cannot be edited

Creation was changed to use create_with_orgs, but the edit path still calls update_package_info(). That function:

  1. validates the URL using check_vcs(repo_url, usr.username) with an empty organization list; and
  2. always rewrites the package name using usr.username as the prefix.

Consequently, editing even just the description of a normal organization package fails because its unchanged URL does not belong to the submitting user’s personal account. If validation were bypassed, the update would incorrectly move the package back into the user namespace. the same exact-owner resolver for both creation and update. The declared OrganizationsRepo interface should probably be injected into UseCase, rather than having the HTTP handler pass a caller-supplied organization array only during creation. 4. [P2] Only the first 30 organizations are stored

The request calls /user/orgs once without pagination parameters. GitHub returns 30 results by default, so users belonging to more than 30 organizations cannot publish from organizations on later pages. ([GitHub Docs]1)

Follow GitHub’s pagination links—or loop over page with per_page=100—until all memberships have been retrieved.

5. [P2] Membership persistence suppresses every database error

save_user_organizations() ignores deletion errors and continues past every insertion error. As a result, its caller’s or block cannot report normal SQL failures. The refresh can silently leave stale authorization records or a partially stored organization list, and the delete/insert sequence is not atomic. A non-200 GitHub response also leaves any existing memberships untouched while login continues. ause these rows are used for authorization, propagate failures and replace the memberships transactionally. Failed authorization refreshes should fail closed rather than silently preserving potentially revoked membership.

Test and CI gaps

The package tests exercise the URL helpers and validator, but not create_with_orgs() or the edit lifecycle. The repository test never calls OrganizationsRepo; it only tests entity values and a separate in-memory helper. minimum additional coverage should include exact owner matching, creation with the organization prefix, editing an unchanged organization package, paginated memberships, and database refresh failures. The current CI workflow has formatting and production-build steps but no test step, and the current PR workflow run concluded action_required, so there is no passing automated validation for this head. ould not execute the V build locally because this environment could not fetch the repository, so the findings above are from static inspection. Reply post review to submit them as a GitHub REQUEST_CHANGES review on PR #138.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants