├── .dockerignore ├── .editorconfig ├── .envrc ├── .github ├── .editorconfig ├── CODE_OF_CONDUCT.md ├── DCO ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ ├── config.yml │ └── feature_request.yaml ├── PULL_REQUEST_TEMPLATE.md ├── SECURITY.md ├── dependabot.yaml ├── release.yml └── workflows │ ├── analysis-scorecard.yaml │ ├── artifacts.yaml │ ├── checks.yaml │ ├── ci.yaml │ ├── release.yaml │ └── trivydb-cache.yaml ├── .gitignore ├── .gitpod.yml ├── .golangci.yml ├── ADOPTERS.md ├── Dockerfile ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.md ├── api ├── api.pb.go ├── api.proto ├── api_grpc.pb.go └── v2 │ ├── api.pb.go │ ├── api.proto │ ├── api_grpc.pb.go │ ├── go.mod │ └── go.sum ├── cmd ├── dex │ ├── config.go │ ├── config_test.go │ ├── logger.go │ ├── main.go │ ├── serve.go │ ├── serve_test.go │ └── version.go └── docker-entrypoint │ ├── main.go │ └── main_test.go ├── config.dev.yaml ├── config.docker.yaml ├── config.yaml.dist ├── connector ├── atlassiancrowd │ ├── atlassiancrowd.go │ └── atlassiancrowd_test.go ├── authproxy │ ├── authproxy.go │ └── authproxy_test.go ├── bitbucketcloud │ ├── bitbucketcloud.go │ └── bitbucketcloud_test.go ├── connector.go ├── gitea │ ├── gitea.go │ └── gitea_test.go ├── github │ ├── github.go │ └── github_test.go ├── gitlab │ ├── gitlab.go │ └── gitlab_test.go ├── google │ ├── google.go │ └── google_test.go ├── keystone │ ├── keystone.go │ └── keystone_test.go ├── ldap │ ├── gen-certs.sh │ ├── ldap.go │ ├── ldap_test.go │ └── testdata │ │ ├── certs │ │ ├── ca.crt │ │ ├── ca.key │ │ ├── dhparam.pem │ │ ├── ldap.crt │ │ └── ldap.key │ │ └── schema.ldif ├── linkedin │ └── linkedin.go ├── microsoft │ ├── microsoft.go │ └── microsoft_test.go ├── mock │ └── connectortest.go ├── oauth │ ├── oauth.go │ └── oauth_test.go ├── oidc │ ├── oidc.go │ └── oidc_test.go ├── openshift │ ├── openshift.go │ └── openshift_test.go └── saml │ ├── saml.go │ ├── saml_test.go │ ├── testdata │ ├── assertion-signed.tmpl │ ├── assertion-signed.xml │ ├── bad-ca.crt │ ├── bad-ca.key │ ├── bad-status.tmpl │ ├── bad-status.xml │ ├── ca.crt │ ├── ca.key │ ├── gen.sh │ ├── good-resp.tmpl │ ├── good-resp.xml │ ├── idp-cert.pem │ ├── idp-resp-signed-assertion.xml │ ├── idp-resp-signed-assertion0.xml │ ├── idp-resp-signed-message-and-assertion.xml │ ├── idp-resp-signed-message.xml │ ├── idp-resp.xml │ ├── oam-ca.pem │ ├── oam-resp.xml │ ├── okta-ca.pem │ ├── okta-resp.xml │ ├── tampered-resp.xml │ ├── two-assertions-first-signed.tmpl │ └── two-assertions-first-signed.xml │ └── types.go ├── docker-compose.override.yaml.dist ├── docker-compose.test.yaml ├── docker-compose.yaml ├── docs ├── README.md ├── enhancements │ ├── README.md │ ├── _title-YYYY-MM-DD-#issue.md │ └── token-exchange-2023-02-03-#2812.md ├── img │ ├── caution.png │ ├── dex-backend-flow.png │ └── dex-flow.png └── logos │ ├── dex-glyph-bw.png │ ├── dex-glyph-bw.svg │ ├── dex-glyph-color.png │ ├── dex-glyph-color.svg │ ├── dex-glyph-white.png │ ├── dex-glyph-white.svg │ ├── dex-horizontal-color.png │ ├── dex-horizontal-color.svg │ ├── dex-horizontal-white.png │ └── dex-horizontal-white.svg ├── examples ├── .gitignore ├── config-ad-kubelogin.yaml ├── config-dev.yaml ├── example-app │ ├── main.go │ └── templates.go ├── go.mod ├── go.sum ├── grpc-client │ ├── .gitignore │ ├── README.md │ ├── cert-destroy │ ├── cert-gen │ ├── client.go │ ├── config.yaml │ └── openssl.conf ├── k8s │ ├── .gitignore │ ├── dex.yaml │ └── gencert.sh └── ldap │ ├── config-ldap.ldif │ ├── config-ldap.yaml │ └── docker-compose.yaml ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum ├── pkg ├── featureflags │ ├── flag.go │ └── set.go ├── groups │ ├── groups.go │ └── groups_test.go └── httpclient │ ├── httpclient.go │ ├── httpclient_test.go │ ├── readme.md │ └── testdata │ ├── rootCA.key │ ├── rootCA.pem │ ├── rootCA.srl │ ├── server.crt │ ├── server.csr │ ├── server.csr.cnf │ ├── server.key │ └── v3.ext ├── scripts ├── git-version └── manifests │ ├── .editorconfig │ └── crds │ ├── authcodes.yaml │ ├── authrequests.yaml │ ├── connectors.yaml │ ├── devicerequests.yaml │ ├── devicetokens.yaml │ ├── oauth2clients.yaml │ ├── offlinesessionses.yaml │ ├── passwords.yaml │ ├── refreshtokens.yaml │ └── signingkeies.yaml ├── server ├── api.go ├── api_test.go ├── deviceflowhandlers.go ├── deviceflowhandlers_test.go ├── doc.go ├── handlers.go ├── handlers_test.go ├── internal │ ├── codec.go │ ├── types.pb.go │ └── types.proto ├── introspectionhandler.go ├── introspectionhandler_test.go ├── oauth2.go ├── oauth2_test.go ├── refreshhandlers.go ├── refreshhandlers_test.go ├── rotation.go ├── rotation_test.go ├── server.go ├── server_test.go ├── templates.go └── templates_test.go ├── storage ├── conformance │ ├── conformance.go │ ├── gen_jwks.go │ ├── jwks.go │ └── transactions.go ├── doc.go ├── ent │ ├── client │ │ ├── authcode.go │ │ ├── authrequest.go │ │ ├── client.go │ │ ├── connector.go │ │ ├── devicerequest.go │ │ ├── devicetoken.go │ │ ├── keys.go │ │ ├── main.go │ │ ├── offlinesession.go │ │ ├── password.go │ │ ├── refreshtoken.go │ │ ├── types.go │ │ └── utils.go │ ├── db │ │ ├── authcode.go │ │ ├── authcode │ │ │ ├── authcode.go │ │ │ └── where.go │ │ ├── authcode_create.go │ │ ├── authcode_delete.go │ │ ├── authcode_query.go │ │ ├── authcode_update.go │ │ ├── authrequest.go │ │ ├── authrequest │ │ │ ├── authrequest.go │ │ │ └── where.go │ │ ├── authrequest_create.go │ │ ├── authrequest_delete.go │ │ ├── authrequest_query.go │ │ ├── authrequest_update.go │ │ ├── client.go │ │ ├── connector.go │ │ ├── connector │ │ │ ├── connector.go │ │ │ └── where.go │ │ ├── connector_create.go │ │ ├── connector_delete.go │ │ ├── connector_query.go │ │ ├── connector_update.go │ │ ├── devicerequest.go │ │ ├── devicerequest │ │ │ ├── devicerequest.go │ │ │ └── where.go │ │ ├── devicerequest_create.go │ │ ├── devicerequest_delete.go │ │ ├── devicerequest_query.go │ │ ├── devicerequest_update.go │ │ ├── devicetoken.go │ │ ├── devicetoken │ │ │ ├── devicetoken.go │ │ │ └── where.go │ │ ├── devicetoken_create.go │ │ ├── devicetoken_delete.go │ │ ├── devicetoken_query.go │ │ ├── devicetoken_update.go │ │ ├── ent.go │ │ ├── enttest │ │ │ └── enttest.go │ │ ├── hook │ │ │ └── hook.go │ │ ├── keys.go │ │ ├── keys │ │ │ ├── keys.go │ │ │ └── where.go │ │ ├── keys_create.go │ │ ├── keys_delete.go │ │ ├── keys_query.go │ │ ├── keys_update.go │ │ ├── migrate │ │ │ ├── migrate.go │ │ │ └── schema.go │ │ ├── mutation.go │ │ ├── oauth2client.go │ │ ├── oauth2client │ │ │ ├── oauth2client.go │ │ │ └── where.go │ │ ├── oauth2client_create.go │ │ ├── oauth2client_delete.go │ │ ├── oauth2client_query.go │ │ ├── oauth2client_update.go │ │ ├── offlinesession.go │ │ ├── offlinesession │ │ │ ├── offlinesession.go │ │ │ └── where.go │ │ ├── offlinesession_create.go │ │ ├── offlinesession_delete.go │ │ ├── offlinesession_query.go │ │ ├── offlinesession_update.go │ │ ├── password.go │ │ ├── password │ │ │ ├── password.go │ │ │ └── where.go │ │ ├── password_create.go │ │ ├── password_delete.go │ │ ├── password_query.go │ │ ├── password_update.go │ │ ├── predicate │ │ │ └── predicate.go │ │ ├── refreshtoken.go │ │ ├── refreshtoken │ │ │ ├── refreshtoken.go │ │ │ └── where.go │ │ ├── refreshtoken_create.go │ │ ├── refreshtoken_delete.go │ │ ├── refreshtoken_query.go │ │ ├── refreshtoken_update.go │ │ ├── runtime.go │ │ ├── runtime │ │ │ └── runtime.go │ │ └── tx.go │ ├── generate.go │ ├── mysql.go │ ├── mysql_test.go │ ├── postgres.go │ ├── postgres_test.go │ ├── schema │ │ ├── authcode.go │ │ ├── authrequest.go │ │ ├── client.go │ │ ├── connector.go │ │ ├── devicerequest.go │ │ ├── devicetoken.go │ │ ├── dialects.go │ │ ├── keys.go │ │ ├── offlinesession.go │ │ ├── password.go │ │ └── refreshtoken.go │ ├── sqlite.go │ ├── sqlite_test.go │ ├── types.go │ └── utils.go ├── etcd │ ├── config.go │ ├── etcd.go │ ├── etcd_test.go │ └── types.go ├── health.go ├── kubernetes │ ├── client.go │ ├── client_test.go │ ├── doc.go │ ├── k8sapi │ │ ├── client.go │ │ ├── crd_extensions.go │ │ ├── doc.go │ │ ├── extensions.go │ │ ├── time.go │ │ ├── unversioned.go │ │ └── v1.go │ ├── lock.go │ ├── storage.go │ ├── storage_test.go │ ├── transport.go │ └── types.go ├── memory │ ├── memory.go │ ├── memory_test.go │ └── static_test.go ├── sql │ ├── config.go │ ├── config_test.go │ ├── crud.go │ ├── crud_test.go │ ├── migrate.go │ ├── migrate_test.go │ ├── postgres_test.go │ ├── sql.go │ ├── sql_test.go │ ├── sqlite.go │ └── sqlite_test.go ├── static.go └── storage.go └── web ├── robots.txt ├── static ├── img │ ├── atlassian-crowd-icon.svg │ ├── bitbucket-icon.svg │ ├── email-icon.svg │ ├── gitea-icon.svg │ ├── github-icon.svg │ ├── gitlab-icon.svg │ ├── google-icon.svg │ ├── keystone-icon.svg │ ├── ldap-icon.svg │ ├── linkedin-icon.svg │ ├── microsoft-icon.svg │ ├── oidc-icon.svg │ └── saml-icon.svg └── main.css ├── templates ├── approval.html ├── device.html ├── device_success.html ├── error.html ├── footer.html ├── header.html ├── login.html ├── oob.html └── password.html ├── themes ├── dark │ ├── favicon.png │ ├── logo.png │ └── styles.css └── light │ ├── favicon.png │ ├── logo.png │ └── styles.css └── web.go /.dockerignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | tmp/ 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.go] 12 | indent_style = tab 13 | 14 | [*.proto] 15 | indent_size = 2 16 | 17 | [{Makefile,*.mk}] 18 | indent_style = tab 19 | 20 | [{config.yaml.dist,config.dev.yaml}] 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | if ! has nix_direnv_version || ! nix_direnv_version 3.0.6; then 2 | source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.6/direnvrc" "sha256-RYcUJaRMf8oF5LznDrlCXbkOQrywm0HDv1VjYGaJGdM=" 3 | fi 4 | use flake . --impure 5 | 6 | dotenv_if_exists 7 | -------------------------------------------------------------------------------- /.github/.editorconfig: -------------------------------------------------------------------------------- 1 | [{*.yml,*.yaml}] 2 | indent_size = 2 3 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Community Code of Conduct 2 | 3 | This project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). 4 | -------------------------------------------------------------------------------- /.github/DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 660 York Street, Suite 102, 6 | San Francisco, CA 94110 USA 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this 9 | license document, but changing it is not allowed. 10 | 11 | 12 | Developer's Certificate of Origin 1.1 13 | 14 | By making a contribution to this project, I certify that: 15 | 16 | (a) The contribution was created in whole or in part by me and I 17 | have the right to submit it under the open source license 18 | indicated in the file; or 19 | 20 | (b) The contribution is based upon previous work that, to the best 21 | of my knowledge, is covered under an appropriate open source 22 | license and I have the right under that license to submit that 23 | work with modifications, whether created in whole or in part 24 | by me, under the same open source license (unless I am 25 | permitted to submit under a different license), as indicated 26 | in the file; or 27 | 28 | (c) The contribution was provided directly to me by some other 29 | person who certified (a), (b) or (c) and I have not modified 30 | it. 31 | 32 | (d) I understand and agree that this project and the contribution 33 | are public and that a record of the contribution (including all 34 | personal information I submit with it, including my sign-off) is 35 | maintained indefinitely and may be redistributed consistent with 36 | this project or the open source license(s) involved. 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 📖 Documentation enhancement 4 | url: https://github.com/dexidp/website/issues 5 | about: Suggest an improvement to the documentation 6 | 7 | - name: ❓ Ask a question 8 | url: https://github.com/dexidp/dex/discussions/new?category=q-a 9 | about: Ask and discuss questions with other Dex community members 10 | 11 | - name: 📚 Documentation 12 | url: https://dexidp.io/docs/ 13 | about: Check the documentation for help 14 | 15 | - name: 💬 Slack channel 16 | url: https://cloud-native.slack.com/messages/dexidp 17 | about: Please ask and answer questions here 18 | 19 | - name: 💡 Dex Enhancement Proposal 20 | url: https://github.com/dexidp/dex/tree/master/docs/enhancements/README.md 21 | about: Open a proposal for significant architectural change 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: 🎉 Feature request 2 | description: Suggest an idea for Dex 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thank you for submitting a feature request! 8 | 9 | Please describe what you would like to change/add and why in detail by filling out the template below. 10 | 11 | If you are not sure if your request fits into Dex, you can contact us via the available [support channels](https://github.com/dexidp/dex/issues/new/choose). 12 | - type: checkboxes 13 | attributes: 14 | label: Preflight Checklist 15 | description: Please ensure you've completed all of the following. 16 | options: 17 | - label: I agree to follow the [Code of Conduct](https://github.com/dexidp/dex/blob/master/.github/CODE_OF_CONDUCT.md) that this project adheres to. 18 | required: true 19 | - label: I have searched the [issue tracker](https://www.github.com/dexidp/dex/issues) for an issue that matches the one I want to file, without success. 20 | required: true 21 | - type: textarea 22 | attributes: 23 | label: Problem Description 24 | description: A clear and concise description of the problem you are seeking to solve with this feature request. 25 | validations: 26 | required: true 27 | - type: textarea 28 | attributes: 29 | label: Proposed Solution 30 | description: A clear and concise description of what would you like to happen. 31 | validations: 32 | required: true 33 | - type: textarea 34 | attributes: 35 | label: Alternatives Considered 36 | description: A clear and concise description of any alternative solutions or features you've considered. 37 | - type: textarea 38 | attributes: 39 | label: Additional Information 40 | description: Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | #### Overview 11 | 12 | 13 | 14 | #### What this PR does / why we need it 15 | 16 | 22 | 23 | #### Special notes for your reviewer 24 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a vulnerability 4 | 5 | To report a vulnerability, send an email to [cncf-dex-maintainers@lists.cncf.io](mailto:cncf-dex-maintainers@lists.cncf.io) 6 | detailing the issue and steps to reproduce. The reporter(s) can expect a 7 | response within 48 hours acknowledging the issue was received. If a response is 8 | not received within 48 hours, please reach out to any maintainer directly 9 | to confirm receipt of the issue. 10 | 11 | ## Review Process 12 | 13 | Once a maintainer has confirmed the relevance of the report, a draft security 14 | advisory will be created on GitHub. The draft advisory will be used to discuss 15 | the issue with maintainers, the reporter(s). 16 | If the reporter(s) wishes to participate in this discussion, then provide 17 | reporter GitHub username(s) to be invited to the discussion. If the reporter(s) 18 | does not wish to participate directly in the discussion, then the reporter(s) 19 | can request to be updated regularly via email. 20 | 21 | If the vulnerability is accepted, a timeline for developing a patch, public 22 | disclosure, and patch release will be determined. The reporter(s) are expected 23 | to participate in the discussion of the timeline and abide by agreed upon dates 24 | for public disclosure. 25 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "gomod" 5 | directory: "/" 6 | labels: 7 | - "area/dependencies" 8 | schedule: 9 | interval: "daily" 10 | groups: 11 | etcd: 12 | patterns: 13 | - "go.etcd.io/*" 14 | 15 | - package-ecosystem: "gomod" 16 | directory: "/api/v2" 17 | labels: 18 | - "area/dependencies" 19 | schedule: 20 | interval: "daily" 21 | 22 | - package-ecosystem: "gomod" 23 | directory: "/examples" 24 | labels: 25 | - "area/dependencies" 26 | schedule: 27 | interval: "daily" 28 | 29 | - package-ecosystem: "docker" 30 | directory: "/" 31 | labels: 32 | - "area/dependencies" 33 | schedule: 34 | interval: "daily" 35 | 36 | - package-ecosystem: "github-actions" 37 | directory: "/" 38 | labels: 39 | - "area/dependencies" 40 | schedule: 41 | interval: "daily" 42 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - release-note/ignore 5 | categories: 6 | - title: Exciting New Features 🎉 7 | labels: 8 | - kind/feature 9 | - release-note/new-feature 10 | - title: Enhancements 🚀 11 | labels: 12 | - kind/enhancement 13 | - release-note/enhancement 14 | - title: Bug Fixes 🐛 15 | labels: 16 | - kind/bug 17 | - release-note/bug-fix 18 | - title: Breaking Changes 🛠 19 | labels: 20 | - release-note/breaking-change 21 | - title: Deprecations ❌ 22 | labels: 23 | - release-note/deprecation 24 | - title: Dependency Updates ⬆️ 25 | labels: 26 | - area/dependencies 27 | - release-note/dependency-update 28 | - title: Other Changes 29 | labels: 30 | - "*" 31 | -------------------------------------------------------------------------------- /.github/workflows/analysis-scorecard.yaml: -------------------------------------------------------------------------------- 1 | name: OpenSSF Scorecard 2 | 3 | on: 4 | branch_protection_rule: 5 | push: 6 | branches: [ main ] 7 | schedule: 8 | - cron: '30 0 * * 5' 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | analyze: 15 | name: Analyze 16 | runs-on: ubuntu-latest 17 | 18 | permissions: 19 | actions: read 20 | contents: read 21 | id-token: write 22 | security-events: write 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 27 | with: 28 | persist-credentials: false 29 | 30 | - name: Run analysis 31 | uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 32 | with: 33 | results_file: results.sarif 34 | results_format: sarif 35 | publish_results: true 36 | 37 | - name: Upload results as artifact 38 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 39 | with: 40 | name: OpenSSF Scorecard results 41 | path: results.sarif 42 | retention-days: 5 43 | 44 | - name: Upload results to GitHub Security tab 45 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 46 | with: 47 | sarif_file: results.sarif 48 | -------------------------------------------------------------------------------- /.github/workflows/checks.yaml: -------------------------------------------------------------------------------- 1 | name: PR Checks 2 | 3 | on: 4 | pull_request: 5 | types: [opened, labeled, unlabeled, synchronize] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | release-label: 12 | name: Release note label 13 | runs-on: ubuntu-latest 14 | 15 | if: github.repository == 'dexidp/dex' 16 | 17 | steps: 18 | - name: Check minimum labels 19 | uses: mheap/github-action-required-labels@388fd6af37b34cdfe5a23b37060e763217e58b03 # v5.5 20 | with: 21 | mode: minimum 22 | count: 1 23 | labels: "release-note/ignore, kind/feature, release-note/new-feature, kind/enhancement, release-note/enhancement, kind/bug, release-note/bug-fix, release-note/breaking-change, release-note/deprecation, area/dependencies, release-note/dependency-update" 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: [ "v[0-9]+.[0-9]+.[0-9]+" ] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | artifacts: 12 | name: Artifacts 13 | uses: ./.github/workflows/artifacts.yaml 14 | with: 15 | publish: true 16 | secrets: 17 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 18 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 19 | permissions: 20 | attestations: write 21 | contents: read 22 | packages: write 23 | id-token: write 24 | security-events: write 25 | -------------------------------------------------------------------------------- /.github/workflows/trivydb-cache.yaml: -------------------------------------------------------------------------------- 1 | # Note: This workflow only updates the cache. You should create a separate workflow for your actual Trivy scans. 2 | # In your scan workflow, set TRIVY_SKIP_DB_UPDATE=true and TRIVY_SKIP_JAVA_DB_UPDATE=true. 3 | name: Update Trivy Cache 4 | 5 | on: 6 | schedule: 7 | - cron: '0 0 * * *' # Run daily at midnight UTC 8 | workflow_dispatch: # Allow manual triggering 9 | 10 | jobs: 11 | update-trivy-db: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Setup oras 15 | uses: oras-project/setup-oras@8d34698a59f5ffe24821f0b48ab62a3de8b64b20 # v1.2.3 16 | 17 | - name: Get current date 18 | id: date 19 | run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT 20 | 21 | - name: Download and extract the vulnerability DB 22 | run: | 23 | mkdir -p $GITHUB_WORKSPACE/.cache/trivy/db 24 | oras pull ghcr.io/aquasecurity/trivy-db:2 25 | tar -xzf db.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/db 26 | rm db.tar.gz 27 | 28 | - name: Download and extract the Java DB 29 | run: | 30 | mkdir -p $GITHUB_WORKSPACE/.cache/trivy/java-db 31 | oras pull ghcr.io/aquasecurity/trivy-java-db:1 32 | tar -xzf javadb.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/java-db 33 | rm javadb.tar.gz 34 | 35 | - name: Cache DBs 36 | uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 37 | with: 38 | path: ${{ github.workspace }}/.cache/trivy 39 | key: cache-trivy-${{ steps.date.outputs.date }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.devenv/ 2 | /.direnv/ 3 | /.idea/ 4 | /bin/ 5 | /config.yaml 6 | /docker-compose.override.yaml 7 | /var/ 8 | /vendor/ 9 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - init: go get && go build ./... && go test ./... && make 3 | command: go run 4 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 4m 3 | 4 | linters-settings: 5 | depguard: 6 | rules: 7 | deprecated: 8 | deny: 9 | - pkg: "io/ioutil" 10 | desc: "The 'io/ioutil' package is deprecated. Use corresponding 'os' or 'io' functions instead." 11 | gci: 12 | sections: 13 | - standard 14 | - default 15 | - prefix(github.com/dexidp/dex) 16 | goimports: 17 | local-prefixes: github.com/dexidp/dex 18 | 19 | linters: 20 | disable-all: true 21 | enable: 22 | - depguard 23 | - dogsled 24 | - exhaustive 25 | - gci 26 | - gochecknoinits 27 | - gocritic 28 | - gofmt 29 | - gofumpt 30 | - goimports 31 | - goprintffuncname 32 | - gosimple 33 | - govet 34 | - ineffassign 35 | - misspell 36 | - nakedret 37 | - nolintlint 38 | - prealloc 39 | # - revive 40 | # - sqlclosecheck 41 | - staticcheck 42 | - stylecheck 43 | - unconvert 44 | - unused 45 | - whitespace 46 | 47 | # Disable temporarily until everything works with Go 1.20 48 | # - bodyclose 49 | # - rowserrcheck 50 | # - tparallel 51 | # - unparam 52 | 53 | # Disable temporarily until everything works with Go 1.18 54 | - typecheck 55 | 56 | # Disable temporarily until the following issue is resolved: https://github.com/golangci/golangci-lint/issues/3086 57 | # - sqlclosecheck 58 | 59 | # TODO: fix linter errors before enabling 60 | # - exhaustivestruct 61 | # - gochecknoglobals 62 | # - errorlint 63 | # - gocognit 64 | # - godot 65 | # - nlreturn 66 | # - noctx 67 | # - revive 68 | # - wrapcheck 69 | 70 | # TODO: fix linter errors before enabling (from original config) 71 | # - dupl 72 | # - errcheck 73 | # - goconst 74 | # - gocyclo 75 | # - gosec 76 | # - lll 77 | # - scopelint 78 | 79 | # unused 80 | # - goheader 81 | # - gomodguard 82 | 83 | # don't enable: 84 | # - asciicheck 85 | # - funlen 86 | # - godox 87 | # - goerr113 88 | # - gomnd 89 | # - interfacer 90 | # - maligned 91 | # - nestif 92 | # - testpackage 93 | # - wsl 94 | 95 | issues: 96 | exclude-dirs: 97 | - storage/ent/db # generated ent code 98 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Joel Speed (@JoelSpeed) 2 | Maksim Nabokikh (@nabokihms) 3 | Mark Sagi-Kazar (@sagikazarmark) 4 | Nandor Kracser (@bonifaido) 5 | Rithu John (@rithujohn191) 6 | Stephen Augustus (@justaugustus) 7 | -------------------------------------------------------------------------------- /api/v2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dexidp/dex/api/v2 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | google.golang.org/grpc v1.72.1 7 | google.golang.org/protobuf v1.36.6 8 | ) 9 | 10 | require ( 11 | golang.org/x/net v0.40.0 // indirect 12 | golang.org/x/sys v0.33.0 // indirect 13 | golang.org/x/text v0.25.0 // indirect 14 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /cmd/dex/logger.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log/slog" 7 | "os" 8 | "strings" 9 | 10 | "github.com/dexidp/dex/server" 11 | ) 12 | 13 | var logFormats = []string{"json", "text"} 14 | 15 | func newLogger(level slog.Level, format string) (*slog.Logger, error) { 16 | var handler slog.Handler 17 | switch strings.ToLower(format) { 18 | case "", "text": 19 | handler = slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ 20 | Level: level, 21 | }) 22 | case "json": 23 | handler = slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ 24 | Level: level, 25 | }) 26 | default: 27 | return nil, fmt.Errorf("log format is not one of the supported values (%s): %s", strings.Join(logFormats, ", "), format) 28 | } 29 | 30 | return slog.New(newRequestContextHandler(handler)), nil 31 | } 32 | 33 | var _ slog.Handler = requestContextHandler{} 34 | 35 | type requestContextHandler struct { 36 | handler slog.Handler 37 | } 38 | 39 | func newRequestContextHandler(handler slog.Handler) slog.Handler { 40 | return requestContextHandler{ 41 | handler: handler, 42 | } 43 | } 44 | 45 | func (h requestContextHandler) Enabled(ctx context.Context, level slog.Level) bool { 46 | return h.handler.Enabled(ctx, level) 47 | } 48 | 49 | func (h requestContextHandler) Handle(ctx context.Context, record slog.Record) error { 50 | if v, ok := ctx.Value(server.RequestKeyRemoteIP).(string); ok { 51 | record.AddAttrs(slog.String(string(server.RequestKeyRemoteIP), v)) 52 | } 53 | 54 | if v, ok := ctx.Value(server.RequestKeyRequestID).(string); ok { 55 | record.AddAttrs(slog.String(string(server.RequestKeyRequestID), v)) 56 | } 57 | 58 | return h.handler.Handle(ctx, record) 59 | } 60 | 61 | func (h requestContextHandler) WithAttrs(attrs []slog.Attr) slog.Handler { 62 | return requestContextHandler{h.handler.WithAttrs(attrs)} 63 | } 64 | 65 | func (h requestContextHandler) WithGroup(name string) slog.Handler { 66 | return requestContextHandler{h.handler.WithGroup(name)} 67 | } 68 | -------------------------------------------------------------------------------- /cmd/dex/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func commandRoot() *cobra.Command { 11 | rootCmd := &cobra.Command{ 12 | Use: "dex", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | cmd.Help() 15 | os.Exit(2) 16 | }, 17 | } 18 | rootCmd.AddCommand(commandServe()) 19 | rootCmd.AddCommand(commandVersion()) 20 | return rootCmd 21 | } 22 | 23 | func main() { 24 | if err := commandRoot().Execute(); err != nil { 25 | fmt.Fprintln(os.Stderr, err.Error()) 26 | os.Exit(2) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cmd/dex/serve_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log/slog" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestNewLogger(t *testing.T) { 11 | t.Run("JSON", func(t *testing.T) { 12 | logger, err := newLogger(slog.LevelInfo, "json") 13 | require.NoError(t, err) 14 | require.NotEqual(t, (*slog.Logger)(nil), logger) 15 | }) 16 | 17 | t.Run("Text", func(t *testing.T) { 18 | logger, err := newLogger(slog.LevelError, "text") 19 | require.NoError(t, err) 20 | require.NotEqual(t, (*slog.Logger)(nil), logger) 21 | }) 22 | 23 | t.Run("Unknown", func(t *testing.T) { 24 | logger, err := newLogger(slog.LevelError, "gofmt") 25 | require.Error(t, err) 26 | require.Equal(t, "log format is not one of the supported values (json, text): gofmt", err.Error()) 27 | require.Equal(t, (*slog.Logger)(nil), logger) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /cmd/dex/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var version = "DEV" 11 | 12 | func commandVersion() *cobra.Command { 13 | return &cobra.Command{ 14 | Use: "version", 15 | Short: "Print the version and exit", 16 | Run: func(_ *cobra.Command, _ []string) { 17 | fmt.Printf( 18 | "Dex Version: %s\nGo Version: %s\nGo OS/ARCH: %s %s\n", 19 | version, 20 | runtime.Version(), 21 | runtime.GOOS, 22 | runtime.GOARCH, 23 | ) 24 | }, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /config.dev.yaml: -------------------------------------------------------------------------------- 1 | issuer: http://127.0.0.1:5556/dex 2 | 3 | storage: 4 | type: sqlite3 5 | config: 6 | file: var/sqlite/dex.db 7 | 8 | web: 9 | http: 127.0.0.1:5556 10 | 11 | telemetry: 12 | http: 127.0.0.1:5558 13 | 14 | grpc: 15 | addr: 127.0.0.1:5557 16 | 17 | staticClients: 18 | - id: example-app 19 | redirectURIs: 20 | - 'http://127.0.0.1:5555/callback' 21 | name: 'Example App' 22 | secret: ZXhhbXBsZS1hcHAtc2VjcmV0 23 | 24 | connectors: 25 | - type: mockCallback 26 | id: mock 27 | name: Example 28 | 29 | enablePasswordDB: true 30 | 31 | staticPasswords: 32 | - email: "admin@example.com" 33 | hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" 34 | username: "admin" 35 | userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" 36 | -------------------------------------------------------------------------------- /config.docker.yaml: -------------------------------------------------------------------------------- 1 | {{- /* NOTE: This configuration file is an example and exists only for development purposes. */ -}} 2 | {{- /* To find more about gomplate formatting, please visit its documentation site - https://docs.gomplate.ca/ */ -}} 3 | issuer: {{ getenv "DEX_ISSUER" "http://127.0.0.1:5556/dex" }} 4 | 5 | storage: 6 | type: sqlite3 7 | config: 8 | file: {{ getenv "DEX_STORAGE_SQLITE3_CONFIG_FILE" "/var/dex/dex.db" }} 9 | 10 | web: 11 | {{- if getenv "DEX_WEB_HTTPS" "" }} 12 | https: {{ .Env.DEX_WEB_HTTPS }} 13 | tlsKey: {{ getenv "DEX_WEB_TLS_KEY" | required "$DEX_WEB_TLS_KEY in case of web.https is enabled" }} 14 | tlsCert: {{ getenv "DEX_WEB_TLS_CERT" | required "$DEX_WEB_TLS_CERT in case of web.https is enabled" }} 15 | {{- end }} 16 | http: {{ getenv "DEX_WEB_HTTP" "0.0.0.0:5556" }} 17 | 18 | {{- if getenv "DEX_TELEMETRY_HTTP" }} 19 | telemetry: 20 | http: {{ .Env.DEX_TELEMETRY_HTTP }} 21 | {{- end }} 22 | 23 | expiry: 24 | deviceRequests: {{ getenv "DEX_EXPIRY_DEVICE_REQUESTS" "5m" }} 25 | signingKeys: {{ getenv "DEX_EXPIRY_SIGNING_KEYS" "6h" }} 26 | idTokens: {{ getenv "DEX_EXPIRY_ID_TOKENS" "24h" }} 27 | authRequests: {{ getenv "DEX_EXPIRY_AUTH_REQUESTS" "24h" }} 28 | 29 | logger: 30 | level: {{ getenv "DEX_LOG_LEVEL" "info" }} 31 | format: {{ getenv "DEX_LOG_FORMAT" "text" }} 32 | 33 | oauth2: 34 | responseTypes: {{ getenv "DEX_OAUTH2_RESPONSE_TYPES" "[code]" }} 35 | skipApprovalScreen: {{ getenv "DEX_OAUTH2_SKIP_APPROVAL_SCREEN" "false" }} 36 | alwaysShowLoginScreen: {{ getenv "DEX_OAUTH2_ALWAYS_SHOW_LOGIN_SCREEN" "false" }} 37 | {{- if getenv "DEX_OAUTH2_PASSWORD_CONNECTOR" "" }} 38 | passwordConnector: {{ .Env.DEX_OAUTH2_PASSWORD_CONNECTOR }} 39 | {{- end }} 40 | 41 | enablePasswordDB: {{ getenv "DEX_ENABLE_PASSWORD_DB" "true" }} 42 | 43 | connectors: 44 | {{- if getenv "DEX_CONNECTORS_ENABLE_MOCK" }} 45 | - type: mockCallback 46 | id: mock 47 | name: Example 48 | {{- end }} 49 | -------------------------------------------------------------------------------- /connector/gitea/gitea_test.go: -------------------------------------------------------------------------------- 1 | package gitea 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "net/url" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/dexidp/dex/connector" 13 | ) 14 | 15 | // tests that the email is used as their username when they have no username set 16 | func TestUsernameIncludedInFederatedIdentity(t *testing.T) { 17 | s := newTestServer(map[string]interface{}{ 18 | "/api/v1/user": giteaUser{Email: "some@email.com", ID: 12345678}, 19 | "/login/oauth/access_token": map[string]interface{}{ 20 | "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", 21 | "expires_in": "30", 22 | }, 23 | }) 24 | defer s.Close() 25 | 26 | hostURL, err := url.Parse(s.URL) 27 | expectNil(t, err) 28 | 29 | req, err := http.NewRequest("GET", hostURL.String(), nil) 30 | expectNil(t, err) 31 | 32 | c := giteaConnector{baseURL: s.URL, httpClient: newClient()} 33 | identity, err := c.HandleCallback(connector.Scopes{}, req) 34 | 35 | expectNil(t, err) 36 | expectEquals(t, identity.Username, "some@email.com") 37 | expectEquals(t, identity.UserID, "12345678") 38 | 39 | c = giteaConnector{baseURL: s.URL, httpClient: newClient()} 40 | identity, err = c.HandleCallback(connector.Scopes{}, req) 41 | 42 | expectNil(t, err) 43 | expectEquals(t, identity.Username, "some@email.com") 44 | expectEquals(t, identity.UserID, "12345678") 45 | } 46 | 47 | func newTestServer(responses map[string]interface{}) *httptest.Server { 48 | return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 49 | response := responses[r.RequestURI] 50 | w.Header().Add("Content-Type", "application/json") 51 | json.NewEncoder(w).Encode(response) 52 | })) 53 | } 54 | 55 | func newClient() *http.Client { 56 | tr := &http.Transport{ 57 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 58 | } 59 | return &http.Client{Transport: tr} 60 | } 61 | 62 | func expectNil(t *testing.T, a interface{}) { 63 | if a != nil { 64 | t.Errorf("Expected %+v to equal nil", a) 65 | } 66 | } 67 | 68 | func expectEquals(t *testing.T, a interface{}, b interface{}) { 69 | if !reflect.DeepEqual(a, b) { 70 | t.Errorf("Expected %+v to equal %+v", a, b) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /connector/ldap/gen-certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Stolen from the coreos/matchbox repo. 4 | 5 | echo " 6 | [req] 7 | req_extensions = v3_req 8 | distinguished_name = req_distinguished_name 9 | 10 | [req_distinguished_name] 11 | 12 | [ v3_req ] 13 | basicConstraints = CA:FALSE 14 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 15 | subjectAltName = @alt_names 16 | 17 | [alt_names] 18 | DNS.101 = localhost 19 | " > openssl.config 20 | 21 | openssl genrsa -out testdata/ca.key 2048 22 | openssl genrsa -out testdata/server.key 2048 23 | 24 | openssl req \ 25 | -x509 -new -nodes \ 26 | -key testdata/ca.key \ 27 | -days 10000 -out testdata/ca.crt \ 28 | -subj "/CN=ldap-tests" 29 | 30 | openssl req \ 31 | -new \ 32 | -key testdata/server.key \ 33 | -out testdata/server.csr \ 34 | -subj "/CN=localhost" \ 35 | -config openssl.config 36 | 37 | openssl x509 -req \ 38 | -in testdata/server.csr \ 39 | -CA testdata/ca.crt \ 40 | -CAkey testdata/ca.key \ 41 | -CAcreateserial \ 42 | -out testdata/server.crt \ 43 | -days 10000 \ 44 | -extensions v3_req \ 45 | -extfile openssl.config 46 | 47 | rm testdata/server.csr 48 | rm testdata/ca.srl 49 | rm openssl.config 50 | -------------------------------------------------------------------------------- /connector/ldap/testdata/certs/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC/TCCAeWgAwIBAgIJAIrt+AlVUsXKMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV 3 | BAMMCmxkYXAtdGVzdHMwHhcNMTcwNDEyMjAxNzI5WhcNNDQwODI4MjAxNzI5WjAV 4 | MRMwEQYDVQQDDApsZGFwLXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 5 | CgKCAQEAzKJkt2WsALUDA3tQsedx7UJKIxis05+dU5FbBxf/BMSch8gCNh/cWErH 6 | IDljWGwLKbc9UefIz3BzbcNBPLgLGMp7t9Pf9HCBNf7lShLZB2BEGpgpCpd0urox 7 | xTqMEfchssJj75HOZRweHfBDDHk8LMHQYUBn5qTiuMYvBUbPVq69argE/kt5yAEW 8 | COZzzx38a11iY0gtPjY4Tc9vICsLHhTssNn/1wf+GFNzSTHqijC7NKW0txUneFQJ 9 | h6LAmKV/uZC84W1tqMDZKKpABiTpB+JbDvwsb9eXJ6YG6TgbKcrXjLy4ogbIrIRA 10 | s2DqMih792mxusIl6lRf3hTtCdyodwIDAQABo1AwTjAdBgNVHQ4EFgQUnfj9sAq4 11 | 2xBbV4rf5FNvYaE2Bg0wHwYDVR0jBBgwFoAUnfj9sAq42xBbV4rf5FNvYaE2Bg0w 12 | DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAFGnBH1qpLJLvrLWKNI5w 13 | u8pFYO3RGqmfJ3BGf60MQxdUaTIUNQxPfPATbth7t8GRJwpWESRDlaXWq9fM9rkt 14 | fbmuqjAMGTFloNd9ra6e2F0CKjwZWcn/3eG/mVw/5d1Ku9Ow8luKrZuzNzVJd13r 15 | hoNc1wYXN0pHWkNiRUuR/E4fE/sn+tYOpJ4XYQvKAcSrNrq8m5O9VG5gLvlTeNno 16 | 6q9hBy+5XKYUdHlzbAGm9QL0e1R45Mu4qxcFluKEmzS1rXlLsLs4/pqHgreXlYgL 17 | f7K0cFvaJGnFRKaxa6Bpf1EPNtqSc/pQZh01Ww8CUu1xh2+5KufgJQjAHVG3a1ow 18 | dQ== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /connector/ldap/testdata/certs/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAzKJkt2WsALUDA3tQsedx7UJKIxis05+dU5FbBxf/BMSch8gC 3 | Nh/cWErHIDljWGwLKbc9UefIz3BzbcNBPLgLGMp7t9Pf9HCBNf7lShLZB2BEGpgp 4 | Cpd0uroxxTqMEfchssJj75HOZRweHfBDDHk8LMHQYUBn5qTiuMYvBUbPVq69argE 5 | /kt5yAEWCOZzzx38a11iY0gtPjY4Tc9vICsLHhTssNn/1wf+GFNzSTHqijC7NKW0 6 | txUneFQJh6LAmKV/uZC84W1tqMDZKKpABiTpB+JbDvwsb9eXJ6YG6TgbKcrXjLy4 7 | ogbIrIRAs2DqMih792mxusIl6lRf3hTtCdyodwIDAQABAoIBAHQpEucQbe0Q058c 8 | VxhF+2PlJ1R441JV3ubbMkL6mibIvNpO7QJwX5I3EIX4Ta6Z1lRd0g82dcVbXgrG 9 | tbeT+aie+E/Hk++cFZzjDqFXxZ7sRHycN1/tzbNZknsU2wIvuQ9STYxmxjSbG3V/ 10 | N3BTOZdmhbVO7Cv/GTwuM+7Y3UWkc74HaXfAgo1UIO9MtqgqP3H1Tv6ZIeKzl+mP 11 | wrvei0eQe6jI4W6+vUOX3SlrlrMxMTLK/Ce2MP1pJx++m8Ga23+vtna+lkOWnwcD 12 | NmhYl4dL31sDcE6Hz/T6Wwfdlfyugw8vi3a3GEYGMIwy27CFf/ccYnWPOI3oIHDe 13 | RwlXLCECgYEA595xJmfUpwqgYY80pT3JG3+64NWJ7f/gH0Ey9fivZfnTegjkI2Kc 14 | Uf7+odCq9I1TFtx10M72N4pXT1uLzJtINYty4ZIfOLG7jSraVbOuf9AvMNCYw+cT 15 | Fcf/HGUJEE95TKYDrGfklOYFNs3ZCcKOCYJOWCuwki8Vm2vtJpV6gnkCgYEA4e5b 16 | DI+YworLjokY8eP4aOF5BMuiFdGkYDjVQZG45RwjJdLwBjaf+HA4pAuJAr2LWiLX 17 | cdKpk+3AlJ8UMLIM+hBP4hBqnrPaRTkEhTXpbUA1lvL9o0mVDFgNh90guu5TeJza 18 | sW7JLaStmAyCxYGxbW4LTjR8GX9DPOPmLs5ZRm8CgYAyFW5DaXIZksYJzLEGcE4c 19 | Tn7DSdy9N+PlXGPxlYHteQUg+wKsUgSKAZZmxXfn0w77hSs9qzar0IoDbjbIP1Jd 20 | nn12E+YCjQGCAJugn2s12HYZCTW2Oxd4QPbt3zUR/NiqocFxYA+TygueRuB2pzue 21 | +jKKAQXmzZzRMYLMLsWDoQKBgAnrCcoyX5VivG7ka9jqlhQcmdBxFAt7KYkj1ZDM 22 | Ud6U7qIRcYIEUd95JbNl4jzhj0WEtAqGIfWhgUvE9ADzQAiWQLt+1v9ii9lwGFe0 23 | tyuZnwCiaCoL5+Qj1Ww6c95g6f8oe51AbMp5KTm8it0axWw1YX+sZCpGYPBCXO9/ 24 | FYI3AoGBAMacjjbPjjfOXxBRhRz1rEDTrIStDj5KM4fgslKVGysqpH/mw7gSC8SK 25 | qn0anL2s3SAe9PQpOzM3pFFRZx4XMOk4ojYRZtp3FjPFDRnYuYzkfkbU7eV04awO 26 | 6nrua8KNLNK+ir9iCi46tP6Zr3F81zWGUoVArVUgCRDbA9e0swB0 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /connector/ldap/testdata/certs/dhparam.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBCAKCAQEAx5y2viJKOAAcDYSj55odZsbA7dkSQ9afEPd9uaCLOvRYKLJY1S1V 3 | C4m1eVfna8JndSLdsBGDQe4BlBTkEYMYR8CJHtUuBxeAucOH8KlF8rIHXXi71oex 4 | T7kPtJEDINQKOn06bHqNcn0a7ZMWP8jiQ708OYr5P+1T/N82QTAFpDuqK42ZnBqf 5 | 8qzQkkTN0UCktY2EWnFTbNIXcMKWQnYP8zt/CG3Q31b2bnQt2iLEa/DIF7RLNjfx 6 | 9wPQBBAqgWbLmWfdPpHsAPtQxtItb+GRbPs3aLm06CFKlQuteDoP+suo0EtglHcV 7 | V9Ynvdz0cdJCJ7EPyET6CtLMzc/Puup/AwIBAg== 8 | -----END DH PARAMETERS----- 9 | -------------------------------------------------------------------------------- /connector/ldap/testdata/certs/ldap.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC3DCCAcSgAwIBAgIJANsmsx7hUWnHMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV 3 | BAMMCmxkYXAtdGVzdHMwHhcNMTcwNDEyMjAxNzI5WhcNNDQwODI4MjAxNzI5WjAU 4 | MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 5 | AoIBAQDlWGC5X/TWgysEimM7n0hSkXRCITwAFxKG0C4EeppmL42DBcjQa0xrElRF 6 | h57EBZltbSfvTMDBZAyhx5oZKoETDfwy5jFzf4L4PazSkvfn4qWmCnrq4HNO5Vl7 7 | GBsW93bljsh2nfvoKDX2vBpEUe0qrZzJtRHq0ytfd6zXZ9+WFMsmhD9poADrH4hB 8 | /UOV3uCJPybOoy/WsANQpSgJPD886zakmF+54XQ3tExKzFA1rR4HJbU26h99U5kH 9 | 346sV7/xKJLENQVIH1qsqyA1UPDZRWusABjdIPc9Racy0/MxTVE0k5lQbBvz9QSe 10 | HZvW+ct/aZX5tjxr9JlSY7tK2I9FAgMBAAGjMDAuMAkGA1UdEwQCMAAwCwYDVR0P 11 | BAQDAgXgMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEA 12 | RZp/fNjoQNaO6KW0Ay0aaPW6jPrcqGjzFgeIXaw/0UaWm5jhptWtjOAILV+afIrd 13 | 4cKDg65o4xRdQYYbqmutFMAO/DeyDyMi3IL60qk0osipPDIORx5Ai2ZBQvUsGtwV 14 | np9UwQGNO5AGeR9N5kndyldbpxaIJFhsKOV8uRSi+4PRbMH3G0kJIX6wwZU4Ri/k 15 | 3lWJQfqULH0vtMQCWSJuaYHxWYFq4AM+H/zpLwg1WG2eKVgSMWotxMRi5LOFSBbG 16 | XuOxAb0SNBcXl6kjRYbQyHBxIJMsB1lk64g7dTJqXuYFUwmIGL/vTr6PL6EKYk65 17 | /aWO8cvwXOrYaf9umgcqvg== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /connector/ldap/testdata/certs/ldap.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA5VhguV/01oMrBIpjO59IUpF0QiE8ABcShtAuBHqaZi+NgwXI 3 | 0GtMaxJURYeexAWZbW0n70zAwWQMoceaGSqBEw38MuYxc3+C+D2s0pL35+Klpgp6 4 | 6uBzTuVZexgbFvd25Y7Idp376Cg19rwaRFHtKq2cybUR6tMrX3es12fflhTLJoQ/ 5 | aaAA6x+IQf1Dld7giT8mzqMv1rADUKUoCTw/POs2pJhfueF0N7RMSsxQNa0eByW1 6 | NuoffVOZB9+OrFe/8SiSxDUFSB9arKsgNVDw2UVrrAAY3SD3PUWnMtPzMU1RNJOZ 7 | UGwb8/UEnh2b1vnLf2mV+bY8a/SZUmO7StiPRQIDAQABAoIBAQDHBbKqK4MkxB8I 8 | ia8jhk4UmPTyjjSrP1pscyv75wkltA5xrQtfEj32jKlkzRQRt2o1c4w8NbbwHAp6 9 | OeSYAjKQfoplAS3YtMbK9XqMIc3QBPcK5/1S5gQqaw0DrR+VBpq/CvEbPm3kQUDT 10 | JNkGgLH3X0G4KNGrniT9a7UqGJIGgdBAr7bPESiDi9wuOwfhm/9TB8LOG8wB9cn4 11 | NcUipvjOcRxMFkyYtq056ZfGeoK2ooFe0lHi4j8sWXfII789OqN0plecAg8NGZsl 12 | klSncpTObE6eTXo9Jncio3pftvszEctKssK7vuL6opajtppT6C5FnKLb6NIAOo7j 13 | CPk1BRPhAoGBAPf8TMTr+l8MHRuVXEx52E1dBH46ZB8bMfvwb7cZ31Fn0EEmygCj 14 | wP9eKZ8MKmHVBbU6CbxYQMICTTwRrw9H0tNoaZBwzWMz/JDHcACfsPKtfrX8T4UQ 15 | wmVwbLctdC1Cbaxn1jYeSLoLfSe8IGPDnLpsMCzpRcQIgPS+gO69zr8vAoGBAOzB 16 | 254TKd2OQPnvUvmAVYGRYyTu/+ShH9fZyDJYtjhQbuxt6eqh3poneWJOW+KPlqDd 17 | J0a8yv1pDXmCy5k1Oo8Nubt7cPI0y2z0nm5LvAaqPaFdUJs9nq9umH3svJh6du6Z 18 | +TZ6MDU/eyJRq7Mc5SQrssziJidS3cU21b560xvLAoGBAPYpZY9Ia7Uz0iUSY5eq 19 | j7Nj9VTT45UZKsnbRxnrvckSEyDJP1XZN3iG4Sv3KI8KpWrbHNTwif/Lxx0stKin 20 | dDjU+Y0e3FJwRXL19lE4M68B17kQp2MAWufU7KX8oclXmoS8YmBAOZMsWmU6ErDV 21 | eVt4j23VdaJ9inzoKhZTJcqTAoGAH9znJZsGo16lt/1ReWqgF1Ptt+bCYY6drnsM 22 | ylnODD4m74LLXFx0jOKLH4PUMeWJLBUXWBnIZ9pfid7kb7YOL3p1aJnwVWhtiDhT 23 | qhxfLbZznOfmFT5xwMJtm2Tk7NBueSYXuBExs7jbZX8AUJau7/NBmPlGkTxBxGzg 24 | z0XQa4kCgYBxYBXwFpLLjBO+bMMkoVOlMDj7feCOWP9CsnKQSHYqPbmmb+8mA7pN 25 | mIWfjSVynVe+Ncn0I5Uijbs9QDYqcfApJQ+iXeb+VGrg4QkLHHGd/5kIY28Evc6A 26 | KVyRIuiYNmgOXGpaFpMXSw718N4U7jWW7lqUxK2rvEupFhaL52oJFQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /connector/saml/testdata/bad-ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDGTCCAgGgAwIBAgIJAINei+KBx541MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV 3 | BAoMA0JBRDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z 4 | NzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0JBRDETMBEGA1UEAwwKY29yZW9zLmNv 5 | bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKfSVQCQMJhwdeesQqZo 6 | YHktgGjKA//p176kWoqVEX8+cVgKc1IEAzN73L0KsBcNkaItbZycQxkku8NGUYvt 7 | inEK8V61vjK3IQBOV2+qKOeWvHPXd280I2JSGy768ELpZcaEmnA0CVbMaLdHLFQ/ 8 | tcRocDcMKDhNexlsJ/cDPwzSkky6FU6UnaSih+I432UeJs/hRvAs1YJ3+G6uSVQ7 9 | xo2Fk2i+qd3IvRWOBagwEhEFcd/MfAu4+w+UYW4W1BC9zJoO2ETBgUVJzaiUGZ5z 10 | rX/KNAX/+kAQWQLKW1sVgRKOI2yfyIVaUzF6V2uxwHrbeV75Pr3ClVdvuUWRcXKH 11 | Wn8CAwEAAaNQME4wHQYDVR0OBBYEFLw+L3SLMIR2wfd7IQG1m3rlgGP7MB8GA1Ud 12 | IwQYMBaAFLw+L3SLMIR2wfd7IQG1m3rlgGP7MAwGA1UdEwQFMAMBAf8wDQYJKoZI 13 | hvcNAQELBQADggEBABH2mDQuf+JqjHtBCp9coi1WuX5xJuoSKyOAvlz7QJk0bb+W 14 | oONg+ETQ7jT7KheiFQorghdulZerv+L1dmLU5ut/Zbf1zoaWLslOgVPpOy6LP0aS 15 | uaa31jm7Qpig47+kTZEpPN6vUPpmAD70/uo3NgRNj8pztkWr08fEIbX/3ukHvFUE 16 | XPxYxgF5tFj8EY6cdflzlC/0TYmJiHt/viv/yQfrvMBRvsVHfjfzNRxbNwfEdKuf 17 | YIs5KeP2al7APsmF5d/UFGzoQGXaKEOpCRJ+Oj3spooLhhzWqV4gPZLVgU0DD6ja 18 | GPb2XgLA9mdEmMukHU0RMeb8R+r8RvytWKvuJQE= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /connector/saml/testdata/bad-ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCn0lUAkDCYcHXn 3 | rEKmaGB5LYBoygP/6de+pFqKlRF/PnFYCnNSBAMze9y9CrAXDZGiLW2cnEMZJLvD 4 | RlGL7YpxCvFetb4ytyEATldvqijnlrxz13dvNCNiUhsu+vBC6WXGhJpwNAlWzGi3 5 | RyxUP7XEaHA3DCg4TXsZbCf3Az8M0pJMuhVOlJ2koofiON9lHibP4UbwLNWCd/hu 6 | rklUO8aNhZNovqndyL0VjgWoMBIRBXHfzHwLuPsPlGFuFtQQvcyaDthEwYFFSc2o 7 | lBmec61/yjQF//pAEFkCyltbFYESjiNsn8iFWlMxeldrscB623le+T69wpVXb7lF 8 | kXFyh1p/AgMBAAECggEAHh/PSk6XqoVlZLSzMhPCXX4hcq3wkdtz8rCl4AJqJaEb 9 | z2Xw1WQK/w7YzMZCXaD951KoPlh+YuEJI0BYGvoEw83nDc0p2wisT9XANDcjKI8S 10 | POkMc1W0lE2Qu5onzpr+vefHoSR2GLKQiXWpK2ZURnFI01jHT3P5CNM1SU2336ES 11 | XT1GXlxTz59BS04bySS3s8PQZTtmFvQAjtjOWkg+nzTYgEO7xU5968Px+jdlO+xf 12 | ZKtjlRKRNRmVlylUvCmIT6kIzIdyjuBqw4yx0bM1Av9QwpPuDkRWBeiT8o5xeY+L 13 | yOSxUCg24CGgAl6mGVXWh+BC9Z3h8dXg9eMervT9gQKBgQDS6QffOBr+j19FiQwH 14 | t+5h2klOR/mu8TX6oW8Dc6o5YsnLt0uQqa2jLq8kYRkOBVg68mMFvZXmhPoKv4hk 15 | rOfDKjVfPz9ShrvuY301BAp/hdi/hNP+MAt683UlcnpwLOaASZMr+MjdnVVzfF6X 16 | iNlV2RE3pAxSFkVlMPJJTmPPEQKBgQDLsxbYFj4JBKhMehdcsqjUtDuzLBPqisVa 17 | cWKACxPAXIjztsEXF4F7sQk7q68uzLU80uPBVAJjf6lWwK9tcUdcBFHCXdqj8ZXB 18 | 77W4gZSkqHY0T6DJF+NZRF+z4cOg3qtDjTvqhetl5yyiY9IPyiSckNAz40pWD/0X 19 | U/0nObuwjwKBgQCpWpUHmHWUkmtd2n3edMLlr/HM+d5zqxw89APAMdAt5DVFbxku 20 | QBE9Ru87tvv3VjNSoe8BXQpQ39Yna0SKEozHGc1hfdfK3IVrFlgjieskGsXAg1f2 21 | c33EbFlUiGfoSyWLPYj/dfVUflFvOh56b1iUpog8tW1vPJLcfkEOu/NJAQKBgQCu 22 | LAp7Z8FRarcQ9VAmhekQPq/RSv4YjOGkrNChVVdlInpDkV9XBFVF0yFm8SzQYl8R 23 | i+0McG2+b/j2YbleZf6zMkpKXH/HsJjxg6qpAbt8c0LnBbMgXxmZSXpfT8o7MknU 24 | b93scOfPcTRcAegqchiN+tDbnRwBrJgmqz0JnjbbBwKBgCXOWMgS8Azvvw+lUqua 25 | yLdcAilAaqchgVBkI5ATZQodopEP+Gvevvzh6G8uQJp9fGrL/fR/tNg2viwCF/X8 26 | ROATx1z98/ItA3kyYhiNt/A6EqOb8SVOMq/eeMpvk+RB6gA9YdMf5H9XClW4vxLy 27 | jaw+YQ6hOyMTmFfUI2/FumFE 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /connector/saml/testdata/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV 3 | BAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z 4 | NzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv 5 | bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI 6 | 4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO 7 | ZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD 8 | AB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA 9 | faXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF 10 | a7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9 11 | QacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud 12 | IwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI 13 | hvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP 14 | oR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS 15 | hSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De 16 | oX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd 17 | esSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j 18 | 1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /connector/saml/testdata/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCh93Slm0agiGQ9 3 | pt2hyOJXwU/rv+AxAnXu+BoKvVroHU7nFUMw2VE2iO8M59eyTPYXT5l72kkRvQWg 4 | dR97jmQrP35nYQREUYT77hFXwGWhX0H0e75+1r1M0+88DCywfPhB8y93zkhjNkJw 5 | jjHlAwAduz29H7gVuNzV2kw/a8HxQh76NVD/kwSSD51euRzHjBiK7Ri7ezGUz7j5 6 | ty52AH2ly39r1ksy+ZwIJyb1IHHmV5srnIgX16bfHNYyLI+Mt7QaEU0amsbmltRK 7 | O1jChWu7Oir0wShKzyrswKCHFQeIq+o5tbYnAIAe9kknCrTySXRD3pk10TTc5IsO 8 | 1W6WPUGnAgMBAAECggEBAJUJUDPHIwE7IAo/Drf9UpFvl2wWPmS6n+yKLeRuA0WN 9 | Gnq27QH5Jqro7BdTCv7NpLEklNYLsar55UCWJacbCn9lSJo2Aqge3yC3GwxFRP9t 10 | 2RHwAAVU8hHM/tmhVkn8ZLDC5o32qlNorVBG+BCEZ0n0bsYlds2+Mq8x1XGSZX7q 11 | Yyjt02q2VCEDPVUGqR2ONEE98Bv++mUfXzZjGohBPre67PuvEppEwo/uf4ILuyYB 12 | lusSYcmPD2IBE/7FcRPefb2uDP9c6Tjo8PHXpK5hDBCtLz8lXheYMuTp9GqJmcOk 13 | kw8af2jNkMUEpFho0RqnrJNQ3A7M5PXQiWxwYSL/2RkCgYEAz4Kg0qm4Oe5jl+4r 14 | R986nElujo9g9Ad2sy9yT6fqceWoMKf68wh6bMx7HWXRqASS7bJ8wmS9EwgaWLBO 15 | mjqZax99F1UiFk3+5lNEaYB1OlFaWRsZSapoX6b2JyfEbODqSgKVIaYVTYBHLip3 16 | 3ab/EKNUrNbW9bLSXgbt3aT2kU0CgYEAx9BiErhZqkgmip+4omCWuTr6Lj/awftB 17 | CVTfwLBYkh7SZcRAWc2bx7a1Bs1rvlczhGYsOlrZXnQRSM4fuUxUb+N5TCzZI9V/ 18 | Prc2r2Lps3AB5CDPLZoyv0efBjSLqAA1tYKTvAHREi9Wfe5PNdfKiwrz6KmIwV3c 19 | +s2YSWcU5MMCgYEArmzvIiTnZkqsDJl2aAOMELLo64w5wuZDMHtBaxOKThLtPXj1 20 | yDPoNGvtUNi1UrYFiyftFrn29HhrLQGGEL4RF6pwS5yT+ou1J4X2i3gfEdYwS5Yr 21 | u3AyK7T8VA1pXtvwFCX3lUE1xt989aFdAEPPQv0HwAEWz5Bwo/jPGPABEkECgYBy 22 | vDWUikbygHuhHhXnJ49kzXjbFc+Hk77EnPferWQug4RM62QILQhGpaNNRKeZpHjw 23 | jbrXx1MJ6ZwDMlkFDc9ucDA2jYoiCXYHjSzZiPKpFqf/VtegV+rL61RlO8b1sSkm 24 | ENTEIEbtKkGADldtk3u6W4+zCaZ9YmiBm4zWmVpmAQKBgHvNRcbITib+sbQE+cJM 25 | 4TtrAHFTLWtGCd+n6rKrE8gjt7ypbBOMlOau60LZ2Pbt3DrqOLtDoOalvZhYraOb 26 | rkoPbDAVaXAmUJ8tw2M07PPLpJuruLSFw16VrBaDiyubO8H/hvnuF4kKpRvt8ty0 27 | DBogMo9McFczyisRKebmANLw 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /connector/saml/testdata/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # Always run from the testdata directory 4 | cd "$(dirname "$0")" 5 | 6 | # Uncomment these commands to regenerate the CA files. 7 | # 8 | # openssl req \ 9 | # -nodes \ 10 | # -newkey rsa:2048 \ 11 | # -keyout ca.key \ 12 | # -new -x509 -days 7300 \ 13 | # -extensions v3_ca \ 14 | # -out ca.crt \ 15 | # -subj "/O=DEX/CN=coreos.com" 16 | # 17 | # openssl req \ 18 | # -nodes \ 19 | # -newkey rsa:2048 \ 20 | # -keyout bad-ca.key \ 21 | # -new -x509 -days 7300 \ 22 | # -extensions v3_ca \ 23 | # -out bad-ca.crt \ 24 | # -subj "/O=BAD/CN=coreos.com" 25 | 26 | # Sign these files using xmlsec1. 27 | # 28 | # Templates MUST have a element already embedded in them so 29 | # xmlsec1 can know where to embed the signature. 30 | # 31 | # See: https://sgros.blogspot.com/2013/01/signing-xml-document-using-xmlsec1.html 32 | 33 | xmlsec1 --sign --privkey-pem ca.key,ca.crt --output good-resp.xml good-resp.tmpl 34 | xmlsec1 --sign --privkey-pem ca.key,ca.crt --output bad-status.xml bad-status.tmpl 35 | 36 | # Sign a specific sub element, not just the root. 37 | # 38 | # Values match up to the element in the documents. 39 | xmlsec1 --sign --privkey-pem ca.key,ca.crt \ 40 | --id-attr:ID Assertion \ 41 | --output assertion-signed.xml assertion-signed.tmpl 42 | 43 | xmlsec1 --sign --privkey-pem ca.key,ca.crt \ 44 | --id-attr:ID Assertion \ 45 | --output two-assertions-first-signed.xml \ 46 | two-assertions-first-signed.tmpl 47 | 48 | -------------------------------------------------------------------------------- /connector/saml/testdata/idp-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNV 3 | BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNV 4 | BAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcN 5 | AQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3 6 | WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMD 7 | SURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEb 8 | MBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOC 9 | AQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3 10 | vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsby 11 | CRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1u 12 | uVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sB 13 | TlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4Xn 14 | XsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQW 15 | BBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsH 16 | TXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3Ju 17 | aWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMT 18 | CmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb3 19 | 9nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW 20 | 6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1 21 | wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUK 22 | FpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+a 23 | jSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor 24 | /NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0De 25 | Jc3men4= 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /connector/saml/testdata/idp-resp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.okta.com/exk91cb99lKkKSYoy0h7 4 | 5 | 6 | 7 | 8 | http://www.okta.com/exk91cb99lKkKSYoy0h7 9 | 10 | eric.chiang+okta@coreos.com 11 | 12 | 13 | 14 | 15 | 16 | 17 | http://localhost:5556/dex/callback 18 | 19 | 20 | 21 | 22 | urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 23 | 24 | 25 | 26 | 27 | admin 28 | 29 | 30 | eric.chiang+okta@coreos.com 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /connector/saml/testdata/oam-ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIB/jCCAWegAwIBAgIBCjANBgkqhkiG9w0BAQQFADAkMSIwIAYDVQQDExlkZWFv 3 | YW0tZGV2MDIuanBsLm5hc2EuZ292MB4XDTE2MDYzMDA0NTQxNloXDTI2MDYyODA0 4 | NTQxNlowJDEiMCAGA1UEAxMZZGVhb2FtLWRldjAyLmpwbC5uYXNhLmdvdjCBnzAN 5 | BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAht1N4lGdwUbl7YRyHwSCrnep6/e2I3+V 6 | eue0pSA/DGn8OuR/udM8UCja5utqlqJdq200ox4b4Mpz0Jg9kMckALtKe+1DgeES 7 | EIx9FpeuBdHlitYQNSbEr30HIG2nmeTOy4Vi5unBO54um3tNazcUTMA0/LJ6KQL8 8 | LeZSlB/IxwUCAwEAAaNAMD4wDAYDVR0TAQH/BAIwADAPBgNVHQ8BAf8EBQMDB9gA 9 | MB0GA1UdDgQWBBRYo1YjfrNonauLzj6/AsueWFGSszANBgkqhkiG9w0BAQQFAAOB 10 | gQACq7GHK/Zsg0+qC0WWa2ZjmOXE6Dqk/xuooG49QT7ihABs7k9U27Fw3xKF6MkC 11 | 7pca1FwT82eZK1N3XKKpZe7Flu1fMKt2o/XSiBkDjWwUcChVnwGsUBe8hJFwFqg7 12 | olNJn1kaVBJUqZIiXF9kS0d+1H55rStOd0CNXAzp9utr2A== 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /connector/saml/testdata/okta-ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDpDCCAoygAwIBAgIGAVjgvNroMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG 3 | A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU 4 | MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05NjkyNDQxHDAaBgkqhkiG9w0BCQEW 5 | DWluZm9Ab2t0YS5jb20wHhcNMTYxMjA4MjMxOTIzWhcNMjYxMjA4MjMyMDIzWjCBkjELMAkGA1UE 6 | BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV 7 | BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTY5MjQ0MRwwGgYJ 8 | KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 9 | 4dW2YlcXjZwnTmLnV7IOBq8hhrdbqlNwdjCHRyx1BizOk3RbVP56grgPdyWScTCPpJ6vZZ8rtrY0 10 | m1rwr+cxifNuQGKTlE33g2hReo/N9f3LFUMITlnnNH80Yium3SYuEqGeHLYerelXOnEKx6x+X5qD 11 | eg2DRW6I9/v/mfN2KAQEDqF9aSNlNFWZWmb52kukMv3tLWw0puaevicIZ/nZrW+D3CLDVVfWHeVt 12 | 46EF2bkLdgbIJOU3GzLoolgBOCkydX9x6xTw6knwQaqYsRGflacw6571IzWEwjmd17uJXkarnhM1 13 | 51pqwIoksTzycbjinIg6B1rNpGFDN7Ah+9EnVQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQDQOtlj 14 | 7Yk1j4GV/135zLOlsaonq8zfsu05VfY5XydNPFEkyIVcegLa8RiqALyFf+FjY/wVyhXtARw7NBUo 15 | u/Jh63cVya/VEoP1SVa0XQLf/ial+XuwdBBL1yc+7o2XHOfluDaXw/v2FWYpZtICf3a39giGaNWp 16 | eCT4g2TDWM4Jf/X7/mRbLX9tQO7XRural1CXx8VIMcmbKNbUtQiO8yEtVQ+FJKOsl7KOSzkgqNiL 17 | rJy+Y0D9biLZVKp07KWAY2FPCEtCkBrvo8BhvWbxWMA8CVQNAiTylM27Pc6kbc64pNr7C1Jx1wuE 18 | mVy9Fgb4PA2N3hPeD7mBmGGp7CfDbGcy 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /docker-compose.override.yaml.dist: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | mysql: 5 | ports: 6 | - "127.0.0.1:3306:3306" 7 | 8 | postgres: 9 | ports: 10 | - "127.0.0.1:5432:5432" 11 | 12 | etcd: 13 | ports: 14 | - "127.0.0.1:2379:2379" 15 | 16 | ldap: 17 | ports: 18 | - "127.0.0.1:389:389" 19 | - "127.0.0.1:636:636" 20 | -------------------------------------------------------------------------------- /docker-compose.test.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | ldap: 5 | image: osixia/openldap:1.4.0 6 | # Copying is required because the entrypoint modifies the *.ldif files. 7 | # For verbose output, use: command: ["--copy-service", "--loglevel", "debug"] 8 | command: ["--copy-service"] 9 | environment: 10 | LDAP_BASE_DN: "dc=example,dc=org" 11 | LDAP_TLS: "true" 12 | LDAP_TLS_VERIFY_CLIENT: try 13 | ports: 14 | - 3890:389 15 | - 6360:636 16 | volumes: 17 | - ./connector/ldap/testdata/certs:/container/service/slapd/assets/certs 18 | - ./connector/ldap/testdata/schema.ldif:/container/service/slapd/assets/config/bootstrap/ldif/99-schema.ldif 19 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # This docker-compose file provides quick setups for testing different storage backend options. 2 | version: "3.8" 3 | 4 | services: 5 | mysql: 6 | # For using percona-xtradb you need to make strict mode permissive with: 7 | # docker-compose exec mysql mysql -uroot -proot -e "SET GLOBAL pxc_strict_mode=PERMISSIVE;" 8 | # See: https://www.percona.com/doc/percona-xtradb-cluster/5.7/features/pxc-strict-mode.html 9 | # image: percona/percona-xtradb-cluster:5.7 10 | # image: mariadb:10.5 11 | # image: mysql:5.6 12 | # image: mysql:8.0 13 | image: mysql:5.7 14 | environment: 15 | MYSQL_DATABASE: dex 16 | MYSQL_USER: mysql 17 | MYSQL_PASSWORD: mysql 18 | MYSQL_ROOT_PASSWORD: root 19 | 20 | postgres: 21 | image: postgres:10.15 22 | environment: 23 | POSTGRES_DB: dex 24 | POSTGRES_USER: postgres 25 | POSTGRES_PASSWORD: postgres 26 | 27 | etcd: 28 | image: gcr.io/etcd-development/etcd:v3.5.0 29 | environment: 30 | ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379 31 | ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379 32 | 33 | # For testing the Kubernetes storage backend we suggest https://kind.sigs.k8s.io/: 34 | # kind create cluster 35 | 36 | ldap: 37 | image: osixia/openldap:1.4.0 38 | # Copying is required because the entrypoint modifies the *.ldif files. 39 | # For verbose output, use: command: ["--copy-service", "--loglevel", "debug"] 40 | command: ["--copy-service"] 41 | environment: 42 | LDAP_BASE_DN: "dc=example,dc=org" 43 | LDAP_TLS: "true" 44 | LDAP_TLS_VERIFY_CLIENT: try 45 | volumes: 46 | - ./connector/ldap/testdata/certs:/container/service/slapd/assets/certs 47 | - ./connector/ldap/testdata/schema.ldif:/container/service/slapd/assets/config/bootstrap/ldif/99-schema.ldif 48 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | These documents have moved to the [dexidp/website repo](https://github.com/dexidp/website). 2 | -------------------------------------------------------------------------------- /docs/enhancements/README.md: -------------------------------------------------------------------------------- 1 | # Dex Enhancement Proposal 2 | 3 | ## Why do we need it? 4 | 5 | Dex Enhancement Proposal (DEP) is a design document providing information to the community, or describing a new feature for Dex. 6 | 7 | We intend DEPs to be the primary mechanisms for proposing major new features or significant changes to existing ones. 8 | This will make it easier for the community to describe, track, and look through the history of changes that affected the development of the project. 9 | 10 | ## Process 11 | 12 | ### Before starting 13 | 1. Search GitHub for previous [issues](https://github.com/dexidp/dex/issues), [discussions](https://github.com/dexidp/dex/discussions) and [DEPs](https://github.com/dexidp/dex/tree/master/docs/enhancements). 14 | 2. If a discussion does not exist, [open it](https://github.com/dexidp/dex/discussions/new?category=Ideas). 15 | 3. Ensure that writing enhancement proposal is necessary for you change by discussing it with a community. 16 | 17 | ### Writing an enhancement proposal 18 | 19 | 1. Fork the repo. 20 | 2. Copy the [`docs/enhancements/_title-YYYY-MM-DD-#issue.md`](docs/enhancements/_title-YYYY-MM-DD-#issue.md) template with the appropriate 21 | name. 22 | 3. Fill all sections according to hints in them. Provide as much information as you can. 23 | 4. Submit your PR and discuss it with the Dex team. 24 | -------------------------------------------------------------------------------- /docs/enhancements/_title-YYYY-MM-DD-#issue.md: -------------------------------------------------------------------------------- 1 | # Dex Enhancement Proposal (DEP) - - 2 | 3 | ## Table of Contents 4 | 5 | - [Summary](#summary) 6 | - [Motivation](#motivation) 7 | - [Goals/Pain](#goals) 8 | - [Non-Goals](#non-goals) 9 | - [Proposal](#proposal) 10 | - [User Experience](#user-experience) 11 | - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints) 12 | - [Risks and Mitigations](#risks-and-mitigations) 13 | - [Alternatives](#alternatives) 14 | - [Future Improvements](#future-improvements) 15 | 16 | ## Summary 17 | 18 | - Provide a one-paragraph description of the expected change here. 19 | 20 | ## Context 21 | 22 | - Link to any previous issues, RFCs, discussions, or briefs. 23 | - Link to any ongoing or future work relevant to this change. 24 | 25 | ## Motivation 26 | 27 | ### Goals/Pain 28 | 29 | - List work that is assumed to be done in the scope of this enhancement. 30 | - Mention problems solve by this enhancement. 31 | 32 | ### Non-goals 33 | 34 | - List work that is entirely out of the scope of this enhancement. Use this to define DEP borders to keep work focused. 35 | - All planned future enhancements should be listed in one of the following blocks - Future Improvements. 36 | 37 | ## Proposal 38 | 39 | ### User Experience 40 | 41 | - Explain your change as if you were describing it to end-users. 42 | - Explain the way users are supposed to use Dex with the proposed enhancement. 43 | 44 | ### Implementation Details/Notes/Constraints 45 | 46 | - Explain your change as if you were at a development team meeting (give more technical and implementation details). 47 | - When possible, demonstrate with pseudo-code, not text. 48 | - Be specific. Be opinionated. Avoid ambiguity. 49 | 50 | ### Risks and Mitigations 51 | 52 | - Mention all expected risks and migrations in detail here. 53 | - Do not forget to mention if the proposed enhancement is a breaking change. 54 | 55 | ### Alternatives 56 | 57 | - What other approaches have been considered, and why did you not choose them? 58 | - What happens if this enhancement will never be accepted and implemented? 59 | 60 | ## Future Improvements 61 | 62 | - List any future improvements. 63 | -------------------------------------------------------------------------------- /docs/img/caution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexidp/dex/7c9744953fdd0c1a86b5eedd71d5684a639dc69f/docs/img/caution.png -------------------------------------------------------------------------------- /docs/img/dex-backend-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexidp/dex/7c9744953fdd0c1a86b5eedd71d5684a639dc69f/docs/img/dex-backend-flow.png -------------------------------------------------------------------------------- /docs/img/dex-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexidp/dex/7c9744953fdd0c1a86b5eedd71d5684a639dc69f/docs/img/dex-flow.png -------------------------------------------------------------------------------- /docs/logos/dex-glyph-bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexidp/dex/7c9744953fdd0c1a86b5eedd71d5684a639dc69f/docs/logos/dex-glyph-bw.png -------------------------------------------------------------------------------- /docs/logos/dex-glyph-bw.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> 3 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> 4 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" 5 | width="112px" height="109px" viewBox="0 0 112 109" enable-background="new 0 0 112 109" xml:space="preserve"> 6 | <g> 7 | <path d="M88.345,51.574c7.588-3.55,12.764-10.49,14.175-18.53C96.396,19.395,84.663,9.054,70.094,4.851 8 | c4.923,7.133,7.272,15.583,6.771,24.17C83.311,34.466,87.716,42.55,88.345,51.574z M27.27,38.542 9 | c-8.207-1.045-16.333,1.973-21.858,8.054C3.23,61.683,7.869,76.84,18.099,88.158c-0.527-8.64,1.856-17.306,6.831-24.483 10 | C22.19,55.048,23.32,45.944,27.27,38.542z M33.01,76.928c-2.997,8.079-1.755,17.193,3.642,24.215 11 | c12.155,4.943,26.051,5.146,38.643-0.035c-7.818-2.516-14.886-7.518-19.887-14.731C47.233,86.23,39.124,83.032,33.01,76.928z 12 | M63.122,22.202c-1.507-8.158-7.053-15.383-15.23-18.732C33.778,5.711,20.745,13.966,12.76,26.631 13 | c8.115-2.487,16.74-2.178,24.529,0.639C44.816,22.008,54.043,20.144,63.122,22.202z M85.891,66.457 14 | c-3.086,7.399-8.722,13.188-15.678,16.61c6.194,5.604,14.805,7.758,22.852,5.834c9.054-9.587,13.884-22.198,13.9-35.009 15 | C101.549,60.198,94.131,64.67,85.891,66.457z"/> 16 | <g> 17 | <circle cx="56.035" cy="53.892" r="15.972"/> 18 | </g> 19 | </g> 20 | </svg> 21 | -------------------------------------------------------------------------------- /docs/logos/dex-glyph-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexidp/dex/7c9744953fdd0c1a86b5eedd71d5684a639dc69f/docs/logos/dex-glyph-color.png -------------------------------------------------------------------------------- /docs/logos/dex-glyph-color.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> 3 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> 4 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" 5 | width="112px" height="109px" viewBox="0 0 112 109" enable-background="new 0 0 112 109" xml:space="preserve"> 6 | <g> 7 | <path fill="#449FD8" d="M88.345,51.574c7.588-3.55,12.764-10.49,14.175-18.53C96.396,19.395,84.663,9.054,70.094,4.851 8 | c4.923,7.133,7.272,15.583,6.771,24.17C83.311,34.466,87.716,42.55,88.345,51.574z M27.27,38.542 9 | c-8.207-1.045-16.333,1.973-21.858,8.054C3.23,61.683,7.869,76.84,18.099,88.158c-0.527-8.64,1.856-17.306,6.831-24.483 10 | C22.19,55.048,23.32,45.944,27.27,38.542z M33.01,76.928c-2.997,8.079-1.755,17.193,3.642,24.215 11 | c12.155,4.943,26.051,5.146,38.643-0.035c-7.818-2.516-14.886-7.518-19.887-14.731C47.233,86.23,39.124,83.032,33.01,76.928z 12 | M63.122,22.202C61.615,14.044,56.069,6.819,47.892,3.47C33.778,5.711,20.745,13.966,12.76,26.631 13 | c8.115-2.487,16.74-2.178,24.529,0.639C44.816,22.008,54.043,20.144,63.122,22.202z M85.891,66.457 14 | c-3.086,7.399-8.722,13.188-15.678,16.61c6.194,5.604,14.805,7.758,22.852,5.834c9.054-9.587,13.884-22.198,13.9-35.009 15 | C101.549,60.198,94.131,64.67,85.891,66.457z"/> 16 | <g> 17 | <circle fill="#F04D5C" cx="56.035" cy="53.892" r="15.972"/> 18 | </g> 19 | </g> 20 | </svg> 21 | -------------------------------------------------------------------------------- /docs/logos/dex-glyph-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexidp/dex/7c9744953fdd0c1a86b5eedd71d5684a639dc69f/docs/logos/dex-glyph-white.png -------------------------------------------------------------------------------- /docs/logos/dex-glyph-white.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> 3 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> 4 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" 5 | width="112px" height="109px" viewBox="0 0 112 109" enable-background="new 0 0 112 109" xml:space="preserve"> 6 | <g> 7 | <g> 8 | <path fill="#FFFFFF" d="M88.345,51.878c7.588-3.55,12.764-10.49,14.175-18.53C96.396,19.699,84.663,9.358,70.094,5.155 9 | c4.923,7.133,7.272,15.583,6.771,24.17C83.311,34.77,87.716,42.854,88.345,51.878z M27.27,38.845 10 | c-8.207-1.045-16.333,1.973-21.858,8.054C3.23,61.987,7.869,77.144,18.099,88.462c-0.527-8.64,1.856-17.306,6.831-24.483 11 | C22.19,55.352,23.32,46.248,27.27,38.845z M33.01,77.231c-2.997,8.079-1.755,17.193,3.642,24.215 12 | c12.155,4.943,26.051,5.146,38.643-0.035c-7.818-2.516-14.886-7.518-19.887-14.731C47.233,86.533,39.124,83.336,33.01,77.231z 13 | M63.122,22.506c-1.506-8.158-7.053-15.383-15.229-18.732C33.778,6.015,20.745,14.27,12.76,26.935 14 | c8.115-2.487,16.74-2.178,24.529,0.639C44.816,22.312,54.043,20.448,63.122,22.506z M85.891,66.761 15 | c-3.086,7.399-8.722,13.188-15.678,16.61c6.194,5.604,14.805,7.758,22.852,5.834c9.054-9.587,13.884-22.198,13.9-35.009 16 | C101.549,60.502,94.131,64.974,85.891,66.761z"/> 17 | <g> 18 | <circle fill="#FFFFFF" cx="56.035" cy="54.196" r="15.972"/> 19 | </g> 20 | </g> 21 | </g> 22 | </svg> 23 | -------------------------------------------------------------------------------- /docs/logos/dex-horizontal-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexidp/dex/7c9744953fdd0c1a86b5eedd71d5684a639dc69f/docs/logos/dex-horizontal-color.png -------------------------------------------------------------------------------- /docs/logos/dex-horizontal-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexidp/dex/7c9744953fdd0c1a86b5eedd71d5684a639dc69f/docs/logos/dex-horizontal-white.png -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | -------------------------------------------------------------------------------- /examples/config-ad-kubelogin.yaml: -------------------------------------------------------------------------------- 1 | # Active Directory and kubelogin Integration sample 2 | issuer: https://dex.example.com:32000/dex 3 | storage: 4 | type: sqlite3 5 | config: 6 | file: examples/dex.db 7 | web: 8 | https: 0.0.0.0:32000 9 | tlsCert: openid-ca.pem 10 | tlsKey: openid-key.pem 11 | 12 | connectors: 13 | - type: ldap 14 | name: OpenLDAP 15 | id: ldap 16 | config: 17 | host: localhost:636 18 | 19 | # No TLS for this setup. 20 | insecureNoSSL: false 21 | insecureSkipVerify: true 22 | 23 | # This would normally be a read-only user. 24 | bindDN: cn=Administrator,cn=users,dc=example,dc=com 25 | bindPW: admin0! 26 | 27 | usernamePrompt: Email Address 28 | 29 | userSearch: 30 | baseDN: cn=Users,dc=example,dc=com 31 | filter: "(objectClass=person)" 32 | username: userPrincipalName 33 | # "DN" (case sensitive) is a special attribute name. It indicates that 34 | # this value should be taken from the entity's DN not an attribute on 35 | # the entity. 36 | idAttr: DN 37 | emailAttr: userPrincipalName 38 | nameAttr: cn 39 | 40 | groupSearch: 41 | baseDN: cn=Users,dc=example,dc=com 42 | filter: "(objectClass=group)" 43 | 44 | userMatchers: 45 | # A user is a member of a group when their DN matches 46 | # the value of a "member" attribute on the group entity. 47 | - userAttr: DN 48 | groupAttr: member 49 | 50 | # The group name should be the "cn" value. 51 | nameAttr: cn 52 | 53 | staticClients: 54 | - id: kubernetes 55 | redirectURIs: 56 | - 'http://localhost:8000' 57 | name: 'Kubernetes' 58 | secret: ZXhhbXBsZS1hcHAtc2VjcmV0 59 | 60 | -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dexidp/dex/examples 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/coreos/go-oidc/v3 v3.14.1 7 | github.com/dexidp/dex/api/v2 v2.3.0 8 | github.com/spf13/cobra v1.9.1 9 | golang.org/x/oauth2 v0.30.0 10 | google.golang.org/grpc v1.72.1 11 | ) 12 | 13 | require ( 14 | github.com/go-jose/go-jose/v4 v4.0.5 // indirect 15 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 16 | github.com/spf13/pflag v1.0.6 // indirect 17 | golang.org/x/crypto v0.38.0 // indirect 18 | golang.org/x/net v0.40.0 // indirect 19 | golang.org/x/sys v0.33.0 // indirect 20 | golang.org/x/text v0.25.0 // indirect 21 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect 22 | google.golang.org/protobuf v1.36.5 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /examples/grpc-client/.gitignore: -------------------------------------------------------------------------------- 1 | *.key 2 | *.crt 3 | *.csr 4 | index.* 5 | serial* 6 | -------------------------------------------------------------------------------- /examples/grpc-client/README.md: -------------------------------------------------------------------------------- 1 | # Running a Dex gRPC client 2 | 3 | Using gRPC, a client application can directly call methods on a server application as if it was a local object. The schema for Dex's gRPC API calls is defined in [`api/api.proto`][api-proto]. [`client.go`][client] is an example client program that makes a bunch of API calls to the dex server. For further details on the Dex API refer the [documentation][https://dexidp.io/docs/api/]. 4 | 5 | ## Generating Credentials 6 | 7 | Before running the client or the server, TLS credentials have to be setup for secure communication. Run the `cred-gen` script to create TLS credentials for running this example. This script generates a `ca.crt`, `server.crt`, `server.key`, `client.crt`, and `client.key`. 8 | 9 | ``` 10 | # Used to set certificate subject alt names. 11 | export SAN=IP.1:127.0.0.1 12 | 13 | # Run the script 14 | ./examples/grpc-client/cert-gen 15 | ``` 16 | To verify that the server and client certificates were signed by the CA, run the following commands: 17 | 18 | ``` 19 | openssl verify -CAfile ca.crt server.crt 20 | openssl verify -CAfile ca.crt client.crt 21 | ``` 22 | 23 | ## Running the Dex server 24 | 25 | To expose the gRPC service, the gRPC option must be enabled via the dex config file as shown below. 26 | 27 | ```yaml 28 | # Enables the gRPC API. 29 | grpc: 30 | addr: 127.0.0.1:5557 31 | tlsCert: server.crt 32 | tlsKey: server.key 33 | 34 | ``` 35 | Start an instance of the dex server with an in-memory data store: 36 | 37 | ``` 38 | ./bin/dex serve examples/grpc-client/config.yaml 39 | ``` 40 | 41 | ## Running the Dex client 42 | 43 | Finally run the Dex client providing the CA certificate, client certificate and client key as arguments. 44 | 45 | ``` 46 | ./bin/grpc-client -ca-crt=ca.crt -client-crt=client.crt -client-key=client.key 47 | ``` 48 | Running the gRPC client will cause the following API calls to be made to the server 49 | 1. CreatePassword 50 | 2. ListPasswords 51 | 3. VerifyPassword 52 | 4. DeletePassword 53 | 54 | ## Cleaning up 55 | 56 | Run the following command to destroy all the credentials files that were created by the `cert-gen` script: 57 | 58 | ``` 59 | ./examples/grpc-client/cert-destroy 60 | ``` 61 | [api-proto]: ../../api/api.proto 62 | [client]: client.go 63 | -------------------------------------------------------------------------------- /examples/grpc-client/cert-destroy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -f ca.key ca.crt server.key server.csr server.crt client.key client.csr client.crt index.* serial* 4 | rm -rf certs crl newcerts 5 | -------------------------------------------------------------------------------- /examples/grpc-client/cert-gen: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z $SAN ] 4 | then echo "Set SAN with a DNS or IP(e.g. export SAN=IP.1:127.0.0.1,IP.2:172.18.0.2)." 5 | exit 1 6 | fi 7 | 8 | echo "Creating CA, server cert/key, and client cert/key..." 9 | 10 | # Creating basic files/directories 11 | mkdir -p {certs,crl,newcerts} 12 | touch index.txt 13 | echo 1000 > serial 14 | 15 | # CA private key (unencrypted) 16 | openssl genrsa -out ca.key 4096 17 | # Certificate Authority (self-signed certificate) 18 | openssl req -config examples/grpc-client/openssl.conf -new -x509 -days 3650 -sha256 -key ca.key -extensions v3_ca -out ca.crt -subj "/CN=fake-ca" 19 | 20 | # Server private key (unencrypted) 21 | openssl genrsa -out server.key 2048 22 | # Server certificate signing request (CSR) 23 | openssl req -config examples/grpc-client/openssl.conf -new -sha256 -key server.key -out server.csr -subj "/CN=fake-server" 24 | # Certificate Authority signs CSR to grant a certificate 25 | openssl ca -batch -config examples/grpc-client/openssl.conf -extensions server_cert -days 365 -notext -md sha256 -in server.csr -out server.crt -cert ca.crt -keyfile ca.key 26 | 27 | # Client private key (unencrypted) 28 | openssl genrsa -out client.key 2048 29 | # Signed client certificate signing request (CSR) 30 | openssl req -config examples/grpc-client/openssl.conf -new -sha256 -key client.key -out client.csr -subj "/CN=fake-client" 31 | # Certificate Authority signs CSR to grant a certificate 32 | openssl ca -batch -config examples/grpc-client/openssl.conf -extensions usr_cert -days 365 -notext -md sha256 -in client.csr -out client.crt -cert ca.crt -keyfile ca.key 33 | 34 | # Remove CSR's 35 | rm *.csr 36 | -------------------------------------------------------------------------------- /examples/grpc-client/config.yaml: -------------------------------------------------------------------------------- 1 | issuer: http://127.0.0.1:5556/dex 2 | 3 | storage: 4 | type: sqlite3 5 | config: 6 | file: examples/dex.db 7 | 8 | # Configuration for the HTTP endpoints. 9 | web: 10 | http: 0.0.0.0:5556 11 | 12 | grpc: 13 | addr: 127.0.0.1:5557 14 | tlsCert: server.crt 15 | tlsKey: server.key 16 | tlsClientCA: ca.crt 17 | 18 | connectors: 19 | - type: mockCallback 20 | id: mock 21 | name: Example 22 | 23 | # Let dex keep a list of passwords which can be used to login to dex. 24 | enablePasswordDB: true 25 | 26 | -------------------------------------------------------------------------------- /examples/grpc-client/openssl.conf: -------------------------------------------------------------------------------- 1 | # OpenSSL configuration file. 2 | # Adapted from https://github.com/coreos/matchbox/blob/master/examples/etc/matchbox/openssl.conf 3 | 4 | # default environment variable values 5 | SAN = 6 | 7 | [ ca ] 8 | # `man ca` 9 | default_ca = CA_default 10 | 11 | [ CA_default ] 12 | # Directory and file locations. 13 | dir = . 14 | certs = $dir/certs 15 | crl_dir = $dir/crl 16 | new_certs_dir = $dir/newcerts 17 | database = $dir/index.txt 18 | serial = $dir/serial 19 | # certificate revocation lists. 20 | crlnumber = $dir/crlnumber 21 | crl = $dir/crl/intermediate-ca.crl 22 | crl_extensions = crl_ext 23 | default_crl_days = 30 24 | default_md = sha256 25 | 26 | name_opt = ca_default 27 | cert_opt = ca_default 28 | default_days = 375 29 | preserve = no 30 | policy = policy_loose 31 | 32 | [ policy_loose ] 33 | # Allow the CA to sign a range of certificates. 34 | countryName = optional 35 | stateOrProvinceName = optional 36 | localityName = optional 37 | organizationName = optional 38 | organizationalUnitName = optional 39 | commonName = supplied 40 | emailAddress = optional 41 | 42 | [ req ] 43 | # `man req` 44 | default_bits = 4096 45 | distinguished_name = req_distinguished_name 46 | string_mask = utf8only 47 | default_md = sha256 48 | 49 | [ req_distinguished_name ] 50 | countryName = Country Name (2 letter code) 51 | stateOrProvinceName = State or Province Name 52 | localityName = Locality Name 53 | 0.organizationName = Organization Name 54 | organizationalUnitName = Organizational Unit Name 55 | commonName = Common Name 56 | 57 | # Certificate extensions (`man x509v3_config`) 58 | 59 | [ v3_ca ] 60 | subjectKeyIdentifier = hash 61 | authorityKeyIdentifier = keyid:always,issuer 62 | basicConstraints = critical, CA:true, pathlen:0 63 | keyUsage = critical, digitalSignature, cRLSign, keyCertSign 64 | 65 | [ usr_cert ] 66 | basicConstraints = CA:FALSE 67 | nsCertType = client 68 | nsComment = "OpenSSL Generated Client Certificate" 69 | subjectKeyIdentifier = hash 70 | authorityKeyIdentifier = keyid,issuer 71 | keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment 72 | extendedKeyUsage = clientAuth 73 | 74 | [ server_cert ] 75 | basicConstraints = CA:FALSE 76 | nsCertType = server 77 | nsComment = "OpenSSL Generated Server Certificate" 78 | subjectKeyIdentifier = hash 79 | authorityKeyIdentifier = keyid,issuer:always 80 | keyUsage = critical, digitalSignature, keyEncipherment 81 | extendedKeyUsage = serverAuth 82 | subjectAltName = $ENV::SAN 83 | -------------------------------------------------------------------------------- /examples/k8s/.gitignore: -------------------------------------------------------------------------------- 1 | ssl/ 2 | -------------------------------------------------------------------------------- /examples/k8s/gencert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p ssl 4 | 5 | cat << EOF > ssl/req.cnf 6 | [req] 7 | req_extensions = v3_req 8 | distinguished_name = req_distinguished_name 9 | 10 | [req_distinguished_name] 11 | 12 | [ v3_req ] 13 | basicConstraints = CA:FALSE 14 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 15 | subjectAltName = @alt_names 16 | 17 | [alt_names] 18 | DNS.1 = dex.example.com 19 | EOF 20 | 21 | openssl genrsa -out ssl/ca-key.pem 2048 22 | openssl req -x509 -new -nodes -key ssl/ca-key.pem -days 10 -out ssl/ca.pem -subj "/CN=kube-ca" 23 | 24 | openssl genrsa -out ssl/key.pem 2048 25 | openssl req -new -key ssl/key.pem -out ssl/csr.pem -subj "/CN=kube-ca" -config ssl/req.cnf 26 | openssl x509 -req -in ssl/csr.pem -CA ssl/ca.pem -CAkey ssl/ca-key.pem -CAcreateserial -out ssl/cert.pem -days 10 -extensions v3_req -extfile ssl/req.cnf 27 | -------------------------------------------------------------------------------- /examples/ldap/config-ldap.ldif: -------------------------------------------------------------------------------- 1 | # Already included in default config of Docker image osixia/openldap:1.4.0. 2 | # 3 | # dn: dc=example,dc=org 4 | # objectClass: dcObject 5 | # objectClass: organization 6 | # o: Example Company 7 | # dc: example 8 | 9 | dn: ou=People,dc=example,dc=org 10 | objectClass: organizationalUnit 11 | ou: People 12 | 13 | dn: cn=jane,ou=People,dc=example,dc=org 14 | objectClass: person 15 | objectClass: inetOrgPerson 16 | sn: doe 17 | cn: jane 18 | mail: janedoe@example.com 19 | userpassword: foo 20 | 21 | dn: cn=john,ou=People,dc=example,dc=org 22 | objectClass: person 23 | objectClass: inetOrgPerson 24 | sn: doe 25 | cn: john 26 | mail: johndoe@example.com 27 | userpassword: bar 28 | 29 | # Group definitions. 30 | 31 | dn: ou=Groups,dc=example,dc=org 32 | objectClass: organizationalUnit 33 | ou: Groups 34 | 35 | dn: cn=admins,ou=Groups,dc=example,dc=org 36 | objectClass: groupOfNames 37 | cn: admins 38 | member: cn=john,ou=People,dc=example,dc=org 39 | member: cn=jane,ou=People,dc=example,dc=org 40 | 41 | dn: cn=developers,ou=Groups,dc=example,dc=org 42 | objectClass: groupOfNames 43 | cn: developers 44 | member: cn=jane,ou=People,dc=example,dc=org 45 | -------------------------------------------------------------------------------- /examples/ldap/config-ldap.yaml: -------------------------------------------------------------------------------- 1 | issuer: http://127.0.0.1:5556/dex 2 | storage: 3 | type: sqlite3 4 | config: 5 | file: examples/dex.db 6 | web: 7 | http: 0.0.0.0:5556 8 | 9 | connectors: 10 | - type: ldap 11 | name: OpenLDAP 12 | id: ldap 13 | config: 14 | # The following configurations seem to work with OpenLDAP: 15 | # 16 | # 1) Plain LDAP, without TLS: 17 | host: localhost:389 18 | insecureNoSSL: true 19 | # 20 | # 2) LDAPS without certificate validation: 21 | #host: localhost:636 22 | #insecureNoSSL: false 23 | #insecureSkipVerify: true 24 | # 25 | # 3) LDAPS with certificate validation: 26 | #host: YOUR-HOSTNAME:636 27 | #insecureNoSSL: false 28 | #insecureSkipVerify: false 29 | #rootCAData: 'CERT' 30 | # ...where CERT="$( base64 -w 0 your-cert.crt )" 31 | 32 | # This would normally be a read-only user. 33 | bindDN: cn=admin,dc=example,dc=org 34 | bindPW: admin 35 | 36 | usernamePrompt: Email Address 37 | 38 | userSearch: 39 | baseDN: ou=People,dc=example,dc=org 40 | filter: "(objectClass=person)" 41 | username: mail 42 | # "DN" (case sensitive) is a special attribute name. It indicates that 43 | # this value should be taken from the entity's DN not an attribute on 44 | # the entity. 45 | idAttr: DN 46 | emailAttr: mail 47 | nameAttr: cn 48 | 49 | groupSearch: 50 | baseDN: ou=Groups,dc=example,dc=org 51 | filter: "(objectClass=groupOfNames)" 52 | 53 | userMatchers: 54 | # A user is a member of a group when their DN matches 55 | # the value of a "member" attribute on the group entity. 56 | - userAttr: DN 57 | groupAttr: member 58 | 59 | # The group name should be the "cn" value. 60 | nameAttr: cn 61 | 62 | staticClients: 63 | - id: example-app 64 | redirectURIs: 65 | - 'http://127.0.0.1:5555/callback' 66 | name: 'Example App' 67 | secret: ZXhhbXBsZS1hcHAtc2VjcmV0 68 | -------------------------------------------------------------------------------- /examples/ldap/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | # For LDAPS with certificate validation: 4 | # How to extract the TLS certificate from the OpenLDAP container, and encode it for the Dex config (`rootCAData`): 5 | # $ docker-compose exec ldap cat /container/run/service/slapd/assets/certs/ca.crt | base64 -w 0 6 | # But note this issue: https://github.com/osixia/docker-openldap/issues/506 7 | 8 | services: 9 | ldap: 10 | image: osixia/openldap:1.4.0 11 | # Copying is required because the entrypoint modifies the *.ldif files. 12 | # For verbose output, use: command: ["--copy-service", "--loglevel", "debug"] 13 | command: ["--copy-service"] 14 | environment: 15 | # Required if using LDAPS: 16 | # Since Dex doesn't use a client TLS certificate, downgrade from "demand" to "try". 17 | LDAP_TLS_VERIFY_CLIENT: try 18 | # The hostname is required if using LDAPS with certificate validation. 19 | # In Dex, use the same hostname (with port) for `connectors[].config.host`. 20 | #hostname: YOUR-HOSTNAME 21 | # 22 | # https://github.com/osixia/docker-openldap#seed-ldap-database-with-ldif 23 | # Option 1: Add custom seed file -> mount to /container/service/slapd/assets/config/bootstrap/ldif/custom/ 24 | # Option 2: Overwrite default seed file -> mount to /container/service/slapd/assets/config/bootstrap/ldif/ 25 | volumes: 26 | - ./config-ldap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/config-ldap.ldif 27 | ports: 28 | - 389:389 29 | - 636:636 30 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 | flake-parts.url = "github:hercules-ci/flake-parts"; 5 | devenv.url = "github:cachix/devenv"; 6 | }; 7 | 8 | outputs = 9 | inputs@{ flake-parts, ... }: 10 | flake-parts.lib.mkFlake { inherit inputs; } { 11 | imports = [ 12 | inputs.devenv.flakeModule 13 | ]; 14 | 15 | systems = [ 16 | "x86_64-linux" 17 | "x86_64-darwin" 18 | "aarch64-darwin" 19 | "aarch64-linux" 20 | ]; 21 | 22 | perSystem = 23 | { pkgs, ... }: 24 | rec { 25 | devenv.shells = { 26 | default = { 27 | languages = { 28 | go = { 29 | enable = true; 30 | package = pkgs.go_1_24; 31 | }; 32 | }; 33 | 34 | packages = with pkgs; [ 35 | gnumake 36 | 37 | golangci-lint 38 | gotestsum 39 | protobuf 40 | protoc-gen-go 41 | protoc-gen-go-grpc 42 | kind 43 | ]; 44 | 45 | # https://github.com/cachix/devenv/issues/528#issuecomment-1556108767 46 | containers = pkgs.lib.mkForce { }; 47 | }; 48 | 49 | ci = devenv.shells.default; 50 | }; 51 | }; 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /pkg/featureflags/flag.go: -------------------------------------------------------------------------------- 1 | package featureflags 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type flag struct { 10 | Name string 11 | Default bool 12 | } 13 | 14 | func (f *flag) env() string { 15 | return "DEX_" + strings.ToUpper(f.Name) 16 | } 17 | 18 | func (f *flag) Enabled() bool { 19 | raw := os.Getenv(f.env()) 20 | if raw == "" { 21 | return f.Default 22 | } 23 | 24 | res, err := strconv.ParseBool(raw) 25 | if err != nil { 26 | return f.Default 27 | } 28 | return res 29 | } 30 | 31 | func newFlag(s string, d bool) *flag { 32 | return &flag{Name: s, Default: d} 33 | } 34 | -------------------------------------------------------------------------------- /pkg/featureflags/set.go: -------------------------------------------------------------------------------- 1 | package featureflags 2 | 3 | var ( 4 | // EntEnabled enables experimental ent-based engine for the database storages. 5 | // https://entgo.io/ 6 | EntEnabled = newFlag("ent_enabled", false) 7 | 8 | // ExpandEnv can enable or disable env expansion in the config which can be useful in environments where, e.g., 9 | // $ sign is a part of the password for LDAP user. 10 | ExpandEnv = newFlag("expand_env", true) 11 | 12 | // APIConnectorsCRUD allows CRUD operations on connectors through the gRPC API 13 | APIConnectorsCRUD = newFlag("api_connectors_crud", false) 14 | ) 15 | -------------------------------------------------------------------------------- /pkg/groups/groups.go: -------------------------------------------------------------------------------- 1 | // Package groups contains helper functions related to groups 2 | package groups 3 | 4 | // Filter filters out any groups of given that are not in required. Thus it may 5 | // happen that the resulting slice is empty. 6 | func Filter(given, required []string) []string { 7 | groups := []string{} 8 | groupFilter := make(map[string]struct{}) 9 | for _, group := range required { 10 | groupFilter[group] = struct{}{} 11 | } 12 | for _, group := range given { 13 | if _, ok := groupFilter[group]; ok { 14 | groups = append(groups, group) 15 | } 16 | } 17 | return groups 18 | } 19 | -------------------------------------------------------------------------------- /pkg/groups/groups_test.go: -------------------------------------------------------------------------------- 1 | package groups_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/dexidp/dex/pkg/groups" 9 | ) 10 | 11 | func TestFilter(t *testing.T) { 12 | cases := map[string]struct { 13 | given, required, expected []string 14 | }{ 15 | "nothing given": {given: []string{}, required: []string{"ops"}, expected: []string{}}, 16 | "exactly one match": {given: []string{"foo"}, required: []string{"foo"}, expected: []string{"foo"}}, 17 | "no group of the required ones": {given: []string{"foo", "bar"}, required: []string{"baz"}, expected: []string{}}, 18 | "subset matching": {given: []string{"foo", "bar", "baz"}, required: []string{"bar", "baz"}, expected: []string{"bar", "baz"}}, 19 | } 20 | for name, tc := range cases { 21 | t.Run(name, func(t *testing.T) { 22 | actual := groups.Filter(tc.given, tc.required) 23 | assert.ElementsMatch(t, tc.expected, actual) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/httpclient/httpclient.go: -------------------------------------------------------------------------------- 1 | package httpclient 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "encoding/base64" 7 | "fmt" 8 | "net" 9 | "net/http" 10 | "os" 11 | "time" 12 | ) 13 | 14 | func extractCAs(input []string) [][]byte { 15 | result := make([][]byte, 0, len(input)) 16 | for _, ca := range input { 17 | if ca == "" { 18 | continue 19 | } 20 | 21 | pemData, err := os.ReadFile(ca) 22 | if err != nil { 23 | pemData, err = base64.StdEncoding.DecodeString(ca) 24 | if err != nil { 25 | pemData = []byte(ca) 26 | } 27 | } 28 | 29 | result = append(result, pemData) 30 | } 31 | return result 32 | } 33 | 34 | func NewHTTPClient(rootCAs []string, insecureSkipVerify bool) (*http.Client, error) { 35 | pool, err := x509.SystemCertPool() 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | tlsConfig := tls.Config{RootCAs: pool, InsecureSkipVerify: insecureSkipVerify} 41 | for index, rootCABytes := range extractCAs(rootCAs) { 42 | if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCABytes) { 43 | return nil, fmt.Errorf("rootCAs.%d is not in PEM format, certificate must be "+ 44 | "a PEM encoded string, a base64 encoded bytes that contain PEM encoded string, "+ 45 | "or a path to a PEM encoded certificate", index) 46 | } 47 | } 48 | 49 | return &http.Client{ 50 | Transport: &http.Transport{ 51 | TLSClientConfig: &tlsConfig, 52 | Proxy: http.ProxyFromEnvironment, 53 | DialContext: (&net.Dialer{ 54 | Timeout: 30 * time.Second, 55 | KeepAlive: 30 * time.Second, 56 | DualStack: true, 57 | }).DialContext, 58 | MaxIdleConns: 100, 59 | IdleConnTimeout: 90 * time.Second, 60 | TLSHandshakeTimeout: 10 * time.Second, 61 | ExpectContinueTimeout: 1 * time.Second, 62 | }, 63 | }, nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/httpclient/httpclient_test.go: -------------------------------------------------------------------------------- 1 | package httpclient_test 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/base64" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/http/httptest" 10 | "os" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/assert" 14 | 15 | "github.com/dexidp/dex/pkg/httpclient" 16 | ) 17 | 18 | func TestRootCAs(t *testing.T) { 19 | ts, err := NewLocalHTTPSTestServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20 | fmt.Fprint(w, "Hello, client") 21 | })) 22 | assert.Nil(t, err) 23 | defer ts.Close() 24 | 25 | runTest := func(name string, certs []string) { 26 | t.Run(name, func(t *testing.T) { 27 | rootCAs := certs 28 | testClient, err := httpclient.NewHTTPClient(rootCAs, false) 29 | assert.Nil(t, err) 30 | 31 | res, err := testClient.Get(ts.URL) 32 | assert.Nil(t, err) 33 | 34 | greeting, err := io.ReadAll(res.Body) 35 | res.Body.Close() 36 | assert.Nil(t, err) 37 | 38 | assert.Equal(t, "Hello, client", string(greeting)) 39 | }) 40 | } 41 | 42 | runTest("From file", []string{"testdata/rootCA.pem"}) 43 | 44 | content, err := os.ReadFile("testdata/rootCA.pem") 45 | assert.NoError(t, err) 46 | runTest("From string", []string{string(content)}) 47 | 48 | contentStr := base64.StdEncoding.EncodeToString(content) 49 | runTest("From bytes", []string{contentStr}) 50 | } 51 | 52 | func TestInsecureSkipVerify(t *testing.T) { 53 | ts, err := NewLocalHTTPSTestServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 54 | fmt.Fprint(w, "Hello, client") 55 | })) 56 | assert.Nil(t, err) 57 | defer ts.Close() 58 | 59 | insecureSkipVerify := true 60 | 61 | testClient, err := httpclient.NewHTTPClient(nil, insecureSkipVerify) 62 | assert.Nil(t, err) 63 | 64 | res, err := testClient.Get(ts.URL) 65 | assert.Nil(t, err) 66 | 67 | greeting, err := io.ReadAll(res.Body) 68 | res.Body.Close() 69 | assert.Nil(t, err) 70 | 71 | assert.Equal(t, "Hello, client", string(greeting)) 72 | } 73 | 74 | func NewLocalHTTPSTestServer(handler http.Handler) (*httptest.Server, error) { 75 | ts := httptest.NewUnstartedServer(handler) 76 | cert, err := tls.LoadX509KeyPair("testdata/server.crt", "testdata/server.key") 77 | if err != nil { 78 | return nil, err 79 | } 80 | ts.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} 81 | ts.StartTLS() 82 | return ts, nil 83 | } 84 | -------------------------------------------------------------------------------- /pkg/httpclient/readme.md: -------------------------------------------------------------------------------- 1 | # Regenerate testdata 2 | 3 | ### server.csr.cnf 4 | 5 | ``` 6 | [req] 7 | default_bits = 2048 8 | prompt = no 9 | default_md = sha256 10 | distinguished_name = dn 11 | 12 | [dn] 13 | C=US 14 | ST=RandomState 15 | L=RandomCity 16 | O=RandomOrganization 17 | OU=RandomOrganizationUnit 18 | emailAddress=hello@example.com 19 | CN = localhost 20 | ``` 21 | 22 | and 23 | 24 | ### v3.ext 25 | ``` 26 | authorityKeyIdentifier=keyid,issuer 27 | basicConstraints=CA:FALSE 28 | keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment 29 | subjectAltName = @alt_names 30 | 31 | [alt_names] 32 | DNS.1 = localhost 33 | IP.1 = 127.0.0.1 34 | ``` 35 | 36 | ### Then enter the following commands: 37 | 38 | `openssl genrsa -out rootCA.key 2048` 39 | 40 | `openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.pem -config server.csr.cnf` 41 | 42 | `openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key -config server.csr.cnf` 43 | 44 | `openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 3650 -sha256 -extfile v3.ext` 45 | -------------------------------------------------------------------------------- /pkg/httpclient/testdata/rootCA.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA4dB5aQCjCmMsW71u9F0WNm1TYjXQBZ4p7oNT+BQwCc/MZ2xc 3 | 5NexS2O86nbRkw5jwyfAAMSMKRr9s2FluVTHqiln78rg+XUgmrmNT3ZroLmW6QL6 4 | Ca8dbMPky+tQclZsvMd3HAeCyyrs4pf7wM1AyUJD7H0xAlVD1fsohkg7jhBFUfV+ 5 | q2VMMdnsaV5vFrW/2vPBWz1SNPW/Xm+Ilny7xg9njQLcPMNtVtF+7EPB6sxD6qrj 6 | BC+Kj5zQ3bZOfdrh7yy63dbh/Kh+3NScgO+k+x92HlAjRIvj5y4KrbGZl7CmOth5 7 | y7fPywApVbDfZRWJChI1PVflOyDdnC+vhMLbHQIDAQABAoIBAEmjrrQrXP/6L3EL 8 | aa+O27uME3Enk1sBpTL+6Ncx3iiU91eS4whNvqeTMvxTGy0VuDrgL6EQd5TAFJP2 9 | 4zF5EFPRhO+R/aPcKnHKqOaM+7RCUZBTRC78SGA70dUeO/HNdVBqy9D8Mg8HRJDw 10 | d0z8om//iB8LBHx6SdDyQtjnnWRKFTzQRurBBoyLe2vPMFtINKtNUkahjc8HE4GO 11 | aIv1LICJUzf4ZnkntKd5cFHZ42R2Tmfj0Y9G9DyJbuSA3+0u5IhYB39Uy6jFxLi8 12 | I5PoIVhgYZ0aivsVBIviShwQ9kgv6807YBxt22eSNovBDrSp+cAnIF9+p0b3MnkU 13 | aCHSiBECgYEA84lssi6AqfCEsSiQMSM9kMCXJ4KQI/l7pmrIA50+V5HSEby9lg2Y 14 | N6XJ4V4q46t8FcZBjmMvzn9fwiPMRw5e995cVNBQ31a1FX/1Hy6RNtEiLZRnkHI5 15 | WznY9IxQ+c9JXJeFY1sO0BfO0TS3WvOf1rwqOb92q+cQaItnPQ+4Ya8CgYEA7V7e 16 | IqW3PpO4H+c5hH9egM0BjAxH71C9YpYzZpF9uiPIkuMnJ8nm9bB6RiuDaYCxvrfE 17 | A0h/SQewoYJKL4OfKGjrbG7U4zLMZHIWlf8Za55Zik5BNjvgBqFFrrSgLUGxdRTX 18 | N0+TlWlW1bvJblWpdjIbJbg/6kCU98TzK852fvMCgYAWYa/apElw1MjtGyQ9T9bN 19 | odWCbQ5gMAJ8Jd4h7uaW17DtrmHiE3fEzXjDPItGhzENMz49HsJ7ANvFFNMmSJzT 20 | vNzRcp+sFuTnh+34Iqh32DqC49usu8KnrqZQu0CJ5NICL26z1d+DolyAf47GThOH 21 | gZ2D1yPJ4p9wbDddtj8kwwKBgCFKB68mPG+rOcxHmjppvnAj0A66/i+izBySYf0F 22 | dHNxZ0SqVKhw2VIlgNBsc86M/OB5VyT6utccG/paklrdg6mgJTwcwwBl9GI12dMJ 23 | ZqBAIeCSnvSjKwTjAynALSKLrv5zgMdCArmWf1YUMuilXNG1rzb4AwawLfQdi9jd 24 | 6KJfAoGBALFl6ldywl3sGPk9K2xCDYYhb1TNQyheA5YvoZzZ6XCo1q0Lbwy/FamZ 25 | 0TSWkoEmGB/Hck3HgtZDRo3CTI1vYfbpAtgI7oD1NA1zMaLulNQxKjH3iVvyb+R7 26 | ZcIT7EVPZgkUwr0bsp22yVDekh/CHoB6FZPCyoAb8WnfJfooTBzB 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /pkg/httpclient/testdata/rootCA.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID1jCCAr4CCQCG4JBeSi6cDjANBgkqhkiG9w0BAQsFADCBrDELMAkGA1UEBhMC 3 | VVMxFDASBgNVBAgMC1JhbmRvbVN0YXRlMRMwEQYDVQQHDApSYW5kb21DaXR5MRsw 4 | GQYDVQQKDBJSYW5kb21Pcmdhbml6YXRpb24xHzAdBgNVBAsMFlJhbmRvbU9yZ2Fu 5 | aXphdGlvblVuaXQxIDAeBgkqhkiG9w0BCQEWEWhlbGxvQGV4YW1wbGUuY29tMRIw 6 | EAYDVQQDDAlsb2NhbGhvc3QwHhcNMjIxMDA3MjIwNjQwWhcNMzIxMDA0MjIwNjQw 7 | WjCBrDELMAkGA1UEBhMCVVMxFDASBgNVBAgMC1JhbmRvbVN0YXRlMRMwEQYDVQQH 8 | DApSYW5kb21DaXR5MRswGQYDVQQKDBJSYW5kb21Pcmdhbml6YXRpb24xHzAdBgNV 9 | BAsMFlJhbmRvbU9yZ2FuaXphdGlvblVuaXQxIDAeBgkqhkiG9w0BCQEWEWhlbGxv 10 | QGV4YW1wbGUuY29tMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB 11 | AQUAA4IBDwAwggEKAoIBAQDh0HlpAKMKYyxbvW70XRY2bVNiNdAFninug1P4FDAJ 12 | z8xnbFzk17FLY7zqdtGTDmPDJ8AAxIwpGv2zYWW5VMeqKWfvyuD5dSCauY1Pdmug 13 | uZbpAvoJrx1sw+TL61ByVmy8x3ccB4LLKuzil/vAzUDJQkPsfTECVUPV+yiGSDuO 14 | EEVR9X6rZUwx2expXm8Wtb/a88FbPVI09b9eb4iWfLvGD2eNAtw8w21W0X7sQ8Hq 15 | zEPqquMEL4qPnNDdtk592uHvLLrd1uH8qH7c1JyA76T7H3YeUCNEi+PnLgqtsZmX 16 | sKY62HnLt8/LAClVsN9lFYkKEjU9V+U7IN2cL6+EwtsdAgMBAAEwDQYJKoZIhvcN 17 | AQELBQADggEBAN6g0qit/3R2X+KdR0LgRXF/h4qQFgcV6cxnhRAmLIDNJlxKSHqN 18 | IE5+bxzCbkblzGfr/jNPqW0s+yaN4CyMgKNYSzkLBPE4FF+19Uv+dyYfFms3mDJ7 19 | 0rGjS5bCscThWhpaSw20LcwQcr/+X+/fGzJ01dVFK1UOjBKg4d4dMwxklbIkZqIq 20 | siRW0GMy26mgVZ/BSjeh5kEjs6h6H3cJsGl7xYT+BI7wnxHwGeT9tkBgiyT5FwaS 21 | vtdZkBpQ9q8f7FwsEm3woLHdWuOnrtUtVpY/oc6WFGdROQdGzjSk0D3kHs9YhueC 22 | GSzZKrqX+TSIgpPrLYNHX4uxlo5TAwP/5GM= 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /pkg/httpclient/testdata/rootCA.srl: -------------------------------------------------------------------------------- 1 | C1B35F0051A641BB 2 | -------------------------------------------------------------------------------- /pkg/httpclient/testdata/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE5TCCA82gAwIBAgIJAMGzXwBRpkG7MA0GCSqGSIb3DQEBCwUAMIGsMQswCQYD 3 | VQQGEwJVUzEUMBIGA1UECAwLUmFuZG9tU3RhdGUxEzARBgNVBAcMClJhbmRvbUNp 4 | dHkxGzAZBgNVBAoMElJhbmRvbU9yZ2FuaXphdGlvbjEfMB0GA1UECwwWUmFuZG9t 5 | T3JnYW5pemF0aW9uVW5pdDEgMB4GCSqGSIb3DQEJARYRaGVsbG9AZXhhbXBsZS5j 6 | b20xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMjEwMDcyMjA3MDhaFw0zMjEwMDQy 7 | MjA3MDhaMIGsMQswCQYDVQQGEwJVUzEUMBIGA1UECAwLUmFuZG9tU3RhdGUxEzAR 8 | BgNVBAcMClJhbmRvbUNpdHkxGzAZBgNVBAoMElJhbmRvbU9yZ2FuaXphdGlvbjEf 9 | MB0GA1UECwwWUmFuZG9tT3JnYW5pemF0aW9uVW5pdDEgMB4GCSqGSIb3DQEJARYR 10 | aGVsbG9AZXhhbXBsZS5jb20xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZI 11 | hvcNAQEBBQADggEPADCCAQoCggEBAMuKdpXP87Q7Kg3iafXzvBuVIyV1K5UmMYiN 12 | koztkC5XrCzHaQRS/CoIb7/nUqmtAxx7RL0jzhZ93zBN4HY/Zcnrd9tXoPPxi0mG 13 | ZZWfFU6nN8nOkMHWzEbHVBmhxpfGtwmLcajQ4HrK1TZwJUn6GqclHQRy/gjxkiw5 14 | KPqzfVOVlA6ht4KdKstKazQkWZ5gdWT4d8yrEy/IT4oaW05xALBMQ7YGjkzWKsSF 15 | 6ygXI7xqF9rg9jCnUsPYg4f8ut3N0c00KjsfKOOj2dF/ZyjedQ5c0u4hHmxSo3Ka 16 | 0ZTmIrMfbVXgGjxRG2HZXLpPvQKoCf/fOX8Irdr+lahFVKASxN0CAwEAAaOCAQYw 17 | ggECMIHLBgNVHSMEgcMwgcChgbKkga8wgawxCzAJBgNVBAYTAlVTMRQwEgYDVQQI 18 | DAtSYW5kb21TdGF0ZTETMBEGA1UEBwwKUmFuZG9tQ2l0eTEbMBkGA1UECgwSUmFu 19 | ZG9tT3JnYW5pemF0aW9uMR8wHQYDVQQLDBZSYW5kb21Pcmdhbml6YXRpb25Vbml0 20 | MSAwHgYJKoZIhvcNAQkBFhFoZWxsb0BleGFtcGxlLmNvbTESMBAGA1UEAwwJbG9j 21 | YWxob3N0ggkAhuCQXkounA4wCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwGgYDVR0R 22 | BBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCWmh5ebpkm 23 | v2B1yQgarSCSSkLZ5DZSAJjrPgW2IJqCW2q2D1HworbW1Yn5jqrM9FKGnJfjCyve 24 | zBB5AOlGp+0bsZGgMRMCavgv4QhTThXUoJqqHcfEu4wHndcgrqSadxmV5aisSR4u 25 | gXnjW43o3akby+h1K40RR3vVkpzPaoC3/bgk7WVpfpPiP32E24a01gETozRb/of/ 26 | ATN3JBe0xh+e63CrPX1sago5+u3UETIoOr0fW8M/gU9GApmJiFAXwHag6j54hLCG 27 | 23EtVDwmlarG8Pj+i0yru8s22QqzAJi5E0OwR4aB8tqicLKYBVfzyLCOielIBUrK 28 | OkuFKp+VjxQX 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /pkg/httpclient/testdata/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC8jCCAdoCAQAwgawxCzAJBgNVBAYTAlVTMRQwEgYDVQQIDAtSYW5kb21TdGF0 3 | ZTETMBEGA1UEBwwKUmFuZG9tQ2l0eTEbMBkGA1UECgwSUmFuZG9tT3JnYW5pemF0 4 | aW9uMR8wHQYDVQQLDBZSYW5kb21Pcmdhbml6YXRpb25Vbml0MSAwHgYJKoZIhvcN 5 | AQkBFhFoZWxsb0BleGFtcGxlLmNvbTESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjAN 6 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy4p2lc/ztDsqDeJp9fO8G5UjJXUr 7 | lSYxiI2SjO2QLlesLMdpBFL8Kghvv+dSqa0DHHtEvSPOFn3fME3gdj9lyet321eg 8 | 8/GLSYZllZ8VTqc3yc6QwdbMRsdUGaHGl8a3CYtxqNDgesrVNnAlSfoapyUdBHL+ 9 | CPGSLDko+rN9U5WUDqG3gp0qy0prNCRZnmB1ZPh3zKsTL8hPihpbTnEAsExDtgaO 10 | TNYqxIXrKBcjvGoX2uD2MKdSw9iDh/y63c3RzTQqOx8o46PZ0X9nKN51DlzS7iEe 11 | bFKjcprRlOYisx9tVeAaPFEbYdlcuk+9AqgJ/985fwit2v6VqEVUoBLE3QIDAQAB 12 | oAAwDQYJKoZIhvcNAQELBQADggEBADjuujIFoDJllR6Xo/w7j5vfNOeHO5GSgxF2 13 | XnuuDOI9Tomi7vURFZNbz3VAYiehpxRxYqLwFoQUwFtux2qRuGyg0P9fP1iQXPUE 14 | QUfFXmvB80uf2bG4lkbUwnmlZLFOEwhGZyPxpvsrxp2Ei2ppkUopCkzOMsSk3m0X 15 | MC50ZsTHOxfkA3r1WmS7oE2c0p0Fvyx+UJw0URAXFvDS1X0ONgww3FxqbBbm9W37 16 | 5N4FZzGAK6j1wzuynKKXrn20YDCANXYH55PZyupfCeSZT0H0AZifWL7rz/G9uqme 17 | RzbIYc/CNQQTympjinBegQdVeB3yjVNZIvpGOuPSKQqhwFtmDFo= 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /pkg/httpclient/testdata/server.csr.cnf: -------------------------------------------------------------------------------- 1 | [req] 2 | default_bits = 2048 3 | prompt = no 4 | default_md = sha256 5 | distinguished_name = dn 6 | 7 | [dn] 8 | C=US 9 | ST=RandomState 10 | L=RandomCity 11 | O=RandomOrganization 12 | OU=RandomOrganizationUnit 13 | emailAddress=hello@example.com 14 | CN = localhost 15 | -------------------------------------------------------------------------------- /pkg/httpclient/testdata/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDLinaVz/O0OyoN 3 | 4mn187wblSMldSuVJjGIjZKM7ZAuV6wsx2kEUvwqCG+/51KprQMce0S9I84Wfd8w 4 | TeB2P2XJ63fbV6Dz8YtJhmWVnxVOpzfJzpDB1sxGx1QZocaXxrcJi3Go0OB6ytU2 5 | cCVJ+hqnJR0Ecv4I8ZIsOSj6s31TlZQOobeCnSrLSms0JFmeYHVk+HfMqxMvyE+K 6 | GltOcQCwTEO2Bo5M1irEhesoFyO8ahfa4PYwp1LD2IOH/LrdzdHNNCo7Hyjjo9nR 7 | f2co3nUOXNLuIR5sUqNymtGU5iKzH21V4Bo8URth2Vy6T70CqAn/3zl/CK3a/pWo 8 | RVSgEsTdAgMBAAECggEAU6cxu7q+54kVbKVsdThaTF/MFR4F7oPHAd9lpuQQSOuh 9 | iLngMHXGy6OyAgYZlEDWMYN8KdwoXFgZPaoUIaVGuWk8Vnq6XOgeHfbNk2PRhwT0 10 | yc1K80/Lnx9XMj2p+EEkgxi7eu12BSGN5ZTLzo6rG50GQwjb3WMjd2d6rybL0GjC 11 | wg2arcBk3sSMYmvZOqlAsaQmtgwkJhvhVkVfEQSD3VKF7g0dh/h3LIPyM0Ff4M67 12 | KpLMPPwzUJ/0Z4ewAP06mMKUA86R93M+dWs2eh1oBGnRkVQdhCJLXJpuGHZ6BTiB 13 | Ry0AeorHfnVXPbtpUeAq6m5/BBl6qX0ooB08BIFwAQKBgQDqJpTZS/ZzqL6Kcs14 14 | MyFu+7DungSxQ5oK9ju7EFSosanSk4UEa/lw992kM6nsIMwgSVQgba5zKcVMeSmk 15 | AVbpznegQD1BYCwOGwbGvkJ8jbhPy+WLbbRjWT/E6AItZgUK+fyTIcNvSehcQqsT 16 | fhgWsK7ueZCmLQfVhK1AxtvY3QKBgQDeiKuo8plsH/7IxDn7KVHBOHKPC2ZPzg03 17 | i7La6zomiRckwwPnhicRSYsjtfCCW6Ms+uzjTEItgFM+5PdrXheeku+z/sExRtZu 18 | emqPqDomixlXDRQ6RN3gnBSk4RU+ROB1u1uBLWXqRz8Gp2zJGRxhHfYt2zefBv4w 19 | /cIuPC3cAQKBgD2UsAkGJWb9tj8LOmama+CYaUwYWvuT3+uKHuNvxBQpxZQQICet 20 | jgjb53rL66Cib4z+PBXbQsoe7jjSlNUBVS5gkq2et31+IZgEG6AhYbMIQrUZ1uD4 21 | lTybuF289vWhoynj3T2E37VhJq89CWky/HrbNOabKiPKLAlHv5kNs7wxAoGBANEJ 22 | XQbU7J2O6Iy7FyQBSlTQq3wHX1Iz4mJ9DcNrFzK/sEfOEMrZT7WDefpPm984KW3F 23 | P+S766ZGVuxLtMbcmh9RM23HLr8VJbSdtZ/AjO9L1r/Y/1lE+49TzmibLpNRq++r 24 | 0WbkuEl8J44ek6fLuMbZmDi3JeZycTCgDlnUGdgBAoGAYdliovtURZCm46t1uE3F 25 | idCLCXCccjkt1hcNGNjck/b0trHA7wOEqICIguoWDlEBTc0PDvHEq6PfKyqptGkj 26 | AgaZTMF/aZiGqlT7VRpBuzxM/uV5xzCg+i2ViaW/p3xq0z2PRljVZiEfe5aWcjiM 27 | ouTtnC3TgmcjhTgGmb48QQE= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /pkg/httpclient/testdata/v3.ext: -------------------------------------------------------------------------------- 1 | authorityKeyIdentifier=keyid,issuer 2 | basicConstraints=CA:FALSE 3 | keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment 4 | subjectAltName = @alt_names 5 | 6 | [alt_names] 7 | DNS.1 = localhost 8 | IP.1 = 127.0.0.1 9 | -------------------------------------------------------------------------------- /scripts/git-version: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # parse the current git commit hash 4 | COMMIT=`git rev-parse HEAD` 5 | 6 | # check if the current commit has a matching tag 7 | TAG=$(git describe --exact-match --abbrev=0 --tags ${COMMIT} 2> /dev/null || true) 8 | 9 | # use the matching tag as the version, if available 10 | if [ -z "$TAG" ]; then 11 | VERSION=$COMMIT 12 | else 13 | VERSION=$TAG 14 | fi 15 | 16 | # check for changed files (not untracked files) 17 | if [ -n "$(git diff --shortstat 2> /dev/null | tail -n1)" ]; then 18 | VERSION="${VERSION}-dirty" 19 | fi 20 | 21 | echo $VERSION 22 | -------------------------------------------------------------------------------- /scripts/manifests/.editorconfig: -------------------------------------------------------------------------------- 1 | [{*.yml,*.yaml}] 2 | indent_size = 2 3 | -------------------------------------------------------------------------------- /scripts/manifests/crds/authcodes.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: authcodes.dex.coreos.com 5 | spec: 6 | group: dex.coreos.com 7 | names: 8 | kind: AuthCode 9 | listKind: AuthCodeList 10 | plural: authcodes 11 | singular: authcode 12 | scope: Namespaced 13 | versions: 14 | - name: v1 15 | served: true 16 | storage: true 17 | schema: 18 | openAPIV3Schema: 19 | type: object 20 | x-kubernetes-preserve-unknown-fields: true 21 | -------------------------------------------------------------------------------- /scripts/manifests/crds/authrequests.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: authrequests.dex.coreos.com 5 | spec: 6 | group: dex.coreos.com 7 | names: 8 | kind: AuthRequest 9 | listKind: AuthRequestList 10 | plural: authrequests 11 | singular: authrequest 12 | scope: Namespaced 13 | versions: 14 | - name: v1 15 | served: true 16 | storage: true 17 | schema: 18 | openAPIV3Schema: 19 | type: object 20 | x-kubernetes-preserve-unknown-fields: true 21 | -------------------------------------------------------------------------------- /scripts/manifests/crds/connectors.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: connectors.dex.coreos.com 5 | spec: 6 | group: dex.coreos.com 7 | names: 8 | kind: Connector 9 | listKind: ConnectorList 10 | plural: connectors 11 | singular: connector 12 | scope: Namespaced 13 | versions: 14 | - name: v1 15 | served: true 16 | storage: true 17 | schema: 18 | openAPIV3Schema: 19 | type: object 20 | x-kubernetes-preserve-unknown-fields: true 21 | -------------------------------------------------------------------------------- /scripts/manifests/crds/devicerequests.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: devicerequests.dex.coreos.com 5 | spec: 6 | group: dex.coreos.com 7 | names: 8 | kind: DeviceRequest 9 | listKind: DeviceRequestList 10 | plural: devicerequests 11 | singular: devicerequest 12 | scope: Namespaced 13 | versions: 14 | - name: v1 15 | served: true 16 | storage: true 17 | schema: 18 | openAPIV3Schema: 19 | type: object 20 | x-kubernetes-preserve-unknown-fields: true 21 | -------------------------------------------------------------------------------- /scripts/manifests/crds/devicetokens.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: devicetokens.dex.coreos.com 5 | spec: 6 | group: dex.coreos.com 7 | names: 8 | kind: DeviceToken 9 | listKind: DeviceTokenList 10 | plural: devicetokens 11 | singular: devicetoken 12 | scope: Namespaced 13 | versions: 14 | - name: v1 15 | served: true 16 | storage: true 17 | schema: 18 | openAPIV3Schema: 19 | type: object 20 | x-kubernetes-preserve-unknown-fields: true 21 | -------------------------------------------------------------------------------- /scripts/manifests/crds/oauth2clients.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: oauth2clients.dex.coreos.com 5 | spec: 6 | group: dex.coreos.com 7 | names: 8 | kind: OAuth2Client 9 | listKind: OAuth2ClientList 10 | plural: oauth2clients 11 | singular: oauth2client 12 | scope: Namespaced 13 | versions: 14 | - name: v1 15 | served: true 16 | storage: true 17 | schema: 18 | openAPIV3Schema: 19 | type: object 20 | x-kubernetes-preserve-unknown-fields: true 21 | -------------------------------------------------------------------------------- /scripts/manifests/crds/offlinesessionses.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: offlinesessionses.dex.coreos.com 5 | spec: 6 | group: dex.coreos.com 7 | names: 8 | kind: OfflineSessions 9 | listKind: OfflineSessionsList 10 | plural: offlinesessionses 11 | singular: offlinesessions 12 | scope: Namespaced 13 | versions: 14 | - name: v1 15 | served: true 16 | storage: true 17 | schema: 18 | openAPIV3Schema: 19 | type: object 20 | x-kubernetes-preserve-unknown-fields: true 21 | -------------------------------------------------------------------------------- /scripts/manifests/crds/passwords.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: passwords.dex.coreos.com 5 | spec: 6 | group: dex.coreos.com 7 | names: 8 | kind: Password 9 | listKind: PasswordList 10 | plural: passwords 11 | singular: password 12 | scope: Namespaced 13 | versions: 14 | - name: v1 15 | served: true 16 | storage: true 17 | schema: 18 | openAPIV3Schema: 19 | type: object 20 | x-kubernetes-preserve-unknown-fields: true 21 | -------------------------------------------------------------------------------- /scripts/manifests/crds/refreshtokens.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: refreshtokens.dex.coreos.com 5 | spec: 6 | group: dex.coreos.com 7 | names: 8 | kind: RefreshToken 9 | listKind: RefreshTokenList 10 | plural: refreshtokens 11 | singular: refreshtoken 12 | scope: Namespaced 13 | versions: 14 | - name: v1 15 | served: true 16 | storage: true 17 | schema: 18 | openAPIV3Schema: 19 | type: object 20 | x-kubernetes-preserve-unknown-fields: true 21 | -------------------------------------------------------------------------------- /scripts/manifests/crds/signingkeies.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: signingkeies.dex.coreos.com 5 | spec: 6 | group: dex.coreos.com 7 | names: 8 | kind: SigningKey 9 | listKind: SigningKeyList 10 | plural: signingkeies 11 | singular: signingkey 12 | scope: Namespaced 13 | versions: 14 | - name: v1 15 | served: true 16 | storage: true 17 | schema: 18 | openAPIV3Schema: 19 | type: object 20 | x-kubernetes-preserve-unknown-fields: true 21 | -------------------------------------------------------------------------------- /server/doc.go: -------------------------------------------------------------------------------- 1 | // Package server implements an OpenID Connect server with federated logins. 2 | package server 3 | -------------------------------------------------------------------------------- /server/internal/codec.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/base64" 5 | 6 | "google.golang.org/protobuf/proto" 7 | ) 8 | 9 | // Marshal converts a protobuf message to a URL legal string. 10 | func Marshal(message proto.Message) (string, error) { 11 | data, err := proto.Marshal(message) 12 | if err != nil { 13 | return "", err 14 | } 15 | return base64.RawURLEncoding.EncodeToString(data), nil 16 | } 17 | 18 | // Unmarshal decodes a protobuf message. 19 | func Unmarshal(s string, message proto.Message) error { 20 | data, err := base64.RawURLEncoding.DecodeString(s) 21 | if err != nil { 22 | return err 23 | } 24 | return proto.Unmarshal(data, message) 25 | } 26 | -------------------------------------------------------------------------------- /server/internal/types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // Package internal holds protobuf types used by the server. 4 | package internal; 5 | 6 | option go_package = "github.com/dexidp/dex/server/internal"; 7 | 8 | // RefreshToken is a message that holds refresh token data used by dex. 9 | message RefreshToken { 10 | string refresh_id = 1; 11 | string token = 2; 12 | } 13 | 14 | // IDTokenSubject represents both the userID and connID which is returned 15 | // as the "sub" claim in the ID Token. 16 | message IDTokenSubject { 17 | string user_id = 1; 18 | string conn_id = 2; 19 | } 20 | -------------------------------------------------------------------------------- /server/templates_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "testing" 4 | 5 | func TestRelativeURL(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | serverPath string 9 | reqPath string 10 | assetPath string 11 | expected string 12 | }{ 13 | { 14 | name: "server-root-req-one-level-asset-two-level", 15 | serverPath: "/", 16 | reqPath: "/auth", 17 | assetPath: "/theme/main.css", 18 | expected: "theme/main.css", 19 | }, 20 | { 21 | name: "server-one-level-req-one-level-asset-two-level", 22 | serverPath: "/dex", 23 | reqPath: "/dex/auth", 24 | assetPath: "/theme/main.css", 25 | expected: "theme/main.css", 26 | }, 27 | { 28 | name: "server-root-req-two-level-asset-three-level", 29 | serverPath: "/dex", 30 | reqPath: "/dex/auth/connector", 31 | assetPath: "assets/css/main.css", 32 | expected: "../assets/css/main.css", 33 | }, 34 | { 35 | name: "external-url", 36 | serverPath: "/dex", 37 | reqPath: "/dex/auth/connector", 38 | assetPath: "https://kubernetes.io/images/favicon.png", 39 | expected: "https://kubernetes.io/images/favicon.png", 40 | }, 41 | } 42 | 43 | for _, test := range tests { 44 | t.Run(test.name, func(t *testing.T) { 45 | actual := relativeURL(test.serverPath, test.reqPath, test.assetPath) 46 | if actual != test.expected { 47 | t.Fatalf("Got '%s'. Expected '%s'", actual, test.expected) 48 | } 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /storage/doc.go: -------------------------------------------------------------------------------- 1 | // Package storage defines the storage interface and types used by the server. 2 | package storage 3 | -------------------------------------------------------------------------------- /storage/ent/client/authcode.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/dexidp/dex/storage" 7 | ) 8 | 9 | // CreateAuthCode saves provided auth code into the database. 10 | func (d *Database) CreateAuthCode(ctx context.Context, code storage.AuthCode) error { 11 | _, err := d.client.AuthCode.Create(). 12 | SetID(code.ID). 13 | SetClientID(code.ClientID). 14 | SetScopes(code.Scopes). 15 | SetRedirectURI(code.RedirectURI). 16 | SetNonce(code.Nonce). 17 | SetClaimsUserID(code.Claims.UserID). 18 | SetClaimsEmail(code.Claims.Email). 19 | SetClaimsEmailVerified(code.Claims.EmailVerified). 20 | SetClaimsUsername(code.Claims.Username). 21 | SetClaimsPreferredUsername(code.Claims.PreferredUsername). 22 | SetClaimsGroups(code.Claims.Groups). 23 | SetCodeChallenge(code.PKCE.CodeChallenge). 24 | SetCodeChallengeMethod(code.PKCE.CodeChallengeMethod). 25 | // Save utc time into database because ent doesn't support comparing dates with different timezones 26 | SetExpiry(code.Expiry.UTC()). 27 | SetConnectorID(code.ConnectorID). 28 | SetConnectorData(code.ConnectorData). 29 | Save(ctx) 30 | if err != nil { 31 | return convertDBError("create auth code: %w", err) 32 | } 33 | return nil 34 | } 35 | 36 | // GetAuthCode extracts an auth code from the database by id. 37 | func (d *Database) GetAuthCode(ctx context.Context, id string) (storage.AuthCode, error) { 38 | authCode, err := d.client.AuthCode.Get(ctx, id) 39 | if err != nil { 40 | return storage.AuthCode{}, convertDBError("get auth code: %w", err) 41 | } 42 | return toStorageAuthCode(authCode), nil 43 | } 44 | 45 | // DeleteAuthCode deletes an auth code from the database by id. 46 | func (d *Database) DeleteAuthCode(ctx context.Context, id string) error { 47 | err := d.client.AuthCode.DeleteOneID(id).Exec(ctx) 48 | if err != nil { 49 | return convertDBError("delete auth code: %w", err) 50 | } 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /storage/ent/client/devicerequest.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/dexidp/dex/storage" 7 | "github.com/dexidp/dex/storage/ent/db/devicerequest" 8 | ) 9 | 10 | // CreateDeviceRequest saves provided device request into the database. 11 | func (d *Database) CreateDeviceRequest(ctx context.Context, request storage.DeviceRequest) error { 12 | _, err := d.client.DeviceRequest.Create(). 13 | SetClientID(request.ClientID). 14 | SetClientSecret(request.ClientSecret). 15 | SetScopes(request.Scopes). 16 | SetUserCode(request.UserCode). 17 | SetDeviceCode(request.DeviceCode). 18 | // Save utc time into database because ent doesn't support comparing dates with different timezones 19 | SetExpiry(request.Expiry.UTC()). 20 | Save(ctx) 21 | if err != nil { 22 | return convertDBError("create device request: %w", err) 23 | } 24 | return nil 25 | } 26 | 27 | // GetDeviceRequest extracts a device request from the database by user code. 28 | func (d *Database) GetDeviceRequest(ctx context.Context, userCode string) (storage.DeviceRequest, error) { 29 | deviceRequest, err := d.client.DeviceRequest.Query(). 30 | Where(devicerequest.UserCode(userCode)). 31 | Only(ctx) 32 | if err != nil { 33 | return storage.DeviceRequest{}, convertDBError("get device request: %w", err) 34 | } 35 | return toStorageDeviceRequest(deviceRequest), nil 36 | } 37 | -------------------------------------------------------------------------------- /storage/ent/client/keys.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/dexidp/dex/storage" 8 | "github.com/dexidp/dex/storage/ent/db" 9 | ) 10 | 11 | func getKeys(ctx context.Context, client *db.KeysClient) (storage.Keys, error) { 12 | rawKeys, err := client.Get(ctx, keysRowID) 13 | if err != nil { 14 | return storage.Keys{}, convertDBError("get keys: %w", err) 15 | } 16 | 17 | return toStorageKeys(rawKeys), nil 18 | } 19 | 20 | // GetKeys returns signing keys, public keys and verification keys from the database. 21 | func (d *Database) GetKeys(ctx context.Context) (storage.Keys, error) { 22 | return getKeys(ctx, d.client.Keys) 23 | } 24 | 25 | // UpdateKeys rotates keys using updater function. 26 | func (d *Database) UpdateKeys(ctx context.Context, updater func(old storage.Keys) (storage.Keys, error)) error { 27 | firstUpdate := false 28 | 29 | tx, err := d.BeginTx(ctx) 30 | if err != nil { 31 | return convertDBError("update keys tx: %w", err) 32 | } 33 | 34 | storageKeys, err := getKeys(ctx, tx.Keys) 35 | if err != nil { 36 | if !errors.Is(err, storage.ErrNotFound) { 37 | return rollback(tx, "update keys get: %w", err) 38 | } 39 | firstUpdate = true 40 | } 41 | 42 | newKeys, err := updater(storageKeys) 43 | if err != nil { 44 | return rollback(tx, "update keys updating: %w", err) 45 | } 46 | 47 | // ent doesn't have an upsert support yet 48 | // https://github.com/facebook/ent/issues/139 49 | if firstUpdate { 50 | _, err = tx.Keys.Create(). 51 | SetID(keysRowID). 52 | SetNextRotation(newKeys.NextRotation). 53 | SetSigningKey(*newKeys.SigningKey). 54 | SetSigningKeyPub(*newKeys.SigningKeyPub). 55 | SetVerificationKeys(newKeys.VerificationKeys). 56 | Save(ctx) 57 | if err != nil { 58 | return rollback(tx, "create keys: %w", err) 59 | } 60 | if err = tx.Commit(); err != nil { 61 | return rollback(tx, "update keys commit: %w", err) 62 | } 63 | return nil 64 | } 65 | 66 | err = tx.Keys.UpdateOneID(keysRowID). 67 | SetNextRotation(newKeys.NextRotation.UTC()). 68 | SetSigningKey(*newKeys.SigningKey). 69 | SetSigningKeyPub(*newKeys.SigningKeyPub). 70 | SetVerificationKeys(newKeys.VerificationKeys). 71 | Exec(ctx) 72 | if err != nil { 73 | return rollback(tx, "update keys uploading: %w", err) 74 | } 75 | 76 | if err = tx.Commit(); err != nil { 77 | return rollback(tx, "update keys commit: %w", err) 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /storage/ent/client/utils.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "hash" 6 | 7 | "github.com/pkg/errors" 8 | 9 | "github.com/dexidp/dex/storage" 10 | "github.com/dexidp/dex/storage/ent/db" 11 | ) 12 | 13 | func rollback(tx *db.Tx, t string, err error) error { 14 | rerr := tx.Rollback() 15 | err = convertDBError(t, err) 16 | 17 | if rerr == nil { 18 | return err 19 | } 20 | return errors.Wrapf(err, "rolling back transaction: %v", rerr) 21 | } 22 | 23 | func convertDBError(t string, err error) error { 24 | if db.IsNotFound(err) { 25 | return storage.ErrNotFound 26 | } 27 | 28 | if db.IsConstraintError(err) { 29 | return storage.ErrAlreadyExists 30 | } 31 | 32 | return fmt.Errorf(t, err) 33 | } 34 | 35 | // compose hashed id from user and connection id to use it as primary key 36 | // ent doesn't support multi-key primary yet 37 | // https://github.com/facebook/ent/issues/400 38 | func offlineSessionID(userID string, connID string, hasher func() hash.Hash) string { 39 | h := hasher() 40 | 41 | h.Write([]byte(userID)) 42 | h.Write([]byte(connID)) 43 | return fmt.Sprintf("%x", h.Sum(nil)) 44 | } 45 | -------------------------------------------------------------------------------- /storage/ent/db/authcode_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package db 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/dexidp/dex/storage/ent/db/authcode" 12 | "github.com/dexidp/dex/storage/ent/db/predicate" 13 | ) 14 | 15 | // AuthCodeDelete is the builder for deleting a AuthCode entity. 16 | type AuthCodeDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *AuthCodeMutation 20 | } 21 | 22 | // Where appends a list predicates to the AuthCodeDelete builder. 23 | func (acd *AuthCodeDelete) Where(ps ...predicate.AuthCode) *AuthCodeDelete { 24 | acd.mutation.Where(ps...) 25 | return acd 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (acd *AuthCodeDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, acd.sqlExec, acd.mutation, acd.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (acd *AuthCodeDelete) ExecX(ctx context.Context) int { 35 | n, err := acd.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (acd *AuthCodeDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(authcode.Table, sqlgraph.NewFieldSpec(authcode.FieldID, field.TypeString)) 44 | if ps := acd.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, acd.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | acd.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // AuthCodeDeleteOne is the builder for deleting a single AuthCode entity. 60 | type AuthCodeDeleteOne struct { 61 | acd *AuthCodeDelete 62 | } 63 | 64 | // Where appends a list predicates to the AuthCodeDelete builder. 65 | func (acdo *AuthCodeDeleteOne) Where(ps ...predicate.AuthCode) *AuthCodeDeleteOne { 66 | acdo.acd.mutation.Where(ps...) 67 | return acdo 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (acdo *AuthCodeDeleteOne) Exec(ctx context.Context) error { 72 | n, err := acdo.acd.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{authcode.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (acdo *AuthCodeDeleteOne) ExecX(ctx context.Context) { 85 | if err := acdo.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /storage/ent/db/authrequest_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package db 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/dexidp/dex/storage/ent/db/authrequest" 12 | "github.com/dexidp/dex/storage/ent/db/predicate" 13 | ) 14 | 15 | // AuthRequestDelete is the builder for deleting a AuthRequest entity. 16 | type AuthRequestDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *AuthRequestMutation 20 | } 21 | 22 | // Where appends a list predicates to the AuthRequestDelete builder. 23 | func (ard *AuthRequestDelete) Where(ps ...predicate.AuthRequest) *AuthRequestDelete { 24 | ard.mutation.Where(ps...) 25 | return ard 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (ard *AuthRequestDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, ard.sqlExec, ard.mutation, ard.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (ard *AuthRequestDelete) ExecX(ctx context.Context) int { 35 | n, err := ard.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (ard *AuthRequestDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(authrequest.Table, sqlgraph.NewFieldSpec(authrequest.FieldID, field.TypeString)) 44 | if ps := ard.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, ard.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | ard.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // AuthRequestDeleteOne is the builder for deleting a single AuthRequest entity. 60 | type AuthRequestDeleteOne struct { 61 | ard *AuthRequestDelete 62 | } 63 | 64 | // Where appends a list predicates to the AuthRequestDelete builder. 65 | func (ardo *AuthRequestDeleteOne) Where(ps ...predicate.AuthRequest) *AuthRequestDeleteOne { 66 | ardo.ard.mutation.Where(ps...) 67 | return ardo 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (ardo *AuthRequestDeleteOne) Exec(ctx context.Context) error { 72 | n, err := ardo.ard.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{authrequest.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (ardo *AuthRequestDeleteOne) ExecX(ctx context.Context) { 85 | if err := ardo.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /storage/ent/db/connector/connector.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package connector 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | ) 8 | 9 | const ( 10 | // Label holds the string label denoting the connector type in the database. 11 | Label = "connector" 12 | // FieldID holds the string denoting the id field in the database. 13 | FieldID = "id" 14 | // FieldType holds the string denoting the type field in the database. 15 | FieldType = "type" 16 | // FieldName holds the string denoting the name field in the database. 17 | FieldName = "name" 18 | // FieldResourceVersion holds the string denoting the resource_version field in the database. 19 | FieldResourceVersion = "resource_version" 20 | // FieldConfig holds the string denoting the config field in the database. 21 | FieldConfig = "config" 22 | // Table holds the table name of the connector in the database. 23 | Table = "connectors" 24 | ) 25 | 26 | // Columns holds all SQL columns for connector fields. 27 | var Columns = []string{ 28 | FieldID, 29 | FieldType, 30 | FieldName, 31 | FieldResourceVersion, 32 | FieldConfig, 33 | } 34 | 35 | // ValidColumn reports if the column name is valid (part of the table columns). 36 | func ValidColumn(column string) bool { 37 | for i := range Columns { 38 | if column == Columns[i] { 39 | return true 40 | } 41 | } 42 | return false 43 | } 44 | 45 | var ( 46 | // TypeValidator is a validator for the "type" field. It is called by the builders before save. 47 | TypeValidator func(string) error 48 | // NameValidator is a validator for the "name" field. It is called by the builders before save. 49 | NameValidator func(string) error 50 | // IDValidator is a validator for the "id" field. It is called by the builders before save. 51 | IDValidator func(string) error 52 | ) 53 | 54 | // OrderOption defines the ordering options for the Connector queries. 55 | type OrderOption func(*sql.Selector) 56 | 57 | // ByID orders the results by the id field. 58 | func ByID(opts ...sql.OrderTermOption) OrderOption { 59 | return sql.OrderByField(FieldID, opts...).ToFunc() 60 | } 61 | 62 | // ByType orders the results by the type field. 63 | func ByType(opts ...sql.OrderTermOption) OrderOption { 64 | return sql.OrderByField(FieldType, opts...).ToFunc() 65 | } 66 | 67 | // ByName orders the results by the name field. 68 | func ByName(opts ...sql.OrderTermOption) OrderOption { 69 | return sql.OrderByField(FieldName, opts...).ToFunc() 70 | } 71 | 72 | // ByResourceVersion orders the results by the resource_version field. 73 | func ByResourceVersion(opts ...sql.OrderTermOption) OrderOption { 74 | return sql.OrderByField(FieldResourceVersion, opts...).ToFunc() 75 | } 76 | -------------------------------------------------------------------------------- /storage/ent/db/connector_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package db 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/dexidp/dex/storage/ent/db/connector" 12 | "github.com/dexidp/dex/storage/ent/db/predicate" 13 | ) 14 | 15 | // ConnectorDelete is the builder for deleting a Connector entity. 16 | type ConnectorDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *ConnectorMutation 20 | } 21 | 22 | // Where appends a list predicates to the ConnectorDelete builder. 23 | func (cd *ConnectorDelete) Where(ps ...predicate.Connector) *ConnectorDelete { 24 | cd.mutation.Where(ps...) 25 | return cd 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (cd *ConnectorDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, cd.sqlExec, cd.mutation, cd.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (cd *ConnectorDelete) ExecX(ctx context.Context) int { 35 | n, err := cd.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (cd *ConnectorDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(connector.Table, sqlgraph.NewFieldSpec(connector.FieldID, field.TypeString)) 44 | if ps := cd.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, cd.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | cd.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // ConnectorDeleteOne is the builder for deleting a single Connector entity. 60 | type ConnectorDeleteOne struct { 61 | cd *ConnectorDelete 62 | } 63 | 64 | // Where appends a list predicates to the ConnectorDelete builder. 65 | func (cdo *ConnectorDeleteOne) Where(ps ...predicate.Connector) *ConnectorDeleteOne { 66 | cdo.cd.mutation.Where(ps...) 67 | return cdo 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (cdo *ConnectorDeleteOne) Exec(ctx context.Context) error { 72 | n, err := cdo.cd.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{connector.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (cdo *ConnectorDeleteOne) ExecX(ctx context.Context) { 85 | if err := cdo.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /storage/ent/db/devicetoken_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package db 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/dexidp/dex/storage/ent/db/devicetoken" 12 | "github.com/dexidp/dex/storage/ent/db/predicate" 13 | ) 14 | 15 | // DeviceTokenDelete is the builder for deleting a DeviceToken entity. 16 | type DeviceTokenDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *DeviceTokenMutation 20 | } 21 | 22 | // Where appends a list predicates to the DeviceTokenDelete builder. 23 | func (dtd *DeviceTokenDelete) Where(ps ...predicate.DeviceToken) *DeviceTokenDelete { 24 | dtd.mutation.Where(ps...) 25 | return dtd 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (dtd *DeviceTokenDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, dtd.sqlExec, dtd.mutation, dtd.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (dtd *DeviceTokenDelete) ExecX(ctx context.Context) int { 35 | n, err := dtd.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (dtd *DeviceTokenDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(devicetoken.Table, sqlgraph.NewFieldSpec(devicetoken.FieldID, field.TypeInt)) 44 | if ps := dtd.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, dtd.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | dtd.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // DeviceTokenDeleteOne is the builder for deleting a single DeviceToken entity. 60 | type DeviceTokenDeleteOne struct { 61 | dtd *DeviceTokenDelete 62 | } 63 | 64 | // Where appends a list predicates to the DeviceTokenDelete builder. 65 | func (dtdo *DeviceTokenDeleteOne) Where(ps ...predicate.DeviceToken) *DeviceTokenDeleteOne { 66 | dtdo.dtd.mutation.Where(ps...) 67 | return dtdo 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (dtdo *DeviceTokenDeleteOne) Exec(ctx context.Context) error { 72 | n, err := dtdo.dtd.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{devicetoken.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (dtdo *DeviceTokenDeleteOne) ExecX(ctx context.Context) { 85 | if err := dtdo.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /storage/ent/db/enttest/enttest.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package enttest 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/dexidp/dex/storage/ent/db" 9 | // required by schema hooks. 10 | _ "github.com/dexidp/dex/storage/ent/db/runtime" 11 | 12 | "entgo.io/ent/dialect/sql/schema" 13 | "github.com/dexidp/dex/storage/ent/db/migrate" 14 | ) 15 | 16 | type ( 17 | // TestingT is the interface that is shared between 18 | // testing.T and testing.B and used by enttest. 19 | TestingT interface { 20 | FailNow() 21 | Error(...any) 22 | } 23 | 24 | // Option configures client creation. 25 | Option func(*options) 26 | 27 | options struct { 28 | opts []db.Option 29 | migrateOpts []schema.MigrateOption 30 | } 31 | ) 32 | 33 | // WithOptions forwards options to client creation. 34 | func WithOptions(opts ...db.Option) Option { 35 | return func(o *options) { 36 | o.opts = append(o.opts, opts...) 37 | } 38 | } 39 | 40 | // WithMigrateOptions forwards options to auto migration. 41 | func WithMigrateOptions(opts ...schema.MigrateOption) Option { 42 | return func(o *options) { 43 | o.migrateOpts = append(o.migrateOpts, opts...) 44 | } 45 | } 46 | 47 | func newOptions(opts []Option) *options { 48 | o := &options{} 49 | for _, opt := range opts { 50 | opt(o) 51 | } 52 | return o 53 | } 54 | 55 | // Open calls db.Open and auto-run migration. 56 | func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *db.Client { 57 | o := newOptions(opts) 58 | c, err := db.Open(driverName, dataSourceName, o.opts...) 59 | if err != nil { 60 | t.Error(err) 61 | t.FailNow() 62 | } 63 | migrateSchema(t, c, o) 64 | return c 65 | } 66 | 67 | // NewClient calls db.NewClient and auto-run migration. 68 | func NewClient(t TestingT, opts ...Option) *db.Client { 69 | o := newOptions(opts) 70 | c := db.NewClient(o.opts...) 71 | migrateSchema(t, c, o) 72 | return c 73 | } 74 | func migrateSchema(t TestingT, c *db.Client, o *options) { 75 | tables, err := schema.CopyTables(migrate.Tables) 76 | if err != nil { 77 | t.Error(err) 78 | t.FailNow() 79 | } 80 | if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil { 81 | t.Error(err) 82 | t.FailNow() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /storage/ent/db/keys/keys.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package keys 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | ) 8 | 9 | const ( 10 | // Label holds the string label denoting the keys type in the database. 11 | Label = "keys" 12 | // FieldID holds the string denoting the id field in the database. 13 | FieldID = "id" 14 | // FieldVerificationKeys holds the string denoting the verification_keys field in the database. 15 | FieldVerificationKeys = "verification_keys" 16 | // FieldSigningKey holds the string denoting the signing_key field in the database. 17 | FieldSigningKey = "signing_key" 18 | // FieldSigningKeyPub holds the string denoting the signing_key_pub field in the database. 19 | FieldSigningKeyPub = "signing_key_pub" 20 | // FieldNextRotation holds the string denoting the next_rotation field in the database. 21 | FieldNextRotation = "next_rotation" 22 | // Table holds the table name of the keys in the database. 23 | Table = "keys" 24 | ) 25 | 26 | // Columns holds all SQL columns for keys fields. 27 | var Columns = []string{ 28 | FieldID, 29 | FieldVerificationKeys, 30 | FieldSigningKey, 31 | FieldSigningKeyPub, 32 | FieldNextRotation, 33 | } 34 | 35 | // ValidColumn reports if the column name is valid (part of the table columns). 36 | func ValidColumn(column string) bool { 37 | for i := range Columns { 38 | if column == Columns[i] { 39 | return true 40 | } 41 | } 42 | return false 43 | } 44 | 45 | var ( 46 | // IDValidator is a validator for the "id" field. It is called by the builders before save. 47 | IDValidator func(string) error 48 | ) 49 | 50 | // OrderOption defines the ordering options for the Keys queries. 51 | type OrderOption func(*sql.Selector) 52 | 53 | // ByID orders the results by the id field. 54 | func ByID(opts ...sql.OrderTermOption) OrderOption { 55 | return sql.OrderByField(FieldID, opts...).ToFunc() 56 | } 57 | 58 | // ByNextRotation orders the results by the next_rotation field. 59 | func ByNextRotation(opts ...sql.OrderTermOption) OrderOption { 60 | return sql.OrderByField(FieldNextRotation, opts...).ToFunc() 61 | } 62 | -------------------------------------------------------------------------------- /storage/ent/db/keys_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package db 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/dexidp/dex/storage/ent/db/keys" 12 | "github.com/dexidp/dex/storage/ent/db/predicate" 13 | ) 14 | 15 | // KeysDelete is the builder for deleting a Keys entity. 16 | type KeysDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *KeysMutation 20 | } 21 | 22 | // Where appends a list predicates to the KeysDelete builder. 23 | func (kd *KeysDelete) Where(ps ...predicate.Keys) *KeysDelete { 24 | kd.mutation.Where(ps...) 25 | return kd 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (kd *KeysDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, kd.sqlExec, kd.mutation, kd.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (kd *KeysDelete) ExecX(ctx context.Context) int { 35 | n, err := kd.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (kd *KeysDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(keys.Table, sqlgraph.NewFieldSpec(keys.FieldID, field.TypeString)) 44 | if ps := kd.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, kd.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | kd.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // KeysDeleteOne is the builder for deleting a single Keys entity. 60 | type KeysDeleteOne struct { 61 | kd *KeysDelete 62 | } 63 | 64 | // Where appends a list predicates to the KeysDelete builder. 65 | func (kdo *KeysDeleteOne) Where(ps ...predicate.Keys) *KeysDeleteOne { 66 | kdo.kd.mutation.Where(ps...) 67 | return kdo 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (kdo *KeysDeleteOne) Exec(ctx context.Context) error { 72 | n, err := kdo.kd.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{keys.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (kdo *KeysDeleteOne) ExecX(ctx context.Context) { 85 | if err := kdo.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /storage/ent/db/migrate/migrate.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package migrate 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "io" 9 | 10 | "entgo.io/ent/dialect" 11 | "entgo.io/ent/dialect/sql/schema" 12 | ) 13 | 14 | var ( 15 | // WithGlobalUniqueID sets the universal ids options to the migration. 16 | // If this option is enabled, ent migration will allocate a 1<<32 range 17 | // for the ids of each entity (table). 18 | // Note that this option cannot be applied on tables that already exist. 19 | WithGlobalUniqueID = schema.WithGlobalUniqueID 20 | // WithDropColumn sets the drop column option to the migration. 21 | // If this option is enabled, ent migration will drop old columns 22 | // that were used for both fields and edges. This defaults to false. 23 | WithDropColumn = schema.WithDropColumn 24 | // WithDropIndex sets the drop index option to the migration. 25 | // If this option is enabled, ent migration will drop old indexes 26 | // that were defined in the schema. This defaults to false. 27 | // Note that unique constraints are defined using `UNIQUE INDEX`, 28 | // and therefore, it's recommended to enable this option to get more 29 | // flexibility in the schema changes. 30 | WithDropIndex = schema.WithDropIndex 31 | // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. 32 | WithForeignKeys = schema.WithForeignKeys 33 | ) 34 | 35 | // Schema is the API for creating, migrating and dropping a schema. 36 | type Schema struct { 37 | drv dialect.Driver 38 | } 39 | 40 | // NewSchema creates a new schema client. 41 | func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } 42 | 43 | // Create creates all schema resources. 44 | func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { 45 | return Create(ctx, s, Tables, opts...) 46 | } 47 | 48 | // Create creates all table resources using the given schema driver. 49 | func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error { 50 | migrate, err := schema.NewMigrate(s.drv, opts...) 51 | if err != nil { 52 | return fmt.Errorf("ent/migrate: %w", err) 53 | } 54 | return migrate.Create(ctx, tables...) 55 | } 56 | 57 | // WriteTo writes the schema changes to w instead of running them against the database. 58 | // 59 | // if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { 60 | // log.Fatal(err) 61 | // } 62 | func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { 63 | return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...) 64 | } 65 | -------------------------------------------------------------------------------- /storage/ent/db/oauth2client_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package db 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/dexidp/dex/storage/ent/db/oauth2client" 12 | "github.com/dexidp/dex/storage/ent/db/predicate" 13 | ) 14 | 15 | // OAuth2ClientDelete is the builder for deleting a OAuth2Client entity. 16 | type OAuth2ClientDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *OAuth2ClientMutation 20 | } 21 | 22 | // Where appends a list predicates to the OAuth2ClientDelete builder. 23 | func (od *OAuth2ClientDelete) Where(ps ...predicate.OAuth2Client) *OAuth2ClientDelete { 24 | od.mutation.Where(ps...) 25 | return od 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (od *OAuth2ClientDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, od.sqlExec, od.mutation, od.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (od *OAuth2ClientDelete) ExecX(ctx context.Context) int { 35 | n, err := od.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (od *OAuth2ClientDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(oauth2client.Table, sqlgraph.NewFieldSpec(oauth2client.FieldID, field.TypeString)) 44 | if ps := od.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, od.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | od.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // OAuth2ClientDeleteOne is the builder for deleting a single OAuth2Client entity. 60 | type OAuth2ClientDeleteOne struct { 61 | od *OAuth2ClientDelete 62 | } 63 | 64 | // Where appends a list predicates to the OAuth2ClientDelete builder. 65 | func (odo *OAuth2ClientDeleteOne) Where(ps ...predicate.OAuth2Client) *OAuth2ClientDeleteOne { 66 | odo.od.mutation.Where(ps...) 67 | return odo 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (odo *OAuth2ClientDeleteOne) Exec(ctx context.Context) error { 72 | n, err := odo.od.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{oauth2client.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (odo *OAuth2ClientDeleteOne) ExecX(ctx context.Context) { 85 | if err := odo.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /storage/ent/db/offlinesession/offlinesession.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package offlinesession 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | ) 8 | 9 | const ( 10 | // Label holds the string label denoting the offlinesession type in the database. 11 | Label = "offline_session" 12 | // FieldID holds the string denoting the id field in the database. 13 | FieldID = "id" 14 | // FieldUserID holds the string denoting the user_id field in the database. 15 | FieldUserID = "user_id" 16 | // FieldConnID holds the string denoting the conn_id field in the database. 17 | FieldConnID = "conn_id" 18 | // FieldRefresh holds the string denoting the refresh field in the database. 19 | FieldRefresh = "refresh" 20 | // FieldConnectorData holds the string denoting the connector_data field in the database. 21 | FieldConnectorData = "connector_data" 22 | // Table holds the table name of the offlinesession in the database. 23 | Table = "offline_sessions" 24 | ) 25 | 26 | // Columns holds all SQL columns for offlinesession fields. 27 | var Columns = []string{ 28 | FieldID, 29 | FieldUserID, 30 | FieldConnID, 31 | FieldRefresh, 32 | FieldConnectorData, 33 | } 34 | 35 | // ValidColumn reports if the column name is valid (part of the table columns). 36 | func ValidColumn(column string) bool { 37 | for i := range Columns { 38 | if column == Columns[i] { 39 | return true 40 | } 41 | } 42 | return false 43 | } 44 | 45 | var ( 46 | // UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. 47 | UserIDValidator func(string) error 48 | // ConnIDValidator is a validator for the "conn_id" field. It is called by the builders before save. 49 | ConnIDValidator func(string) error 50 | // IDValidator is a validator for the "id" field. It is called by the builders before save. 51 | IDValidator func(string) error 52 | ) 53 | 54 | // OrderOption defines the ordering options for the OfflineSession queries. 55 | type OrderOption func(*sql.Selector) 56 | 57 | // ByID orders the results by the id field. 58 | func ByID(opts ...sql.OrderTermOption) OrderOption { 59 | return sql.OrderByField(FieldID, opts...).ToFunc() 60 | } 61 | 62 | // ByUserID orders the results by the user_id field. 63 | func ByUserID(opts ...sql.OrderTermOption) OrderOption { 64 | return sql.OrderByField(FieldUserID, opts...).ToFunc() 65 | } 66 | 67 | // ByConnID orders the results by the conn_id field. 68 | func ByConnID(opts ...sql.OrderTermOption) OrderOption { 69 | return sql.OrderByField(FieldConnID, opts...).ToFunc() 70 | } 71 | -------------------------------------------------------------------------------- /storage/ent/db/password/password.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package password 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | ) 8 | 9 | const ( 10 | // Label holds the string label denoting the password type in the database. 11 | Label = "password" 12 | // FieldID holds the string denoting the id field in the database. 13 | FieldID = "id" 14 | // FieldEmail holds the string denoting the email field in the database. 15 | FieldEmail = "email" 16 | // FieldHash holds the string denoting the hash field in the database. 17 | FieldHash = "hash" 18 | // FieldUsername holds the string denoting the username field in the database. 19 | FieldUsername = "username" 20 | // FieldUserID holds the string denoting the user_id field in the database. 21 | FieldUserID = "user_id" 22 | // Table holds the table name of the password in the database. 23 | Table = "passwords" 24 | ) 25 | 26 | // Columns holds all SQL columns for password fields. 27 | var Columns = []string{ 28 | FieldID, 29 | FieldEmail, 30 | FieldHash, 31 | FieldUsername, 32 | FieldUserID, 33 | } 34 | 35 | // ValidColumn reports if the column name is valid (part of the table columns). 36 | func ValidColumn(column string) bool { 37 | for i := range Columns { 38 | if column == Columns[i] { 39 | return true 40 | } 41 | } 42 | return false 43 | } 44 | 45 | var ( 46 | // EmailValidator is a validator for the "email" field. It is called by the builders before save. 47 | EmailValidator func(string) error 48 | // UsernameValidator is a validator for the "username" field. It is called by the builders before save. 49 | UsernameValidator func(string) error 50 | // UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. 51 | UserIDValidator func(string) error 52 | ) 53 | 54 | // OrderOption defines the ordering options for the Password queries. 55 | type OrderOption func(*sql.Selector) 56 | 57 | // ByID orders the results by the id field. 58 | func ByID(opts ...sql.OrderTermOption) OrderOption { 59 | return sql.OrderByField(FieldID, opts...).ToFunc() 60 | } 61 | 62 | // ByEmail orders the results by the email field. 63 | func ByEmail(opts ...sql.OrderTermOption) OrderOption { 64 | return sql.OrderByField(FieldEmail, opts...).ToFunc() 65 | } 66 | 67 | // ByUsername orders the results by the username field. 68 | func ByUsername(opts ...sql.OrderTermOption) OrderOption { 69 | return sql.OrderByField(FieldUsername, opts...).ToFunc() 70 | } 71 | 72 | // ByUserID orders the results by the user_id field. 73 | func ByUserID(opts ...sql.OrderTermOption) OrderOption { 74 | return sql.OrderByField(FieldUserID, opts...).ToFunc() 75 | } 76 | -------------------------------------------------------------------------------- /storage/ent/db/password_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package db 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/dexidp/dex/storage/ent/db/password" 12 | "github.com/dexidp/dex/storage/ent/db/predicate" 13 | ) 14 | 15 | // PasswordDelete is the builder for deleting a Password entity. 16 | type PasswordDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *PasswordMutation 20 | } 21 | 22 | // Where appends a list predicates to the PasswordDelete builder. 23 | func (pd *PasswordDelete) Where(ps ...predicate.Password) *PasswordDelete { 24 | pd.mutation.Where(ps...) 25 | return pd 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (pd *PasswordDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, pd.sqlExec, pd.mutation, pd.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (pd *PasswordDelete) ExecX(ctx context.Context) int { 35 | n, err := pd.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (pd *PasswordDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(password.Table, sqlgraph.NewFieldSpec(password.FieldID, field.TypeInt)) 44 | if ps := pd.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, pd.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | pd.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // PasswordDeleteOne is the builder for deleting a single Password entity. 60 | type PasswordDeleteOne struct { 61 | pd *PasswordDelete 62 | } 63 | 64 | // Where appends a list predicates to the PasswordDelete builder. 65 | func (pdo *PasswordDeleteOne) Where(ps ...predicate.Password) *PasswordDeleteOne { 66 | pdo.pd.mutation.Where(ps...) 67 | return pdo 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (pdo *PasswordDeleteOne) Exec(ctx context.Context) error { 72 | n, err := pdo.pd.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{password.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (pdo *PasswordDeleteOne) ExecX(ctx context.Context) { 85 | if err := pdo.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /storage/ent/db/predicate/predicate.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package predicate 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | ) 8 | 9 | // AuthCode is the predicate function for authcode builders. 10 | type AuthCode func(*sql.Selector) 11 | 12 | // AuthRequest is the predicate function for authrequest builders. 13 | type AuthRequest func(*sql.Selector) 14 | 15 | // Connector is the predicate function for connector builders. 16 | type Connector func(*sql.Selector) 17 | 18 | // DeviceRequest is the predicate function for devicerequest builders. 19 | type DeviceRequest func(*sql.Selector) 20 | 21 | // DeviceToken is the predicate function for devicetoken builders. 22 | type DeviceToken func(*sql.Selector) 23 | 24 | // Keys is the predicate function for keys builders. 25 | type Keys func(*sql.Selector) 26 | 27 | // OAuth2Client is the predicate function for oauth2client builders. 28 | type OAuth2Client func(*sql.Selector) 29 | 30 | // OfflineSession is the predicate function for offlinesession builders. 31 | type OfflineSession func(*sql.Selector) 32 | 33 | // Password is the predicate function for password builders. 34 | type Password func(*sql.Selector) 35 | 36 | // RefreshToken is the predicate function for refreshtoken builders. 37 | type RefreshToken func(*sql.Selector) 38 | -------------------------------------------------------------------------------- /storage/ent/db/refreshtoken_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package db 4 | 5 | import ( 6 | "context" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | "entgo.io/ent/dialect/sql/sqlgraph" 10 | "entgo.io/ent/schema/field" 11 | "github.com/dexidp/dex/storage/ent/db/predicate" 12 | "github.com/dexidp/dex/storage/ent/db/refreshtoken" 13 | ) 14 | 15 | // RefreshTokenDelete is the builder for deleting a RefreshToken entity. 16 | type RefreshTokenDelete struct { 17 | config 18 | hooks []Hook 19 | mutation *RefreshTokenMutation 20 | } 21 | 22 | // Where appends a list predicates to the RefreshTokenDelete builder. 23 | func (rtd *RefreshTokenDelete) Where(ps ...predicate.RefreshToken) *RefreshTokenDelete { 24 | rtd.mutation.Where(ps...) 25 | return rtd 26 | } 27 | 28 | // Exec executes the deletion query and returns how many vertices were deleted. 29 | func (rtd *RefreshTokenDelete) Exec(ctx context.Context) (int, error) { 30 | return withHooks(ctx, rtd.sqlExec, rtd.mutation, rtd.hooks) 31 | } 32 | 33 | // ExecX is like Exec, but panics if an error occurs. 34 | func (rtd *RefreshTokenDelete) ExecX(ctx context.Context) int { 35 | n, err := rtd.Exec(ctx) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return n 40 | } 41 | 42 | func (rtd *RefreshTokenDelete) sqlExec(ctx context.Context) (int, error) { 43 | _spec := sqlgraph.NewDeleteSpec(refreshtoken.Table, sqlgraph.NewFieldSpec(refreshtoken.FieldID, field.TypeString)) 44 | if ps := rtd.mutation.predicates; len(ps) > 0 { 45 | _spec.Predicate = func(selector *sql.Selector) { 46 | for i := range ps { 47 | ps[i](selector) 48 | } 49 | } 50 | } 51 | affected, err := sqlgraph.DeleteNodes(ctx, rtd.driver, _spec) 52 | if err != nil && sqlgraph.IsConstraintError(err) { 53 | err = &ConstraintError{msg: err.Error(), wrap: err} 54 | } 55 | rtd.mutation.done = true 56 | return affected, err 57 | } 58 | 59 | // RefreshTokenDeleteOne is the builder for deleting a single RefreshToken entity. 60 | type RefreshTokenDeleteOne struct { 61 | rtd *RefreshTokenDelete 62 | } 63 | 64 | // Where appends a list predicates to the RefreshTokenDelete builder. 65 | func (rtdo *RefreshTokenDeleteOne) Where(ps ...predicate.RefreshToken) *RefreshTokenDeleteOne { 66 | rtdo.rtd.mutation.Where(ps...) 67 | return rtdo 68 | } 69 | 70 | // Exec executes the deletion query. 71 | func (rtdo *RefreshTokenDeleteOne) Exec(ctx context.Context) error { 72 | n, err := rtdo.rtd.Exec(ctx) 73 | switch { 74 | case err != nil: 75 | return err 76 | case n == 0: 77 | return &NotFoundError{refreshtoken.Label} 78 | default: 79 | return nil 80 | } 81 | } 82 | 83 | // ExecX is like Exec, but panics if an error occurs. 84 | func (rtdo *RefreshTokenDeleteOne) ExecX(ctx context.Context) { 85 | if err := rtdo.Exec(ctx); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /storage/ent/db/runtime/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package runtime 4 | 5 | // The schema-stitching logic is generated in github.com/dexidp/dex/storage/ent/db/runtime.go 6 | 7 | const ( 8 | Version = "v0.14.4" // Version of ent codegen. 9 | Sum = "h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI=" // Sum of ent codegen. 10 | ) 11 | -------------------------------------------------------------------------------- /storage/ent/generate.go: -------------------------------------------------------------------------------- 1 | package ent 2 | 3 | //go:generate go tool entgo.io/ent/cmd/ent generate ./schema --target ./db 4 | -------------------------------------------------------------------------------- /storage/ent/schema/authcode.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | /* Original SQL table: 9 | create table auth_code 10 | ( 11 | id text not null primary key, 12 | client_id text not null, 13 | scopes blob not null, 14 | nonce text not null, 15 | redirect_uri text not null, 16 | claims_user_id text not null, 17 | claims_username text not null, 18 | claims_email text not null, 19 | claims_email_verified integer not null, 20 | claims_groups blob not null, 21 | connector_id text not null, 22 | connector_data blob, 23 | expiry timestamp not null, 24 | claims_preferred_username text default '' not null, 25 | code_challenge text default '' not null, 26 | code_challenge_method text default '' not null 27 | ); 28 | */ 29 | 30 | // AuthCode holds the schema definition for the AuthCode entity. 31 | type AuthCode struct { 32 | ent.Schema 33 | } 34 | 35 | // Fields of the AuthCode. 36 | func (AuthCode) Fields() []ent.Field { 37 | return []ent.Field{ 38 | field.Text("id"). 39 | SchemaType(textSchema). 40 | NotEmpty(). 41 | Unique(), 42 | field.Text("client_id"). 43 | SchemaType(textSchema). 44 | NotEmpty(), 45 | field.JSON("scopes", []string{}). 46 | Optional(), 47 | field.Text("nonce"). 48 | SchemaType(textSchema). 49 | NotEmpty(), 50 | field.Text("redirect_uri"). 51 | SchemaType(textSchema). 52 | NotEmpty(), 53 | 54 | field.Text("claims_user_id"). 55 | SchemaType(textSchema). 56 | NotEmpty(), 57 | field.Text("claims_username"). 58 | SchemaType(textSchema). 59 | NotEmpty(), 60 | field.Text("claims_email"). 61 | SchemaType(textSchema). 62 | NotEmpty(), 63 | field.Bool("claims_email_verified"), 64 | field.JSON("claims_groups", []string{}). 65 | Optional(), 66 | field.Text("claims_preferred_username"). 67 | SchemaType(textSchema). 68 | Default(""), 69 | 70 | field.Text("connector_id"). 71 | SchemaType(textSchema). 72 | NotEmpty(), 73 | field.Bytes("connector_data"). 74 | Nillable(). 75 | Optional(), 76 | field.Time("expiry"). 77 | SchemaType(timeSchema), 78 | field.Text("code_challenge"). 79 | SchemaType(textSchema). 80 | Default(""), 81 | field.Text("code_challenge_method"). 82 | SchemaType(textSchema). 83 | Default(""), 84 | } 85 | } 86 | 87 | // Edges of the AuthCode. 88 | func (AuthCode) Edges() []ent.Edge { 89 | return []ent.Edge{} 90 | } 91 | -------------------------------------------------------------------------------- /storage/ent/schema/client.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | /* Original SQL table: 9 | create table client 10 | ( 11 | id text not null primary key, 12 | secret text not null, 13 | redirect_uris blob not null, 14 | trusted_peers blob not null, 15 | public integer not null, 16 | name text not null, 17 | logo_url text not null 18 | ); 19 | */ 20 | 21 | // OAuth2Client holds the schema definition for the Client entity. 22 | type OAuth2Client struct { 23 | ent.Schema 24 | } 25 | 26 | // Fields of the OAuth2Client. 27 | func (OAuth2Client) Fields() []ent.Field { 28 | return []ent.Field{ 29 | field.Text("id"). 30 | SchemaType(textSchema). 31 | MaxLen(100). 32 | NotEmpty(). 33 | Unique(), 34 | field.Text("secret"). 35 | SchemaType(textSchema). 36 | NotEmpty(), 37 | field.JSON("redirect_uris", []string{}). 38 | Optional(), 39 | field.JSON("trusted_peers", []string{}). 40 | Optional(), 41 | field.Bool("public"), 42 | field.Text("name"). 43 | SchemaType(textSchema). 44 | NotEmpty(), 45 | field.Text("logo_url"). 46 | SchemaType(textSchema). 47 | NotEmpty(), 48 | } 49 | } 50 | 51 | // Edges of the OAuth2Client. 52 | func (OAuth2Client) Edges() []ent.Edge { 53 | return []ent.Edge{} 54 | } 55 | -------------------------------------------------------------------------------- /storage/ent/schema/connector.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | /* Original SQL table: 9 | create table connector 10 | ( 11 | id text not null primary key, 12 | type text not null, 13 | name text not null, 14 | resource_version text not null, 15 | config blob 16 | ); 17 | */ 18 | 19 | // Connector holds the schema definition for the Client entity. 20 | type Connector struct { 21 | ent.Schema 22 | } 23 | 24 | // Fields of the Connector. 25 | func (Connector) Fields() []ent.Field { 26 | return []ent.Field{ 27 | field.Text("id"). 28 | SchemaType(textSchema). 29 | MaxLen(100). 30 | NotEmpty(). 31 | Unique(), 32 | field.Text("type"). 33 | SchemaType(textSchema). 34 | NotEmpty(), 35 | field.Text("name"). 36 | SchemaType(textSchema). 37 | NotEmpty(), 38 | field.Text("resource_version"). 39 | SchemaType(textSchema), 40 | field.Bytes("config"), 41 | } 42 | } 43 | 44 | // Edges of the Connector. 45 | func (Connector) Edges() []ent.Edge { 46 | return []ent.Edge{} 47 | } 48 | -------------------------------------------------------------------------------- /storage/ent/schema/devicerequest.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | /* Original SQL table: 9 | create table device_request 10 | ( 11 | user_code text not null primary key, 12 | device_code text not null, 13 | client_id text not null, 14 | client_secret text, 15 | scopes blob not null, 16 | expiry timestamp not null 17 | ); 18 | */ 19 | 20 | // DeviceRequest holds the schema definition for the DeviceRequest entity. 21 | type DeviceRequest struct { 22 | ent.Schema 23 | } 24 | 25 | // Fields of the DeviceRequest. 26 | func (DeviceRequest) Fields() []ent.Field { 27 | return []ent.Field{ 28 | field.Text("user_code"). 29 | SchemaType(textSchema). 30 | NotEmpty(). 31 | Unique(), 32 | field.Text("device_code"). 33 | SchemaType(textSchema). 34 | NotEmpty(), 35 | field.Text("client_id"). 36 | SchemaType(textSchema). 37 | NotEmpty(), 38 | field.Text("client_secret"). 39 | SchemaType(textSchema). 40 | NotEmpty(), 41 | field.JSON("scopes", []string{}). 42 | Optional(), 43 | field.Time("expiry"). 44 | SchemaType(timeSchema), 45 | } 46 | } 47 | 48 | // Edges of the DeviceRequest. 49 | func (DeviceRequest) Edges() []ent.Edge { 50 | return []ent.Edge{} 51 | } 52 | -------------------------------------------------------------------------------- /storage/ent/schema/devicetoken.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | /* Original SQL table: 9 | create table device_token 10 | ( 11 | device_code text not null primary key, 12 | status text not null, 13 | token blob, 14 | expiry timestamp not null, 15 | last_request timestamp not null, 16 | poll_interval integer not null, 17 | code_challenge text default '' not null, 18 | code_challenge_method text default '' not null 19 | ); 20 | */ 21 | 22 | // DeviceToken holds the schema definition for the DeviceToken entity. 23 | type DeviceToken struct { 24 | ent.Schema 25 | } 26 | 27 | // Fields of the DeviceToken. 28 | func (DeviceToken) Fields() []ent.Field { 29 | return []ent.Field{ 30 | field.Text("device_code"). 31 | SchemaType(textSchema). 32 | NotEmpty(). 33 | Unique(), 34 | field.Text("status"). 35 | SchemaType(textSchema). 36 | NotEmpty(), 37 | field.Bytes("token").Nillable().Optional(), 38 | field.Time("expiry"). 39 | SchemaType(timeSchema), 40 | field.Time("last_request"). 41 | SchemaType(timeSchema), 42 | field.Int("poll_interval"), 43 | field.Text("code_challenge"). 44 | SchemaType(textSchema). 45 | Default(""), 46 | field.Text("code_challenge_method"). 47 | SchemaType(textSchema). 48 | Default(""), 49 | } 50 | } 51 | 52 | // Edges of the DeviceToken. 53 | func (DeviceToken) Edges() []ent.Edge { 54 | return []ent.Edge{} 55 | } 56 | -------------------------------------------------------------------------------- /storage/ent/schema/dialects.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent/dialect" 5 | ) 6 | 7 | var textSchema = map[string]string{ 8 | dialect.Postgres: "text", 9 | dialect.SQLite: "text", 10 | // MySQL doesn't support indices on text fields w/o 11 | // specifying key length. Use varchar instead (767 byte 12 | // is the max key length for InnoDB with 4k pages). 13 | // For compound indexes (with two keys) even less. 14 | dialect.MySQL: "varchar(384)", 15 | } 16 | 17 | var timeSchema = map[string]string{ 18 | dialect.Postgres: "timestamptz", 19 | dialect.SQLite: "timestamp", 20 | dialect.MySQL: "datetime(3)", 21 | } 22 | -------------------------------------------------------------------------------- /storage/ent/schema/keys.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | "github.com/go-jose/go-jose/v4" 7 | 8 | "github.com/dexidp/dex/storage" 9 | ) 10 | 11 | /* Original SQL table: 12 | create table keys 13 | ( 14 | id text not null primary key, 15 | verification_keys blob not null, 16 | signing_key blob not null, 17 | signing_key_pub blob not null, 18 | next_rotation timestamp not null 19 | ); 20 | */ 21 | 22 | // Keys holds the schema definition for the Keys entity. 23 | type Keys struct { 24 | ent.Schema 25 | } 26 | 27 | // Fields of the Keys. 28 | func (Keys) Fields() []ent.Field { 29 | return []ent.Field{ 30 | field.Text("id"). 31 | SchemaType(textSchema). 32 | NotEmpty(). 33 | Unique(), 34 | field.JSON("verification_keys", []storage.VerificationKey{}), 35 | field.JSON("signing_key", jose.JSONWebKey{}), 36 | field.JSON("signing_key_pub", jose.JSONWebKey{}), 37 | field.Time("next_rotation"). 38 | SchemaType(timeSchema), 39 | } 40 | } 41 | 42 | // Edges of the Keys. 43 | func (Keys) Edges() []ent.Edge { 44 | return []ent.Edge{} 45 | } 46 | -------------------------------------------------------------------------------- /storage/ent/schema/offlinesession.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | /* Original SQL table: 9 | create table offline_session 10 | ( 11 | user_id text not null, 12 | conn_id text not null, 13 | refresh blob not null, 14 | connector_data blob, 15 | primary key (user_id, conn_id) 16 | ); 17 | */ 18 | 19 | // OfflineSession holds the schema definition for the OfflineSession entity. 20 | type OfflineSession struct { 21 | ent.Schema 22 | } 23 | 24 | // Fields of the OfflineSession. 25 | func (OfflineSession) Fields() []ent.Field { 26 | return []ent.Field{ 27 | // Using id field here because it's impossible to create multi-key primary yet 28 | field.Text("id"). 29 | SchemaType(textSchema). 30 | NotEmpty(). 31 | Unique(), 32 | field.Text("user_id"). 33 | SchemaType(textSchema). 34 | NotEmpty(), 35 | field.Text("conn_id"). 36 | SchemaType(textSchema). 37 | NotEmpty(), 38 | field.Bytes("refresh"), 39 | field.Bytes("connector_data").Nillable().Optional(), 40 | } 41 | } 42 | 43 | // Edges of the OfflineSession. 44 | func (OfflineSession) Edges() []ent.Edge { 45 | return []ent.Edge{} 46 | } 47 | -------------------------------------------------------------------------------- /storage/ent/schema/password.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | /* Original SQL table: 9 | create table password 10 | ( 11 | email text not null primary key, 12 | hash blob not null, 13 | username text not null, 14 | user_id text not null 15 | ); 16 | */ 17 | 18 | // Password holds the schema definition for the Password entity. 19 | type Password struct { 20 | ent.Schema 21 | } 22 | 23 | // Fields of the Password. 24 | func (Password) Fields() []ent.Field { 25 | return []ent.Field{ 26 | field.Text("email"). 27 | SchemaType(textSchema). 28 | StorageKey("email"). // use email as ID field to make querying easier 29 | NotEmpty(). 30 | Unique(), 31 | field.Bytes("hash"), 32 | field.Text("username"). 33 | SchemaType(textSchema). 34 | NotEmpty(), 35 | field.Text("user_id"). 36 | SchemaType(textSchema). 37 | NotEmpty(), 38 | } 39 | } 40 | 41 | // Edges of the Password. 42 | func (Password) Edges() []ent.Edge { 43 | return []ent.Edge{} 44 | } 45 | -------------------------------------------------------------------------------- /storage/ent/sqlite.go: -------------------------------------------------------------------------------- 1 | package ent 2 | 3 | import ( 4 | "context" 5 | "crypto/sha256" 6 | "log/slog" 7 | "strings" 8 | 9 | "entgo.io/ent/dialect/sql" 10 | _ "github.com/mattn/go-sqlite3" // Register sqlite driver. 11 | 12 | "github.com/dexidp/dex/storage" 13 | "github.com/dexidp/dex/storage/ent/client" 14 | "github.com/dexidp/dex/storage/ent/db" 15 | ) 16 | 17 | // SQLite3 options for creating an SQL db. 18 | type SQLite3 struct { 19 | File string `json:"file"` 20 | } 21 | 22 | // Open always returns a new in sqlite3 storage. 23 | func (s *SQLite3) Open(logger *slog.Logger) (storage.Storage, error) { 24 | logger.Debug("experimental ent-based storage driver is enabled") 25 | 26 | // Implicitly set foreign_keys pragma to "on" because it is required by ent 27 | s.File = addFK(s.File) 28 | 29 | drv, err := sql.Open("sqlite3", s.File) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | // always allow only one connection to sqlite3, any other thread/go-routine 35 | // attempting concurrent access will have to wait 36 | pool := drv.DB() 37 | pool.SetMaxOpenConns(1) 38 | 39 | databaseClient := client.NewDatabase( 40 | client.WithClient(db.NewClient(db.Driver(drv))), 41 | client.WithHasher(sha256.New), 42 | ) 43 | 44 | if err := databaseClient.Schema().Create(context.TODO()); err != nil { 45 | return nil, err 46 | } 47 | 48 | return databaseClient, nil 49 | } 50 | 51 | func addFK(dsn string) string { 52 | if strings.Contains(dsn, "_fk") { 53 | return dsn 54 | } 55 | 56 | delim := "?" 57 | if strings.Contains(dsn, "?") { 58 | delim = "&" 59 | } 60 | return dsn + delim + "_fk=1" 61 | } 62 | -------------------------------------------------------------------------------- /storage/ent/sqlite_test.go: -------------------------------------------------------------------------------- 1 | package ent 2 | 3 | import ( 4 | "log/slog" 5 | "testing" 6 | 7 | "github.com/dexidp/dex/storage" 8 | "github.com/dexidp/dex/storage/conformance" 9 | ) 10 | 11 | func newSQLiteStorage() storage.Storage { 12 | logger := slog.New(slog.DiscardHandler) 13 | 14 | cfg := SQLite3{File: ":memory:"} 15 | s, err := cfg.Open(logger) 16 | if err != nil { 17 | panic(err) 18 | } 19 | return s 20 | } 21 | 22 | func TestSQLite3(t *testing.T) { 23 | conformance.RunTests(t, newSQLiteStorage) 24 | } 25 | -------------------------------------------------------------------------------- /storage/ent/types.go: -------------------------------------------------------------------------------- 1 | package ent 2 | 3 | // NetworkDB contains options common to SQL databases accessed over network. 4 | type NetworkDB struct { 5 | Database string 6 | User string 7 | Password string 8 | Host string 9 | Port uint16 10 | 11 | ConnectionTimeout int // Seconds 12 | 13 | MaxOpenConns int // default: 5 14 | MaxIdleConns int // default: 5 15 | ConnMaxLifetime int // Seconds, default: not set 16 | } 17 | 18 | // SSL represents SSL options for network databases. 19 | type SSL struct { 20 | Mode string 21 | CAFile string 22 | // Files for client auth. 23 | KeyFile string 24 | CertFile string 25 | } 26 | -------------------------------------------------------------------------------- /storage/ent/utils.go: -------------------------------------------------------------------------------- 1 | package ent 2 | 3 | import "os" 4 | 5 | func getenv(key, defaultVal string) string { 6 | if val := os.Getenv(key); val != "" { 7 | return val 8 | } 9 | return defaultVal 10 | } 11 | -------------------------------------------------------------------------------- /storage/etcd/config.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "log/slog" 5 | "time" 6 | 7 | "go.etcd.io/etcd/client/pkg/v3/transport" 8 | clientv3 "go.etcd.io/etcd/client/v3" 9 | "go.etcd.io/etcd/client/v3/namespace" 10 | 11 | "github.com/dexidp/dex/storage" 12 | ) 13 | 14 | var defaultDialTimeout = 2 * time.Second 15 | 16 | // SSL represents SSL options for etcd databases. 17 | type SSL struct { 18 | ServerName string `json:"serverName" yaml:"serverName"` 19 | CAFile string `json:"caFile" yaml:"caFile"` 20 | KeyFile string `json:"keyFile" yaml:"keyFile"` 21 | CertFile string `json:"certFile" yaml:"certFile"` 22 | } 23 | 24 | // Etcd options for connecting to etcd databases. 25 | // If you are using a shared etcd cluster for storage, it might be useful to 26 | // configure an etcd namespace either via Namespace field or using `etcd grpc-proxy 27 | // --namespace=<prefix>` 28 | type Etcd struct { 29 | Endpoints []string `json:"endpoints" yaml:"endpoints"` 30 | Namespace string `json:"namespace" yaml:"namespace"` 31 | Username string `json:"username" yaml:"username"` 32 | Password string `json:"password" yaml:"password"` 33 | SSL SSL `json:"ssl" yaml:"ssl"` 34 | } 35 | 36 | // Open creates a new storage implementation backed by Etcd 37 | func (p *Etcd) Open(logger *slog.Logger) (storage.Storage, error) { 38 | return p.open(logger) 39 | } 40 | 41 | func (p *Etcd) open(logger *slog.Logger) (*conn, error) { 42 | cfg := clientv3.Config{ 43 | Endpoints: p.Endpoints, 44 | DialTimeout: defaultDialTimeout, 45 | Username: p.Username, 46 | Password: p.Password, 47 | } 48 | 49 | var cfgtls *transport.TLSInfo 50 | tlsinfo := transport.TLSInfo{} 51 | if p.SSL.CertFile != "" { 52 | tlsinfo.CertFile = p.SSL.CertFile 53 | cfgtls = &tlsinfo 54 | } 55 | 56 | if p.SSL.KeyFile != "" { 57 | tlsinfo.KeyFile = p.SSL.KeyFile 58 | cfgtls = &tlsinfo 59 | } 60 | 61 | if p.SSL.CAFile != "" { 62 | tlsinfo.TrustedCAFile = p.SSL.CAFile 63 | cfgtls = &tlsinfo 64 | } 65 | 66 | if p.SSL.ServerName != "" { 67 | tlsinfo.ServerName = p.SSL.ServerName 68 | cfgtls = &tlsinfo 69 | } 70 | 71 | if cfgtls != nil { 72 | clientTLS, err := cfgtls.ClientConfig() 73 | if err != nil { 74 | return nil, err 75 | } 76 | cfg.TLS = clientTLS 77 | } 78 | 79 | db, err := clientv3.New(cfg) 80 | if err != nil { 81 | return nil, err 82 | } 83 | if len(p.Namespace) > 0 { 84 | db.KV = namespace.NewKV(db.KV, p.Namespace) 85 | } 86 | c := &conn{ 87 | db: db, 88 | logger: logger, 89 | } 90 | return c, nil 91 | } 92 | -------------------------------------------------------------------------------- /storage/etcd/etcd_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log/slog" 7 | "os" 8 | "runtime" 9 | "strings" 10 | "testing" 11 | "time" 12 | 13 | clientv3 "go.etcd.io/etcd/client/v3" 14 | 15 | "github.com/dexidp/dex/storage" 16 | "github.com/dexidp/dex/storage/conformance" 17 | ) 18 | 19 | func withTimeout(t time.Duration, f func()) { 20 | c := make(chan struct{}) 21 | defer close(c) 22 | 23 | go func() { 24 | select { 25 | case <-c: 26 | case <-time.After(t): 27 | // Dump a stack trace of the program. Useful for debugging deadlocks. 28 | buf := make([]byte, 2<<20) 29 | fmt.Fprintf(os.Stderr, "%s\n", buf[:runtime.Stack(buf, true)]) 30 | panic("test took too long") 31 | } 32 | }() 33 | 34 | f() 35 | } 36 | 37 | func cleanDB(c *conn) error { 38 | ctx := context.TODO() 39 | for _, prefix := range []string{ 40 | clientPrefix, 41 | authCodePrefix, 42 | refreshTokenPrefix, 43 | authRequestPrefix, 44 | passwordPrefix, 45 | offlineSessionPrefix, 46 | connectorPrefix, 47 | deviceRequestPrefix, 48 | deviceTokenPrefix, 49 | } { 50 | _, err := c.db.Delete(ctx, prefix, clientv3.WithPrefix()) 51 | if err != nil { 52 | return err 53 | } 54 | } 55 | return nil 56 | } 57 | 58 | var logger = slog.New(slog.DiscardHandler) 59 | 60 | func TestEtcd(t *testing.T) { 61 | testEtcdEnv := "DEX_ETCD_ENDPOINTS" 62 | endpointsStr := os.Getenv(testEtcdEnv) 63 | if endpointsStr == "" { 64 | t.Skipf("test environment variable %q not set, skipping", testEtcdEnv) 65 | return 66 | } 67 | endpoints := strings.Split(endpointsStr, ",") 68 | 69 | newStorage := func() storage.Storage { 70 | s := &Etcd{ 71 | Endpoints: endpoints, 72 | } 73 | conn, err := s.open(logger) 74 | if err != nil { 75 | fmt.Fprintln(os.Stdout, err) 76 | t.Fatal(err) 77 | } 78 | 79 | if err := cleanDB(conn); err != nil { 80 | fmt.Fprintln(os.Stdout, err) 81 | t.Fatal(err) 82 | } 83 | return conn 84 | } 85 | 86 | withTimeout(time.Second*10, func() { 87 | conformance.RunTests(t, newStorage) 88 | }) 89 | 90 | withTimeout(time.Minute*1, func() { 91 | conformance.RunTransactionTests(t, newStorage) 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /storage/health.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "crypto" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | // NewCustomHealthCheckFunc returns a new health check function. 11 | func NewCustomHealthCheckFunc(s Storage, now func() time.Time) func(context.Context) (details interface{}, err error) { 12 | return func(ctx context.Context) (details interface{}, err error) { 13 | a := AuthRequest{ 14 | ID: NewID(), 15 | ClientID: NewID(), 16 | 17 | // Set a short expiry so if the delete fails this will be cleaned up quickly by garbage collection. 18 | Expiry: now().Add(time.Minute), 19 | HMACKey: NewHMACKey(crypto.SHA256), 20 | } 21 | 22 | if err := s.CreateAuthRequest(ctx, a); err != nil { 23 | return nil, fmt.Errorf("create auth request: %v", err) 24 | } 25 | 26 | if err := s.DeleteAuthRequest(ctx, a.ID); err != nil { 27 | return nil, fmt.Errorf("delete auth request: %v", err) 28 | } 29 | 30 | return nil, nil 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /storage/kubernetes/doc.go: -------------------------------------------------------------------------------- 1 | // Package kubernetes provides a storage implementation using Kubernetes third party APIs. 2 | package kubernetes 3 | -------------------------------------------------------------------------------- /storage/kubernetes/k8sapi/doc.go: -------------------------------------------------------------------------------- 1 | // Package k8sapi holds vendored Kubernetes types. 2 | package k8sapi 3 | -------------------------------------------------------------------------------- /storage/kubernetes/k8sapi/extensions.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8sapi 18 | 19 | // An APIVersion represents a single concrete version of an object model. 20 | type APIVersion struct { 21 | // Name of this version (e.g. 'v1'). 22 | Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` 23 | } 24 | -------------------------------------------------------------------------------- /storage/kubernetes/k8sapi/unversioned.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 The Kubernetes Authors All rights reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8sapi 18 | 19 | // TypeMeta describes an individual object in an API response or request 20 | // with strings representing the type of the object and its API schema version. 21 | // Structures that are versioned or persisted should inline TypeMeta. 22 | type TypeMeta struct { 23 | // Kind is a string value representing the REST resource this object represents. 24 | // Servers may infer this from the endpoint the client submits requests to. 25 | // Cannot be updated. 26 | // In CamelCase. 27 | // More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#types-kinds 28 | Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"` 29 | 30 | // APIVersion defines the versioned schema of this representation of an object. 31 | // Servers should convert recognized schemas to the latest internal value, and 32 | // may reject unrecognized values. 33 | // More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#resources 34 | APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"` 35 | } 36 | 37 | // ListMeta describes metadata that synthetic resources must have, including lists and 38 | // various status objects. A resource may have only one of {ObjectMeta, ListMeta}. 39 | type ListMeta struct { 40 | // SelfLink is a URL representing this object. 41 | // Populated by the system. 42 | // Read-only. 43 | SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,1,opt,name=selfLink"` 44 | 45 | // String that identifies the server's internal version of this object that 46 | // can be used by clients to determine when objects have changed. 47 | // Value must be treated as opaque by clients and passed unmodified back to the server. 48 | // Populated by the system. 49 | // Read-only. 50 | // More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#concurrency-control-and-consistency 51 | ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,2,opt,name=resourceVersion"` 52 | } 53 | -------------------------------------------------------------------------------- /storage/memory/memory_test.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "log/slog" 5 | "testing" 6 | 7 | "github.com/dexidp/dex/storage" 8 | "github.com/dexidp/dex/storage/conformance" 9 | ) 10 | 11 | func TestStorage(t *testing.T) { 12 | logger := slog.New(slog.DiscardHandler) 13 | 14 | newStorage := func() storage.Storage { 15 | return New(logger) 16 | } 17 | conformance.RunTests(t, newStorage) 18 | } 19 | -------------------------------------------------------------------------------- /storage/sql/crud_test.go: -------------------------------------------------------------------------------- 1 | //go:build cgo 2 | // +build cgo 3 | 4 | package sql 5 | 6 | import ( 7 | "database/sql" 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func TestDecoder(t *testing.T) { 13 | db, err := sql.Open("sqlite3", ":memory:") 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | defer db.Close() 18 | 19 | if _, err := db.Exec(`create table foo ( id integer primary key, bar blob );`); err != nil { 20 | t.Fatal(err) 21 | } 22 | if _, err := db.Exec(`insert into foo ( id, bar ) values (1, ?);`, []byte(`["a", "b"]`)); err != nil { 23 | t.Fatal(err) 24 | } 25 | var got []string 26 | if err := db.QueryRow(`select bar from foo where id = 1;`).Scan(decoder(&got)); err != nil { 27 | t.Fatal(err) 28 | } 29 | want := []string{"a", "b"} 30 | if !reflect.DeepEqual(got, want) { 31 | t.Errorf("wanted %q got %q", want, got) 32 | } 33 | } 34 | 35 | func TestEncoder(t *testing.T) { 36 | db, err := sql.Open("sqlite3", ":memory:") 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | defer db.Close() 41 | 42 | if _, err := db.Exec(`create table foo ( id integer primary key, bar blob );`); err != nil { 43 | t.Fatal(err) 44 | } 45 | put := []string{"a", "b"} 46 | if _, err := db.Exec(`insert into foo ( id, bar ) values (1, ?)`, encoder(put)); err != nil { 47 | t.Fatal(err) 48 | } 49 | 50 | var got []byte 51 | if err := db.QueryRow(`select bar from foo where id = 1;`).Scan(&got); err != nil { 52 | t.Fatal(err) 53 | } 54 | want := []byte(`["a","b"]`) 55 | if !reflect.DeepEqual(got, want) { 56 | t.Errorf("wanted %q got %q", want, got) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /storage/sql/migrate_test.go: -------------------------------------------------------------------------------- 1 | //go:build cgo 2 | // +build cgo 3 | 4 | package sql 5 | 6 | import ( 7 | "database/sql" 8 | "log/slog" 9 | "testing" 10 | 11 | sqlite3 "github.com/mattn/go-sqlite3" 12 | ) 13 | 14 | func TestMigrate(t *testing.T) { 15 | db, err := sql.Open("sqlite3", ":memory:") 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | defer db.Close() 20 | 21 | logger := slog.New(slog.DiscardHandler) 22 | 23 | errCheck := func(err error) bool { 24 | sqlErr, ok := err.(sqlite3.Error) 25 | if !ok { 26 | return false 27 | } 28 | return sqlErr.ExtendedCode == sqlite3.ErrConstraintUnique 29 | } 30 | 31 | var sqliteMigrations []migration 32 | for _, m := range migrations { 33 | if m.flavor == nil || m.flavor == &flavorSQLite3 { 34 | sqliteMigrations = append(sqliteMigrations, m) 35 | } 36 | } 37 | 38 | c := &conn{db, &flavorSQLite3, logger, errCheck} 39 | for _, want := range []int{len(sqliteMigrations), 0} { 40 | got, err := c.migrate() 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | if got != want { 45 | t.Errorf("expected %d migrations, got %d", want, got) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /storage/sql/postgres_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.11 2 | // +build go1.11 3 | 4 | package sql 5 | 6 | import ( 7 | "os" 8 | "strconv" 9 | "testing" 10 | ) 11 | 12 | func TestPostgresTunables(t *testing.T) { 13 | host := os.Getenv(testPostgresEnv) 14 | if host == "" { 15 | t.Skipf("test environment variable %q not set, skipping", testPostgresEnv) 16 | } 17 | 18 | port := uint64(5432) 19 | if rawPort := os.Getenv("DEX_POSTGRES_PORT"); rawPort != "" { 20 | var err error 21 | 22 | port, err = strconv.ParseUint(rawPort, 10, 32) 23 | if err != nil { 24 | t.Fatalf("invalid postgres port %q: %s", rawPort, err) 25 | } 26 | } 27 | 28 | baseCfg := &Postgres{ 29 | NetworkDB: NetworkDB{ 30 | Database: getenv("DEX_POSTGRES_DATABASE", "postgres"), 31 | User: getenv("DEX_POSTGRES_USER", "postgres"), 32 | Password: getenv("DEX_POSTGRES_PASSWORD", "postgres"), 33 | Host: host, 34 | Port: uint16(port), 35 | }, 36 | SSL: SSL{ 37 | Mode: pgSSLDisable, // Postgres container doesn't support SSL. 38 | }, 39 | } 40 | 41 | t.Run("with nothing set, uses defaults", func(t *testing.T) { 42 | cfg := *baseCfg 43 | c, err := cfg.open(logger) 44 | if err != nil { 45 | t.Fatalf("error opening connector: %s", err.Error()) 46 | } 47 | defer c.db.Close() 48 | if m := c.db.Stats().MaxOpenConnections; m != 5 { 49 | t.Errorf("expected MaxOpenConnections to have its default (5), got %d", m) 50 | } 51 | }) 52 | 53 | t.Run("with something set, uses that", func(t *testing.T) { 54 | cfg := *baseCfg 55 | cfg.MaxOpenConns = 101 56 | c, err := cfg.open(logger) 57 | if err != nil { 58 | t.Fatalf("error opening connector: %s", err.Error()) 59 | } 60 | defer c.db.Close() 61 | if m := c.db.Stats().MaxOpenConnections; m != 101 { 62 | t.Errorf("expected MaxOpenConnections to be set to 101, got %d", m) 63 | } 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /storage/sql/sql_test.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import "testing" 4 | 5 | func TestTranslate(t *testing.T) { 6 | tests := []struct { 7 | testCase string 8 | flavor flavor 9 | query string 10 | exp string 11 | }{ 12 | { 13 | "sqlite3 query bind replacement", 14 | flavorSQLite3, 15 | `select foo from bar where foo.zam = $1;`, 16 | `select foo from bar where foo.zam = ?;`, 17 | }, 18 | { 19 | "sqlite3 query bind replacement at newline", 20 | flavorSQLite3, 21 | `select foo from bar where foo.zam = $1`, 22 | `select foo from bar where foo.zam = ?`, 23 | }, 24 | { 25 | "sqlite3 query true", 26 | flavorSQLite3, 27 | `select foo from bar where foo.zam = true`, 28 | `select foo from bar where foo.zam = 1`, 29 | }, 30 | { 31 | "sqlite3 query false", 32 | flavorSQLite3, 33 | `select foo from bar where foo.zam = false`, 34 | `select foo from bar where foo.zam = 0`, 35 | }, 36 | { 37 | "sqlite3 bytea", 38 | flavorSQLite3, 39 | `"connector_data" bytea not null,`, 40 | `"connector_data" blob not null,`, 41 | }, 42 | { 43 | "sqlite3 now", 44 | flavorSQLite3, 45 | `now(),`, 46 | `date('now'),`, 47 | }, 48 | } 49 | 50 | for _, tc := range tests { 51 | if got := tc.flavor.translate(tc.query); got != tc.exp { 52 | t.Errorf("%s: want=%q, got=%q", tc.testCase, tc.exp, got) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /storage/sql/sqlite.go: -------------------------------------------------------------------------------- 1 | //go:build cgo 2 | // +build cgo 3 | 4 | package sql 5 | 6 | import ( 7 | "database/sql" 8 | "fmt" 9 | "log/slog" 10 | 11 | sqlite3 "github.com/mattn/go-sqlite3" 12 | 13 | "github.com/dexidp/dex/storage" 14 | ) 15 | 16 | // SQLite3 options for creating an SQL db. 17 | type SQLite3 struct { 18 | // File to 19 | File string `json:"file"` 20 | } 21 | 22 | // Open creates a new storage implementation backed by SQLite3 23 | func (s *SQLite3) Open(logger *slog.Logger) (storage.Storage, error) { 24 | conn, err := s.open(logger) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return conn, nil 29 | } 30 | 31 | func (s *SQLite3) open(logger *slog.Logger) (*conn, error) { 32 | db, err := sql.Open("sqlite3", s.File) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | // always allow only one connection to sqlite3, any other thread/go-routine 38 | // attempting concurrent access will have to wait 39 | db.SetMaxOpenConns(1) 40 | errCheck := func(err error) bool { 41 | sqlErr, ok := err.(sqlite3.Error) 42 | if !ok { 43 | return false 44 | } 45 | return sqlErr.ExtendedCode == sqlite3.ErrConstraintPrimaryKey 46 | } 47 | 48 | c := &conn{db, &flavorSQLite3, logger, errCheck} 49 | if _, err := c.migrate(); err != nil { 50 | return nil, fmt.Errorf("failed to perform migrations: %v", err) 51 | } 52 | return c, nil 53 | } 54 | -------------------------------------------------------------------------------- /storage/sql/sqlite_test.go: -------------------------------------------------------------------------------- 1 | //go:build cgo 2 | // +build cgo 3 | 4 | package sql 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestSQLite3(t *testing.T) { 11 | testDB(t, &SQLite3{":memory:"}, false) 12 | } 13 | -------------------------------------------------------------------------------- /web/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /web/static/img/atlassian-crowd-icon.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" standalone="no"?> 2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" 3 | "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> 4 | <svg version="1.0" xmlns="http://www.w3.org/2000/svg" 5 | width="180.000000pt" height="180.000000pt" viewBox="0 0 180.000000 180.000000" 6 | preserveAspectRatio="xMidYMid meet"> 7 | 8 | <g transform="translate(0.000000,180.000000) scale(0.100000,-0.100000)" 9 | fill="#4169E1" stroke="none"> 10 | <path d="M580 1422 l-315 -117 3 -214 c4 -298 25 -400 113 -548 73 -122 257 11 | -285 302 -267 11 4 157 326 157 347 0 3 -16 11 -35 17 -54 18 -122 92 -140 12 | 152 -19 65 -19 93 0 149 56 165 256 222 386 110 77 -65 107 -169 74 -260 -22 13 | -64 -52 -99 -111 -132 -30 -17 -54 -37 -54 -45 0 -26 133 -336 147 -341 25 14 | -10 58 6 128 62 122 97 210 219 255 355 32 96 39 150 46 391 l6 219 -33 14 15 | c-48 20 -606 226 -610 226 -2 -1 -146 -54 -319 -118z"/> 16 | </g> 17 | </svg> 18 | -------------------------------------------------------------------------------- /web/static/img/bitbucket-icon.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> 3 | <svg xmlns="http://www.w3.org/2000/svg" height="1000" width="785.714"> 4 | <path d="M454.77 479.386q4.464 35.154 -28.179 56.358t-62.217 3.348q-21.762 -9.486 -29.853 -32.364t-.279 -45.756 29.016 -32.364q20.088 -10.044 40.455 -6.696t35.712 19.809 15.345 37.665zm61.938 -11.718q-7.812 -59.706 -63.054 -91.512t-109.926 -7.254q-35.154 15.624 -56.079 49.383t-19.251 72.261q2.232 50.778 43.245 86.49t92.349 31.248q50.778 -4.464 84.816 -46.872t27.9 -93.744zm133.362 -302.436q-11.16 -15.066 -31.248 -24.831t-32.364 -12.276 -39.618 -6.975q-162.378 -26.226 -315.828 1.116 -23.994 3.906 -36.828 6.696t-30.69 12.276 -27.9 23.994q16.74 15.624 42.408 25.389t41.013 12.276 48.825 6.417q127.224 16.182 249.984 .558 35.154 -4.464 49.941 -6.696t40.455 -11.997 41.85 -25.947zm31.806 577.53q-4.464 14.508 -8.649 42.687t-7.812 46.872 -15.903 39.06 -32.364 31.527q-47.988 26.784 -105.741 39.897t-112.716 12.276 -112.437 -10.323q-25.668 -4.464 -45.477 -10.044t-42.687 -15.066 -40.734 -24.273 -29.016 -34.317q-13.95 -53.568 -31.806 -162.936l3.348 -8.928 10.044 -5.022q124.434 82.584 282.627 82.584t283.185 -82.584q11.718 3.348 13.392 12.834t-2.79 25.11 -4.464 20.646zm100.998 -536.238q-14.508 93.186 -61.938 365.49 -2.79 16.74 -15.066 31.248t-24.273 22.32 -30.411 17.298q-140.616 70.308 -340.38 49.104 -138.384 -15.066 -219.852 -77.562 -8.37 -6.696 -14.229 -14.787t-9.486 -19.53 -5.022 -18.972 -3.348 -22.041 -3.069 -19.53q-5.022 -27.9 -14.787 -83.7t-15.624 -90.117 -13.113 -82.305 -12.276 -88.164q1.674 -14.508 9.765 -27.063t17.577 -20.925 25.11 -16.74 25.668 -12.555 26.784 -10.323q69.75 -25.668 174.654 -35.712 211.482 -20.646 377.208 27.9 86.49 25.668 119.97 68.076 8.928 11.16 9.207 28.458t-3.069 30.132z" fill="#FFFFFF"/> 5 | </svg> 6 | -------------------------------------------------------------------------------- /web/static/img/email-icon.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <svg width="27px" height="21px" viewBox="0 0 27 21" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> 3 | <!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch --> 4 | <title>Shape 5 | Created with Sketch. 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web/static/img/gitea-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/static/img/github-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/static/img/google-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logo_googleg_48dp 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /web/static/img/keystone-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OpenStack_Logo_Mark 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web/static/img/ldap-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Combined-Shape 5 | Created with Sketch. 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web/static/img/linkedin-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/static/img/microsoft-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /web/static/img/saml-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Combined-Shape 5 | Created with Sketch. 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web/templates/approval.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" . }} 2 | 3 |
4 |

