Skip to content

Commit

Permalink
feature: allow the org option to be false
Browse files Browse the repository at this point in the history
Previously, being part of a certain GitHub org was a hard requirement.

The ability to opt out of this behaviour allows granting access to
packages for outside collaborators of a repository, and adds the ability
to use multiple GitHub orgs in the package access config.
  • Loading branch information
n4bb12 committed Nov 27, 2021
1 parent 1b48183 commit c383166
Show file tree
Hide file tree
Showing 12 changed files with 500 additions and 88 deletions.
162 changes: 114 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ This is a Verdaccio plugin that offers GitHub OAuth integration for both the bro

### Features

- The Verdaccio login button redirects you to GitHub. If you have access, you return as a logged-in user. Logout works, too.
- The Verdaccio usage info is updated including copy-to-clipboard.
- Use the built-in command-line tool for quick and easy npm configuration.
- Use GitHub organizations, teams, or repositories to configure permissions.
- The Verdaccio login button redirects you to GitHub instead of showing a login form. Logout works, too.
- Login can be limited to members of a certain GitHub org.
- Package access/publish/unpublish can be limited to GitHub orgs, teams, repos, and users.
- The usage info is updated for use with GitHub OAuth.
- A built-in command-line tool helps you configure npm.

### Compatibility

Expand Down Expand Up @@ -73,44 +74,70 @@ middlewares:

auth:
github-oauth-ui:
org: GITHUB_ORG # (required)
client-id: GITHUB_CLIENT_ID # (required)
client-secret: GITHUB_CLIENT_SECRET # (required)
enterprise-origin: GITHUB_ENTERPRISE_ORIGIN # (optional)
client-id: GITHUB_CLIENT_ID
client-secret: GITHUB_CLIENT_SECRET
org: GITHUB_ORG
enterprise-origin: GITHUB_ENTERPRISE_ORIGIN # (if you are using an enterprise instance)
```
#### Notes
#### Using environment variables
- The plugin options can be actual values or the names of environment variables containing the values.
- The plugin options can be specified under either the `middlewares` or the `auth` node.
- The plugin name must be included under both `middlewares` and `auth` nodes.
The plugin options can be actual values or the names of environment variables containing the values.
#### `org` (required)
For example, either of the below will work:
- `client-id: abc`
- `client-id: GITHUB_CLIENT_ID` and set an environment variable `GITHUB_CLIENT_ID=abc`.

Members of this org will be able to authenticate.
The environment variable names can be freely chosen. The above is just an example.

#### `client-id` and `client-secret` (required)
#### `client-id` and `client-secret` (required, string)

These values can be obtained from the GitHub OAuth app page at https://github.com/settings/developers.

#### `enterprise-origin` (optional)
#### `org` (required, string | false)

Set this if you are using GitHub Enterprise. Example: `https://github.example.com`
The name of a GitHub org, for example, `n4bb12-oauth-test`.

If set to a `string`, it limits the ability to log in to Verdaccio to members of
this GitHub org.

