{"id":15041872,"url":"https://github.com/discord/access","last_synced_at":"2025-04-08T09:12:19.476Z","repository":{"id":230095751,"uuid":"778067377","full_name":"discord/access","owner":"discord","description":"Access, a centralized portal for employees to transparently discover, request, and manage their access for all internal systems needed to do their jobs","archived":false,"fork":false,"pushed_at":"2025-03-31T00:25:26.000Z","size":1159,"stargazers_count":379,"open_issues_count":8,"forks_count":52,"subscribers_count":80,"default_branch":"main","last_synced_at":"2025-04-01T08:39:52.388Z","etag":null,"topics":["access","authorization","okta","permissions","rbac","security"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/discord.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-27T02:26:38.000Z","updated_at":"2025-03-27T06:10:37.000Z","dependencies_parsed_at":"2024-06-03T19:06:36.229Z","dependency_job_id":"45eba12f-6f48-405c-9042-ad3914a03977","html_url":"https://github.com/discord/access","commit_stats":null,"previous_names":["discord/access"],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/discord%2Faccess","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/discord%2Faccess/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/discord%2Faccess/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/discord%2Faccess/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/discord","download_url":"https://codeload.github.com/discord/access/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247809964,"owners_count":20999816,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["access","authorization","okta","permissions","rbac","security"],"created_at":"2024-09-24T20:46:35.086Z","updated_at":"2025-04-08T09:12:19.444Z","avatar_url":"https://github.com/discord.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/discord/access/main/public/logo.png\" width=\"350\"\u003e\u003c/p\u003e\n\n# ACCESS\n\nMeet Access, a centralized portal for employees to transparently discover, request, and manage their access for all internal systems needed to do their jobs. If you're interested in the project, come chat with us!\n\n\u003cp align=\"center\"\u003e\u003ca href=\"https://discord.gg/access-enjoyers\"\u003e\u003cimg src=\"https://discordapp.com/api/guilds/1232815453907189922/widget.png?style=banner2\" alt=\"Join our Discord!\"\u003e\u003c/a\u003e\u003c/p\u003e\n\n## Purpose\n\nThe access service exists to help answer the following questions for each persona:\n\n- All Users\n  - What do I have access to?\n  - What does a teammate have access to that I don’t?\n  - What groups and roles are available?\n  - Can I get access?\n- Team Leads\n  - How do I give access to a new team member easily?\n  - How do I give temporary access to an individual for a cross-functional effort?\n  - Which roles do I administer?\n  - How can I create, merge, or split a role based on a team re-org?\n- Application Owners\n  - Who has access to my application?\n  - How do I setup access for a new application?\n  - How do I create a new access group for my application?\n  - How do I give a role access to one of my application's groups?\n\n## Development Setup\n\nAccess is a React and Typescript single-page application (SPA) with a Flask API that connects to the Okta API.\n\nYou'll need an Okta API Token from an Okta user with the `Group Admin` and `Application Admin`\nOkta administrator roles granted as well as all Group permissions (ie. `Manage groups` checkbox checked)\nin a custom Admin role. If you want to manage Groups which grant Okta Admin permissions, then the Okta API\nToken will need to be created from an Okta user with the `Super Admin` Okta administrator role.\n\n### Flask\n\nCreate a `.env` file in the repo root with the following variables:\n\n```\nCURRENT_OKTA_USER_EMAIL=\u003cYOUR_OKTA_USER_EMAIL\u003e\nOKTA_DOMAIN=\u003cYOUR_OKTA_DOMAIN\u003e # For example, \"mydomain.oktapreview.com\"\nOKTA_API_TOKEN=\u003cYOUR_SANDBOX_API_TOKEN\u003e\nDATABASE_URI=\"sqlite:///access.db\"\nCLIENT_ORIGIN_URL=http://localhost:3000\nREACT_APP_API_SERVER_URL=http://localhost:6060\n```\n\nNext, run the following commands to set up your python virtual environment. Access can be run with Python 3.11 and above:\n\n```\npython3 -m venv venv\n. venv/bin/activate\npip install -r requirements.txt\n```\n\nAfterwards, seed the db:\n\n```\nflask db upgrade\nflask init \u003cYOUR_OKTA_USER_EMAIL\u003e\n```\n\nFinally, you can run the server:\n\n```\nflask run\n```\n\nGo to [http://localhost:6060/api/users](http://localhost:6060/api/users) to view the API.\n\n### Node\n\nIn a separate window, setup and run nodejs:\n\n```\nnpm install\n```\n\n```\nnpm start\n```\n\nGo to [http://localhost:3000/](http://localhost:3000/) to view the React SPA.\n\n#### Generating Typescript React-Query API Client\n\nWe use [openapi-codegen](https://github.com/fabien0102/openapi-codegen) to generate a Typescript React-Query v4 API Fetch Client based on our Swagger API schema available at [http://localhost:6060/api/swagger.json](http://localhost:6060/api/swagger.json). We've modified that generated Swagger schema in [api/swagger.json](api/swagger.json), which is then used in [openapi-codegen.config.ts](openapi-codegen.config.ts) by the following commands:\n\n```\nnpm install @openapi-codegen/cli\nnpm install @openapi-codegen/typescript\nnpm install --only=dev\nnpx openapi-codegen gen api\n```\n\n## Tests\n\nWe use tox to run our tests, which should be installed into the python venv from\nour `requirements.txt`.\n\nInvoke the tests using `tox -e test`.\n\n## Linting\n\nRun `tox -e ruff` and `tox -e mypy` to run the linters.\n\n## Production Setup\n\nCreate a `.env.production` file in the repo root with the following variables. Access supports running against PostgreSQL 14 and above.\n\n```\nOKTA_DOMAIN=\u003cYOUR_OKTA_DOMAIN\u003e # For example, \"mydomain.okta.com\"\nOKTA_API_TOKEN=\u003cYOUR_OKTA_API_TOKEN\u003e\nDATABASE_URI=\u003cYOUR_DATABASE_URI\u003e # For example, \"postgresql+pg8000://postgres:postgres@localhost:5432/access\"\nCLIENT_ORIGIN_URL=http://localhost:3000\nREACT_APP_API_SERVER_URL=\"\"\nFLASK_SENTRY_DSN=https://\u003ckey\u003e@sentry.io/\u003cproject\u003e\nREACT_SENTRY_DSN=https://\u003ckey\u003e@sentry.io/\u003cproject\u003e\n```\n\n### Google Cloud CloudSQL Configuration\n\nIf you want to use the CloudSQL Python Connector, set the following variables in your `.env.production` file:\n\n```\nCLOUDSQL_CONNECTION_NAME=\u003cYOUR_CLOUDSQL_CONNECTION_NAME\u003e # For example, \"project:region:instance-name\"\nDATABASE_URI=\"postgresql+pg8000://\"\nDATABASE_USER=\u003cYOUR_DATABASE_USER\u003e # For a service account, this is the service account's email without the .gserviceaccount.com domain suffix.\nDATABASE_NAME=\u003cYOUR_DATABASE_NAME\u003e\nDATABASE_USES_PUBLIC_IP=[True|False]\n```\n\n### Authentication\n\nAuthentication is required when running Access in production. Currently, we support\n[OpenID Connect (OIDC)](https://openid.net/developers/how-connect-works/) (including Okta)\nand [Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/applications/configure-apps/self-hosted-apps/) as methods to authenticate users to Access.\n\n#### OpenID Connect (OIDC)\n\nTo use OpenID Connect (OIDC) authentication, such as with Okta:\n\nGo to your Okta Admin dashboard -\u003e Applications -\u003e Create App Integration.\n\nIn the Create a new app integration, select:\n- Sign-in method: `OIDC - OpenID Connect`\n- Application type: `Web Application`\n\nThen on the New Web App Integration page:\n- App integration name: `Access`\n- Logo: (optional)\n- Grant type:\n  - Client acting on behalf of user: `Authorization Code`\n- Sign-in redirect URIs: `https://\u003cYOUR_ACCESS_DEPLOYMENT_DOMAIN_NAME\u003e/oidc/authorize`\n- Sign-out redirect URIs: `https://\u003cYOUR_ACCESS_DEPLOYMENT_DOMAIN_NAME\u003e/oidc/logout`\n\nThen click `Save` and go to the General tab of the new app integration to find\nthe `Client ID` and `Client secret`. You'll need these for the next step.\n\nCreate a `client_secrets.json` file containing your OIDC client secrets, that looks something like the following:\n```\n{\n  \"secrets\": {\n    \"client_id\":\"\u003cYOUR_OKTA_APPLICATION_CLIENT_ID\u003e\",\n    \"client_secret\":\"\u003cYOUR_OKTA_APPLICATION_CLIENT_SECRET\u003e\",\n    \"issuer\": \"https://\u003cYOUR_OKTA_INSTANCE\u003e.okta.com/\"\n  }\n}\n```\n\nThen set the following variables in your `.env.production` file:\n```\n# Generate a good secret key using `python -c 'import secrets; print(secrets.token_hex())'`\n# this is used to encrypt Flask cookies\nSECRET_KEY=\u003cYOUR_SECRET_KEY\u003e\n# The path to your client_secrets.json file or if you prefer, inline the entire JSON string\nOIDC_CLIENT_SECRETS=./client_secrets.json or '{\"secrets\":..'\n```\n\n#### Cloudflare Access\n\nTo use Cloudflare Access authentication, set up a\n[Self-Hosted Cloudflare Access Application](https://developers.cloudflare.com/cloudflare-one/applications/configure-apps/self-hosted-apps/)\nusing a Cloudflare Tunnel. Next, set the following variables in your `.env.production` file:\n\n```\n# Your Cloudflare \"Team domain\" under Zero Trust -\u003e Settings -\u003e Custom Pages in the Cloudflare dashboard\n# For example, \"mydomain.cloudflareaccess.com\"\nCLOUDFLARE_TEAM_DOMAIN=\u003cCLOUDFLARE_ACCESS_TEAM_DOMAIN\u003e\n# Your Cloudflare \"Audience\" tag under Zero Trust -\u003e Access -\u003e Applications -\u003e \u003cYour Application\u003e -\u003e Overview in the Cloudflare dashboard\n# found under \"Application Audience (AUD) Tag\"\nCLOUDFLARE_APPLICATION_AUDIENCE=\u003cCLOUFLARE_ACCESS_AUDIENCE_TAG\u003e\n```\n\n### Docker Build and Run\n\nBuild the Docker image:\n\n```\ndocker build -t access .\n```\n\nOr build and run it using Docker Compose:\n\n```\ndocker compose up --build\n```\n\nThe command above will build and run the container.\n\nGo to [http://localhost:3000/](http://localhost:3000/) to view the application.\n\n### Docker configuration\n\nBefore launching the container with Docker, make sure to configure `.env.psql` and `.env.production`:\n\n#### Configuration for `.env.psql`\n\nThe `.env.psql` file is where you configure the PostgreSQL server credentials, which is also Dockerized.\n\n- `POSTGRES_USER`: Specifies the username for the PostgreSQL server.\n- `POSTGRES_PASSWORD`: Specifies the password for the PostgreSQL server.\n\n#### Configuration for `.env.production`\n\nThe `.env.production` file is where you configure the application.\n\n- `OKTA_DOMAIN`: Specifies the [Okta](https://okta.com) domain to use.\n- `OKTA_API_TOKEN`: Specifies the [Okta](https://okta.com) [API Token](https://developer.okta.com/docs/api/openapi/okta-management/management/tag/ApiToken/) to use.\n- `DATABASE_URI`: Specifies the Database connection URI. **Example:** `postgresql+pg8000://\u003cPOSTGRES_USER\u003e:\u003cPOSTGRES_PASSWORD\u003e@postgres:5432/\u003cDB_NAME\u003e`.\n- `CLIENT_ORIGIN_URL`: Specifies the origin URL which is used by CORS.\n- `REACT_APP_API_SERVER_URL`: Specifies the API base URL which is used by the frontend. Set to an empty string \"\" to use the same URL as the frontend.\n- `FLASK_SENTRY_DSN`: See the [Sentry documentation](https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/). **[OPTIONAL] You can safely remove this from your env file**\n- `REACT_SENTRY_DSN`: See the [Sentry documentation](https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/). **[OPTIONAL] You can safely remove this from your env file**\n- `CLOUDFLARE_TEAM_DOMAIN`: Specifies the Team Domain used by [Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/).\n- `CLOUDFLARE_APPLICATION_AUDIENCE`: Specifies the Audience Tag used by [Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/).\n- `SECRET_KEY`: Specifies the secret key used to encrypt flask cookies. WARNING: Ensure this is something secure you can generate a good secret key using `python -c 'import secrets; print(secrets.token_hex())'`.\n- `OIDC_CLIENT_SECRETS`: Specifies the path to your client_secrets.json file or if you prefer, inline the entire JSON string.\n\n**Check out `.env.psql.example` or `.env.production.example` for an example configuration file structure**.\n\n**NOTE:**\n\nIf you are using Cloudflare Access, ensure that you configure `CLOUDFLARE_TEAM_DOMAIN` and `CLOUDFLARE_APPLICATION_AUDIENCE`. `SECRET_KEY` and `OIDC_CLIENT_SECRETS` do not need to be set and can be removed from your env file.\n\nElse, if you are using a generic OIDC identity provider (such as Okta), then you should configure `SECRET_KEY` and `OIDC_CLIENT_SECRETS`. `CLOUDFLARE_TEAM_DOMAIN` and `CLOUDFLARE_APPLICATION_AUDIENCE` do not need to be set and can be removed from your env file. Make sure to also mount your `client-secrets.json` file to the container if you don't have it inline.\n\n### Access application configuration\n\n_All front-end and back-end configuration overrides are **optional**._\n\nThe default config for the application is at [`config/config.default.json`](config/config.default.json).\n\nThe file is structured with two keys, `FRONTEND` and `BACKEND`, which contain the configuration overrides for the\nfront-end and back-end respectively.\n\nIf you want to override either front-end or back-end values, create your own config file based on \n[`config/config.default.json`](config/config.default.json). Any values that you don't override will fall back to \nthe values in the default config.\n\nTo use your custom config file, set the `ACCESS_CONFIG_FILE` environment variable to the name of your config\noverride file in the project-level `config` directory.\n\n### Sample Usage\n\nTo override environment variables, create an override config file in the `config` directory. (You can name\nthis file whatever you want because the name of the file is specified by your `ACCESS_CONFIG_FILE` environment\nvariable.)\n\nFor example, if you want to set the default access time to 5 days in production, you might create a file named\n`config.production.json` in the `config` directory:\n\n```json\n{\n  \"FRONTEND\": {\n    \"DEFAULT_ACCESS_TIME\": \"432000\"\n  }\n}\n```\n\nThen, in your `.env.production` file, set the `ACCESS_CONFIG_FILE` environment variable to the name of your\nconfig file:\n\n```\nACCESS_CONFIG_FILE=config.production.json\n```\n\nThis tells the application to use `config.production.json` for configuration overrides.\n\n#### Frontend Configuration\n\nTo override values on the front-end, modify these key-value pairs inside the `FRONTEND` key in your custom config file.\n\n| Name                      | Details                                                                                                                                                                                                  | Example                                                        |\n|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------|\n| `ACCESS_TIME_LABELS`      | Specifies the time access labels to use for dropdowns on the front end. Contains a JSON object of the format `{\"NUM_SECONDS\": \"LABEL\"}`.                                                                 | `{\"86400\": \"1 day\", \"604800\": \"1 week\", \"2592000\": \"1 month\"}` |\n| `DEFAULT_ACCESS_TIME`     | Specifies the default time access label to use for dropdowns on the front end. Contains a string with a number of seconds corresponding to a key in the access time labels.                              | `\"86400\"`                                                      |\n| `NAME_VALIDATION_PATTERN` | Specifies the regex pattern to use for validating role, group, and tag names.  Should include preceding `^` and trailing `$` but is not a regex literal so omit `/`  at beginning and end of the pattern | `\"^[a-zA-Z0-9-]*$\"`                                            |\n| `NAME_VALIDATION_ERROR`   | Specifies the error message to display when a name does not match the validation pattern.                                                                                                                | `\"Name must contain only letters, numbers, and underscores.\"`  |\n\nThe front-end config is loaded in [`craco.config.js`](craco.config.js). See\n[`src/config/loadAccessConfig.js`](src/config/loadAccessConfig.js) for more details.\n\n#### Backend Configuration\n\nTo override values on the back-end, modify these key-value pairs inside the `BACKEND` key in your custom config file.\n\n| Name                      | Details                                                                                                                                                                                            | Example                                                                                |\n|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|\n| `NAME_VALIDATION_PATTERN` | PCRE regex used for validating role, group, and tag names. Should not explicitly declare pattern boundaries: depending on context, may be used with or without a preceding `^` and a trailing `$`. | `[A-Z][A-Za-z0-9-]*`                                                                   |\n| `NAME_VALIDATION_ERROR`   | Error message to display when a name does not match the validation pattern.                                                                                                                        | `Name must start with a capital letter and contain only letters, numbers, and hypens.` |\n\nThe back-end config is loaded in [`api/access_config.py`](api/access_config.py).\n\nSee [`api/views/schemas/core_schemas.py`](api/views/schemas/core_schemas.py) for details about how the pattern override\nsupplied here will be used.\n\n#### Database Setup\n\nAfter `docker compose up --build`, you can run the following commands to setup the database:\n\nCreate the database in the postgres container:\n```\ndocker compose exec postgres createdb -U \u003cPOSTGRES_USER\u003e \u003cDB_NAME\u003e\n```\n\nRun the initial migrations and seed the initial data from Okta:\n```\ndocker compose exec discord-access /bin/bash\n```\n\nThen run the following commands inside the container:\n\n```\nflask db upgrade\nflask init \u003cYOUR_OKTA_USER_EMAIL\u003e\n```\n\nVisit [http://localhost:3000/](http://localhost:3000/) to view your running version of Access!\n\n### Kubernetes Deployment and CronJobs\n\nAs Access is a web application packaged with Docker, it can easily be deployed to a Kubernetes cluster. We've included example Kubernetes yaml objects you can use to deploy Access in the [examples/kubernetes](https://github.com/discord/access/tree/main/examples/kubernetes) directory.\n\nThese examples include a Deployment, Service, Namespace, and Service Account object for serving the stateless web application. Additionally there are examples for deploying the `flask sync` and `flask notify` commands as cronjobs to periodically synchronize users, groups, and their memberships and send expiring access notifications respectively.\n\n## Plugins\n\nAccess uses the [Python pluggy framework](https://pluggy.readthedocs.io/en/latest/) to allow for new functionality to be added to the system. Plugins are Python packages that are installed into the Access Docker container. For example, a notification plugin could add a new type of notification such as Email, SMS, or a Discord message for when new access requests are made and resolved.\n\n### Creating a Plugin\n\nPlugins in Access follow the conventions defined by the [Python pluggy framework](https://pluggy.readthedocs.io/en/latest/).\n\nAn example implementation of a notification plugin is included in [examples/plugins/notifications](https://github.com/discord/access/tree/main/examples/plugins/notifications), which can be extended to send messages using custom Python code. It implements the `NotificationPluginSpec` found in [notifications.py](https://github.com/discord/access/blob/main/api/plugins/notifications.py)\n\nThere's also an example implementation of a conditional access plugin in [examples/plugins/conditional_access](https://github.com/discord/access/tree/main/examples/plugins/conditional_access), which can be extended to conditionally approve or deny requests. It implements the `ConditionalAccessPluginSpec` found in [requests.py](https://github.com/discord/access/blob/main/api/plugins/conditional_access.py).\n\n### Installing a Plugin in the Docker Container\n\nBelow is an example Dockerfile that would install the example notification plugin into the Access Docker container, which was built above using the top-level application [Dockerfile](https://github.com/discord/access/blob/main/Dockerfile). The plugin is installed into the `/app/plugins` directory and then installed using pip.\n\n```Dockerfile\nFROM access:latest\n\nWORKDIR /app/plugins\nADD ./examples/plugins/ ./\n\nRUN pip install ./notifications\n\nWORKDIR /app\n```\n\n## TODO\n\nHere are some of the features we're potentially planning to add to Access:\n\n- A Group Lifecycle and User Lifecycle plugin framework\n- Support for Google Groups and Github Teams via Group Lifecycle plugins\n- Group (and Role) creation requests\n- Role membership requests, so Role owners can request to add their Role to a Group\n- OktaApp model with many-to-many relationship to App for automatically assigning AppGroups to Okta application tiles\n- A webhook to synchronize group memberships and disabling users in real-time from Okta\n\n## License\n\n```\nCopyright (C) 2024 Discord Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n\nFor code dependencies, libraries, and frameworks used by this project that are dual-licensed or allow the option under their terms to select either the Apache Version 2.0 License, MIT License, or BSD 3-Clause License, this project selects those licenses for use of those dependencies in that order of preference.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiscord%2Faccess","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdiscord%2Faccess","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiscord%2Faccess/lists"}