Grant Access

5 | 6 |
7 |
8 | {{ if .Scopes }} 9 |
{{ .Client }} would like to:
10 |
    11 | {{ range $scope := .Scopes }} 12 |
  • {{ $scope }}
  • 13 | {{ end }} 14 |
15 | {{ else }} 16 |
{{ .Client }} has not requested any personal information
17 | {{ end }} 18 |
19 |
20 | 21 |
22 |
23 |
24 | 25 | 26 | 29 |
30 |
31 |
32 |
33 | 34 | 35 | 38 |
39 |
40 |
41 | 42 |
43 | 44 | {{ template "footer.html" . }} 45 | -------------------------------------------------------------------------------- /web/templates/device.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" . }} 2 | 3 |
4 |

Enter User Code

5 |
6 |
7 | {{ if( .UserCode )}} 8 | 9 | {{ else }} 10 | 11 | {{ end }} 12 |
13 | 14 | {{ if .Invalid }} 15 |
16 | Invalid or Expired User Code 17 |
18 | {{ end }} 19 | 20 |
21 |
22 | 23 | {{ template "footer.html" . }} 24 | -------------------------------------------------------------------------------- /web/templates/device_success.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" . }} 2 | 3 |
4 |

Login Successful for {{ .ClientName }}

5 |