If set to `false`, everybody with a GitHub account can log in. Permissions are
then purely determined by the [package access configuration](https://verdaccio.org/docs/packages/).

Note that setting this to `false` changes the semantics of `$authenticated`
since everybody can have a GitHub account. To limit package access based on
GitHub orgs, teams, repos, and users, you can use one of the `github/`-prefixed
group names as described below.

For example, to limit package access to members of a GitHub org, you can use
`github/owner/ORG_NAME` as a permission group. This effectively results in the
same access restrictions as using `$authenticated` with this option set to a
string, except that every GitHub user can log in (but not see or use anything).

#### `enterprise-origin` (optional, string | false)

If you are using a GitHub Enterprise instance, set this to the base URL of your
instance, for example: `https://github.example.com`.

Remove this option, or set it to `false` if you are using the public GitHub.

### Package Access

The following groups are added during login and can be used to configure package permissions:

- `$authenticated`
- `github/owner/GITHUB_USER` for the user's personal GitHub account
- `github/owner/GITHUB_ORG` for every GitHub org the user is a member of
- `github/owner/GITHUB_ORG/team/GITHUB_TEAM` for every GitHub team the user is a member of
- `github/owner/GITHUB_ORG/repo/GITHUB_REPO` for every GitHub repository the user has access to
- `GITHUB_USER` — the user's login name
- `github/owner/GITHUB_USER` — the user's personal GitHub account
- `github/owner/GITHUB_ORG` — for every GitHub org the user is a member of
- `github/owner/GITHUB_ORG/team/GITHUB_TEAM` — for every GitHub team the user is a member of
- `github/owner/GITHUB_ORG/repo/GITHUB_REPO` — for every GitHub repository the user has access to (including outside collaborators)

Note that visibility to orgs, org teams, and org repositories requires

These groups are deprecated but still work:
Additionally, the following deprecated groups are added:

- `github/GITHUB_ORG` for every GitHub org the user is a member of
- `github/GITHUB_ORG/GITHUB_TEAM` for every GitHub team the user is a member of
- `github/GITHUB_ORG` for every GitHub org the user is a member of
- `github/GITHUB_ORG/GITHUB_TEAM` for every GitHub team the user is a member of

You can use these groups as shown below:

Expand All @@ -126,7 +153,7 @@ packages:
# limit actions to team members
unpublish: github/owner/GITHUB_ORG/team/GITHUB_TEAM
bar:
# limit actions to repository members (does not work for outside collaborators)
# limit actions to repository members (including outside collaborators)
access: github/owner/GITHUB_ORG/repo/GITHUB_REPO
```

Expand All @@ -149,10 +176,12 @@ See the [global-agent](https://github.com/gajus/global-agent#environment-variabl
### Verdaccio UI
- Click the login button and get redirected to GitHub.
- Authorize the registry to access your GitHub user and org info. You only need to do this once. If your org is private, make sure to click the <kbd>Request</kbd> or <kbd>Grant</kbd> button to get `read:org` access when prompted to authorize.
- Once completed, you'll be redirected back to the Verdaccio registry.
You are now logged in.
- Authorize the registry to access your GitHub user and org info.
You only need to do this once. If your org is private, make sure to click the
<kbd>Request</kbd> or <kbd>Grant</kbd> button to get `read:org` access when
prompted to authorize Verdaccio.
- Once completed, you'll be redirected back to Verdaccio.
- You are now logged in 🎉.
### Command Line
Expand Down Expand Up @@ -186,7 +215,8 @@ $ npm whoami --registry http://localhost:4873
n4bb12
```
If you see your GitHub username, you are ready to start installing and publishing packages.
If you see your GitHub username, you are ready to start installing and
publishing packages.
## Logout
Expand All @@ -196,48 +226,84 @@ Click the <kbd>Logout</kbd> button as per usual.
### Command Line
Unless OAuth access is revoked in the GitHub settings, the token is valid indefinitely.
Unless OAuth access is revoked in the GitHub settings, the token is valid
indefinitely.
## Revoke Tokens
To invalidate your active login tokens you need to revoke access on the GitHub OAuth app:
To invalidate your active login tokens you need to revoke access on the GitHub
OAuth app:
- Go to https://github.com/settings/applications
- Find your Verdaccio app
- Click the <kbd>Revoke</kbd> button as shown below
![](screenshots/revoke.png)
If you have created the GitHub OAuth app, you can also revoke access for all users:
If you have created the GitHub OAuth app, you can also revoke access for all
users:
- Go to https://github.com/settings/applications
- Find your Verdaccio app
- Click the app name
- On the app detail page click the <kbd>Revoke all user tokens</kbd> button
## Troubleshooting
### "Failed requesting GitHub user info"
### Missing permission groups after logging in
- Double-check that your configured client id and client secret are correct.
- If you are behind a proxy, make sure you are also passing through the query parameters to Verdaccio, see https://github.com/n4bb12/verdaccio-github-oauth-ui/issues/47#issuecomment-643814163 for an `nginx` example.
If the GitHub org or some of its contents are private, users will need to grant
`read:org` permission during login to allow Verdaccio to see this information.
Users can request or grant this permission during the OAuth flow (i.e. during
first login) by clicking on the <kbd>Request</kbd> or <kbd>Grant</kbd> button
next to each org when prompted to authorize Verdaccio to access GitHub information.
If users accidentally skipped this step, go to https://github.com/settings/applications,
find the Verdaccio app, and grant `read:org` access from there.
### Error: "verdaccio-github-oauth-ui plugin not found"
Avoid using a global installation of Verdaccio. Despite what Verdaccio examples
or documentation suggest, globally installed plugins may not work.
### Plugin not detected when installed globally
Verdaccio loads plugins by requiring them from various locations.
Global `node_modules` are NOT included in this search because they are NOT part
of the Node.js resolve algorithm. See
[#13](https://github.com/n4bb12/verdaccio-github-oauth-ui/issues/13#issuecomment-435296117)
for more info.
Verdaccio loads plugins by requiring them but global `node_modules` are NOT searched by the node resolve algorithm. Despite what examples or documentation might be suggesting, globally installed plugins are not supported. Some solutions that worked for others:
Solutions that worked for others:
- Add your global `node_modules` folder to the `NODE_PATH` environment variable.
This hints to Node.js where else to search in addition to default locations.
- Create a `package.json` and install Verdaccio + plugins locally.
- If you are using npm, try using yarn classic. Yarn installs modules a bit
differently such that globally installed plugins are found.
- Deploy Verdaccio by extending the official docker image. It uses a local
Verdaccio installation by default. See `Dockerfile` and `docker.sh` in
[this example](https://gist.github.com/n4bb12/523e8347a580f596cbf14d0d791b5927).
### Error: "Failed requesting GitHub user info"
- Double-check that your configured client id and client secret are correct.
- If you are behind a proxy, make sure you are also passing through the query
parameters to Verdaccio. See
[#47](https://github.com/n4bb12/verdaccio-github-oauth-ui/issues/47#issuecomment-643814163)
for an example using `nginx`.
- If you are using npm, switch to yarn. yarn installs modules a bit differently, such that globally installed plugins are found.
- Create a `package.json` and install verdaccio + plugins locally.
- Add your global `node_modules` folder to the `NODE_PATH` environment variable to give Node.js a hint to search for modules here, too.
- Extend the official docker image. See this `docker.sh` and `Dockerfile` in this [example](https://gist.github.com/n4bb12/523e8347a580f596cbf14d0d791b5927).
### Error: "Your auth token is no longer valid. Please log in again."
More info: https://github.com/n4bb12/verdaccio-github-oauth-ui/issues/13#issuecomment-435296117
If login access is restricted to a certain GitHub org, please see
[missing orgs, teams, repos](missing-orgs-teams-repos) and
[#5](https://github.com/n4bb12/verdaccio-github-oauth-ui/issues/5#issuecomment-417371679).
### "Your auth token is no longer valid. Please log in again."
### Error: "Access denied: User "..." is not a member of "..."
If your GitHub org is private, the org membership visibility might be restricted. If this is the case, your org members need to grant `read:org` permission during login.
If login access is restricted to a certain GitHub org, this could be an expected
error meaning that the user trying to log in is not a member of the required org.
They can request this during fist login by clicking the <kbd>Request</kbd> or <kbd>Grant</kbd> button when prompted to authorize Verdaccio with GitHub.
See the related [org](#org-required-string-false) plugin option.
If you or a team member accidentally skipped this step, go to https://github.com/settings/applications, find your Verdaccio registry, and grant `read:org` access from there.
If you see this error despite being a member of the configured org, please see
[missing orgs, teams, repos](missing-orgs-teams-repos).
18 changes: 10 additions & 8 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ middlewares:

auth:
github-oauth-ui:
org: GITHUB_ORG # required, people within this org will be able to authenticate
client-id: GITHUB_CLIENT_ID # required
client-secret: GITHUB_CLIENT_SECRET # required
# enterprise-origin: GITHUB_ENTERPRISE_ORIGIN # optional, set this if you are using github enterprise
client-id: GITHUB_CLIENT_ID
client-secret: GITHUB_CLIENT_SECRET
org: false
enterprise-origin: false
htpasswd:
file: ./htpasswd

Expand All @@ -30,9 +30,11 @@ security:

packages:
"@*/*":
access: $authenticated
publish: $authenticated
access: github/owner/n4bb12-oauth-testing
publish: github/owner/n4bb12-oauth-testing
unpublish: github/owner/n4bb12-oauth-testing

"**":
access: $authenticated
publish: $authenticated
access: github/owner/n4bb12-oauth-testing
publish: github/owner/n4bb12-oauth-testing
unpublish: github/owner/n4bb12-oauth-testing
10 changes: 10 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,13 @@ export const cliAuthorizeUrl = "/oauth/authorize"

export const publicGitHubOrigin = "https://github.com"
export const publicGitHubApiOrigin = "https://api.github.com"

/**
* See https://verdaccio.org/docs/en/packages
*/
export const authenticatedUserGroups = [
"$all",
"@all",
"$authenticated",
"@authenticated",
] as const
29 changes: 20 additions & 9 deletions src/server/plugin/AuthCore.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import uniq from "lodash/uniq"
import { stringify } from "querystring"

import { authenticatedUserGroups } from "../../constants"
import { logger } from "../../logger"
import { User, Verdaccio } from "../verdaccio"
import { Config, getConfig } from "./Config"

export class AuthCore {
private readonly requiredOrg = "github/" + getConfig(this.config, "org")
private readonly org = getConfig(this.config, "org")
private readonly requiredGroup = this.org ? "github/owner/" + this.org : null
private readonly configuredGroups = this.getConfiguredGroups()

constructor(
Expand All @@ -32,17 +33,22 @@ export class AuthCore {

async createAuthenticatedUser(
username: string,
providerGroups: string[],
groups: string[],
): Promise<User> {
const relevantGroups = providerGroups.filter(
const relevantGroups = groups.filter(
(group) => group in this.configuredGroups,
)

// See https://verdaccio.org/docs/en/packages
relevantGroups.push(username)

if (this.requiredGroup) {
relevantGroups.push(this.requiredGroup)
}

const user: User = {
name: username,
groups: ["$all", "@all", "$authenticated", "@authenticated"],
real_groups: uniq([username, this.requiredOrg, ...relevantGroups]),
groups: [...authenticatedUserGroups],
real_groups: uniq(relevantGroups.filter(Boolean).sort()),
}
logger.log("Created authenticated user", user)

Expand All @@ -66,13 +72,18 @@ export class AuthCore {
}

authenticate(username: string, groups: string[]): boolean {
if (!groups.includes(this.requiredOrg)) {
if (this.requiredGroup && !groups.includes(this.requiredGroup)) {
logger.error(
`Access denied: User "${username}" is not a member of "${this.requiredOrg}"`,
`Access denied: User "${username}" is not a member of "${this.requiredGroup}"`,
)
return false
}

if (!groups.length) {
logger.error(`Access denied: User "${username}" does not have any groups`)
return false
}

return true
}
}
10 changes: 7 additions & 3 deletions src/server/plugin/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ export type VerdaccioConfig = Omit<IncorrectVerdaccioConfig, "packages"> & {
}

export interface PluginConfig {
org: string
"client-id": string
"client-secret": string
"enterprise-origin"?: string
org: string | false
"enterprise-origin"?: string | false
}

export type PluginConfigKey = keyof PluginConfig
Expand Down Expand Up @@ -98,14 +98,18 @@ export function validateConfig(config: Config) {
validateProp(
config,
"org",
ow.string.nonEmpty.not.startsWith(publicGitHubOrigin),
ow.any(
ow.string.nonEmpty.not.startsWith(publicGitHubOrigin),
ow.boolean.false,
),
)
validateProp(
config,
"enterprise-origin",
ow.any(
ow.undefined,
ow.string.url.nonEmpty.not.startsWith(publicGitHubOrigin),
ow.boolean.false,
),
)
}
Loading

0 comments on commit c383166

Please sign in to comment.