Return to your device to continue

6 |
7 | 8 | {{ template "footer.html" . }} 9 | -------------------------------------------------------------------------------- /web/templates/error.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" . }} 2 | 3 |
4 |

{{ .ErrType }}

5 |

{{ .ErrMsg }}

6 |
7 | 8 | {{ template "footer.html" . }} 9 | -------------------------------------------------------------------------------- /web/templates/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /web/templates/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ issuer }} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | 17 |
18 |
19 | 20 |
21 | -------------------------------------------------------------------------------- /web/templates/login.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" . }} 2 | 3 |
4 |

Log in to {{ issuer }}

5 |
6 | {{ range $c := .Connectors }} 7 | 15 | {{ end }} 16 |
17 |
18 | 19 | {{ template "footer.html" . }} 20 | -------------------------------------------------------------------------------- /web/templates/oob.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" . }} 2 | 3 |
4 |

Login Successful

5 |

Please copy this code, switch to your application and paste it there:

6 | 7 |
8 | 9 | {{ template "footer.html" . }} 10 | -------------------------------------------------------------------------------- /web/templates/password.html: -------------------------------------------------------------------------------- 1 | {{ template "header.html" . }} 2 | 3 |
4 |

Log in to Your Account

5 |
6 |
7 |
8 | 9 |
10 | 11 |
12 |
13 |
14 | 15 |
16 | 17 |
18 | 19 | {{ if .Invalid }} 20 |
21 | Invalid {{ .UsernamePrompt }} and password. 22 |
23 | {{ end }} 24 | 25 | 26 | 27 |
28 | {{ if .BackLink }} 29 | 32 | {{ end }} 33 |
34 | 35 | 36 | 42 | 43 | {{ template "footer.html" . }} 44 | -------------------------------------------------------------------------------- /web/themes/dark/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexidp/dex/7c9744953fdd0c1a86b5eedd71d5684a639dc69f/web/themes/dark/favicon.png -------------------------------------------------------------------------------- /web/themes/dark/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexidp/dex/7c9744953fdd0c1a86b5eedd71d5684a639dc69f/web/themes/dark/logo.png -------------------------------------------------------------------------------- /web/themes/dark/styles.css: -------------------------------------------------------------------------------- 1 | .theme-body { 2 | background-color: #0f1218; 3 | color: #c8d1d9; 4 | font-family: 'Source Sans Pro', Helvetica, sans-serif; 5 | } 6 | 7 | .theme-navbar { 8 | background-color: #161b22; 9 | box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); 10 | color: #161B2B; 11 | font-size: 13px; 12 | font-weight: 100; 13 | height: 46px; 14 | overflow: hidden; 15 | padding: 0 10px; 16 | } 17 | 18 | .theme-navbar__logo-wrap { 19 | display: inline-block; 20 | height: 100%; 21 | overflow: hidden; 22 | padding: 10px 15px; 23 | width: 300px; 24 | } 25 | 26 | .theme-navbar__logo { 27 | height: 100%; 28 | max-height: 25px; 29 | } 30 | 31 | .theme-heading { 32 | font-size: 20px; 33 | font-weight: 500; 34 | margin-top: 0; 35 | color: #c8d1d9; 36 | } 37 | 38 | .theme-panel { 39 | background-color: #161b22; 40 | box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); 41 | padding: 30px; 42 | } 43 | 44 | .theme-btn-provider { 45 | background-color: #1e242d; 46 | color: #c8d1d9; 47 | border: 1px solid #30373c; 48 | min-width: 250px; 49 | } 50 | 51 | .theme-btn-provider:hover { 52 | background-color: #212731; 53 | color: #ffffff; 54 | } 55 | 56 | .theme-btn--primary { 57 | background-color: #1e242d; 58 | border: none; 59 | color: #c8d1d9; 60 | min-width: 200px; 61 | padding: 6px 12px; 62 | } 63 | 64 | .theme-btn--primary:hover { 65 | background-color: #212731; 66 | color: #e9e9e9; 67 | } 68 | 69 | .theme-btn--success { 70 | background-color: #1891bb; 71 | color: #e9e9e9; 72 | width: 250px; 73 | } 74 | 75 | .theme-btn--success:hover { 76 | background-color: #1da5d4; 77 | } 78 | 79 | .theme-form-row { 80 | display: block; 81 | margin: 20px auto; 82 | } 83 | 84 | .theme-form-input { 85 | display: block; 86 | height: 36px; 87 | padding: 6px 12px; 88 | font-size: 14px; 89 | line-height: 1.42857143; 90 | border: 1px solid #515559; 91 | border-radius: 4px; 92 | color: #c8d1d9; 93 | background-color: #0f1218; 94 | box-shadow: inset 0 1px 1px rgb(27, 40, 46); 95 | width: 250px; 96 | margin: auto; 97 | } 98 | 99 | .theme-form-input:focus, 100 | .theme-form-input:active { 101 | outline: none; 102 | border-color: #f8f9f9; 103 | color: #c8d1d9; 104 | } 105 | 106 | .theme-form-label { 107 | width: 250px; 108 | margin: 4px auto; 109 | text-align: left; 110 | position: relative; 111 | font-size: 13px; 112 | font-weight: 600; 113 | color: #c8d1d9; 114 | } 115 | 116 | .theme-link-back { 117 | margin-top: 4px; 118 | } 119 | 120 | .dex-container { 121 | color: #c8d1d9; 122 | } 123 | -------------------------------------------------------------------------------- /web/themes/light/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexidp/dex/7c9744953fdd0c1a86b5eedd71d5684a639dc69f/web/themes/light/favicon.png -------------------------------------------------------------------------------- /web/themes/light/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dexidp/dex/7c9744953fdd0c1a86b5eedd71d5684a639dc69f/web/themes/light/logo.png -------------------------------------------------------------------------------- /web/themes/light/styles.css: -------------------------------------------------------------------------------- 1 | .theme-body { 2 | background-color: #efefef; 3 | color: #333; 4 | font-family: 'Source Sans Pro', Helvetica, sans-serif; 5 | } 6 | 7 | .theme-navbar { 8 | background-color: #fff; 9 | box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); 10 | color: #333; 11 | font-size: 13px; 12 | font-weight: 100; 13 | height: 46px; 14 | overflow: hidden; 15 | padding: 0 10px; 16 | } 17 | 18 | .theme-navbar__logo-wrap { 19 | display: inline-block; 20 | height: 100%; 21 | overflow: hidden; 22 | padding: 10px 15px; 23 | width: 300px; 24 | } 25 | 26 | .theme-navbar__logo { 27 | height: 100%; 28 | max-height: 25px; 29 | } 30 | 31 | .theme-heading { 32 | font-size: 20px; 33 | font-weight: 500; 34 | margin-bottom: 10px; 35 | margin-top: 0; 36 | } 37 | 38 | .theme-panel { 39 | background-color: #fff; 40 | box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); 41 | padding: 30px; 42 | } 43 | 44 | .theme-btn-provider { 45 | background-color: #fff; 46 | color: #333; 47 | min-width: 250px; 48 | } 49 | 50 | .theme-btn-provider:hover { 51 | color: #999; 52 | } 53 | 54 | .theme-btn--primary { 55 | background-color: #333; 56 | border: none; 57 | color: #fff; 58 | min-width: 200px; 59 | padding: 6px 12px; 60 | } 61 | 62 | .theme-btn--primary:hover { 63 | background-color: #666; 64 | color: #fff; 65 | } 66 | 67 | .theme-btn--success { 68 | background-color: #2FC98E; 69 | color: #fff; 70 | width: 250px; 71 | } 72 | 73 | .theme-btn--success:hover { 74 | background-color: #49E3A8; 75 | } 76 | 77 | .theme-form-row { 78 | display: block; 79 | margin: 20px auto; 80 | } 81 | 82 | .theme-form-input { 83 | border-radius: 4px; 84 | border: 1px solid #CCC; 85 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 86 | color: #666; 87 | display: block; 88 | font-size: 14px; 89 | height: 36px; 90 | line-height: 1.42857143; 91 | margin: auto; 92 | padding: 6px 12px; 93 | width: 250px; 94 | } 95 | 96 | .theme-form-input:focus, 97 | .theme-form-input:active { 98 | border-color: #66AFE9; 99 | outline: none; 100 | } 101 | 102 | .theme-form-label { 103 | font-size: 13px; 104 | font-weight: 600; 105 | margin: 4px auto; 106 | position: relative; 107 | text-align: left; 108 | width: 250px; 109 | } 110 | 111 | .theme-link-back { 112 | margin-top: 4px; 113 | } 114 | -------------------------------------------------------------------------------- /web/web.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "embed" 5 | "io/fs" 6 | ) 7 | 8 | //go:embed static/* templates/* themes/* robots.txt 9 | var files embed.FS 10 | 11 | // FS returns a filesystem with the default web assets. 12 | func FS() fs.FS { 13 | return files 14 | } 15 | --------------------------------------------------------------------------------