Repository: dexidp/dex Branch: master Commit: 3c7e159750b9 Files: 432 Total size: 3.6 MB Directory structure: gitextract_0njidk3i/ ├── .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.yaml ├── 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 │ │ ├── excluding_handler.go │ │ ├── excluding_handler_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 │ │ └── testdata/ │ │ ├── rootCA.pem │ │ ├── server.crt │ │ └── server.key │ ├── 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 │ ├── auth-sessions-2026-02-18.md │ ├── cel-expressions-2026-02-28.md │ ├── id-jag-2026-03-02#4600.md │ └── token-exchange-2023-02-03-#2812.md ├── examples/ │ ├── .gitignore │ ├── config-ad-kubelogin.yaml │ ├── config-dev.yaml │ ├── example-app/ │ │ ├── handlers.go │ │ ├── handlers_device.go │ │ ├── handlers_userinfo.go │ │ ├── main.go │ │ ├── static/ │ │ │ ├── app.js │ │ │ ├── device.js │ │ │ ├── style.css │ │ │ └── token.js │ │ ├── templates/ │ │ │ ├── device.html │ │ │ ├── index.html │ │ │ └── token.html │ │ ├── templates.go │ │ └── utils.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 │ └── oidc-conformance/ │ ├── config.yaml.tmpl │ └── run.sh ├── flake.nix ├── go.mod ├── go.sum ├── pkg/ │ ├── cel/ │ │ ├── cel.go │ │ ├── cel_test.go │ │ ├── cost.go │ │ ├── cost_test.go │ │ ├── doc.go │ │ ├── library/ │ │ │ ├── doc.go │ │ │ ├── email.go │ │ │ ├── email_test.go │ │ │ ├── groups.go │ │ │ └── groups_test.go │ │ └── types.go │ ├── featureflags/ │ │ ├── doc.go │ │ ├── flag.go │ │ └── set.go │ ├── groups/ │ │ ├── doc.go │ │ ├── groups.go │ │ └── groups_test.go │ └── httpclient/ │ ├── doc.go │ ├── 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 │ └── update-gomplate ├── server/ │ ├── api.go │ ├── api_cache_test.go │ ├── api_test.go │ ├── deviceflowhandlers.go │ ├── deviceflowhandlers_test.go │ ├── doc.go │ ├── errors.go │ ├── errors_test.go │ ├── handlers.go │ ├── handlers_approval_test.go │ ├── handlers_test.go │ ├── internal/ │ │ ├── codec.go │ │ ├── types.pb.go │ │ └── types.proto │ ├── introspectionhandler.go │ ├── introspectionhandler_test.go │ ├── mfa.go │ ├── oauth2.go │ ├── oauth2_test.go │ ├── prompt.go │ ├── prompt_test.go │ ├── refreshhandlers.go │ ├── refreshhandlers_test.go │ ├── server.go │ ├── server_test.go │ ├── session.go │ ├── session_test.go │ ├── signer/ │ │ ├── local.go │ │ ├── local_test.go │ │ ├── mock.go │ │ ├── rotation.go │ │ ├── rotation_test.go │ │ ├── signer.go │ │ ├── utils.go │ │ ├── vault.go │ │ ├── vault_integration_test.go │ │ └── vault_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 │ │ │ ├── authsession.go │ │ │ ├── client.go │ │ │ ├── connector.go │ │ │ ├── devicerequest.go │ │ │ ├── devicetoken.go │ │ │ ├── keys.go │ │ │ ├── main.go │ │ │ ├── offlinesession.go │ │ │ ├── password.go │ │ │ ├── refreshtoken.go │ │ │ ├── types.go │ │ │ ├── useridentity.go │ │ │ └── utils.go │ │ ├── db/ │ │ │ ├── authcode/ │ │ │ │ ├── authcode.go │ │ │ │ └── where.go │ │ │ ├── authcode.go │ │ │ ├── authcode_create.go │ │ │ ├── authcode_delete.go │ │ │ ├── authcode_query.go │ │ │ ├── authcode_update.go │ │ │ ├── authrequest/ │ │ │ │ ├── authrequest.go │ │ │ │ └── where.go │ │ │ ├── authrequest.go │ │ │ ├── authrequest_create.go │ │ │ ├── authrequest_delete.go │ │ │ ├── authrequest_query.go │ │ │ ├── authrequest_update.go │ │ │ ├── authsession/ │ │ │ │ ├── authsession.go │ │ │ │ └── where.go │ │ │ ├── authsession.go │ │ │ ├── authsession_create.go │ │ │ ├── authsession_delete.go │ │ │ ├── authsession_query.go │ │ │ ├── authsession_update.go │ │ │ ├── client.go │ │ │ ├── connector/ │ │ │ │ ├── connector.go │ │ │ │ └── where.go │ │ │ ├── connector.go │ │ │ ├── connector_create.go │ │ │ ├── connector_delete.go │ │ │ ├── connector_query.go │ │ │ ├── connector_update.go │ │ │ ├── devicerequest/ │ │ │ │ ├── devicerequest.go │ │ │ │ └── where.go │ │ │ ├── devicerequest.go │ │ │ ├── devicerequest_create.go │ │ │ ├── devicerequest_delete.go │ │ │ ├── devicerequest_query.go │ │ │ ├── devicerequest_update.go │ │ │ ├── devicetoken/ │ │ │ │ ├── devicetoken.go │ │ │ │ └── where.go │ │ │ ├── devicetoken.go │ │ │ ├── devicetoken_create.go │ │ │ ├── devicetoken_delete.go │ │ │ ├── devicetoken_query.go │ │ │ ├── devicetoken_update.go │ │ │ ├── ent.go │ │ │ ├── enttest/ │ │ │ │ └── enttest.go │ │ │ ├── hook/ │ │ │ │ └── hook.go │ │ │ ├── keys/ │ │ │ │ ├── keys.go │ │ │ │ └── where.go │ │ │ ├── keys.go │ │ │ ├── keys_create.go │ │ │ ├── keys_delete.go │ │ │ ├── keys_query.go │ │ │ ├── keys_update.go │ │ │ ├── migrate/ │ │ │ │ ├── migrate.go │ │ │ │ └── schema.go │ │ │ ├── mutation.go │ │ │ ├── oauth2client/ │ │ │ │ ├── oauth2client.go │ │ │ │ └── where.go │ │ │ ├── oauth2client.go │ │ │ ├── oauth2client_create.go │ │ │ ├── oauth2client_delete.go │ │ │ ├── oauth2client_query.go │ │ │ ├── oauth2client_update.go │ │ │ ├── offlinesession/ │ │ │ │ ├── offlinesession.go │ │ │ │ └── where.go │ │ │ ├── offlinesession.go │ │ │ ├── offlinesession_create.go │ │ │ ├── offlinesession_delete.go │ │ │ ├── offlinesession_query.go │ │ │ ├── offlinesession_update.go │ │ │ ├── password/ │ │ │ │ ├── password.go │ │ │ │ └── where.go │ │ │ ├── password.go │ │ │ ├── password_create.go │ │ │ ├── password_delete.go │ │ │ ├── password_query.go │ │ │ ├── password_update.go │ │ │ ├── predicate/ │ │ │ │ └── predicate.go │ │ │ ├── refreshtoken/ │ │ │ │ ├── refreshtoken.go │ │ │ │ └── where.go │ │ │ ├── refreshtoken.go │ │ │ ├── refreshtoken_create.go │ │ │ ├── refreshtoken_delete.go │ │ │ ├── refreshtoken_query.go │ │ │ ├── refreshtoken_update.go │ │ │ ├── runtime/ │ │ │ │ └── runtime.go │ │ │ ├── runtime.go │ │ │ ├── tx.go │ │ │ ├── useridentity/ │ │ │ │ ├── useridentity.go │ │ │ │ └── where.go │ │ │ ├── useridentity.go │ │ │ ├── useridentity_create.go │ │ │ ├── useridentity_delete.go │ │ │ ├── useridentity_query.go │ │ │ └── useridentity_update.go │ │ ├── generate.go │ │ ├── mysql.go │ │ ├── mysql_test.go │ │ ├── postgres.go │ │ ├── postgres_test.go │ │ ├── schema/ │ │ │ ├── authcode.go │ │ │ ├── authrequest.go │ │ │ ├── authsession.go │ │ │ ├── client.go │ │ │ ├── connector.go │ │ │ ├── devicerequest.go │ │ │ ├── devicetoken.go │ │ │ ├── dialects.go │ │ │ ├── keys.go │ │ │ ├── offlinesession.go │ │ │ ├── password.go │ │ │ ├── refreshtoken.go │ │ │ └── useridentity.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_no_cgo.go │ │ └── sqlite_test.go │ ├── static.go │ └── storage.go └── web/ ├── robots.txt ├── static/ │ └── main.css ├── templates/ │ ├── approval.html │ ├── device.html │ ├── device_success.html │ ├── error.html │ ├── footer.html │ ├── header.html │ ├── login.html │ ├── oob.html │ ├── password.html │ └── totp_verify.html ├── themes/ │ ├── dark/ │ │ └── styles.css │ └── light/ │ └── styles.css └── web.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ bin/ tmp/ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true [*.go] indent_style = tab [*.proto] indent_size = 2 [{Makefile,*.mk}] indent_style = tab [{config.yaml.dist,config.dev.yaml}] indent_size = 2 [.golangci.yaml] indent_size = 2 ================================================ FILE: .envrc ================================================ if ! has nix_direnv_version || ! nix_direnv_version 3.0.6; then source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.6/direnvrc" "sha256-RYcUJaRMf8oF5LznDrlCXbkOQrywm0HDv1VjYGaJGdM=" fi use flake . --impure dotenv_if_exists ================================================ FILE: .github/.editorconfig ================================================ [{*.yml,*.yaml}] indent_size = 2 ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ ## Community Code of Conduct This project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). ================================================ FILE: .github/DCO ================================================ Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 York Street, Suite 102, San Francisco, CA 94110 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yaml ================================================ name: 🐛 Bug report description: Report a bug to help us improve Dex body: - type: markdown attributes: value: | Thank you for submitting a bug report! Please fill out the template below to make it easier to debug your problem. If you are not sure if it is a bug or not, you can contact us via the available [support channels](https://github.com/dexidp/dex/issues/new/choose). - type: checkboxes attributes: label: Preflight Checklist description: Please ensure you've completed all of the following. options: - 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. required: true - 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. required: true - label: I am not looking for support or already pursued the available [support channels](https://github.com/dexidp/dex/issues/new/choose) without success. required: true - type: input attributes: label: Version description: What version of Dex are you running? placeholder: 2.29.0 validations: required: true - type: dropdown attributes: label: Storage Type description: Which persistent storage type are you using? options: - etcd - Kubernetes - In-memory - Postgres - MySQL - SQLite validations: required: true - type: dropdown attributes: label: Installation Type description: How did you install Dex? options: - Binary - Official container image - Official Helm chart - Custom container image - Custom Helm chart - Other (specify below) multiple: true validations: required: true - type: textarea attributes: label: Expected Behavior description: A clear and concise description of what you expected to happen. validations: required: true - type: textarea attributes: label: Actual Behavior description: A clear description of what actually happens. validations: required: true - type: textarea attributes: label: Steps To Reproduce description: Steps to reproduce the behavior if it is not self-explanatory. placeholder: | 1. In this environment... 2. With this config... 3. Run '...' 4. See error... - type: textarea attributes: label: Additional Information description: Links? References? Anything that will give us more context about the issue that you are encountering! - type: textarea attributes: label: Configuration description: Contents of your configuration file (if relevant). render: yaml placeholder: | issuer: http://127.0.0.1:5556/dex storage: # ... connectors: # ... staticClients: # ... - type: textarea attributes: label: Logs description: Dex application logs (if relevant). render: shell ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: 📖 Documentation enhancement url: https://github.com/dexidp/website/issues about: Suggest an improvement to the documentation - name: ❓ Ask a question url: https://github.com/dexidp/dex/discussions/new?category=q-a about: Ask and discuss questions with other Dex community members - name: 📚 Documentation url: https://dexidp.io/docs/ about: Check the documentation for help - name: 💬 Slack channel url: https://cloud-native.slack.com/messages/dexidp about: Please ask and answer questions here - name: 💡 Dex Enhancement Proposal url: https://github.com/dexidp/dex/tree/master/docs/enhancements/README.md about: Open a proposal for significant architectural change ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yaml ================================================ name: 🎉 Feature request description: Suggest an idea for Dex body: - type: markdown attributes: value: | Thank you for submitting a feature request! Please describe what you would like to change/add and why in detail by filling out the template below. 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). - type: checkboxes attributes: label: Preflight Checklist description: Please ensure you've completed all of the following. options: - 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. required: true - 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. required: true - type: textarea attributes: label: Problem Description description: A clear and concise description of the problem you are seeking to solve with this feature request. validations: required: true - type: textarea attributes: label: Proposed Solution description: A clear and concise description of what would you like to happen. validations: required: true - type: textarea attributes: label: Alternatives Considered description: A clear and concise description of any alternative solutions or features you've considered. - type: textarea attributes: label: Additional Information description: Add any other context about the problem here. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ #### Overview #### What this PR does / why we need it #### Special notes for your reviewer ================================================ FILE: .github/SECURITY.md ================================================ # Security Policy ## Reporting a vulnerability To report a vulnerability, send an email to [cncf-dex-maintainers@lists.cncf.io](mailto:cncf-dex-maintainers@lists.cncf.io) detailing the issue and steps to reproduce. The reporter(s) can expect a response within 48 hours acknowledging the issue was received. If a response is not received within 48 hours, please reach out to any maintainer directly to confirm receipt of the issue. ## Review Process Once a maintainer has confirmed the relevance of the report, a draft security advisory will be created on GitHub. The draft advisory will be used to discuss the issue with maintainers, the reporter(s). If the reporter(s) wishes to participate in this discussion, then provide reporter GitHub username(s) to be invited to the discussion. If the reporter(s) does not wish to participate directly in the discussion, then the reporter(s) can request to be updated regularly via email. If the vulnerability is accepted, a timeline for developing a patch, public disclosure, and patch release will be determined. The reporter(s) are expected to participate in the discussion of the timeline and abide by agreed upon dates for public disclosure. ================================================ FILE: .github/dependabot.yaml ================================================ version: 2 updates: - package-ecosystem: "gomod" directory: "/" labels: - "area/dependencies" schedule: interval: "daily" groups: etcd: patterns: - "go.etcd.io/*" - package-ecosystem: "gomod" directory: "/api/v2" labels: - "area/dependencies" schedule: interval: "daily" - package-ecosystem: "gomod" directory: "/examples" labels: - "area/dependencies" schedule: interval: "daily" - package-ecosystem: "docker" directory: "/" labels: - "area/dependencies" schedule: interval: "daily" - package-ecosystem: "github-actions" directory: "/" labels: - "area/dependencies" schedule: interval: "daily" ================================================ FILE: .github/release.yml ================================================ changelog: exclude: labels: - release-note/ignore categories: - title: Exciting New Features 🎉 labels: - kind/feature - release-note/new-feature - title: Enhancements 🚀 labels: - kind/enhancement - release-note/enhancement - title: Bug Fixes 🐛 labels: - kind/bug - release-note/bug-fix - title: Breaking Changes 🛠 labels: - release-note/breaking-change - title: Deprecations ❌ labels: - release-note/deprecation - title: Dependency Updates ⬆️ labels: - area/dependencies - release-note/dependency-update - title: Other Changes labels: - "*" ================================================ FILE: .github/workflows/analysis-scorecard.yaml ================================================ name: OpenSSF Scorecard on: branch_protection_rule: push: branches: [ main ] schedule: - cron: '30 0 * * 5' permissions: contents: read jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read id-token: write security-events: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Run analysis uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif publish_results: true - name: Upload results as artifact uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: OpenSSF Scorecard results path: results.sarif retention-days: 5 - name: Upload results to GitHub Security tab uses: github/codeql-action/upload-sarif@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v3.29.5 with: sarif_file: results.sarif ================================================ FILE: .github/workflows/artifacts.yaml ================================================ name: Artifacts on: workflow_call: inputs: publish: description: Publish artifacts to the artifact store default: false required: false type: boolean secrets: DOCKER_USERNAME: required: true DOCKER_PASSWORD: required: true outputs: container-image-name: description: Container image name value: ${{ jobs.container-images.outputs.name }} container-image-digest: description: Container image digest value: ${{ jobs.container-images.outputs.digest }} container-image-ref: description: Container image ref value: ${{ jobs.container-images.outputs.ref }} permissions: contents: read jobs: container-images: name: Container images runs-on: ubuntu-latest strategy: matrix: variant: - alpine - distroless permissions: attestations: write contents: read packages: write id-token: write security-events: write outputs: name: ${{ steps.image-name.outputs.value }} digest: ${{ steps.build.outputs.digest }} ref: ${{ steps.image-ref.outputs.value }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-tags: true - name: Set up QEMU uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Set up Syft uses: anchore/sbom-action/download-syft@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0.23.1 - name: Install cosign uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0 - name: Set image name id: image-name run: echo "value=ghcr.io/${{ github.repository }}" >> "$GITHUB_OUTPUT" - name: Gather build metadata id: meta uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: images: | ${{ steps.image-name.outputs.value }} ${{ github.repository == 'dexidp/dex' && 'dexidp/dex' || '' }} flavor: | latest = false tags: | type=ref,event=branch,enable=${{ matrix.variant == 'alpine' }} type=ref,event=pr,prefix=pr-,enable=${{ matrix.variant == 'alpine' }} type=semver,pattern={{raw}},enable=${{ matrix.variant == 'alpine' }} type=raw,value=latest,enable=${{ github.ref_name == github.event.repository.default_branch && matrix.variant == 'alpine' }} type=ref,event=branch,suffix=-${{ matrix.variant }} type=ref,event=pr,prefix=pr-,suffix=-${{ matrix.variant }} type=semver,pattern={{raw}},suffix=-${{ matrix.variant }} type=raw,value=latest,enable={{is_default_branch}},suffix=-${{ matrix.variant }} labels: | org.opencontainers.image.documentation=https://dexidp.io/docs/ # Multiple exporters are not supported yet # See https://github.com/moby/buildkit/pull/2760 - name: Get version from git-version script id: version run: echo "value=$(bash ./scripts/git-version)" >> "$GITHUB_OUTPUT" # Multiple exporters are not supported yet # See https://github.com/moby/buildkit/pull/2760 - name: Determine build output uses: haya14busa/action-cond@94f77f7a80cd666cb3155084e428254fea4281fd # v1.2.1 id: build-output with: cond: ${{ inputs.publish }} if_true: type=image,push=true if_false: type=oci,dest=image.tar - name: Login to GitHub Container Registry uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} if: inputs.publish - name: Login to Docker Hub uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} if: inputs.publish - name: Build and push image id: build uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: . platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x tags: ${{ steps.meta.outputs.tags }} build-args: | BASE_IMAGE=${{ matrix.variant }} VERSION=${{ steps.version.outputs.value }} COMMIT_HASH=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} labels: | ${{ steps.meta.outputs.labels }} # cache-from: type=gha # cache-to: type=gha,mode=max outputs: ${{ steps.build-output.outputs.value }} # push: ${{ inputs.publish }} - name: Sign the images with GitHub OIDC Token run: | cosign sign --yes ${{ steps.image-name.outputs.value }}@${{ steps.build.outputs.digest }} if: inputs.publish - name: Set image ref id: image-ref run: echo "value=${{ steps.image-name.outputs.value }}@${{ steps.build.outputs.digest }}" >> "$GITHUB_OUTPUT" - name: Fetch image run: skopeo --insecure-policy copy docker://${{ steps.image-ref.outputs.value }} oci-archive:image.tar if: inputs.publish # Uncomment the following lines for debugging: # - name: Upload image as artifact # uses: actions/upload-artifact@v3 # with: # name: "[${{ github.job }}] OCI tarball" # path: image.tar - name: Extract OCI tarball id: extract-oci run: | mkdir -p image tar -xf image.tar -C image image_name=$(jq -r '.manifests[0].annotations["io.containerd.image.name"]' image/index.json) image_tag=$(jq -r '.manifests[0].annotations["org.opencontainers.image.ref.name"]' image/index.json) echo "Copying $image_tag -> $image_name" skopeo copy "oci:image:$image_tag" "docker-daemon:$image_name" echo "value=$image_name" >> "$GITHUB_OUTPUT" if: ${{ !inputs.publish }} # - name: List tags # run: skopeo --insecure-policy list-tags oci:image # # # See https://github.com/anchore/syft/issues/1545 # - name: Extract image from multi-arch image # run: skopeo --override-os linux --override-arch amd64 --insecure-policy copy oci:image:${{ steps.image-name.outputs.value }}:${{ steps.meta.outputs.version }} docker-archive:docker.tar # # - name: Generate SBOM # run: syft -o spdx-json=sbom-spdx.json docker-archive:docker.tar # # - name: Upload SBOM as artifact # uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 # with: # name: "[${{ github.job }}] SBOM" # path: sbom-spdx.json # retention-days: 5 # TODO: uncomment when the action is working for non ghcr.io pushes. GH Issue: https://github.com/actions/attest-build-provenance/issues/80 # - name: Generate build provenance attestation # uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 # with: # subject-name: dexidp/dex # subject-digest: ${{ steps.build.outputs.digest }} # push-to-registry: true - name: Generate build provenance attestation uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-name: ghcr.io/${{ github.repository }} subject-digest: ${{ steps.build.outputs.digest }} push-to-registry: true if: inputs.publish - name: Prepare image fs for scanning run: | image_ref=${{ steps.extract-oci.outputs.value != '' && steps.extract-oci.outputs.value || steps.image-ref.outputs.value }} docker export $(docker create --rm $image_ref) -o docker-image.tar mkdir -p docker-image tar -xf docker-image.tar -C docker-image ## Use cache for the trivy-db to avoid the TOOMANYREQUESTS error https://github.com/aquasecurity/trivy-action/pull/397 ## To avoid the trivy-db becoming outdated, we save the cache for one day - name: Get data id: date run: echo "date=$(date +%Y-%m-%d)" >> $GITHUB_OUTPUT - name: Restore trivy cache uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: cache/db key: trivy-cache-${{ steps.date.outputs.date }} restore-keys: trivy-cache- - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 with: input: docker-image format: sarif output: trivy-results.sarif scan-type: "rootfs" scan-ref: "." cache-dir: "./cache" # Disable skipping trivy cache for now env: TRIVY_SKIP_DB_UPDATE: true TRIVY_SKIP_JAVA_DB_UPDATE: true ## Trivy-db uses `0600` permissions. ## But `action/cache` use `runner` user by default ## So we need to change the permissions before caching the database. - name: change permissions for trivy.db run: sudo chmod 0644 ./cache/db/trivy.db - name: Check Trivy sarif run: cat trivy-results.sarif - name: Upload Trivy scan results as artifact uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: "[${{ github.job }}] Trivy scan results" path: trivy-results.sarif retention-days: 5 overwrite: true - name: Upload Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v3.29.5 with: sarif_file: trivy-results.sarif ================================================ FILE: .github/workflows/checks.yaml ================================================ name: PR Checks on: pull_request: types: [opened, labeled, unlabeled, synchronize] permissions: contents: read jobs: release-label: name: Release note label runs-on: ubuntu-latest if: github.repository == 'dexidp/dex' steps: - name: Check minimum labels uses: mheap/github-action-required-labels@0ac283b4e65c1fb28ce6079dea5546ceca98ccbe # v5.5 with: mode: minimum count: 1 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" ================================================ FILE: .github/workflows/ci.yaml ================================================ name: CI on: push: branches: [master] pull_request: permissions: contents: read jobs: test: name: Test runs-on: ubuntu-latest services: postgres: image: postgres:10.8 env: TZ: UTC ports: - 5432 options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 postgres-ent: image: postgres:10.8 env: TZ: UTC ports: - 5432 options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 mysql: image: mysql:5.7 env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: dex ports: - 3306 options: --health-cmd "mysql -proot -e \"show databases;\"" --health-interval 10s --health-timeout 5s --health-retries 5 mysql-ent: image: mysql:5.7 env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: dex ports: - 3306 options: --health-cmd "mysql -proot -e \"show databases;\"" --health-interval 10s --health-timeout 5s --health-retries 5 mysql8: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: dex ports: - 3306 options: --health-cmd "mysql -proot -e \"show databases;\"" --health-interval 10s --health-timeout 5s --health-retries 5 mysql8-ent: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: dex ports: - 3306 options: --health-cmd "mysql -proot -e \"show databases;\"" --health-interval 10s --health-timeout 5s --health-retries 5 etcd: image: gcr.io/etcd-development/etcd:v3.5.0 ports: - 2379 env: ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379 ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379 options: --health-cmd "ETCDCTL_API=3 etcdctl --endpoints http://localhost:2379 endpoint health" --health-interval 10s --health-timeout 5s --health-retries 5 keystone: image: openio/openstack-keystone:rocky ports: - 5000 - 35357 options: --health-cmd "curl --fail http://localhost:5000/v3" --health-interval 10s --health-timeout 5s --health-retries 5 vault: image: hashicorp/vault:1.21 ports: - 8200 env: VAULT_DEV_ROOT_TOKEN_ID: root-token VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200" options: --health-cmd "vault status -address=http://localhost:8200 || exit 1" --health-interval 10s --health-timeout 5s --health-retries 5 openbao: image: quay.io/openbao/openbao:2.5 ports: - 8210 env: BAO_DEV_ROOT_TOKEN_ID: root-token BAO_DEV_LISTEN_ADDRESS: "0.0.0.0:8210" options: --health-cmd "bao status -address=http://localhost:8210 || exit 1" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: "1.25" - name: Download tool dependencies run: make deps # Ensure that generated files were committed. # It can help us determine, that the code is in the intermediate state, which should not be tested. # Thus, heavy jobs like creating a kind cluster and testing / linting will be skipped. - name: Verify run: make verify - name: Start services run: docker compose -f docker-compose.test.yaml up -d - name: Create kind cluster uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0 with: version: "v0.17.0" node_image: "kindest/node:v1.25.3@sha256:cd248d1438192f7814fbca8fede13cfe5b9918746dfa12583976158a834fd5c5" - name: Test run: make testall env: DEX_MYSQL_DATABASE: dex DEX_MYSQL_USER: root DEX_MYSQL_PASSWORD: root DEX_MYSQL_HOST: 127.0.0.1 DEX_MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} DEX_MYSQL_ENT_DATABASE: dex DEX_MYSQL_ENT_USER: root DEX_MYSQL_ENT_PASSWORD: root DEX_MYSQL_ENT_HOST: 127.0.0.1 DEX_MYSQL_ENT_PORT: ${{ job.services.mysql-ent.ports[3306] }} DEX_MYSQL8_DATABASE: dex DEX_MYSQL8_USER: root DEX_MYSQL8_PASSWORD: root DEX_MYSQL8_HOST: 127.0.0.1 DEX_MYSQL8_PORT: ${{ job.services.mysql8.ports[3306] }} DEX_MYSQL8_ENT_DATABASE: dex DEX_MYSQL8_ENT_USER: root DEX_MYSQL8_ENT_PASSWORD: root DEX_MYSQL8_ENT_HOST: 127.0.0.1 DEX_MYSQL8_ENT_PORT: ${{ job.services.mysql8-ent.ports[3306] }} DEX_POSTGRES_DATABASE: postgres DEX_POSTGRES_USER: postgres DEX_POSTGRES_PASSWORD: postgres DEX_POSTGRES_HOST: localhost DEX_POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} DEX_POSTGRES_ENT_DATABASE: postgres DEX_POSTGRES_ENT_USER: postgres DEX_POSTGRES_ENT_PASSWORD: postgres DEX_POSTGRES_ENT_HOST: localhost DEX_POSTGRES_ENT_PORT: ${{ job.services.postgres-ent.ports[5432] }} DEX_ETCD_ENDPOINTS: http://localhost:${{ job.services.etcd.ports[2379] }} DEX_LDAP_HOST: localhost DEX_LDAP_PORT: 3890 DEX_LDAP_TLS_PORT: 6360 DEX_KEYSTONE_URL: http://localhost:${{ job.services.keystone.ports[5000] }} DEX_KEYSTONE_ADMIN_URL: http://localhost:${{ job.services.keystone.ports[35357] }} DEX_KEYSTONE_ADMIN_USER: demo DEX_KEYSTONE_ADMIN_PASS: DEMO_PASS DEX_VAULT_ADDR: http://localhost:${{ job.services.vault.ports[8200] }} DEX_VAULT_TOKEN: root-token DEX_OPENBAO_ADDR: http://localhost:${{ job.services.openbao.ports[8210] }} DEX_OPENBAO_TOKEN: root-token DEX_KUBERNETES_CONFIG_PATH: ~/.kube/config lint: name: Lint runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: "1.25" - name: Download golangci-lint run: make bin/golangci-lint - name: Lint run: make lint artifacts: name: Artifacts uses: ./.github/workflows/artifacts.yaml with: publish: ${{ github.event_name == 'push' }} secrets: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} permissions: attestations: write contents: read packages: write id-token: write security-events: write dependency-review: name: Dependency review runs-on: ubuntu-latest if: github.event_name == 'pull_request' steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Dependency Review uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 ================================================ FILE: .github/workflows/release.yaml ================================================ name: Release on: push: tags: [ "v[0-9]+.[0-9]+.[0-9]+" ] permissions: contents: read jobs: artifacts: name: Artifacts uses: ./.github/workflows/artifacts.yaml with: publish: true secrets: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} permissions: attestations: write contents: read packages: write id-token: write security-events: write ================================================ FILE: .github/workflows/trivydb-cache.yaml ================================================ # Note: This workflow only updates the cache. You should create a separate workflow for your actual Trivy scans. # In your scan workflow, set TRIVY_SKIP_DB_UPDATE=true and TRIVY_SKIP_JAVA_DB_UPDATE=true. name: Update Trivy Cache on: schedule: - cron: '0 0 * * *' # Run daily at midnight UTC workflow_dispatch: # Allow manual triggering permissions: contents: read jobs: update-trivy-db: runs-on: ubuntu-latest steps: - name: Setup oras uses: oras-project/setup-oras@22ce207df3b08e061f537244349aac6ae1d214f6 # v1.2.4 - name: Get current date id: date run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - name: Download and extract the vulnerability DB run: | mkdir -p $GITHUB_WORKSPACE/.cache/trivy/db oras pull ghcr.io/aquasecurity/trivy-db:2 tar -xzf db.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/db rm db.tar.gz - name: Download and extract the Java DB run: | mkdir -p $GITHUB_WORKSPACE/.cache/trivy/java-db oras pull ghcr.io/aquasecurity/trivy-java-db:1 tar -xzf javadb.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/java-db rm javadb.tar.gz - name: Cache DBs uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ${{ github.workspace }}/.cache/trivy key: cache-trivy-${{ steps.date.outputs.date }} ================================================ FILE: .gitignore ================================================ /.devenv/ /.direnv/ /.idea/ /bin/ /config.yaml /docker-compose.override.yaml /var/ /vendor/ *.db ================================================ FILE: .gitpod.yml ================================================ tasks: - init: go get && go build ./... && go test ./... && make command: go run ================================================ FILE: .golangci.yaml ================================================ version: "2" run: timeout: 5m linters: disable: - staticcheck - errcheck enable: - depguard - dogsled - exhaustive - gochecknoinits # - gocritic - goprintffuncname - govet - ineffassign - misspell - nakedret - nolintlint - prealloc # - revive # - sqlclosecheck # - staticcheck - unconvert - unused - whitespace # Disable temporarily until everything works with Go 1.20 # - bodyclose # - rowserrcheck # - tparallel # - unparam # Disable temporarily until the following issue is resolved: https://github.com/golangci/golangci-lint/issues/3086 # - sqlclosecheck # TODO: fix linter errors before enabling # - exhaustivestruct # - gochecknoglobals # - errorlint # - gocognit # - godot # - nlreturn # - noctx # - revive # - wrapcheck # TODO: fix linter errors before enabling (from original config) # - dupl # - errcheck # - goconst # - gocyclo # - gosec # - lll # - scopelint # unused # - goheader # - gomodguard # don't enable: # - asciicheck # - funlen # - godox # - goerr113 # - gomnd # - interfacer # - maligned # - nestif # - testpackage # - wsl exclusions: rules: - linters: - errcheck - noctx path: _test.go presets: - comments - std-error-handling settings: misspell: locale: US nolintlint: allow-unused: false # report any unused nolint directives require-specific: false # don't require nolint directives to be specific about which linter is being skipped gocritic: # Enable multiple checks by tags. See "Tags" section in https://github.com/go-critic/go-critic#usage. enabled-tags: - diagnostic - experimental - opinionated - style disabled-checks: - importShadow - unnamedResult depguard: rules: deprecated: deny: - pkg: "io/ioutil" desc: "The 'io/ioutil' package is deprecated. Use corresponding 'os' or 'io' functions instead." formatters: enable: - gci - gofmt - gofumpt - goimports # - golines settings: gci: sections: - standard - default - localmodule # issues: # exclude-dirs: # - storage/ent/db # generated ent code ================================================ FILE: ADOPTERS.md ================================================ # Adopters This is a list of production adopters of Dex (in alphabetical order). # Companies - [Aspect](https://www.aspect.com/) uses Dex for authenticating users across their Kubernetes infrastructure (using Kubernetes OIDC support). - [Banzai Cloud](https://banzaicloud.com) is using Dex for authenticating to its Pipeline control plane and also to authenticate users against provisioned Kubernetes clusters (via Kubernetes OIDC support). - [Ericsson](https://www.ericsson.com) is using Dex to authenticate access to Kubernetes API server in [Cloud Container Distribution](https://www.ericsson.com/en/portfolio/cloud-software-and-services/cloud-core/cloud-infrastructure/nfvi/cloud-container-distribution). - [Flant](https://flant.com) uses Dex for providing access to core components of [Managed Kubernetes as a Service](https://flant.com/services/managed-kubernetes-as-a-service), integration with various authentication providers, plugging custom applications. - [JuliaBox](https://juliabox.com/) is leveraging federated OIDC provided by Dex for authenticating users to their compute infrastructure based on Kubernetes. - [Pusher](https://pusher.com) uses Dex for authenticating users across their Kubernetes infrastructure (using Kubernetes OIDC support) in conjunction with the [OAuth2 Proxy](https://github.com/pusher/oauth2_proxy) for protecting web UIs. # Projects - [Argo CD](https://argoproj.github.io/cd) integrates Dex to provide convenient Single Sign On capabilities to its web UI and CLI - [Chef](https://chef.io) uses Dex for authenticating users in [Chef Automate](https://automate.chef.io/). The code is Open Source, available at [`github.com/chef/automate`](https://github.com/chef/automate). - [Elastisys](https://elastisys.com) uses Dex for authentication in [Welkin, The Application Platform for Software Critical to Society](https://elastisys.io/welkin/), including SSO to Grafana, OpenSearch, and Harbor. - [Kasten](https://www.kasten.io) is using Dex for authenticating access to the dashboard of [K10](https://www.kasten.io/product/), a Kubernetes-native platform for backup, disaster recovery and mobility of Kubernetes applications. K10 is widely used by a variety of customers including large enterprises, financial services, design firms, and IT companies. - [Kubeflow](https://www.kubeflow.org/) [uses](https://github.com/kubeflow/manifests#dex) Dex as one of its components in the Kubeflow Platform for external OIDC authentication. - [Kyma](https://kyma-project.io) is using Dex to authenticate access to Kubernetes API server (even for managed Kubernetes like Google Kubernetes Engine or Azure Kubernetes Service) and for protecting web UI of [Kyma Console](https://github.com/kyma-project/console) and other UIs integrated in Kyma ([Grafana](https://github.com/grafana/grafana), [Loki](https://github.com/grafana/loki), and [Jaeger](https://github.com/jaegertracing/jaeger)). Kyma is an open-source project ([`github.com/kyma-project`](https://github.com/kyma-project/kyma)) designed natively on Kubernetes, that allows you to extend and customize your applications in a quick and modern way, using serverless computing or microservice architecture. - [LitmusChaos](https://litmuschaos.io/) uses Dex to [implement](https://docs.litmuschaos.io/docs/user-guides/chaoscenter-oauth-dex-installation#deploy-dex-oidc-provider) OAuth2 login support in ChaosCenter, its centralized chaos management tool. - [LLMariner](https://llmariner.ai/) uses Dex for [user management](https://llmariner.ai/docs/features/user_management/). - [Pydio](https://pydio.com/) Pydio Cells is an open source sync & share platform written in Go. Cells is using Dex as an OIDC service for authentication and authorizations. Check out [Pydio Cells repository](https://github.com/pydio/cells) for more information and/or to contribute. - [sigstore](https://sigstore.dev) uses Dex for authentication in their public Fulcio instance, which is a certificate authority for code signing certificates bound to OIDC-based identities. - [Terrakube](https://docs.terrakube.io/) relies on Dex for [user authentication](https://docs.terrakube.io/getting-started/deployment/user-authentication-dex). Its Helm chart uses Dex as a dependency. ================================================ FILE: Dockerfile ================================================ ARG BASE_IMAGE=alpine FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.9.0@sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707 AS xx FROM --platform=$BUILDPLATFORM golang:1.26.1-alpine3.22@sha256:07e91d24f6330432729082bb580983181809e0a48f0f38ecde26868d4568c6ac AS builder COPY --from=xx / / RUN apk add --update alpine-sdk ca-certificates openssl clang lld ARG TARGETPLATFORM RUN xx-apk --update add musl-dev gcc # lld has issues building static binaries for ppc so prefer ld for it RUN [ "$(xx-info arch)" != "ppc64le" ] || XX_CC_PREFER_LINKER=ld xx-clang --setup-target-triple RUN xx-go --wrap WORKDIR /usr/local/src/dex ARG GOPROXY ENV CGO_ENABLED=1 COPY go.mod go.sum ./ COPY api/v2/go.mod api/v2/go.sum ./api/v2/ RUN go mod download COPY . . # Propagate Dex version from build args to the build environment ARG VERSION RUN make release-binary RUN xx-verify /go/bin/dex && xx-verify /go/bin/docker-entrypoint FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS stager RUN mkdir -p /var/dex RUN mkdir -p /etc/dex COPY config.docker.yaml /etc/dex/ FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS gomplate ARG TARGETOS ARG TARGETARCH ARG TARGETVARIANT ENV GOMPLATE_VERSION=v5.0.0 RUN wget -O /usr/local/bin/gomplate \ "https://github.com/hairyhenderson/gomplate/releases/download/${GOMPLATE_VERSION}/gomplate_${TARGETOS:-linux}-${TARGETARCH:-amd64}${TARGETVARIANT}" \ && chmod +x /usr/local/bin/gomplate # For Dependabot to detect base image versions FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS alpine FROM alpine AS user-setup RUN addgroup -g 1001 -S dex && adduser -u 1001 -S -G dex -D -H -s /sbin/nologin dex FROM gcr.io/distroless/static-debian13:nonroot@sha256:e3f945647ffb95b5839c07038d64f9811adf17308b9121d8a2b87b6a22a80a39 AS distroless FROM $BASE_IMAGE # Dex connectors, such as GitHub and Google logins require root certificates. # Proper installations should manage those certificates, but it's a bad user # experience when this doesn't work out of the box. # # See https://go.dev/src/crypto/x509/root_linux.go for Go root CA bundle locations. COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt # Ensure the dex user/group exist before setting ownership or switching to them. COPY --from=user-setup /etc/passwd /etc/passwd COPY --from=user-setup /etc/group /etc/group COPY --from=stager --chown=1001:1001 /var/dex /var/dex COPY --from=stager --chown=1001:1001 /etc/dex /etc/dex # Copy module files for CVE scanning / dependency analysis. COPY --from=builder /usr/local/src/dex/go.mod /usr/local/src/dex/go.sum /usr/local/src/dex/ COPY --from=builder /usr/local/src/dex/api/v2/go.mod /usr/local/src/dex/api/v2/go.sum /usr/local/src/dex/api/v2/ COPY --from=builder /go/bin/dex /usr/local/bin/dex COPY --from=builder /go/bin/docker-entrypoint /usr/local/bin/docker-entrypoint COPY --from=builder /usr/local/src/dex/web /srv/dex/web COPY --from=gomplate /usr/local/bin/gomplate /usr/local/bin/gomplate USER dex:dex ENTRYPOINT ["/usr/local/bin/docker-entrypoint"] CMD ["dex", "serve", "/etc/dex/config.docker.yaml"] ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: MAINTAINERS ================================================ Joel Speed (@JoelSpeed) Maksim Nabokikh (@nabokihms) Mark Sagi-Kazar (@sagikazarmark) Nandor Kracser (@bonifaido) Rithu John (@rithujohn191) Stephen Augustus (@justaugustus) ================================================ FILE: Makefile ================================================ export PATH := $(abspath bin/protoc/bin/):$(abspath bin/):${PATH} OS = $(shell uname | tr A-Z a-z) user=$(shell id -u -n) group=$(shell id -g -n) $( shell mkdir -p bin ) PROJ = dex ORG_PATH = github.com/dexidp REPO_PATH = $(ORG_PATH)/$(PROJ) VERSION ?= $(shell ./scripts/git-version) export GOBIN=$(PWD)/bin LD_FLAGS="-w -X main.version=$(VERSION)" # Dependency versions GOLANGCI_VERSION = 2.4.0 GOTESTSUM_VERSION ?= 1.12.0 PROTOC_VERSION = 29.3 PROTOC_GEN_GO_VERSION = 1.36.5 PROTOC_GEN_GO_GRPC_VERSION = 1.5.1 KIND_VERSION = 0.22.0 KIND_NODE_IMAGE = "kindest/node:v1.25.3@sha256:cd248d1438192f7814fbca8fede13cfe5b9918746dfa12583976158a834fd5c5" KIND_TMP_DIR = "$(PWD)/bin/test/dex-kind-kubeconfig" ##@ Build build: bin/dex ## Build Dex binaries. examples: bin/grpc-client bin/example-app ## Build example app. .PHONY: update-gomplate update-gomplate: ## Check and update gomplate version in Dockerfile. @./scripts/update-gomplate .PHONY: release-binary release-binary: LD_FLAGS = "-w -X main.version=$(VERSION) -extldflags \"-static\"" release-binary: ## Build release binaries (used to build a final container image). @go build -o /go/bin/dex -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex @go build -o /go/bin/docker-entrypoint -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/docker-entrypoint bin/dex: @mkdir -p bin/ @go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex bin/grpc-client: @mkdir -p bin/ @cd examples/ && go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/examples/grpc-client bin/example-app: @mkdir -p bin/ @cd examples/ && go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/examples/example-app ##@ Generate .PHONY: generate generate: generate-proto generate-proto-internal generate-ent go-mod-tidy ## Run all generators. .PHONY: generate-ent generate-ent: ## Generate code for database ORM. @go generate $(REPO_PATH)/storage/ent/ .PHONY: generate-proto generate-proto: ## Generate the Dex client's protobuf code. @protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. api/v2/*.proto @protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. api/*.proto .PHONY: generate-proto-internal generate-proto-internal: ## Generate protobuf code for token encoding. @protoc --go_out=paths=source_relative:. server/internal/*.proto go-mod-tidy: ## Run go mod tidy for all targets. @go mod tidy @cd examples/ && go mod tidy @cd api/v2/ && go mod tidy bin/protoc: @mkdir -p bin/protoc ifeq ($(shell uname | tr A-Z a-z), darwin) curl -L https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-osx-x86_64.zip > bin/protoc.zip endif ifeq ($(shell uname | tr A-Z a-z), linux) curl -L https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-x86_64.zip > bin/protoc.zip endif unzip bin/protoc.zip -d bin/protoc rm bin/protoc.zip bin/protoc-gen-go: @mkdir -p bin curl -L https://github.com/protocolbuffers/protobuf-go/releases/download/v${PROTOC_GEN_GO_VERSION}/protoc-gen-go.v${PROTOC_GEN_GO_VERSION}.$(shell uname | tr A-Z a-z).amd64.tar.gz | tar -zOxf - protoc-gen-go > ./bin/protoc-gen-go @chmod +x ./bin/protoc-gen-go bin/protoc-gen-go-grpc: @mkdir -p bin curl -L https://github.com/grpc/grpc-go/releases/download/cmd/protoc-gen-go-grpc/v${PROTOC_GEN_GO_GRPC_VERSION}/protoc-gen-go-grpc.v${PROTOC_GEN_GO_GRPC_VERSION}.$(shell uname | tr A-Z a-z).amd64.tar.gz | tar -zOxf - ./protoc-gen-go-grpc > ./bin/protoc-gen-go-grpc @chmod +x ./bin/protoc-gen-go-grpc ##@ Verify verify: generate ## Verify that all the code was generated and committed to repository. @git diff --exit-code .PHONY: verify-proto verify-proto: generate-proto ## Verify that the Dex client's protobuf code was generated. @git diff --exit-code .PHONY: verify-proto verify-proto-internal: generate-proto-internal ## Verify internal protobuf code for token encoding was generated. @git diff --exit-code .PHONY: verify-ent verify-ent: generate-ent ## Verify code for database ORM was generated. @git diff --exit-code .PHONY: verify-go-mod verify-go-mod: go-mod-tidy ## Check that go.mod and go.sum formatted according to the changes. @git diff --exit-code ##@ Test and Lint deps: bin/gotestsum bin/golangci-lint bin/protoc bin/protoc-gen-go bin/protoc-gen-go-grpc bin/kind ## Install dev dependencies. # Detect if we're running in GitHub Actions ifdef GITHUB_ACTIONS GOTESTSUM_FORMAT = github-actions else GOTESTSUM_FORMAT = testname GOTESTSUM_FORMAT_ICONS = hivis endif .PHONY: test testrace testall test: bin/gotestsum ## Test go code. ifdef GOTESTSUM_FORMAT_ICONS @gotestsum --format $(GOTESTSUM_FORMAT) --format-icons $(GOTESTSUM_FORMAT_ICONS) -- -v ./... else @gotestsum --format $(GOTESTSUM_FORMAT) -- -v ./... endif testrace: bin/gotestsum ## Test go code and check for possible race conditions. ifdef GOTESTSUM_FORMAT_ICONS @gotestsum --format $(GOTESTSUM_FORMAT) --format-icons $(GOTESTSUM_FORMAT_ICONS) -- -v --race ./... else @gotestsum --format $(GOTESTSUM_FORMAT) -- -v --race ./... endif testall: testrace ## Run all tests for go code. .PHONY: lint lint: ## Run linter. @golangci-lint version @golangci-lint run .PHONY: fix fix: ## Fix lint violations. @golangci-lint version @golangci-lint fmt docker-compose.override.yaml: cp docker-compose.override.yaml.dist docker-compose.override.yaml .PHONY: up up: docker-compose.override.yaml ## Launch the development environment. @ if [ docker-compose.override.yaml -ot docker-compose.override.yaml.dist ]; then diff -u docker-compose.override.yaml docker-compose.override.yaml.dist || (echo "!!! The distributed docker-compose.override.yaml example changed. Please update your file accordingly (or at least touch it). !!!" && false); fi docker-compose up -d .PHONY: down down: clear ## Destroy the development environment. docker-compose down --volumes --remove-orphans --rmi local .PHONY: kind-up kind-down kind-tests kind-up: ## Create a kind cluster. @mkdir -p bin/test @kind create cluster --image ${KIND_NODE_IMAGE} --kubeconfig ${KIND_TMP_DIR} --name dex-tests kind-tests: export DEX_KUBERNETES_CONFIG_PATH=${KIND_TMP_DIR} kind-tests: testall ## Run test on kind cluster (kind cluster must be created). kind-down: ## Delete the kind cluster. @kind delete cluster --name dex-tests rm ${KIND_TMP_DIR} bin/golangci-lint: @mkdir -p bin curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | BINARY=golangci-lint bash -s -- v${GOLANGCI_VERSION} bin/gotestsum: @mkdir -p bin curl -L https://github.com/gotestyourself/gotestsum/releases/download/v${GOTESTSUM_VERSION}/gotestsum_${GOTESTSUM_VERSION}_$(shell uname | tr A-Z a-z)_amd64.tar.gz | tar -zOxf - gotestsum > ./bin/gotestsum @chmod +x ./bin/gotestsum bin/kind: @mkdir -p bin curl -L https://github.com/kubernetes-sigs/kind/releases/download/v${KIND_VERSION}/kind-$(shell uname | tr A-Z a-z)-amd64 > ./bin/kind @chmod +x ./bin/kind ##@ Clean clean: ## Delete all builds and downloaded dependencies. @rm -rf bin/ FORMATTING_BEGIN_YELLOW = \033[0;33m FORMATTING_BEGIN_BLUE = \033[36m FORMATTING_END = \033[0m .PHONY: help help: @printf -- "${FORMATTING_BEGIN_BLUE}%s${FORMATTING_END}\n" \ "" \ " ___ " \ " / _ \_____ __ " \ " / // / -_) \ / " \ " /____/\__/_\_\ " \ "" \ "-----------------------" \ "" @awk 'BEGIN {\ FS = ":.*##"; \ printf "Usage: ${FORMATTING_BEGIN_BLUE}OPTION${FORMATTING_END}= make ${FORMATTING_BEGIN_YELLOW}${FORMATTING_END}\n"\ } \ /^[a-zA-Z0-9_-]+:.*?##/ { printf " ${FORMATTING_BEGIN_BLUE}%-46s${FORMATTING_END} %s\n", $$1, $$2 } \ /^.?.?##~/ { printf " %-46s${FORMATTING_BEGIN_YELLOW}%-46s${FORMATTING_END}\n", "", substr($$1, 6) } \ /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) ================================================ FILE: README.md ================================================ # dex - A federated OpenID Connect provider ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/dexidp/dex/ci.yaml?style=flat-square&branch=master) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/dexidp/dex/badge?style=flat-square)](https://api.securityscorecards.dev/projects/github.com/dexidp/dex) [![Go Report Card](https://goreportcard.com/badge/github.com/dexidp/dex?style=flat-square)](https://goreportcard.com/report/github.com/dexidp/dex) [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/dexidp/dex) ![logo](docs/logos/dex-horizontal-color.png) Dex is an identity service that uses [OpenID Connect][openid-connect] to drive authentication for other apps. Dex acts as a portal to other identity providers through ["connectors."](#connectors) This lets dex defer authentication to LDAP servers, SAML providers, or established identity providers like GitHub, Google, and Active Directory. Clients write their authentication logic once to talk to dex, then dex handles the protocols for a given backend. ## ID Tokens ID Tokens are an OAuth2 extension introduced by OpenID Connect and dex's primary feature. ID Tokens are [JSON Web Tokens][jwt-io] (JWTs) signed by dex and returned as part of the OAuth2 response that attests to the end user's identity. An example JWT might look like: ``` eyJhbGciOiJSUzI1NiIsImtpZCI6IjlkNDQ3NDFmNzczYjkzOGNmNjVkZDMyNjY4NWI4NjE4MGMzMjRkOTkifQ.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjU1NTYvZGV4Iiwic3ViIjoiQ2djeU16UXlOelE1RWdabmFYUm9kV0kiLCJhdWQiOiJleGFtcGxlLWFwcCIsImV4cCI6MTQ5Mjg4MjA0MiwiaWF0IjoxNDkyNzk1NjQyLCJhdF9oYXNoIjoiYmk5NmdPWFpTaHZsV1l0YWw5RXFpdyIsImVtYWlsIjoiZXJpYy5jaGlhbmdAY29yZW9zLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJncm91cHMiOlsiYWRtaW5zIiwiZGV2ZWxvcGVycyJdLCJuYW1lIjoiRXJpYyBDaGlhbmcifQ.OhROPq_0eP-zsQRjg87KZ4wGkjiQGnTi5QuG877AdJDb3R2ZCOk2Vkf5SdP8cPyb3VMqL32G4hLDayniiv8f1_ZXAde0sKrayfQ10XAXFgZl_P1yilkLdknxn6nbhDRVllpWcB12ki9vmAxklAr0B1C4kr5nI3-BZLrFcUR5sQbxwJj4oW1OuG6jJCNGHXGNTBTNEaM28eD-9nhfBeuBTzzO7BKwPsojjj4C9ogU4JQhGvm_l4yfVi0boSx8c0FX3JsiB0yLa1ZdJVWVl9m90XmbWRSD85pNDQHcWZP9hR6CMgbvGkZsgjG32qeRwUL_eNkNowSBNWLrGNPoON1gMg ``` ID Tokens contains standard claims assert which client app logged the user in, when the token expires, and the identity of the user. ```json { "iss": "http://127.0.0.1:5556/dex", "sub": "CgcyMzQyNzQ5EgZnaXRodWI", "aud": "example-app", "exp": 1492882042, "iat": 1492795642, "at_hash": "bi96gOXZShvlWYtal9Eqiw", "email": "jane.doe@coreos.com", "email_verified": true, "groups": [ "admins", "developers" ], "name": "Jane Doe" } ``` Because these tokens are signed by dex and [contain standard-based claims][standard-claims] other services can consume them as service-to-service credentials. Systems that can already consume OpenID Connect ID Tokens issued by dex include: * [Kubernetes][kubernetes] * [AWS STS][aws-sts] For details on how to request or validate an ID Token, see [_"Writing apps that use dex"_][using-dex]. ## Kubernetes and Dex Dex runs natively on top of any Kubernetes cluster using Custom Resource Definitions and can drive API server authentication through the OpenID Connect plugin. Clients, such as the [`kubernetes-dashboard`](https://github.com/kubernetes/dashboard) and `kubectl`, can act on behalf of users who can login to the cluster through any identity provider dex supports. * More docs for running dex as a Kubernetes authenticator can be found [here](https://dexidp.io/docs/guides/kubernetes/). * You can find more about companies and projects which use dex, [here](./ADOPTERS.md). ## Connectors When a user logs in through dex, the user's identity is usually stored in another user-management system: a LDAP directory, a GitHub org, etc. Dex acts as a shim between a client app and the upstream identity provider. The client only needs to understand OpenID Connect to query dex, while dex implements an array of protocols for querying other user-management systems. ![](docs/img/dex-flow.png) A "connector" is a strategy used by dex for authenticating a user against another identity provider. Dex implements connectors that target specific platforms such as GitHub, LinkedIn, and Microsoft as well as established protocols like LDAP and SAML. Depending on the connectors limitations in protocols can prevent dex from issuing [refresh tokens][scopes] or returning [group membership][scopes] claims. For example, because SAML doesn't provide a non-interactive way to refresh assertions, if a user logs in through the SAML connector dex won't issue a refresh token to its client. Refresh token support is required for clients that require offline access, such as `kubectl`. Dex implements the following connectors: | Name | supports refresh tokens | supports groups claim | supports preferred_username claim | status | notes | | ---- | ----------------------- | --------------------- | --------------------------------- | ------ | ----- | | [LDAP](https://dexidp.io/docs/connectors/ldap/) | yes | yes | yes | stable | | | [GitHub](https://dexidp.io/docs/connectors/github/) | yes | yes | yes | stable | | | [SAML 2.0](https://dexidp.io/docs/connectors/saml/) | no | yes | no | stable | WARNING: Unmaintained and likely vulnerable to auth bypasses ([#1884](https://github.com/dexidp/dex/discussions/1884)) | | [GitLab](https://dexidp.io/docs/connectors/gitlab/) | yes | yes | yes | beta | | | [OpenID Connect](https://dexidp.io/docs/connectors/oidc/) | yes | yes | yes | beta | Includes Salesforce, Azure, etc. | | [OAuth 2.0](https://dexidp.io/docs/connectors/oauth/) | no | yes | yes | alpha | | | [Google](https://dexidp.io/docs/connectors/google/) | yes | yes | yes | alpha | | | [LinkedIn](https://dexidp.io/docs/connectors/linkedin/) | yes | no | no | beta | | | [Microsoft](https://dexidp.io/docs/connectors/microsoft/) | yes | yes | no | beta | | | [AuthProxy](https://dexidp.io/docs/connectors/authproxy/) | no | yes | no | alpha | Authentication proxies such as Apache2 mod_auth, etc. | | [Bitbucket Cloud](https://dexidp.io/docs/connectors/bitbucketcloud/) | yes | yes | no | alpha | | | [OpenShift](https://dexidp.io/docs/connectors/openshift/) | yes | yes | no | alpha | | | [Atlassian Crowd](https://dexidp.io/docs/connectors/atlassian-crowd/) | yes | yes | yes * | beta | preferred_username claim must be configured through config | | [Gitea](https://dexidp.io/docs/connectors/gitea/) | yes | no | yes | beta | | | [OpenStack Keystone](https://dexidp.io/docs/connectors/keystone/) | yes | yes | no | alpha | | Stable, beta, and alpha are defined as: * Stable: well tested, in active use, and will not change in backward incompatible ways. * Beta: tested and unlikely to change in backward incompatible ways. * Alpha: may be untested by core maintainers and is subject to change in backward incompatible ways. All changes or deprecations of connector features will be announced in the [release notes][release-notes]. ## Documentation * [Getting started](https://dexidp.io/docs/getting-started/) * [Intro to OpenID Connect](https://dexidp.io/docs/openid-connect/) * [Writing apps that use dex][using-dex] * [What's new in v2](https://dexidp.io/docs/archive/v2/) * [Custom scopes, claims, and client features](https://dexidp.io/docs/custom-scopes-claims-clients/) * [Storage options](https://dexidp.io/docs/storage/) * [gRPC API](https://dexidp.io/docs/api/) * [Using Kubernetes with dex](https://dexidp.io/docs/kubernetes/) * Client libraries * [Go][go-oidc] ## Reporting a vulnerability Please see our [security policy](.github/SECURITY.md) for details about reporting vulnerabilities. ## Getting help - For feature requests and bugs, file an [issue](https://github.com/dexidp/dex/issues). - For general discussion about both using and developing Dex: - join the [#dexidp](https://cloud-native.slack.com/messages/dexidp) on the CNCF Slack - open a new [discussion](https://github.com/dexidp/dex/discussions) - join the [dex-dev](https://groups.google.com/forum/#!forum/dex-dev) mailing list [openid-connect]: https://openid.net/connect/ [standard-claims]: https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims [scopes]: https://dexidp.io/docs/custom-scopes-claims-clients/#scopes [using-dex]: https://dexidp.io/docs/using-dex/ [jwt-io]: https://jwt.io/ [kubernetes]: https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens [aws-sts]: https://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html [go-oidc]: https://github.com/coreos/go-oidc [issue-1065]: https://github.com/dexidp/dex/issues/1065 [release-notes]: https://github.com/dexidp/dex/releases ## Development When all coding and testing is done, please run the test suite: ```shell make testall ``` For the best developer experience, install [Nix](https://builtwithnix.org/) and [direnv](https://direnv.net/). Alternatively, install Go and Docker manually or using a package manager. Install the rest of the dependencies by running `make deps`. For release process, please read the [release documentation](https://dexidp.io/docs/development/releases/). ## License The project is licensed under the [Apache License, Version 2.0](LICENSE). ================================================ FILE: api/api.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.5 // protoc v5.29.3 // source: api/api.proto package api import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // Client represents an OAuth2 client. type Client struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Secret string `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"` RedirectUris []string `protobuf:"bytes,3,rep,name=redirect_uris,json=redirectUris,proto3" json:"redirect_uris,omitempty"` TrustedPeers []string `protobuf:"bytes,4,rep,name=trusted_peers,json=trustedPeers,proto3" json:"trusted_peers,omitempty"` Public bool `protobuf:"varint,5,opt,name=public,proto3" json:"public,omitempty"` Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` LogoUrl string `protobuf:"bytes,7,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"` AllowedConnectors []string `protobuf:"bytes,8,rep,name=allowed_connectors,json=allowedConnectors,proto3" json:"allowed_connectors,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Client) Reset() { *x = Client{} mi := &file_api_api_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *Client) String() string { return protoimpl.X.MessageStringOf(x) } func (*Client) ProtoMessage() {} func (x *Client) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Client.ProtoReflect.Descriptor instead. func (*Client) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{0} } func (x *Client) GetId() string { if x != nil { return x.Id } return "" } func (x *Client) GetSecret() string { if x != nil { return x.Secret } return "" } func (x *Client) GetRedirectUris() []string { if x != nil { return x.RedirectUris } return nil } func (x *Client) GetTrustedPeers() []string { if x != nil { return x.TrustedPeers } return nil } func (x *Client) GetPublic() bool { if x != nil { return x.Public } return false } func (x *Client) GetName() string { if x != nil { return x.Name } return "" } func (x *Client) GetLogoUrl() string { if x != nil { return x.LogoUrl } return "" } func (x *Client) GetAllowedConnectors() []string { if x != nil { return x.AllowedConnectors } return nil } // CreateClientReq is a request to make a client. type CreateClientReq struct { state protoimpl.MessageState `protogen:"open.v1"` Client *Client `protobuf:"bytes,1,opt,name=client,proto3" json:"client,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreateClientReq) Reset() { *x = CreateClientReq{} mi := &file_api_api_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreateClientReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreateClientReq) ProtoMessage() {} func (x *CreateClientReq) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreateClientReq.ProtoReflect.Descriptor instead. func (*CreateClientReq) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{1} } func (x *CreateClientReq) GetClient() *Client { if x != nil { return x.Client } return nil } // CreateClientResp returns the response from creating a client. type CreateClientResp struct { state protoimpl.MessageState `protogen:"open.v1"` AlreadyExists bool `protobuf:"varint,1,opt,name=already_exists,json=alreadyExists,proto3" json:"already_exists,omitempty"` Client *Client `protobuf:"bytes,2,opt,name=client,proto3" json:"client,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreateClientResp) Reset() { *x = CreateClientResp{} mi := &file_api_api_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreateClientResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreateClientResp) ProtoMessage() {} func (x *CreateClientResp) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreateClientResp.ProtoReflect.Descriptor instead. func (*CreateClientResp) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{2} } func (x *CreateClientResp) GetAlreadyExists() bool { if x != nil { return x.AlreadyExists } return false } func (x *CreateClientResp) GetClient() *Client { if x != nil { return x.Client } return nil } // DeleteClientReq is a request to delete a client. type DeleteClientReq struct { state protoimpl.MessageState `protogen:"open.v1"` // The ID of the client. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeleteClientReq) Reset() { *x = DeleteClientReq{} mi := &file_api_api_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DeleteClientReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeleteClientReq) ProtoMessage() {} func (x *DeleteClientReq) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DeleteClientReq.ProtoReflect.Descriptor instead. func (*DeleteClientReq) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{3} } func (x *DeleteClientReq) GetId() string { if x != nil { return x.Id } return "" } // DeleteClientResp determines if the client is deleted successfully. type DeleteClientResp struct { state protoimpl.MessageState `protogen:"open.v1"` NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeleteClientResp) Reset() { *x = DeleteClientResp{} mi := &file_api_api_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DeleteClientResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeleteClientResp) ProtoMessage() {} func (x *DeleteClientResp) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DeleteClientResp.ProtoReflect.Descriptor instead. func (*DeleteClientResp) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{4} } func (x *DeleteClientResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } // UpdateClientReq is a request to update an existing client. type UpdateClientReq struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` RedirectUris []string `protobuf:"bytes,2,rep,name=redirect_uris,json=redirectUris,proto3" json:"redirect_uris,omitempty"` TrustedPeers []string `protobuf:"bytes,3,rep,name=trusted_peers,json=trustedPeers,proto3" json:"trusted_peers,omitempty"` Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` LogoUrl string `protobuf:"bytes,5,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"` AllowedConnectors []string `protobuf:"bytes,6,rep,name=allowed_connectors,json=allowedConnectors,proto3" json:"allowed_connectors,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateClientReq) Reset() { *x = UpdateClientReq{} mi := &file_api_api_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdateClientReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateClientReq) ProtoMessage() {} func (x *UpdateClientReq) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateClientReq.ProtoReflect.Descriptor instead. func (*UpdateClientReq) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{5} } func (x *UpdateClientReq) GetId() string { if x != nil { return x.Id } return "" } func (x *UpdateClientReq) GetRedirectUris() []string { if x != nil { return x.RedirectUris } return nil } func (x *UpdateClientReq) GetTrustedPeers() []string { if x != nil { return x.TrustedPeers } return nil } func (x *UpdateClientReq) GetName() string { if x != nil { return x.Name } return "" } func (x *UpdateClientReq) GetLogoUrl() string { if x != nil { return x.LogoUrl } return "" } func (x *UpdateClientReq) GetAllowedConnectors() []string { if x != nil { return x.AllowedConnectors } return nil } // UpdateClientResp returns the response from updating a client. type UpdateClientResp struct { state protoimpl.MessageState `protogen:"open.v1"` NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateClientResp) Reset() { *x = UpdateClientResp{} mi := &file_api_api_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdateClientResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateClientResp) ProtoMessage() {} func (x *UpdateClientResp) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateClientResp.ProtoReflect.Descriptor instead. func (*UpdateClientResp) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{6} } func (x *UpdateClientResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } // Password is an email for password mapping managed by the storage. type Password struct { state protoimpl.MessageState `protogen:"open.v1"` Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` // Currently we do not accept plain text passwords. Could be an option in the future. Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"` UserId string `protobuf:"bytes,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Password) Reset() { *x = Password{} mi := &file_api_api_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *Password) String() string { return protoimpl.X.MessageStringOf(x) } func (*Password) ProtoMessage() {} func (x *Password) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Password.ProtoReflect.Descriptor instead. func (*Password) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{7} } func (x *Password) GetEmail() string { if x != nil { return x.Email } return "" } func (x *Password) GetHash() []byte { if x != nil { return x.Hash } return nil } func (x *Password) GetUsername() string { if x != nil { return x.Username } return "" } func (x *Password) GetUserId() string { if x != nil { return x.UserId } return "" } // CreatePasswordReq is a request to make a password. type CreatePasswordReq struct { state protoimpl.MessageState `protogen:"open.v1"` Password *Password `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreatePasswordReq) Reset() { *x = CreatePasswordReq{} mi := &file_api_api_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreatePasswordReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreatePasswordReq) ProtoMessage() {} func (x *CreatePasswordReq) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreatePasswordReq.ProtoReflect.Descriptor instead. func (*CreatePasswordReq) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{8} } func (x *CreatePasswordReq) GetPassword() *Password { if x != nil { return x.Password } return nil } // CreatePasswordResp returns the response from creating a password. type CreatePasswordResp struct { state protoimpl.MessageState `protogen:"open.v1"` AlreadyExists bool `protobuf:"varint,1,opt,name=already_exists,json=alreadyExists,proto3" json:"already_exists,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreatePasswordResp) Reset() { *x = CreatePasswordResp{} mi := &file_api_api_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreatePasswordResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreatePasswordResp) ProtoMessage() {} func (x *CreatePasswordResp) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreatePasswordResp.ProtoReflect.Descriptor instead. func (*CreatePasswordResp) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{9} } func (x *CreatePasswordResp) GetAlreadyExists() bool { if x != nil { return x.AlreadyExists } return false } // UpdatePasswordReq is a request to modify an existing password. type UpdatePasswordReq struct { state protoimpl.MessageState `protogen:"open.v1"` // The email used to lookup the password. This field cannot be modified Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` NewHash []byte `protobuf:"bytes,2,opt,name=new_hash,json=newHash,proto3" json:"new_hash,omitempty"` NewUsername string `protobuf:"bytes,3,opt,name=new_username,json=newUsername,proto3" json:"new_username,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdatePasswordReq) Reset() { *x = UpdatePasswordReq{} mi := &file_api_api_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdatePasswordReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePasswordReq) ProtoMessage() {} func (x *UpdatePasswordReq) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdatePasswordReq.ProtoReflect.Descriptor instead. func (*UpdatePasswordReq) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{10} } func (x *UpdatePasswordReq) GetEmail() string { if x != nil { return x.Email } return "" } func (x *UpdatePasswordReq) GetNewHash() []byte { if x != nil { return x.NewHash } return nil } func (x *UpdatePasswordReq) GetNewUsername() string { if x != nil { return x.NewUsername } return "" } // UpdatePasswordResp returns the response from modifying an existing password. type UpdatePasswordResp struct { state protoimpl.MessageState `protogen:"open.v1"` NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdatePasswordResp) Reset() { *x = UpdatePasswordResp{} mi := &file_api_api_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdatePasswordResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePasswordResp) ProtoMessage() {} func (x *UpdatePasswordResp) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdatePasswordResp.ProtoReflect.Descriptor instead. func (*UpdatePasswordResp) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{11} } func (x *UpdatePasswordResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } // DeletePasswordReq is a request to delete a password. type DeletePasswordReq struct { state protoimpl.MessageState `protogen:"open.v1"` Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeletePasswordReq) Reset() { *x = DeletePasswordReq{} mi := &file_api_api_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DeletePasswordReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeletePasswordReq) ProtoMessage() {} func (x *DeletePasswordReq) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DeletePasswordReq.ProtoReflect.Descriptor instead. func (*DeletePasswordReq) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{12} } func (x *DeletePasswordReq) GetEmail() string { if x != nil { return x.Email } return "" } // DeletePasswordResp returns the response from deleting a password. type DeletePasswordResp struct { state protoimpl.MessageState `protogen:"open.v1"` NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeletePasswordResp) Reset() { *x = DeletePasswordResp{} mi := &file_api_api_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DeletePasswordResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeletePasswordResp) ProtoMessage() {} func (x *DeletePasswordResp) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DeletePasswordResp.ProtoReflect.Descriptor instead. func (*DeletePasswordResp) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{13} } func (x *DeletePasswordResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } // ListPasswordReq is a request to enumerate passwords. type ListPasswordReq struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListPasswordReq) Reset() { *x = ListPasswordReq{} mi := &file_api_api_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ListPasswordReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListPasswordReq) ProtoMessage() {} func (x *ListPasswordReq) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListPasswordReq.ProtoReflect.Descriptor instead. func (*ListPasswordReq) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{14} } // ListPasswordResp returns a list of passwords. type ListPasswordResp struct { state protoimpl.MessageState `protogen:"open.v1"` Passwords []*Password `protobuf:"bytes,1,rep,name=passwords,proto3" json:"passwords,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListPasswordResp) Reset() { *x = ListPasswordResp{} mi := &file_api_api_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ListPasswordResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListPasswordResp) ProtoMessage() {} func (x *ListPasswordResp) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListPasswordResp.ProtoReflect.Descriptor instead. func (*ListPasswordResp) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{15} } func (x *ListPasswordResp) GetPasswords() []*Password { if x != nil { return x.Passwords } return nil } // VersionReq is a request to fetch version info. type VersionReq struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *VersionReq) Reset() { *x = VersionReq{} mi := &file_api_api_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *VersionReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*VersionReq) ProtoMessage() {} func (x *VersionReq) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use VersionReq.ProtoReflect.Descriptor instead. func (*VersionReq) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{16} } // VersionResp holds the version info of components. type VersionResp struct { state protoimpl.MessageState `protogen:"open.v1"` // Semantic version of the server. Server string `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` // Numeric version of the API. It increases every time a new call is added to the API. // Clients should use this info to determine if the server supports specific features. Api int32 `protobuf:"varint,2,opt,name=api,proto3" json:"api,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *VersionResp) Reset() { *x = VersionResp{} mi := &file_api_api_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *VersionResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*VersionResp) ProtoMessage() {} func (x *VersionResp) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use VersionResp.ProtoReflect.Descriptor instead. func (*VersionResp) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{17} } func (x *VersionResp) GetServer() string { if x != nil { return x.Server } return "" } func (x *VersionResp) GetApi() int32 { if x != nil { return x.Api } return 0 } // RefreshTokenRef contains the metadata for a refresh token that is managed by the storage. type RefreshTokenRef struct { state protoimpl.MessageState `protogen:"open.v1"` // ID of the refresh token. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` CreatedAt int64 `protobuf:"varint,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` LastUsed int64 `protobuf:"varint,6,opt,name=last_used,json=lastUsed,proto3" json:"last_used,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RefreshTokenRef) Reset() { *x = RefreshTokenRef{} mi := &file_api_api_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RefreshTokenRef) String() string { return protoimpl.X.MessageStringOf(x) } func (*RefreshTokenRef) ProtoMessage() {} func (x *RefreshTokenRef) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RefreshTokenRef.ProtoReflect.Descriptor instead. func (*RefreshTokenRef) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{18} } func (x *RefreshTokenRef) GetId() string { if x != nil { return x.Id } return "" } func (x *RefreshTokenRef) GetClientId() string { if x != nil { return x.ClientId } return "" } func (x *RefreshTokenRef) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } func (x *RefreshTokenRef) GetLastUsed() int64 { if x != nil { return x.LastUsed } return 0 } // ListRefreshReq is a request to enumerate the refresh tokens of a user. type ListRefreshReq struct { state protoimpl.MessageState `protogen:"open.v1"` // The "sub" claim returned in the ID Token. UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListRefreshReq) Reset() { *x = ListRefreshReq{} mi := &file_api_api_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ListRefreshReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListRefreshReq) ProtoMessage() {} func (x *ListRefreshReq) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListRefreshReq.ProtoReflect.Descriptor instead. func (*ListRefreshReq) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{19} } func (x *ListRefreshReq) GetUserId() string { if x != nil { return x.UserId } return "" } // ListRefreshResp returns a list of refresh tokens for a user. type ListRefreshResp struct { state protoimpl.MessageState `protogen:"open.v1"` RefreshTokens []*RefreshTokenRef `protobuf:"bytes,1,rep,name=refresh_tokens,json=refreshTokens,proto3" json:"refresh_tokens,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListRefreshResp) Reset() { *x = ListRefreshResp{} mi := &file_api_api_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ListRefreshResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListRefreshResp) ProtoMessage() {} func (x *ListRefreshResp) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListRefreshResp.ProtoReflect.Descriptor instead. func (*ListRefreshResp) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{20} } func (x *ListRefreshResp) GetRefreshTokens() []*RefreshTokenRef { if x != nil { return x.RefreshTokens } return nil } // RevokeRefreshReq is a request to revoke the refresh token of the user-client pair. type RevokeRefreshReq struct { state protoimpl.MessageState `protogen:"open.v1"` // The "sub" claim returned in the ID Token. UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RevokeRefreshReq) Reset() { *x = RevokeRefreshReq{} mi := &file_api_api_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RevokeRefreshReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*RevokeRefreshReq) ProtoMessage() {} func (x *RevokeRefreshReq) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RevokeRefreshReq.ProtoReflect.Descriptor instead. func (*RevokeRefreshReq) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{21} } func (x *RevokeRefreshReq) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *RevokeRefreshReq) GetClientId() string { if x != nil { return x.ClientId } return "" } // RevokeRefreshResp determines if the refresh token is revoked successfully. type RevokeRefreshResp struct { state protoimpl.MessageState `protogen:"open.v1"` // Set to true is refresh token was not found and token could not be revoked. NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RevokeRefreshResp) Reset() { *x = RevokeRefreshResp{} mi := &file_api_api_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RevokeRefreshResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*RevokeRefreshResp) ProtoMessage() {} func (x *RevokeRefreshResp) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RevokeRefreshResp.ProtoReflect.Descriptor instead. func (*RevokeRefreshResp) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{22} } func (x *RevokeRefreshResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } type VerifyPasswordReq struct { state protoimpl.MessageState `protogen:"open.v1"` Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *VerifyPasswordReq) Reset() { *x = VerifyPasswordReq{} mi := &file_api_api_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *VerifyPasswordReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*VerifyPasswordReq) ProtoMessage() {} func (x *VerifyPasswordReq) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use VerifyPasswordReq.ProtoReflect.Descriptor instead. func (*VerifyPasswordReq) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{23} } func (x *VerifyPasswordReq) GetEmail() string { if x != nil { return x.Email } return "" } func (x *VerifyPasswordReq) GetPassword() string { if x != nil { return x.Password } return "" } type VerifyPasswordResp struct { state protoimpl.MessageState `protogen:"open.v1"` Verified bool `protobuf:"varint,1,opt,name=verified,proto3" json:"verified,omitempty"` NotFound bool `protobuf:"varint,2,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *VerifyPasswordResp) Reset() { *x = VerifyPasswordResp{} mi := &file_api_api_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *VerifyPasswordResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*VerifyPasswordResp) ProtoMessage() {} func (x *VerifyPasswordResp) ProtoReflect() protoreflect.Message { mi := &file_api_api_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use VerifyPasswordResp.ProtoReflect.Descriptor instead. func (*VerifyPasswordResp) Descriptor() ([]byte, []int) { return file_api_api_proto_rawDescGZIP(), []int{24} } func (x *VerifyPasswordResp) GetVerified() bool { if x != nil { return x.Verified } return false } func (x *VerifyPasswordResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } var File_api_api_proto protoreflect.FileDescriptor var file_api_api_proto_rawDesc = string([]byte{ 0x0a, 0x0d, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x22, 0xf0, 0x01, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x36, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x5e, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x21, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2f, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xc9, 0x01, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x2f, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x69, 0x0a, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x3e, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x29, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x3b, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x22, 0x67, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x31, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x29, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x31, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x11, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x22, 0x3f, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2b, 0x0a, 0x09, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x09, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x0c, 0x0a, 0x0a, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x22, 0x37, 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x70, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x70, 0x69, 0x22, 0x7a, 0x0a, 0x0f, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x66, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x73, 0x65, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4e, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x12, 0x3b, 0x0a, 0x0e, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x66, 0x52, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x22, 0x48, 0x0a, 0x10, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x30, 0x0a, 0x11, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x45, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x4d, 0x0a, 0x12, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x32, 0xc7, 0x05, 0x0a, 0x03, 0x44, 0x65, 0x78, 0x12, 0x3d, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x2f, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x6f, 0x73, 0x2e, 0x64, 0x65, 0x78, 0x2e, 0x61, 0x70, 0x69, 0x5a, 0x19, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x78, 0x69, 0x64, 0x70, 0x2f, 0x64, 0x65, 0x78, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( file_api_api_proto_rawDescOnce sync.Once file_api_api_proto_rawDescData []byte ) func file_api_api_proto_rawDescGZIP() []byte { file_api_api_proto_rawDescOnce.Do(func() { file_api_api_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_api_proto_rawDesc), len(file_api_api_proto_rawDesc))) }) return file_api_api_proto_rawDescData } var file_api_api_proto_msgTypes = make([]protoimpl.MessageInfo, 25) var file_api_api_proto_goTypes = []any{ (*Client)(nil), // 0: api.Client (*CreateClientReq)(nil), // 1: api.CreateClientReq (*CreateClientResp)(nil), // 2: api.CreateClientResp (*DeleteClientReq)(nil), // 3: api.DeleteClientReq (*DeleteClientResp)(nil), // 4: api.DeleteClientResp (*UpdateClientReq)(nil), // 5: api.UpdateClientReq (*UpdateClientResp)(nil), // 6: api.UpdateClientResp (*Password)(nil), // 7: api.Password (*CreatePasswordReq)(nil), // 8: api.CreatePasswordReq (*CreatePasswordResp)(nil), // 9: api.CreatePasswordResp (*UpdatePasswordReq)(nil), // 10: api.UpdatePasswordReq (*UpdatePasswordResp)(nil), // 11: api.UpdatePasswordResp (*DeletePasswordReq)(nil), // 12: api.DeletePasswordReq (*DeletePasswordResp)(nil), // 13: api.DeletePasswordResp (*ListPasswordReq)(nil), // 14: api.ListPasswordReq (*ListPasswordResp)(nil), // 15: api.ListPasswordResp (*VersionReq)(nil), // 16: api.VersionReq (*VersionResp)(nil), // 17: api.VersionResp (*RefreshTokenRef)(nil), // 18: api.RefreshTokenRef (*ListRefreshReq)(nil), // 19: api.ListRefreshReq (*ListRefreshResp)(nil), // 20: api.ListRefreshResp (*RevokeRefreshReq)(nil), // 21: api.RevokeRefreshReq (*RevokeRefreshResp)(nil), // 22: api.RevokeRefreshResp (*VerifyPasswordReq)(nil), // 23: api.VerifyPasswordReq (*VerifyPasswordResp)(nil), // 24: api.VerifyPasswordResp } var file_api_api_proto_depIdxs = []int32{ 0, // 0: api.CreateClientReq.client:type_name -> api.Client 0, // 1: api.CreateClientResp.client:type_name -> api.Client 7, // 2: api.CreatePasswordReq.password:type_name -> api.Password 7, // 3: api.ListPasswordResp.passwords:type_name -> api.Password 18, // 4: api.ListRefreshResp.refresh_tokens:type_name -> api.RefreshTokenRef 1, // 5: api.Dex.CreateClient:input_type -> api.CreateClientReq 5, // 6: api.Dex.UpdateClient:input_type -> api.UpdateClientReq 3, // 7: api.Dex.DeleteClient:input_type -> api.DeleteClientReq 8, // 8: api.Dex.CreatePassword:input_type -> api.CreatePasswordReq 10, // 9: api.Dex.UpdatePassword:input_type -> api.UpdatePasswordReq 12, // 10: api.Dex.DeletePassword:input_type -> api.DeletePasswordReq 14, // 11: api.Dex.ListPasswords:input_type -> api.ListPasswordReq 16, // 12: api.Dex.GetVersion:input_type -> api.VersionReq 19, // 13: api.Dex.ListRefresh:input_type -> api.ListRefreshReq 21, // 14: api.Dex.RevokeRefresh:input_type -> api.RevokeRefreshReq 23, // 15: api.Dex.VerifyPassword:input_type -> api.VerifyPasswordReq 2, // 16: api.Dex.CreateClient:output_type -> api.CreateClientResp 6, // 17: api.Dex.UpdateClient:output_type -> api.UpdateClientResp 4, // 18: api.Dex.DeleteClient:output_type -> api.DeleteClientResp 9, // 19: api.Dex.CreatePassword:output_type -> api.CreatePasswordResp 11, // 20: api.Dex.UpdatePassword:output_type -> api.UpdatePasswordResp 13, // 21: api.Dex.DeletePassword:output_type -> api.DeletePasswordResp 15, // 22: api.Dex.ListPasswords:output_type -> api.ListPasswordResp 17, // 23: api.Dex.GetVersion:output_type -> api.VersionResp 20, // 24: api.Dex.ListRefresh:output_type -> api.ListRefreshResp 22, // 25: api.Dex.RevokeRefresh:output_type -> api.RevokeRefreshResp 24, // 26: api.Dex.VerifyPassword:output_type -> api.VerifyPasswordResp 16, // [16:27] is the sub-list for method output_type 5, // [5:16] is the sub-list for method input_type 5, // [5:5] is the sub-list for extension type_name 5, // [5:5] is the sub-list for extension extendee 0, // [0:5] is the sub-list for field type_name } func init() { file_api_api_proto_init() } func file_api_api_proto_init() { if File_api_api_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_api_proto_rawDesc), len(file_api_api_proto_rawDesc)), NumEnums: 0, NumMessages: 25, NumExtensions: 0, NumServices: 1, }, GoTypes: file_api_api_proto_goTypes, DependencyIndexes: file_api_api_proto_depIdxs, MessageInfos: file_api_api_proto_msgTypes, }.Build() File_api_api_proto = out.File file_api_api_proto_goTypes = nil file_api_api_proto_depIdxs = nil } ================================================ FILE: api/api.proto ================================================ syntax = "proto3"; package api; option java_package = "com.coreos.dex.api"; option go_package = "github.com/dexidp/dex/api"; // Client represents an OAuth2 client. message Client { string id = 1; string secret = 2; repeated string redirect_uris = 3; repeated string trusted_peers = 4; bool public = 5; string name = 6; string logo_url = 7; repeated string allowed_connectors = 8; } // CreateClientReq is a request to make a client. message CreateClientReq { Client client = 1; } // CreateClientResp returns the response from creating a client. message CreateClientResp { bool already_exists = 1; Client client = 2; } // DeleteClientReq is a request to delete a client. message DeleteClientReq { // The ID of the client. string id = 1; } // DeleteClientResp determines if the client is deleted successfully. message DeleteClientResp { bool not_found = 1; } // UpdateClientReq is a request to update an existing client. message UpdateClientReq { string id = 1; repeated string redirect_uris = 2; repeated string trusted_peers = 3; string name = 4; string logo_url = 5; repeated string allowed_connectors = 6; } // UpdateClientResp returns the response from updating a client. message UpdateClientResp { bool not_found = 1; } // TODO(ericchiang): expand this. // Password is an email for password mapping managed by the storage. message Password { string email = 1; // Currently we do not accept plain text passwords. Could be an option in the future. bytes hash = 2; string username = 3; string user_id = 4; } // CreatePasswordReq is a request to make a password. message CreatePasswordReq { Password password = 1; } // CreatePasswordResp returns the response from creating a password. message CreatePasswordResp { bool already_exists = 1; } // UpdatePasswordReq is a request to modify an existing password. message UpdatePasswordReq { // The email used to lookup the password. This field cannot be modified string email = 1; bytes new_hash = 2; string new_username = 3; } // UpdatePasswordResp returns the response from modifying an existing password. message UpdatePasswordResp { bool not_found = 1; } // DeletePasswordReq is a request to delete a password. message DeletePasswordReq { string email = 1; } // DeletePasswordResp returns the response from deleting a password. message DeletePasswordResp { bool not_found = 1; } // ListPasswordReq is a request to enumerate passwords. message ListPasswordReq {} // ListPasswordResp returns a list of passwords. message ListPasswordResp { repeated Password passwords = 1; } // VersionReq is a request to fetch version info. message VersionReq {} // VersionResp holds the version info of components. message VersionResp { // Semantic version of the server. string server = 1; // Numeric version of the API. It increases every time a new call is added to the API. // Clients should use this info to determine if the server supports specific features. int32 api = 2; } // RefreshTokenRef contains the metadata for a refresh token that is managed by the storage. message RefreshTokenRef { // ID of the refresh token. string id = 1; string client_id = 2; int64 created_at = 5; int64 last_used = 6; } // ListRefreshReq is a request to enumerate the refresh tokens of a user. message ListRefreshReq { // The "sub" claim returned in the ID Token. string user_id = 1; } // ListRefreshResp returns a list of refresh tokens for a user. message ListRefreshResp { repeated RefreshTokenRef refresh_tokens = 1; } // RevokeRefreshReq is a request to revoke the refresh token of the user-client pair. message RevokeRefreshReq { // The "sub" claim returned in the ID Token. string user_id = 1; string client_id = 2; } // RevokeRefreshResp determines if the refresh token is revoked successfully. message RevokeRefreshResp { // Set to true is refresh token was not found and token could not be revoked. bool not_found = 1; } message VerifyPasswordReq { string email = 1; string password = 2; } message VerifyPasswordResp { bool verified = 1; bool not_found = 2; } // Dex represents the dex gRPC service. service Dex { // CreateClient creates a client. rpc CreateClient(CreateClientReq) returns (CreateClientResp) {}; // UpdateClient updates an existing client rpc UpdateClient(UpdateClientReq) returns (UpdateClientResp) {}; // DeleteClient deletes the provided client. rpc DeleteClient(DeleteClientReq) returns (DeleteClientResp) {}; // CreatePassword creates a password. rpc CreatePassword(CreatePasswordReq) returns (CreatePasswordResp) {}; // UpdatePassword modifies existing password. rpc UpdatePassword(UpdatePasswordReq) returns (UpdatePasswordResp) {}; // DeletePassword deletes the password. rpc DeletePassword(DeletePasswordReq) returns (DeletePasswordResp) {}; // ListPassword lists all password entries. rpc ListPasswords(ListPasswordReq) returns (ListPasswordResp) {}; // GetVersion returns version information of the server. rpc GetVersion(VersionReq) returns (VersionResp) {}; // ListRefresh lists all the refresh token entries for a particular user. rpc ListRefresh(ListRefreshReq) returns (ListRefreshResp) {}; // RevokeRefresh revokes the refresh token for the provided user-client pair. // // Note that each user-client pair can have only one refresh token at a time. rpc RevokeRefresh(RevokeRefreshReq) returns (RevokeRefreshResp) {}; // VerifyPassword returns whether a password matches a hash for a specific email or not. rpc VerifyPassword(VerifyPasswordReq) returns (VerifyPasswordResp) {}; } ================================================ FILE: api/api_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 // - protoc v5.29.3 // source: api/api.proto package api import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.64.0 or later. const _ = grpc.SupportPackageIsVersion9 const ( Dex_CreateClient_FullMethodName = "/api.Dex/CreateClient" Dex_UpdateClient_FullMethodName = "/api.Dex/UpdateClient" Dex_DeleteClient_FullMethodName = "/api.Dex/DeleteClient" Dex_CreatePassword_FullMethodName = "/api.Dex/CreatePassword" Dex_UpdatePassword_FullMethodName = "/api.Dex/UpdatePassword" Dex_DeletePassword_FullMethodName = "/api.Dex/DeletePassword" Dex_ListPasswords_FullMethodName = "/api.Dex/ListPasswords" Dex_GetVersion_FullMethodName = "/api.Dex/GetVersion" Dex_ListRefresh_FullMethodName = "/api.Dex/ListRefresh" Dex_RevokeRefresh_FullMethodName = "/api.Dex/RevokeRefresh" Dex_VerifyPassword_FullMethodName = "/api.Dex/VerifyPassword" ) // DexClient is the client API for Dex service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // // Dex represents the dex gRPC service. type DexClient interface { // CreateClient creates a client. CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error) // UpdateClient updates an existing client UpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error) // DeleteClient deletes the provided client. DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error) // CreatePassword creates a password. CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error) // UpdatePassword modifies existing password. UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error) // DeletePassword deletes the password. DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error) // ListPassword lists all password entries. ListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error) // GetVersion returns version information of the server. GetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error) // ListRefresh lists all the refresh token entries for a particular user. ListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error) // RevokeRefresh revokes the refresh token for the provided user-client pair. // // Note that each user-client pair can have only one refresh token at a time. RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error) // VerifyPassword returns whether a password matches a hash for a specific email or not. VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error) } type dexClient struct { cc grpc.ClientConnInterface } func NewDexClient(cc grpc.ClientConnInterface) DexClient { return &dexClient{cc} } func (c *dexClient) CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CreateClientResp) err := c.cc.Invoke(ctx, Dex_CreateClient_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) UpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UpdateClientResp) err := c.cc.Invoke(ctx, Dex_UpdateClient_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeleteClientResp) err := c.cc.Invoke(ctx, Dex_DeleteClient_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CreatePasswordResp) err := c.cc.Invoke(ctx, Dex_CreatePassword_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UpdatePasswordResp) err := c.cc.Invoke(ctx, Dex_UpdatePassword_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeletePasswordResp) err := c.cc.Invoke(ctx, Dex_DeletePassword_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) ListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListPasswordResp) err := c.cc.Invoke(ctx, Dex_ListPasswords_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) GetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(VersionResp) err := c.cc.Invoke(ctx, Dex_GetVersion_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) ListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListRefreshResp) err := c.cc.Invoke(ctx, Dex_ListRefresh_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(RevokeRefreshResp) err := c.cc.Invoke(ctx, Dex_RevokeRefresh_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(VerifyPasswordResp) err := c.cc.Invoke(ctx, Dex_VerifyPassword_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // DexServer is the server API for Dex service. // All implementations must embed UnimplementedDexServer // for forward compatibility. // // Dex represents the dex gRPC service. type DexServer interface { // CreateClient creates a client. CreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error) // UpdateClient updates an existing client UpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error) // DeleteClient deletes the provided client. DeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error) // CreatePassword creates a password. CreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error) // UpdatePassword modifies existing password. UpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error) // DeletePassword deletes the password. DeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error) // ListPassword lists all password entries. ListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error) // GetVersion returns version information of the server. GetVersion(context.Context, *VersionReq) (*VersionResp, error) // ListRefresh lists all the refresh token entries for a particular user. ListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error) // RevokeRefresh revokes the refresh token for the provided user-client pair. // // Note that each user-client pair can have only one refresh token at a time. RevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error) // VerifyPassword returns whether a password matches a hash for a specific email or not. VerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error) mustEmbedUnimplementedDexServer() } // UnimplementedDexServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedDexServer struct{} func (UnimplementedDexServer) CreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateClient not implemented") } func (UnimplementedDexServer) UpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateClient not implemented") } func (UnimplementedDexServer) DeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error) { return nil, status.Errorf(codes.Unimplemented, "method DeleteClient not implemented") } func (UnimplementedDexServer) CreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error) { return nil, status.Errorf(codes.Unimplemented, "method CreatePassword not implemented") } func (UnimplementedDexServer) UpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdatePassword not implemented") } func (UnimplementedDexServer) DeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error) { return nil, status.Errorf(codes.Unimplemented, "method DeletePassword not implemented") } func (UnimplementedDexServer) ListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ListPasswords not implemented") } func (UnimplementedDexServer) GetVersion(context.Context, *VersionReq) (*VersionResp, error) { return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented") } func (UnimplementedDexServer) ListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ListRefresh not implemented") } func (UnimplementedDexServer) RevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error) { return nil, status.Errorf(codes.Unimplemented, "method RevokeRefresh not implemented") } func (UnimplementedDexServer) VerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error) { return nil, status.Errorf(codes.Unimplemented, "method VerifyPassword not implemented") } func (UnimplementedDexServer) mustEmbedUnimplementedDexServer() {} func (UnimplementedDexServer) testEmbeddedByValue() {} // UnsafeDexServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to DexServer will // result in compilation errors. type UnsafeDexServer interface { mustEmbedUnimplementedDexServer() } func RegisterDexServer(s grpc.ServiceRegistrar, srv DexServer) { // If the following call pancis, it indicates UnimplementedDexServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&Dex_ServiceDesc, srv) } func _Dex_CreateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CreateClientReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).CreateClient(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_CreateClient_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).CreateClient(ctx, req.(*CreateClientReq)) } return interceptor(ctx, in, info, handler) } func _Dex_UpdateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpdateClientReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).UpdateClient(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_UpdateClient_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).UpdateClient(ctx, req.(*UpdateClientReq)) } return interceptor(ctx, in, info, handler) } func _Dex_DeleteClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteClientReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).DeleteClient(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_DeleteClient_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).DeleteClient(ctx, req.(*DeleteClientReq)) } return interceptor(ctx, in, info, handler) } func _Dex_CreatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CreatePasswordReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).CreatePassword(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_CreatePassword_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).CreatePassword(ctx, req.(*CreatePasswordReq)) } return interceptor(ctx, in, info, handler) } func _Dex_UpdatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpdatePasswordReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).UpdatePassword(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_UpdatePassword_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).UpdatePassword(ctx, req.(*UpdatePasswordReq)) } return interceptor(ctx, in, info, handler) } func _Dex_DeletePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeletePasswordReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).DeletePassword(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_DeletePassword_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).DeletePassword(ctx, req.(*DeletePasswordReq)) } return interceptor(ctx, in, info, handler) } func _Dex_ListPasswords_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListPasswordReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).ListPasswords(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_ListPasswords_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).ListPasswords(ctx, req.(*ListPasswordReq)) } return interceptor(ctx, in, info, handler) } func _Dex_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(VersionReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).GetVersion(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_GetVersion_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).GetVersion(ctx, req.(*VersionReq)) } return interceptor(ctx, in, info, handler) } func _Dex_ListRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListRefreshReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).ListRefresh(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_ListRefresh_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).ListRefresh(ctx, req.(*ListRefreshReq)) } return interceptor(ctx, in, info, handler) } func _Dex_RevokeRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(RevokeRefreshReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).RevokeRefresh(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_RevokeRefresh_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).RevokeRefresh(ctx, req.(*RevokeRefreshReq)) } return interceptor(ctx, in, info, handler) } func _Dex_VerifyPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(VerifyPasswordReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).VerifyPassword(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_VerifyPassword_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).VerifyPassword(ctx, req.(*VerifyPasswordReq)) } return interceptor(ctx, in, info, handler) } // Dex_ServiceDesc is the grpc.ServiceDesc for Dex service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Dex_ServiceDesc = grpc.ServiceDesc{ ServiceName: "api.Dex", HandlerType: (*DexServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "CreateClient", Handler: _Dex_CreateClient_Handler, }, { MethodName: "UpdateClient", Handler: _Dex_UpdateClient_Handler, }, { MethodName: "DeleteClient", Handler: _Dex_DeleteClient_Handler, }, { MethodName: "CreatePassword", Handler: _Dex_CreatePassword_Handler, }, { MethodName: "UpdatePassword", Handler: _Dex_UpdatePassword_Handler, }, { MethodName: "DeletePassword", Handler: _Dex_DeletePassword_Handler, }, { MethodName: "ListPasswords", Handler: _Dex_ListPasswords_Handler, }, { MethodName: "GetVersion", Handler: _Dex_GetVersion_Handler, }, { MethodName: "ListRefresh", Handler: _Dex_ListRefresh_Handler, }, { MethodName: "RevokeRefresh", Handler: _Dex_RevokeRefresh_Handler, }, { MethodName: "VerifyPassword", Handler: _Dex_VerifyPassword_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "api/api.proto", } ================================================ FILE: api/v2/api.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.5 // protoc v5.29.3 // source: api/v2/api.proto package api import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // Client represents an OAuth2 client. type Client struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Secret string `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"` RedirectUris []string `protobuf:"bytes,3,rep,name=redirect_uris,json=redirectUris,proto3" json:"redirect_uris,omitempty"` TrustedPeers []string `protobuf:"bytes,4,rep,name=trusted_peers,json=trustedPeers,proto3" json:"trusted_peers,omitempty"` Public bool `protobuf:"varint,5,opt,name=public,proto3" json:"public,omitempty"` Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` LogoUrl string `protobuf:"bytes,7,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"` AllowedConnectors []string `protobuf:"bytes,8,rep,name=allowed_connectors,json=allowedConnectors,proto3" json:"allowed_connectors,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Client) Reset() { *x = Client{} mi := &file_api_v2_api_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *Client) String() string { return protoimpl.X.MessageStringOf(x) } func (*Client) ProtoMessage() {} func (x *Client) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Client.ProtoReflect.Descriptor instead. func (*Client) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{0} } func (x *Client) GetId() string { if x != nil { return x.Id } return "" } func (x *Client) GetSecret() string { if x != nil { return x.Secret } return "" } func (x *Client) GetRedirectUris() []string { if x != nil { return x.RedirectUris } return nil } func (x *Client) GetTrustedPeers() []string { if x != nil { return x.TrustedPeers } return nil } func (x *Client) GetPublic() bool { if x != nil { return x.Public } return false } func (x *Client) GetName() string { if x != nil { return x.Name } return "" } func (x *Client) GetLogoUrl() string { if x != nil { return x.LogoUrl } return "" } func (x *Client) GetAllowedConnectors() []string { if x != nil { return x.AllowedConnectors } return nil } // ClientInfo represents an OAuth2 client without sensitive information. type ClientInfo struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` RedirectUris []string `protobuf:"bytes,2,rep,name=redirect_uris,json=redirectUris,proto3" json:"redirect_uris,omitempty"` TrustedPeers []string `protobuf:"bytes,3,rep,name=trusted_peers,json=trustedPeers,proto3" json:"trusted_peers,omitempty"` Public bool `protobuf:"varint,4,opt,name=public,proto3" json:"public,omitempty"` Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"` LogoUrl string `protobuf:"bytes,6,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"` AllowedConnectors []string `protobuf:"bytes,7,rep,name=allowed_connectors,json=allowedConnectors,proto3" json:"allowed_connectors,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ClientInfo) Reset() { *x = ClientInfo{} mi := &file_api_v2_api_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ClientInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*ClientInfo) ProtoMessage() {} func (x *ClientInfo) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ClientInfo.ProtoReflect.Descriptor instead. func (*ClientInfo) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{1} } func (x *ClientInfo) GetId() string { if x != nil { return x.Id } return "" } func (x *ClientInfo) GetRedirectUris() []string { if x != nil { return x.RedirectUris } return nil } func (x *ClientInfo) GetTrustedPeers() []string { if x != nil { return x.TrustedPeers } return nil } func (x *ClientInfo) GetPublic() bool { if x != nil { return x.Public } return false } func (x *ClientInfo) GetName() string { if x != nil { return x.Name } return "" } func (x *ClientInfo) GetLogoUrl() string { if x != nil { return x.LogoUrl } return "" } func (x *ClientInfo) GetAllowedConnectors() []string { if x != nil { return x.AllowedConnectors } return nil } // GetClientReq is a request to retrieve client details. type GetClientReq struct { state protoimpl.MessageState `protogen:"open.v1"` // The ID of the client. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetClientReq) Reset() { *x = GetClientReq{} mi := &file_api_v2_api_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetClientReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetClientReq) ProtoMessage() {} func (x *GetClientReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetClientReq.ProtoReflect.Descriptor instead. func (*GetClientReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{2} } func (x *GetClientReq) GetId() string { if x != nil { return x.Id } return "" } // GetClientResp returns the client details. type GetClientResp struct { state protoimpl.MessageState `protogen:"open.v1"` Client *Client `protobuf:"bytes,1,opt,name=client,proto3" json:"client,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GetClientResp) Reset() { *x = GetClientResp{} mi := &file_api_v2_api_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GetClientResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetClientResp) ProtoMessage() {} func (x *GetClientResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetClientResp.ProtoReflect.Descriptor instead. func (*GetClientResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{3} } func (x *GetClientResp) GetClient() *Client { if x != nil { return x.Client } return nil } // CreateClientReq is a request to make a client. type CreateClientReq struct { state protoimpl.MessageState `protogen:"open.v1"` Client *Client `protobuf:"bytes,1,opt,name=client,proto3" json:"client,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreateClientReq) Reset() { *x = CreateClientReq{} mi := &file_api_v2_api_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreateClientReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreateClientReq) ProtoMessage() {} func (x *CreateClientReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreateClientReq.ProtoReflect.Descriptor instead. func (*CreateClientReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{4} } func (x *CreateClientReq) GetClient() *Client { if x != nil { return x.Client } return nil } // CreateClientResp returns the response from creating a client. type CreateClientResp struct { state protoimpl.MessageState `protogen:"open.v1"` AlreadyExists bool `protobuf:"varint,1,opt,name=already_exists,json=alreadyExists,proto3" json:"already_exists,omitempty"` Client *Client `protobuf:"bytes,2,opt,name=client,proto3" json:"client,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreateClientResp) Reset() { *x = CreateClientResp{} mi := &file_api_v2_api_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreateClientResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreateClientResp) ProtoMessage() {} func (x *CreateClientResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreateClientResp.ProtoReflect.Descriptor instead. func (*CreateClientResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{5} } func (x *CreateClientResp) GetAlreadyExists() bool { if x != nil { return x.AlreadyExists } return false } func (x *CreateClientResp) GetClient() *Client { if x != nil { return x.Client } return nil } // DeleteClientReq is a request to delete a client. type DeleteClientReq struct { state protoimpl.MessageState `protogen:"open.v1"` // The ID of the client. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeleteClientReq) Reset() { *x = DeleteClientReq{} mi := &file_api_v2_api_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DeleteClientReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeleteClientReq) ProtoMessage() {} func (x *DeleteClientReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DeleteClientReq.ProtoReflect.Descriptor instead. func (*DeleteClientReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{6} } func (x *DeleteClientReq) GetId() string { if x != nil { return x.Id } return "" } // DeleteClientResp determines if the client is deleted successfully. type DeleteClientResp struct { state protoimpl.MessageState `protogen:"open.v1"` NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeleteClientResp) Reset() { *x = DeleteClientResp{} mi := &file_api_v2_api_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DeleteClientResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeleteClientResp) ProtoMessage() {} func (x *DeleteClientResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DeleteClientResp.ProtoReflect.Descriptor instead. func (*DeleteClientResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{7} } func (x *DeleteClientResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } // UpdateClientReq is a request to update an existing client. type UpdateClientReq struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` RedirectUris []string `protobuf:"bytes,2,rep,name=redirect_uris,json=redirectUris,proto3" json:"redirect_uris,omitempty"` TrustedPeers []string `protobuf:"bytes,3,rep,name=trusted_peers,json=trustedPeers,proto3" json:"trusted_peers,omitempty"` Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` LogoUrl string `protobuf:"bytes,5,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"` AllowedConnectors []string `protobuf:"bytes,6,rep,name=allowed_connectors,json=allowedConnectors,proto3" json:"allowed_connectors,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateClientReq) Reset() { *x = UpdateClientReq{} mi := &file_api_v2_api_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdateClientReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateClientReq) ProtoMessage() {} func (x *UpdateClientReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateClientReq.ProtoReflect.Descriptor instead. func (*UpdateClientReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{8} } func (x *UpdateClientReq) GetId() string { if x != nil { return x.Id } return "" } func (x *UpdateClientReq) GetRedirectUris() []string { if x != nil { return x.RedirectUris } return nil } func (x *UpdateClientReq) GetTrustedPeers() []string { if x != nil { return x.TrustedPeers } return nil } func (x *UpdateClientReq) GetName() string { if x != nil { return x.Name } return "" } func (x *UpdateClientReq) GetLogoUrl() string { if x != nil { return x.LogoUrl } return "" } func (x *UpdateClientReq) GetAllowedConnectors() []string { if x != nil { return x.AllowedConnectors } return nil } // UpdateClientResp returns the response from updating a client. type UpdateClientResp struct { state protoimpl.MessageState `protogen:"open.v1"` NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateClientResp) Reset() { *x = UpdateClientResp{} mi := &file_api_v2_api_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdateClientResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateClientResp) ProtoMessage() {} func (x *UpdateClientResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateClientResp.ProtoReflect.Descriptor instead. func (*UpdateClientResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{9} } func (x *UpdateClientResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } // ListClientReq is a request to enumerate clients. type ListClientReq struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListClientReq) Reset() { *x = ListClientReq{} mi := &file_api_v2_api_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ListClientReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListClientReq) ProtoMessage() {} func (x *ListClientReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListClientReq.ProtoReflect.Descriptor instead. func (*ListClientReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{10} } // ListClientResp returns a list of clients. type ListClientResp struct { state protoimpl.MessageState `protogen:"open.v1"` Clients []*ClientInfo `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListClientResp) Reset() { *x = ListClientResp{} mi := &file_api_v2_api_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ListClientResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListClientResp) ProtoMessage() {} func (x *ListClientResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListClientResp.ProtoReflect.Descriptor instead. func (*ListClientResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{11} } func (x *ListClientResp) GetClients() []*ClientInfo { if x != nil { return x.Clients } return nil } // Password is an email for password mapping managed by the storage. type Password struct { state protoimpl.MessageState `protogen:"open.v1"` Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` // Currently we do not accept plain text passwords. Could be an option in the future. Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"` UserId string `protobuf:"bytes,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Password) Reset() { *x = Password{} mi := &file_api_v2_api_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *Password) String() string { return protoimpl.X.MessageStringOf(x) } func (*Password) ProtoMessage() {} func (x *Password) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Password.ProtoReflect.Descriptor instead. func (*Password) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{12} } func (x *Password) GetEmail() string { if x != nil { return x.Email } return "" } func (x *Password) GetHash() []byte { if x != nil { return x.Hash } return nil } func (x *Password) GetUsername() string { if x != nil { return x.Username } return "" } func (x *Password) GetUserId() string { if x != nil { return x.UserId } return "" } // CreatePasswordReq is a request to make a password. type CreatePasswordReq struct { state protoimpl.MessageState `protogen:"open.v1"` Password *Password `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreatePasswordReq) Reset() { *x = CreatePasswordReq{} mi := &file_api_v2_api_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreatePasswordReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreatePasswordReq) ProtoMessage() {} func (x *CreatePasswordReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreatePasswordReq.ProtoReflect.Descriptor instead. func (*CreatePasswordReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{13} } func (x *CreatePasswordReq) GetPassword() *Password { if x != nil { return x.Password } return nil } // CreatePasswordResp returns the response from creating a password. type CreatePasswordResp struct { state protoimpl.MessageState `protogen:"open.v1"` AlreadyExists bool `protobuf:"varint,1,opt,name=already_exists,json=alreadyExists,proto3" json:"already_exists,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreatePasswordResp) Reset() { *x = CreatePasswordResp{} mi := &file_api_v2_api_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreatePasswordResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreatePasswordResp) ProtoMessage() {} func (x *CreatePasswordResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreatePasswordResp.ProtoReflect.Descriptor instead. func (*CreatePasswordResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{14} } func (x *CreatePasswordResp) GetAlreadyExists() bool { if x != nil { return x.AlreadyExists } return false } // UpdatePasswordReq is a request to modify an existing password. type UpdatePasswordReq struct { state protoimpl.MessageState `protogen:"open.v1"` // The email used to lookup the password. This field cannot be modified Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` NewHash []byte `protobuf:"bytes,2,opt,name=new_hash,json=newHash,proto3" json:"new_hash,omitempty"` NewUsername string `protobuf:"bytes,3,opt,name=new_username,json=newUsername,proto3" json:"new_username,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdatePasswordReq) Reset() { *x = UpdatePasswordReq{} mi := &file_api_v2_api_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdatePasswordReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePasswordReq) ProtoMessage() {} func (x *UpdatePasswordReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdatePasswordReq.ProtoReflect.Descriptor instead. func (*UpdatePasswordReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{15} } func (x *UpdatePasswordReq) GetEmail() string { if x != nil { return x.Email } return "" } func (x *UpdatePasswordReq) GetNewHash() []byte { if x != nil { return x.NewHash } return nil } func (x *UpdatePasswordReq) GetNewUsername() string { if x != nil { return x.NewUsername } return "" } // UpdatePasswordResp returns the response from modifying an existing password. type UpdatePasswordResp struct { state protoimpl.MessageState `protogen:"open.v1"` NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdatePasswordResp) Reset() { *x = UpdatePasswordResp{} mi := &file_api_v2_api_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdatePasswordResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdatePasswordResp) ProtoMessage() {} func (x *UpdatePasswordResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdatePasswordResp.ProtoReflect.Descriptor instead. func (*UpdatePasswordResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{16} } func (x *UpdatePasswordResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } // DeletePasswordReq is a request to delete a password. type DeletePasswordReq struct { state protoimpl.MessageState `protogen:"open.v1"` Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeletePasswordReq) Reset() { *x = DeletePasswordReq{} mi := &file_api_v2_api_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DeletePasswordReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeletePasswordReq) ProtoMessage() {} func (x *DeletePasswordReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DeletePasswordReq.ProtoReflect.Descriptor instead. func (*DeletePasswordReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{17} } func (x *DeletePasswordReq) GetEmail() string { if x != nil { return x.Email } return "" } // DeletePasswordResp returns the response from deleting a password. type DeletePasswordResp struct { state protoimpl.MessageState `protogen:"open.v1"` NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeletePasswordResp) Reset() { *x = DeletePasswordResp{} mi := &file_api_v2_api_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DeletePasswordResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeletePasswordResp) ProtoMessage() {} func (x *DeletePasswordResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DeletePasswordResp.ProtoReflect.Descriptor instead. func (*DeletePasswordResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{18} } func (x *DeletePasswordResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } // ListPasswordReq is a request to enumerate passwords. type ListPasswordReq struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListPasswordReq) Reset() { *x = ListPasswordReq{} mi := &file_api_v2_api_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ListPasswordReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListPasswordReq) ProtoMessage() {} func (x *ListPasswordReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListPasswordReq.ProtoReflect.Descriptor instead. func (*ListPasswordReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{19} } // ListPasswordResp returns a list of passwords. type ListPasswordResp struct { state protoimpl.MessageState `protogen:"open.v1"` Passwords []*Password `protobuf:"bytes,1,rep,name=passwords,proto3" json:"passwords,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListPasswordResp) Reset() { *x = ListPasswordResp{} mi := &file_api_v2_api_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ListPasswordResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListPasswordResp) ProtoMessage() {} func (x *ListPasswordResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListPasswordResp.ProtoReflect.Descriptor instead. func (*ListPasswordResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{20} } func (x *ListPasswordResp) GetPasswords() []*Password { if x != nil { return x.Passwords } return nil } // Connector is a strategy used by Dex for authenticating a user against another identity provider type Connector struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` Config []byte `protobuf:"bytes,4,opt,name=config,proto3" json:"config,omitempty"` GrantTypes []string `protobuf:"bytes,5,rep,name=grant_types,json=grantTypes,proto3" json:"grant_types,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Connector) Reset() { *x = Connector{} mi := &file_api_v2_api_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *Connector) String() string { return protoimpl.X.MessageStringOf(x) } func (*Connector) ProtoMessage() {} func (x *Connector) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Connector.ProtoReflect.Descriptor instead. func (*Connector) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{21} } func (x *Connector) GetId() string { if x != nil { return x.Id } return "" } func (x *Connector) GetType() string { if x != nil { return x.Type } return "" } func (x *Connector) GetName() string { if x != nil { return x.Name } return "" } func (x *Connector) GetConfig() []byte { if x != nil { return x.Config } return nil } func (x *Connector) GetGrantTypes() []string { if x != nil { return x.GrantTypes } return nil } // CreateConnectorReq is a request to make a connector. type CreateConnectorReq struct { state protoimpl.MessageState `protogen:"open.v1"` Connector *Connector `protobuf:"bytes,1,opt,name=connector,proto3" json:"connector,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreateConnectorReq) Reset() { *x = CreateConnectorReq{} mi := &file_api_v2_api_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreateConnectorReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreateConnectorReq) ProtoMessage() {} func (x *CreateConnectorReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreateConnectorReq.ProtoReflect.Descriptor instead. func (*CreateConnectorReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{22} } func (x *CreateConnectorReq) GetConnector() *Connector { if x != nil { return x.Connector } return nil } // CreateConnectorResp returns the response from creating a connector. type CreateConnectorResp struct { state protoimpl.MessageState `protogen:"open.v1"` AlreadyExists bool `protobuf:"varint,1,opt,name=already_exists,json=alreadyExists,proto3" json:"already_exists,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *CreateConnectorResp) Reset() { *x = CreateConnectorResp{} mi := &file_api_v2_api_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *CreateConnectorResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*CreateConnectorResp) ProtoMessage() {} func (x *CreateConnectorResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CreateConnectorResp.ProtoReflect.Descriptor instead. func (*CreateConnectorResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{23} } func (x *CreateConnectorResp) GetAlreadyExists() bool { if x != nil { return x.AlreadyExists } return false } // GrantTypes wraps a list of grant types to distinguish between // "not specified" (no update) and "empty list" (unrestricted). type GrantTypes struct { state protoimpl.MessageState `protogen:"open.v1"` GrantTypes []string `protobuf:"bytes,1,rep,name=grant_types,json=grantTypes,proto3" json:"grant_types,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GrantTypes) Reset() { *x = GrantTypes{} mi := &file_api_v2_api_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *GrantTypes) String() string { return protoimpl.X.MessageStringOf(x) } func (*GrantTypes) ProtoMessage() {} func (x *GrantTypes) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GrantTypes.ProtoReflect.Descriptor instead. func (*GrantTypes) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{24} } func (x *GrantTypes) GetGrantTypes() []string { if x != nil { return x.GrantTypes } return nil } // UpdateConnectorReq is a request to modify an existing connector. type UpdateConnectorReq struct { state protoimpl.MessageState `protogen:"open.v1"` // The id used to lookup the connector. This field cannot be modified Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` NewType string `protobuf:"bytes,2,opt,name=new_type,json=newType,proto3" json:"new_type,omitempty"` NewName string `protobuf:"bytes,3,opt,name=new_name,json=newName,proto3" json:"new_name,omitempty"` NewConfig []byte `protobuf:"bytes,4,opt,name=new_config,json=newConfig,proto3" json:"new_config,omitempty"` // If set, updates the connector's allowed grant types. // An empty grant_types list means unrestricted (all grant types allowed). // If not set (null), grant types are not modified. NewGrantTypes *GrantTypes `protobuf:"bytes,5,opt,name=new_grant_types,json=newGrantTypes,proto3" json:"new_grant_types,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateConnectorReq) Reset() { *x = UpdateConnectorReq{} mi := &file_api_v2_api_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdateConnectorReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateConnectorReq) ProtoMessage() {} func (x *UpdateConnectorReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateConnectorReq.ProtoReflect.Descriptor instead. func (*UpdateConnectorReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{25} } func (x *UpdateConnectorReq) GetId() string { if x != nil { return x.Id } return "" } func (x *UpdateConnectorReq) GetNewType() string { if x != nil { return x.NewType } return "" } func (x *UpdateConnectorReq) GetNewName() string { if x != nil { return x.NewName } return "" } func (x *UpdateConnectorReq) GetNewConfig() []byte { if x != nil { return x.NewConfig } return nil } func (x *UpdateConnectorReq) GetNewGrantTypes() *GrantTypes { if x != nil { return x.NewGrantTypes } return nil } // UpdateConnectorResp returns the response from modifying an existing connector. type UpdateConnectorResp struct { state protoimpl.MessageState `protogen:"open.v1"` NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateConnectorResp) Reset() { *x = UpdateConnectorResp{} mi := &file_api_v2_api_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *UpdateConnectorResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateConnectorResp) ProtoMessage() {} func (x *UpdateConnectorResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateConnectorResp.ProtoReflect.Descriptor instead. func (*UpdateConnectorResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{26} } func (x *UpdateConnectorResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } // DeleteConnectorReq is a request to delete a connector. type DeleteConnectorReq struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeleteConnectorReq) Reset() { *x = DeleteConnectorReq{} mi := &file_api_v2_api_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DeleteConnectorReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeleteConnectorReq) ProtoMessage() {} func (x *DeleteConnectorReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DeleteConnectorReq.ProtoReflect.Descriptor instead. func (*DeleteConnectorReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{27} } func (x *DeleteConnectorReq) GetId() string { if x != nil { return x.Id } return "" } // DeleteConnectorResp returns the response from deleting a connector. type DeleteConnectorResp struct { state protoimpl.MessageState `protogen:"open.v1"` NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DeleteConnectorResp) Reset() { *x = DeleteConnectorResp{} mi := &file_api_v2_api_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DeleteConnectorResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*DeleteConnectorResp) ProtoMessage() {} func (x *DeleteConnectorResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DeleteConnectorResp.ProtoReflect.Descriptor instead. func (*DeleteConnectorResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{28} } func (x *DeleteConnectorResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } // ListConnectorReq is a request to enumerate connectors. type ListConnectorReq struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListConnectorReq) Reset() { *x = ListConnectorReq{} mi := &file_api_v2_api_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ListConnectorReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListConnectorReq) ProtoMessage() {} func (x *ListConnectorReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListConnectorReq.ProtoReflect.Descriptor instead. func (*ListConnectorReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{29} } // ListConnectorResp returns a list of connectors. type ListConnectorResp struct { state protoimpl.MessageState `protogen:"open.v1"` Connectors []*Connector `protobuf:"bytes,1,rep,name=connectors,proto3" json:"connectors,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListConnectorResp) Reset() { *x = ListConnectorResp{} mi := &file_api_v2_api_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ListConnectorResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListConnectorResp) ProtoMessage() {} func (x *ListConnectorResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListConnectorResp.ProtoReflect.Descriptor instead. func (*ListConnectorResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{30} } func (x *ListConnectorResp) GetConnectors() []*Connector { if x != nil { return x.Connectors } return nil } // VersionReq is a request to fetch version info. type VersionReq struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *VersionReq) Reset() { *x = VersionReq{} mi := &file_api_v2_api_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *VersionReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*VersionReq) ProtoMessage() {} func (x *VersionReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use VersionReq.ProtoReflect.Descriptor instead. func (*VersionReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{31} } // VersionResp holds the version info of components. type VersionResp struct { state protoimpl.MessageState `protogen:"open.v1"` // Semantic version of the server. Server string `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` // Numeric version of the API. It increases every time a new call is added to the API. // Clients should use this info to determine if the server supports specific features. Api int32 `protobuf:"varint,2,opt,name=api,proto3" json:"api,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *VersionResp) Reset() { *x = VersionResp{} mi := &file_api_v2_api_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *VersionResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*VersionResp) ProtoMessage() {} func (x *VersionResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use VersionResp.ProtoReflect.Descriptor instead. func (*VersionResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{32} } func (x *VersionResp) GetServer() string { if x != nil { return x.Server } return "" } func (x *VersionResp) GetApi() int32 { if x != nil { return x.Api } return 0 } // DiscoveryReq is a request to fetch discover information. type DiscoveryReq struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DiscoveryReq) Reset() { *x = DiscoveryReq{} mi := &file_api_v2_api_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DiscoveryReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*DiscoveryReq) ProtoMessage() {} func (x *DiscoveryReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DiscoveryReq.ProtoReflect.Descriptor instead. func (*DiscoveryReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{33} } // DiscoverResp holds the version oidc disovery info. type DiscoveryResp struct { state protoimpl.MessageState `protogen:"open.v1"` Issuer string `protobuf:"bytes,1,opt,name=issuer,proto3" json:"issuer,omitempty"` AuthorizationEndpoint string `protobuf:"bytes,2,opt,name=authorization_endpoint,json=authorizationEndpoint,proto3" json:"authorization_endpoint,omitempty"` TokenEndpoint string `protobuf:"bytes,3,opt,name=token_endpoint,json=tokenEndpoint,proto3" json:"token_endpoint,omitempty"` JwksUri string `protobuf:"bytes,4,opt,name=jwks_uri,json=jwksUri,proto3" json:"jwks_uri,omitempty"` UserinfoEndpoint string `protobuf:"bytes,5,opt,name=userinfo_endpoint,json=userinfoEndpoint,proto3" json:"userinfo_endpoint,omitempty"` DeviceAuthorizationEndpoint string `protobuf:"bytes,6,opt,name=device_authorization_endpoint,json=deviceAuthorizationEndpoint,proto3" json:"device_authorization_endpoint,omitempty"` IntrospectionEndpoint string `protobuf:"bytes,7,opt,name=introspection_endpoint,json=introspectionEndpoint,proto3" json:"introspection_endpoint,omitempty"` GrantTypesSupported []string `protobuf:"bytes,8,rep,name=grant_types_supported,json=grantTypesSupported,proto3" json:"grant_types_supported,omitempty"` ResponseTypesSupported []string `protobuf:"bytes,9,rep,name=response_types_supported,json=responseTypesSupported,proto3" json:"response_types_supported,omitempty"` SubjectTypesSupported []string `protobuf:"bytes,10,rep,name=subject_types_supported,json=subjectTypesSupported,proto3" json:"subject_types_supported,omitempty"` IdTokenSigningAlgValuesSupported []string `protobuf:"bytes,11,rep,name=id_token_signing_alg_values_supported,json=idTokenSigningAlgValuesSupported,proto3" json:"id_token_signing_alg_values_supported,omitempty"` CodeChallengeMethodsSupported []string `protobuf:"bytes,12,rep,name=code_challenge_methods_supported,json=codeChallengeMethodsSupported,proto3" json:"code_challenge_methods_supported,omitempty"` ScopesSupported []string `protobuf:"bytes,13,rep,name=scopes_supported,json=scopesSupported,proto3" json:"scopes_supported,omitempty"` TokenEndpointAuthMethodsSupported []string `protobuf:"bytes,14,rep,name=token_endpoint_auth_methods_supported,json=tokenEndpointAuthMethodsSupported,proto3" json:"token_endpoint_auth_methods_supported,omitempty"` ClaimsSupported []string `protobuf:"bytes,15,rep,name=claims_supported,json=claimsSupported,proto3" json:"claims_supported,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *DiscoveryResp) Reset() { *x = DiscoveryResp{} mi := &file_api_v2_api_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *DiscoveryResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*DiscoveryResp) ProtoMessage() {} func (x *DiscoveryResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DiscoveryResp.ProtoReflect.Descriptor instead. func (*DiscoveryResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{34} } func (x *DiscoveryResp) GetIssuer() string { if x != nil { return x.Issuer } return "" } func (x *DiscoveryResp) GetAuthorizationEndpoint() string { if x != nil { return x.AuthorizationEndpoint } return "" } func (x *DiscoveryResp) GetTokenEndpoint() string { if x != nil { return x.TokenEndpoint } return "" } func (x *DiscoveryResp) GetJwksUri() string { if x != nil { return x.JwksUri } return "" } func (x *DiscoveryResp) GetUserinfoEndpoint() string { if x != nil { return x.UserinfoEndpoint } return "" } func (x *DiscoveryResp) GetDeviceAuthorizationEndpoint() string { if x != nil { return x.DeviceAuthorizationEndpoint } return "" } func (x *DiscoveryResp) GetIntrospectionEndpoint() string { if x != nil { return x.IntrospectionEndpoint } return "" } func (x *DiscoveryResp) GetGrantTypesSupported() []string { if x != nil { return x.GrantTypesSupported } return nil } func (x *DiscoveryResp) GetResponseTypesSupported() []string { if x != nil { return x.ResponseTypesSupported } return nil } func (x *DiscoveryResp) GetSubjectTypesSupported() []string { if x != nil { return x.SubjectTypesSupported } return nil } func (x *DiscoveryResp) GetIdTokenSigningAlgValuesSupported() []string { if x != nil { return x.IdTokenSigningAlgValuesSupported } return nil } func (x *DiscoveryResp) GetCodeChallengeMethodsSupported() []string { if x != nil { return x.CodeChallengeMethodsSupported } return nil } func (x *DiscoveryResp) GetScopesSupported() []string { if x != nil { return x.ScopesSupported } return nil } func (x *DiscoveryResp) GetTokenEndpointAuthMethodsSupported() []string { if x != nil { return x.TokenEndpointAuthMethodsSupported } return nil } func (x *DiscoveryResp) GetClaimsSupported() []string { if x != nil { return x.ClaimsSupported } return nil } // RefreshTokenRef contains the metadata for a refresh token that is managed by the storage. type RefreshTokenRef struct { state protoimpl.MessageState `protogen:"open.v1"` // ID of the refresh token. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` CreatedAt int64 `protobuf:"varint,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` LastUsed int64 `protobuf:"varint,6,opt,name=last_used,json=lastUsed,proto3" json:"last_used,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RefreshTokenRef) Reset() { *x = RefreshTokenRef{} mi := &file_api_v2_api_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RefreshTokenRef) String() string { return protoimpl.X.MessageStringOf(x) } func (*RefreshTokenRef) ProtoMessage() {} func (x *RefreshTokenRef) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RefreshTokenRef.ProtoReflect.Descriptor instead. func (*RefreshTokenRef) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{35} } func (x *RefreshTokenRef) GetId() string { if x != nil { return x.Id } return "" } func (x *RefreshTokenRef) GetClientId() string { if x != nil { return x.ClientId } return "" } func (x *RefreshTokenRef) GetCreatedAt() int64 { if x != nil { return x.CreatedAt } return 0 } func (x *RefreshTokenRef) GetLastUsed() int64 { if x != nil { return x.LastUsed } return 0 } // ListRefreshReq is a request to enumerate the refresh tokens of a user. type ListRefreshReq struct { state protoimpl.MessageState `protogen:"open.v1"` // The "sub" claim returned in the ID Token. UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListRefreshReq) Reset() { *x = ListRefreshReq{} mi := &file_api_v2_api_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ListRefreshReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListRefreshReq) ProtoMessage() {} func (x *ListRefreshReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListRefreshReq.ProtoReflect.Descriptor instead. func (*ListRefreshReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{36} } func (x *ListRefreshReq) GetUserId() string { if x != nil { return x.UserId } return "" } // ListRefreshResp returns a list of refresh tokens for a user. type ListRefreshResp struct { state protoimpl.MessageState `protogen:"open.v1"` RefreshTokens []*RefreshTokenRef `protobuf:"bytes,1,rep,name=refresh_tokens,json=refreshTokens,proto3" json:"refresh_tokens,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *ListRefreshResp) Reset() { *x = ListRefreshResp{} mi := &file_api_v2_api_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *ListRefreshResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListRefreshResp) ProtoMessage() {} func (x *ListRefreshResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListRefreshResp.ProtoReflect.Descriptor instead. func (*ListRefreshResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{37} } func (x *ListRefreshResp) GetRefreshTokens() []*RefreshTokenRef { if x != nil { return x.RefreshTokens } return nil } // RevokeRefreshReq is a request to revoke the refresh token of the user-client pair. type RevokeRefreshReq struct { state protoimpl.MessageState `protogen:"open.v1"` // The "sub" claim returned in the ID Token. UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RevokeRefreshReq) Reset() { *x = RevokeRefreshReq{} mi := &file_api_v2_api_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RevokeRefreshReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*RevokeRefreshReq) ProtoMessage() {} func (x *RevokeRefreshReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RevokeRefreshReq.ProtoReflect.Descriptor instead. func (*RevokeRefreshReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{38} } func (x *RevokeRefreshReq) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *RevokeRefreshReq) GetClientId() string { if x != nil { return x.ClientId } return "" } // RevokeRefreshResp determines if the refresh token is revoked successfully. type RevokeRefreshResp struct { state protoimpl.MessageState `protogen:"open.v1"` // Set to true is refresh token was not found and token could not be revoked. NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RevokeRefreshResp) Reset() { *x = RevokeRefreshResp{} mi := &file_api_v2_api_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RevokeRefreshResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*RevokeRefreshResp) ProtoMessage() {} func (x *RevokeRefreshResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RevokeRefreshResp.ProtoReflect.Descriptor instead. func (*RevokeRefreshResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{39} } func (x *RevokeRefreshResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } type VerifyPasswordReq struct { state protoimpl.MessageState `protogen:"open.v1"` Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *VerifyPasswordReq) Reset() { *x = VerifyPasswordReq{} mi := &file_api_v2_api_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *VerifyPasswordReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*VerifyPasswordReq) ProtoMessage() {} func (x *VerifyPasswordReq) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use VerifyPasswordReq.ProtoReflect.Descriptor instead. func (*VerifyPasswordReq) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{40} } func (x *VerifyPasswordReq) GetEmail() string { if x != nil { return x.Email } return "" } func (x *VerifyPasswordReq) GetPassword() string { if x != nil { return x.Password } return "" } type VerifyPasswordResp struct { state protoimpl.MessageState `protogen:"open.v1"` Verified bool `protobuf:"varint,1,opt,name=verified,proto3" json:"verified,omitempty"` NotFound bool `protobuf:"varint,2,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *VerifyPasswordResp) Reset() { *x = VerifyPasswordResp{} mi := &file_api_v2_api_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *VerifyPasswordResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*VerifyPasswordResp) ProtoMessage() {} func (x *VerifyPasswordResp) ProtoReflect() protoreflect.Message { mi := &file_api_v2_api_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use VerifyPasswordResp.ProtoReflect.Descriptor instead. func (*VerifyPasswordResp) Descriptor() ([]byte, []int) { return file_api_v2_api_proto_rawDescGZIP(), []int{41} } func (x *VerifyPasswordResp) GetVerified() bool { if x != nil { return x.Verified } return false } func (x *VerifyPasswordResp) GetNotFound() bool { if x != nil { return x.NotFound } return false } var File_api_v2_api_proto protoreflect.FileDescriptor var file_api_v2_api_proto_rawDesc = string([]byte{ 0x0a, 0x10, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x22, 0xf0, 0x01, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0xdc, 0x01, 0x0a, 0x0a, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x1e, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x34, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x36, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x5e, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x21, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2f, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xc9, 0x01, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x2f, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x0f, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x22, 0x3b, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x29, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x69, 0x0a, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x3e, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x29, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x3b, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x22, 0x67, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x31, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x29, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x31, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x11, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x22, 0x3f, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2b, 0x0a, 0x09, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x09, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x7c, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0x42, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x12, 0x2c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x3c, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x22, 0x2d, 0x0a, 0x0a, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0xb2, 0x01, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x65, 0x77, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6e, 0x65, 0x77, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x37, 0x0a, 0x0f, 0x6e, 0x65, 0x77, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x52, 0x0d, 0x6e, 0x65, 0x77, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0x32, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x24, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x32, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x12, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x22, 0x43, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x0c, 0x0a, 0x0a, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x22, 0x37, 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x70, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x70, 0x69, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x22, 0xb0, 0x06, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x16, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6a, 0x77, 0x6b, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x77, 0x6b, 0x73, 0x55, 0x72, 0x69, 0x12, 0x2b, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x75, 0x73, 0x65, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x42, 0x0a, 0x1d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x18, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x16, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x4f, 0x0a, 0x25, 0x69, 0x64, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6c, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x20, 0x69, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x6c, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x47, 0x0a, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x1d, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x25, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x21, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x22, 0x7a, 0x0a, 0x0f, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x66, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x73, 0x65, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4e, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x12, 0x3b, 0x0a, 0x0e, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x66, 0x52, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x22, 0x48, 0x0a, 0x10, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x30, 0x0a, 0x11, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x45, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x4d, 0x0a, 0x12, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x32, 0x8b, 0x09, 0x0a, 0x03, 0x44, 0x65, 0x78, 0x12, 0x34, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x12, 0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x36, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x6f, 0x73, 0x2e, 0x64, 0x65, 0x78, 0x2e, 0x61, 0x70, 0x69, 0x5a, 0x20, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x78, 0x69, 0x64, 0x70, 0x2f, 0x64, 0x65, 0x78, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x3b, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( file_api_v2_api_proto_rawDescOnce sync.Once file_api_v2_api_proto_rawDescData []byte ) func file_api_v2_api_proto_rawDescGZIP() []byte { file_api_v2_api_proto_rawDescOnce.Do(func() { file_api_v2_api_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_v2_api_proto_rawDesc), len(file_api_v2_api_proto_rawDesc))) }) return file_api_v2_api_proto_rawDescData } var file_api_v2_api_proto_msgTypes = make([]protoimpl.MessageInfo, 42) var file_api_v2_api_proto_goTypes = []any{ (*Client)(nil), // 0: api.Client (*ClientInfo)(nil), // 1: api.ClientInfo (*GetClientReq)(nil), // 2: api.GetClientReq (*GetClientResp)(nil), // 3: api.GetClientResp (*CreateClientReq)(nil), // 4: api.CreateClientReq (*CreateClientResp)(nil), // 5: api.CreateClientResp (*DeleteClientReq)(nil), // 6: api.DeleteClientReq (*DeleteClientResp)(nil), // 7: api.DeleteClientResp (*UpdateClientReq)(nil), // 8: api.UpdateClientReq (*UpdateClientResp)(nil), // 9: api.UpdateClientResp (*ListClientReq)(nil), // 10: api.ListClientReq (*ListClientResp)(nil), // 11: api.ListClientResp (*Password)(nil), // 12: api.Password (*CreatePasswordReq)(nil), // 13: api.CreatePasswordReq (*CreatePasswordResp)(nil), // 14: api.CreatePasswordResp (*UpdatePasswordReq)(nil), // 15: api.UpdatePasswordReq (*UpdatePasswordResp)(nil), // 16: api.UpdatePasswordResp (*DeletePasswordReq)(nil), // 17: api.DeletePasswordReq (*DeletePasswordResp)(nil), // 18: api.DeletePasswordResp (*ListPasswordReq)(nil), // 19: api.ListPasswordReq (*ListPasswordResp)(nil), // 20: api.ListPasswordResp (*Connector)(nil), // 21: api.Connector (*CreateConnectorReq)(nil), // 22: api.CreateConnectorReq (*CreateConnectorResp)(nil), // 23: api.CreateConnectorResp (*GrantTypes)(nil), // 24: api.GrantTypes (*UpdateConnectorReq)(nil), // 25: api.UpdateConnectorReq (*UpdateConnectorResp)(nil), // 26: api.UpdateConnectorResp (*DeleteConnectorReq)(nil), // 27: api.DeleteConnectorReq (*DeleteConnectorResp)(nil), // 28: api.DeleteConnectorResp (*ListConnectorReq)(nil), // 29: api.ListConnectorReq (*ListConnectorResp)(nil), // 30: api.ListConnectorResp (*VersionReq)(nil), // 31: api.VersionReq (*VersionResp)(nil), // 32: api.VersionResp (*DiscoveryReq)(nil), // 33: api.DiscoveryReq (*DiscoveryResp)(nil), // 34: api.DiscoveryResp (*RefreshTokenRef)(nil), // 35: api.RefreshTokenRef (*ListRefreshReq)(nil), // 36: api.ListRefreshReq (*ListRefreshResp)(nil), // 37: api.ListRefreshResp (*RevokeRefreshReq)(nil), // 38: api.RevokeRefreshReq (*RevokeRefreshResp)(nil), // 39: api.RevokeRefreshResp (*VerifyPasswordReq)(nil), // 40: api.VerifyPasswordReq (*VerifyPasswordResp)(nil), // 41: api.VerifyPasswordResp } var file_api_v2_api_proto_depIdxs = []int32{ 0, // 0: api.GetClientResp.client:type_name -> api.Client 0, // 1: api.CreateClientReq.client:type_name -> api.Client 0, // 2: api.CreateClientResp.client:type_name -> api.Client 1, // 3: api.ListClientResp.clients:type_name -> api.ClientInfo 12, // 4: api.CreatePasswordReq.password:type_name -> api.Password 12, // 5: api.ListPasswordResp.passwords:type_name -> api.Password 21, // 6: api.CreateConnectorReq.connector:type_name -> api.Connector 24, // 7: api.UpdateConnectorReq.new_grant_types:type_name -> api.GrantTypes 21, // 8: api.ListConnectorResp.connectors:type_name -> api.Connector 35, // 9: api.ListRefreshResp.refresh_tokens:type_name -> api.RefreshTokenRef 2, // 10: api.Dex.GetClient:input_type -> api.GetClientReq 4, // 11: api.Dex.CreateClient:input_type -> api.CreateClientReq 8, // 12: api.Dex.UpdateClient:input_type -> api.UpdateClientReq 6, // 13: api.Dex.DeleteClient:input_type -> api.DeleteClientReq 10, // 14: api.Dex.ListClients:input_type -> api.ListClientReq 13, // 15: api.Dex.CreatePassword:input_type -> api.CreatePasswordReq 15, // 16: api.Dex.UpdatePassword:input_type -> api.UpdatePasswordReq 17, // 17: api.Dex.DeletePassword:input_type -> api.DeletePasswordReq 19, // 18: api.Dex.ListPasswords:input_type -> api.ListPasswordReq 22, // 19: api.Dex.CreateConnector:input_type -> api.CreateConnectorReq 25, // 20: api.Dex.UpdateConnector:input_type -> api.UpdateConnectorReq 27, // 21: api.Dex.DeleteConnector:input_type -> api.DeleteConnectorReq 29, // 22: api.Dex.ListConnectors:input_type -> api.ListConnectorReq 31, // 23: api.Dex.GetVersion:input_type -> api.VersionReq 33, // 24: api.Dex.GetDiscovery:input_type -> api.DiscoveryReq 36, // 25: api.Dex.ListRefresh:input_type -> api.ListRefreshReq 38, // 26: api.Dex.RevokeRefresh:input_type -> api.RevokeRefreshReq 40, // 27: api.Dex.VerifyPassword:input_type -> api.VerifyPasswordReq 3, // 28: api.Dex.GetClient:output_type -> api.GetClientResp 5, // 29: api.Dex.CreateClient:output_type -> api.CreateClientResp 9, // 30: api.Dex.UpdateClient:output_type -> api.UpdateClientResp 7, // 31: api.Dex.DeleteClient:output_type -> api.DeleteClientResp 11, // 32: api.Dex.ListClients:output_type -> api.ListClientResp 14, // 33: api.Dex.CreatePassword:output_type -> api.CreatePasswordResp 16, // 34: api.Dex.UpdatePassword:output_type -> api.UpdatePasswordResp 18, // 35: api.Dex.DeletePassword:output_type -> api.DeletePasswordResp 20, // 36: api.Dex.ListPasswords:output_type -> api.ListPasswordResp 23, // 37: api.Dex.CreateConnector:output_type -> api.CreateConnectorResp 26, // 38: api.Dex.UpdateConnector:output_type -> api.UpdateConnectorResp 28, // 39: api.Dex.DeleteConnector:output_type -> api.DeleteConnectorResp 30, // 40: api.Dex.ListConnectors:output_type -> api.ListConnectorResp 32, // 41: api.Dex.GetVersion:output_type -> api.VersionResp 34, // 42: api.Dex.GetDiscovery:output_type -> api.DiscoveryResp 37, // 43: api.Dex.ListRefresh:output_type -> api.ListRefreshResp 39, // 44: api.Dex.RevokeRefresh:output_type -> api.RevokeRefreshResp 41, // 45: api.Dex.VerifyPassword:output_type -> api.VerifyPasswordResp 28, // [28:46] is the sub-list for method output_type 10, // [10:28] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name } func init() { file_api_v2_api_proto_init() } func file_api_v2_api_proto_init() { if File_api_v2_api_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v2_api_proto_rawDesc), len(file_api_v2_api_proto_rawDesc)), NumEnums: 0, NumMessages: 42, NumExtensions: 0, NumServices: 1, }, GoTypes: file_api_v2_api_proto_goTypes, DependencyIndexes: file_api_v2_api_proto_depIdxs, MessageInfos: file_api_v2_api_proto_msgTypes, }.Build() File_api_v2_api_proto = out.File file_api_v2_api_proto_goTypes = nil file_api_v2_api_proto_depIdxs = nil } ================================================ FILE: api/v2/api.proto ================================================ syntax = "proto3"; package api; option java_package = "com.coreos.dex.api"; option go_package = "github.com/dexidp/dex/api/v2;api"; // Client represents an OAuth2 client. message Client { string id = 1; string secret = 2; repeated string redirect_uris = 3; repeated string trusted_peers = 4; bool public = 5; string name = 6; string logo_url = 7; repeated string allowed_connectors = 8; } // ClientInfo represents an OAuth2 client without sensitive information. message ClientInfo { string id = 1; repeated string redirect_uris = 2; repeated string trusted_peers = 3; bool public = 4; string name = 5; string logo_url = 6; repeated string allowed_connectors = 7; } // GetClientReq is a request to retrieve client details. message GetClientReq { // The ID of the client. string id = 1; } // GetClientResp returns the client details. message GetClientResp { Client client = 1; } // CreateClientReq is a request to make a client. message CreateClientReq { Client client = 1; } // CreateClientResp returns the response from creating a client. message CreateClientResp { bool already_exists = 1; Client client = 2; } // DeleteClientReq is a request to delete a client. message DeleteClientReq { // The ID of the client. string id = 1; } // DeleteClientResp determines if the client is deleted successfully. message DeleteClientResp { bool not_found = 1; } // UpdateClientReq is a request to update an existing client. message UpdateClientReq { string id = 1; repeated string redirect_uris = 2; repeated string trusted_peers = 3; string name = 4; string logo_url = 5; repeated string allowed_connectors = 6; } // UpdateClientResp returns the response from updating a client. message UpdateClientResp { bool not_found = 1; } // ListClientReq is a request to enumerate clients. message ListClientReq {} // ListClientResp returns a list of clients. message ListClientResp { repeated ClientInfo clients = 1; } // TODO(ericchiang): expand this. // Password is an email for password mapping managed by the storage. message Password { string email = 1; // Currently we do not accept plain text passwords. Could be an option in the future. bytes hash = 2; string username = 3; string user_id = 4; } // CreatePasswordReq is a request to make a password. message CreatePasswordReq { Password password = 1; } // CreatePasswordResp returns the response from creating a password. message CreatePasswordResp { bool already_exists = 1; } // UpdatePasswordReq is a request to modify an existing password. message UpdatePasswordReq { // The email used to lookup the password. This field cannot be modified string email = 1; bytes new_hash = 2; string new_username = 3; } // UpdatePasswordResp returns the response from modifying an existing password. message UpdatePasswordResp { bool not_found = 1; } // DeletePasswordReq is a request to delete a password. message DeletePasswordReq { string email = 1; } // DeletePasswordResp returns the response from deleting a password. message DeletePasswordResp { bool not_found = 1; } // ListPasswordReq is a request to enumerate passwords. message ListPasswordReq {} // ListPasswordResp returns a list of passwords. message ListPasswordResp { repeated Password passwords = 1; } // Connector is a strategy used by Dex for authenticating a user against another identity provider message Connector { string id = 1; string type = 2; string name = 3; bytes config = 4; repeated string grant_types = 5; } // CreateConnectorReq is a request to make a connector. message CreateConnectorReq { Connector connector = 1; } // CreateConnectorResp returns the response from creating a connector. message CreateConnectorResp { bool already_exists = 1; } // GrantTypes wraps a list of grant types to distinguish between // "not specified" (no update) and "empty list" (unrestricted). message GrantTypes { repeated string grant_types = 1; } // UpdateConnectorReq is a request to modify an existing connector. message UpdateConnectorReq { // The id used to lookup the connector. This field cannot be modified string id = 1; string new_type = 2; string new_name = 3; bytes new_config = 4; // If set, updates the connector's allowed grant types. // An empty grant_types list means unrestricted (all grant types allowed). // If not set (null), grant types are not modified. GrantTypes new_grant_types = 5; } // UpdateConnectorResp returns the response from modifying an existing connector. message UpdateConnectorResp { bool not_found = 1; } // DeleteConnectorReq is a request to delete a connector. message DeleteConnectorReq { string id = 1; } // DeleteConnectorResp returns the response from deleting a connector. message DeleteConnectorResp { bool not_found = 1; } // ListConnectorReq is a request to enumerate connectors. message ListConnectorReq {} // ListConnectorResp returns a list of connectors. message ListConnectorResp { repeated Connector connectors = 1; } // VersionReq is a request to fetch version info. message VersionReq {} // VersionResp holds the version info of components. message VersionResp { // Semantic version of the server. string server = 1; // Numeric version of the API. It increases every time a new call is added to the API. // Clients should use this info to determine if the server supports specific features. int32 api = 2; } // DiscoveryReq is a request to fetch discover information. message DiscoveryReq {} //DiscoverResp holds the version oidc disovery info. message DiscoveryResp { string issuer = 1; string authorization_endpoint = 2; string token_endpoint = 3; string jwks_uri = 4; string userinfo_endpoint = 5; string device_authorization_endpoint = 6; string introspection_endpoint = 7; repeated string grant_types_supported = 8; repeated string response_types_supported = 9; repeated string subject_types_supported = 10; repeated string id_token_signing_alg_values_supported = 11; repeated string code_challenge_methods_supported = 12; repeated string scopes_supported = 13; repeated string token_endpoint_auth_methods_supported = 14; repeated string claims_supported = 15; } // RefreshTokenRef contains the metadata for a refresh token that is managed by the storage. message RefreshTokenRef { // ID of the refresh token. string id = 1; string client_id = 2; int64 created_at = 5; int64 last_used = 6; } // ListRefreshReq is a request to enumerate the refresh tokens of a user. message ListRefreshReq { // The "sub" claim returned in the ID Token. string user_id = 1; } // ListRefreshResp returns a list of refresh tokens for a user. message ListRefreshResp { repeated RefreshTokenRef refresh_tokens = 1; } // RevokeRefreshReq is a request to revoke the refresh token of the user-client pair. message RevokeRefreshReq { // The "sub" claim returned in the ID Token. string user_id = 1; string client_id = 2; } // RevokeRefreshResp determines if the refresh token is revoked successfully. message RevokeRefreshResp { // Set to true is refresh token was not found and token could not be revoked. bool not_found = 1; } message VerifyPasswordReq { string email = 1; string password = 2; } message VerifyPasswordResp { bool verified = 1; bool not_found = 2; } // Dex represents the dex gRPC service. service Dex { // GetClient gets a client. rpc GetClient(GetClientReq) returns (GetClientResp) {}; // CreateClient creates a client. rpc CreateClient(CreateClientReq) returns (CreateClientResp) {}; // UpdateClient updates an existing client rpc UpdateClient(UpdateClientReq) returns (UpdateClientResp) {}; // DeleteClient deletes the provided client. rpc DeleteClient(DeleteClientReq) returns (DeleteClientResp) {}; // ListClients lists all client entries. rpc ListClients(ListClientReq) returns (ListClientResp) {}; // CreatePassword creates a password. rpc CreatePassword(CreatePasswordReq) returns (CreatePasswordResp) {}; // UpdatePassword modifies existing password. rpc UpdatePassword(UpdatePasswordReq) returns (UpdatePasswordResp) {}; // DeletePassword deletes the password. rpc DeletePassword(DeletePasswordReq) returns (DeletePasswordResp) {}; // ListPassword lists all password entries. rpc ListPasswords(ListPasswordReq) returns (ListPasswordResp) {}; // CreateConnector creates a connector. rpc CreateConnector(CreateConnectorReq) returns (CreateConnectorResp) {}; // UpdateConnector modifies existing connector. rpc UpdateConnector(UpdateConnectorReq) returns (UpdateConnectorResp) {}; // DeleteConnector deletes the connector. rpc DeleteConnector(DeleteConnectorReq) returns (DeleteConnectorResp) {}; // ListConnectors lists all connector entries. rpc ListConnectors(ListConnectorReq) returns (ListConnectorResp) {}; // GetVersion returns version information of the server. rpc GetVersion(VersionReq) returns (VersionResp) {}; // GetDiscovery returns discovery information of the server. rpc GetDiscovery(DiscoveryReq) returns (DiscoveryResp) {}; // ListRefresh lists all the refresh token entries for a particular user. rpc ListRefresh(ListRefreshReq) returns (ListRefreshResp) {}; // RevokeRefresh revokes the refresh token for the provided user-client pair. // // Note that each user-client pair can have only one refresh token at a time. rpc RevokeRefresh(RevokeRefreshReq) returns (RevokeRefreshResp) {}; // VerifyPassword returns whether a password matches a hash for a specific email or not. rpc VerifyPassword(VerifyPasswordReq) returns (VerifyPasswordResp) {}; } ================================================ FILE: api/v2/api_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 // - protoc v5.29.3 // source: api/v2/api.proto package api import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.64.0 or later. const _ = grpc.SupportPackageIsVersion9 const ( Dex_GetClient_FullMethodName = "/api.Dex/GetClient" Dex_CreateClient_FullMethodName = "/api.Dex/CreateClient" Dex_UpdateClient_FullMethodName = "/api.Dex/UpdateClient" Dex_DeleteClient_FullMethodName = "/api.Dex/DeleteClient" Dex_ListClients_FullMethodName = "/api.Dex/ListClients" Dex_CreatePassword_FullMethodName = "/api.Dex/CreatePassword" Dex_UpdatePassword_FullMethodName = "/api.Dex/UpdatePassword" Dex_DeletePassword_FullMethodName = "/api.Dex/DeletePassword" Dex_ListPasswords_FullMethodName = "/api.Dex/ListPasswords" Dex_CreateConnector_FullMethodName = "/api.Dex/CreateConnector" Dex_UpdateConnector_FullMethodName = "/api.Dex/UpdateConnector" Dex_DeleteConnector_FullMethodName = "/api.Dex/DeleteConnector" Dex_ListConnectors_FullMethodName = "/api.Dex/ListConnectors" Dex_GetVersion_FullMethodName = "/api.Dex/GetVersion" Dex_GetDiscovery_FullMethodName = "/api.Dex/GetDiscovery" Dex_ListRefresh_FullMethodName = "/api.Dex/ListRefresh" Dex_RevokeRefresh_FullMethodName = "/api.Dex/RevokeRefresh" Dex_VerifyPassword_FullMethodName = "/api.Dex/VerifyPassword" ) // DexClient is the client API for Dex service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // // Dex represents the dex gRPC service. type DexClient interface { // GetClient gets a client. GetClient(ctx context.Context, in *GetClientReq, opts ...grpc.CallOption) (*GetClientResp, error) // CreateClient creates a client. CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error) // UpdateClient updates an existing client UpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error) // DeleteClient deletes the provided client. DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error) // ListClients lists all client entries. ListClients(ctx context.Context, in *ListClientReq, opts ...grpc.CallOption) (*ListClientResp, error) // CreatePassword creates a password. CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error) // UpdatePassword modifies existing password. UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error) // DeletePassword deletes the password. DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error) // ListPassword lists all password entries. ListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error) // CreateConnector creates a connector. CreateConnector(ctx context.Context, in *CreateConnectorReq, opts ...grpc.CallOption) (*CreateConnectorResp, error) // UpdateConnector modifies existing connector. UpdateConnector(ctx context.Context, in *UpdateConnectorReq, opts ...grpc.CallOption) (*UpdateConnectorResp, error) // DeleteConnector deletes the connector. DeleteConnector(ctx context.Context, in *DeleteConnectorReq, opts ...grpc.CallOption) (*DeleteConnectorResp, error) // ListConnectors lists all connector entries. ListConnectors(ctx context.Context, in *ListConnectorReq, opts ...grpc.CallOption) (*ListConnectorResp, error) // GetVersion returns version information of the server. GetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error) // GetDiscovery returns discovery information of the server. GetDiscovery(ctx context.Context, in *DiscoveryReq, opts ...grpc.CallOption) (*DiscoveryResp, error) // ListRefresh lists all the refresh token entries for a particular user. ListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error) // RevokeRefresh revokes the refresh token for the provided user-client pair. // // Note that each user-client pair can have only one refresh token at a time. RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error) // VerifyPassword returns whether a password matches a hash for a specific email or not. VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error) } type dexClient struct { cc grpc.ClientConnInterface } func NewDexClient(cc grpc.ClientConnInterface) DexClient { return &dexClient{cc} } func (c *dexClient) GetClient(ctx context.Context, in *GetClientReq, opts ...grpc.CallOption) (*GetClientResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetClientResp) err := c.cc.Invoke(ctx, Dex_GetClient_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CreateClientResp) err := c.cc.Invoke(ctx, Dex_CreateClient_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) UpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UpdateClientResp) err := c.cc.Invoke(ctx, Dex_UpdateClient_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeleteClientResp) err := c.cc.Invoke(ctx, Dex_DeleteClient_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) ListClients(ctx context.Context, in *ListClientReq, opts ...grpc.CallOption) (*ListClientResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListClientResp) err := c.cc.Invoke(ctx, Dex_ListClients_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CreatePasswordResp) err := c.cc.Invoke(ctx, Dex_CreatePassword_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UpdatePasswordResp) err := c.cc.Invoke(ctx, Dex_UpdatePassword_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeletePasswordResp) err := c.cc.Invoke(ctx, Dex_DeletePassword_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) ListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListPasswordResp) err := c.cc.Invoke(ctx, Dex_ListPasswords_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) CreateConnector(ctx context.Context, in *CreateConnectorReq, opts ...grpc.CallOption) (*CreateConnectorResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(CreateConnectorResp) err := c.cc.Invoke(ctx, Dex_CreateConnector_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) UpdateConnector(ctx context.Context, in *UpdateConnectorReq, opts ...grpc.CallOption) (*UpdateConnectorResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(UpdateConnectorResp) err := c.cc.Invoke(ctx, Dex_UpdateConnector_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) DeleteConnector(ctx context.Context, in *DeleteConnectorReq, opts ...grpc.CallOption) (*DeleteConnectorResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeleteConnectorResp) err := c.cc.Invoke(ctx, Dex_DeleteConnector_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) ListConnectors(ctx context.Context, in *ListConnectorReq, opts ...grpc.CallOption) (*ListConnectorResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListConnectorResp) err := c.cc.Invoke(ctx, Dex_ListConnectors_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) GetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(VersionResp) err := c.cc.Invoke(ctx, Dex_GetVersion_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) GetDiscovery(ctx context.Context, in *DiscoveryReq, opts ...grpc.CallOption) (*DiscoveryResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DiscoveryResp) err := c.cc.Invoke(ctx, Dex_GetDiscovery_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) ListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListRefreshResp) err := c.cc.Invoke(ctx, Dex_ListRefresh_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(RevokeRefreshResp) err := c.cc.Invoke(ctx, Dex_RevokeRefresh_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } func (c *dexClient) VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(VerifyPasswordResp) err := c.cc.Invoke(ctx, Dex_VerifyPassword_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } // DexServer is the server API for Dex service. // All implementations must embed UnimplementedDexServer // for forward compatibility. // // Dex represents the dex gRPC service. type DexServer interface { // GetClient gets a client. GetClient(context.Context, *GetClientReq) (*GetClientResp, error) // CreateClient creates a client. CreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error) // UpdateClient updates an existing client UpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error) // DeleteClient deletes the provided client. DeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error) // ListClients lists all client entries. ListClients(context.Context, *ListClientReq) (*ListClientResp, error) // CreatePassword creates a password. CreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error) // UpdatePassword modifies existing password. UpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error) // DeletePassword deletes the password. DeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error) // ListPassword lists all password entries. ListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error) // CreateConnector creates a connector. CreateConnector(context.Context, *CreateConnectorReq) (*CreateConnectorResp, error) // UpdateConnector modifies existing connector. UpdateConnector(context.Context, *UpdateConnectorReq) (*UpdateConnectorResp, error) // DeleteConnector deletes the connector. DeleteConnector(context.Context, *DeleteConnectorReq) (*DeleteConnectorResp, error) // ListConnectors lists all connector entries. ListConnectors(context.Context, *ListConnectorReq) (*ListConnectorResp, error) // GetVersion returns version information of the server. GetVersion(context.Context, *VersionReq) (*VersionResp, error) // GetDiscovery returns discovery information of the server. GetDiscovery(context.Context, *DiscoveryReq) (*DiscoveryResp, error) // ListRefresh lists all the refresh token entries for a particular user. ListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error) // RevokeRefresh revokes the refresh token for the provided user-client pair. // // Note that each user-client pair can have only one refresh token at a time. RevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error) // VerifyPassword returns whether a password matches a hash for a specific email or not. VerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error) mustEmbedUnimplementedDexServer() } // UnimplementedDexServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. type UnimplementedDexServer struct{} func (UnimplementedDexServer) GetClient(context.Context, *GetClientReq) (*GetClientResp, error) { return nil, status.Errorf(codes.Unimplemented, "method GetClient not implemented") } func (UnimplementedDexServer) CreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateClient not implemented") } func (UnimplementedDexServer) UpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateClient not implemented") } func (UnimplementedDexServer) DeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error) { return nil, status.Errorf(codes.Unimplemented, "method DeleteClient not implemented") } func (UnimplementedDexServer) ListClients(context.Context, *ListClientReq) (*ListClientResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ListClients not implemented") } func (UnimplementedDexServer) CreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error) { return nil, status.Errorf(codes.Unimplemented, "method CreatePassword not implemented") } func (UnimplementedDexServer) UpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdatePassword not implemented") } func (UnimplementedDexServer) DeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error) { return nil, status.Errorf(codes.Unimplemented, "method DeletePassword not implemented") } func (UnimplementedDexServer) ListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ListPasswords not implemented") } func (UnimplementedDexServer) CreateConnector(context.Context, *CreateConnectorReq) (*CreateConnectorResp, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateConnector not implemented") } func (UnimplementedDexServer) UpdateConnector(context.Context, *UpdateConnectorReq) (*UpdateConnectorResp, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateConnector not implemented") } func (UnimplementedDexServer) DeleteConnector(context.Context, *DeleteConnectorReq) (*DeleteConnectorResp, error) { return nil, status.Errorf(codes.Unimplemented, "method DeleteConnector not implemented") } func (UnimplementedDexServer) ListConnectors(context.Context, *ListConnectorReq) (*ListConnectorResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ListConnectors not implemented") } func (UnimplementedDexServer) GetVersion(context.Context, *VersionReq) (*VersionResp, error) { return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented") } func (UnimplementedDexServer) GetDiscovery(context.Context, *DiscoveryReq) (*DiscoveryResp, error) { return nil, status.Errorf(codes.Unimplemented, "method GetDiscovery not implemented") } func (UnimplementedDexServer) ListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ListRefresh not implemented") } func (UnimplementedDexServer) RevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error) { return nil, status.Errorf(codes.Unimplemented, "method RevokeRefresh not implemented") } func (UnimplementedDexServer) VerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error) { return nil, status.Errorf(codes.Unimplemented, "method VerifyPassword not implemented") } func (UnimplementedDexServer) mustEmbedUnimplementedDexServer() {} func (UnimplementedDexServer) testEmbeddedByValue() {} // UnsafeDexServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to DexServer will // result in compilation errors. type UnsafeDexServer interface { mustEmbedUnimplementedDexServer() } func RegisterDexServer(s grpc.ServiceRegistrar, srv DexServer) { // If the following call pancis, it indicates UnimplementedDexServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } s.RegisterService(&Dex_ServiceDesc, srv) } func _Dex_GetClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetClientReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).GetClient(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_GetClient_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).GetClient(ctx, req.(*GetClientReq)) } return interceptor(ctx, in, info, handler) } func _Dex_CreateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CreateClientReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).CreateClient(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_CreateClient_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).CreateClient(ctx, req.(*CreateClientReq)) } return interceptor(ctx, in, info, handler) } func _Dex_UpdateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpdateClientReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).UpdateClient(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_UpdateClient_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).UpdateClient(ctx, req.(*UpdateClientReq)) } return interceptor(ctx, in, info, handler) } func _Dex_DeleteClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteClientReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).DeleteClient(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_DeleteClient_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).DeleteClient(ctx, req.(*DeleteClientReq)) } return interceptor(ctx, in, info, handler) } func _Dex_ListClients_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListClientReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).ListClients(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_ListClients_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).ListClients(ctx, req.(*ListClientReq)) } return interceptor(ctx, in, info, handler) } func _Dex_CreatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CreatePasswordReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).CreatePassword(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_CreatePassword_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).CreatePassword(ctx, req.(*CreatePasswordReq)) } return interceptor(ctx, in, info, handler) } func _Dex_UpdatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpdatePasswordReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).UpdatePassword(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_UpdatePassword_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).UpdatePassword(ctx, req.(*UpdatePasswordReq)) } return interceptor(ctx, in, info, handler) } func _Dex_DeletePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeletePasswordReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).DeletePassword(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_DeletePassword_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).DeletePassword(ctx, req.(*DeletePasswordReq)) } return interceptor(ctx, in, info, handler) } func _Dex_ListPasswords_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListPasswordReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).ListPasswords(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_ListPasswords_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).ListPasswords(ctx, req.(*ListPasswordReq)) } return interceptor(ctx, in, info, handler) } func _Dex_CreateConnector_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CreateConnectorReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).CreateConnector(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_CreateConnector_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).CreateConnector(ctx, req.(*CreateConnectorReq)) } return interceptor(ctx, in, info, handler) } func _Dex_UpdateConnector_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpdateConnectorReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).UpdateConnector(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_UpdateConnector_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).UpdateConnector(ctx, req.(*UpdateConnectorReq)) } return interceptor(ctx, in, info, handler) } func _Dex_DeleteConnector_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteConnectorReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).DeleteConnector(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_DeleteConnector_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).DeleteConnector(ctx, req.(*DeleteConnectorReq)) } return interceptor(ctx, in, info, handler) } func _Dex_ListConnectors_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListConnectorReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).ListConnectors(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_ListConnectors_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).ListConnectors(ctx, req.(*ListConnectorReq)) } return interceptor(ctx, in, info, handler) } func _Dex_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(VersionReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).GetVersion(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_GetVersion_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).GetVersion(ctx, req.(*VersionReq)) } return interceptor(ctx, in, info, handler) } func _Dex_GetDiscovery_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DiscoveryReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).GetDiscovery(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_GetDiscovery_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).GetDiscovery(ctx, req.(*DiscoveryReq)) } return interceptor(ctx, in, info, handler) } func _Dex_ListRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListRefreshReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).ListRefresh(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_ListRefresh_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).ListRefresh(ctx, req.(*ListRefreshReq)) } return interceptor(ctx, in, info, handler) } func _Dex_RevokeRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(RevokeRefreshReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).RevokeRefresh(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_RevokeRefresh_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).RevokeRefresh(ctx, req.(*RevokeRefreshReq)) } return interceptor(ctx, in, info, handler) } func _Dex_VerifyPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(VerifyPasswordReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(DexServer).VerifyPassword(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: Dex_VerifyPassword_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(DexServer).VerifyPassword(ctx, req.(*VerifyPasswordReq)) } return interceptor(ctx, in, info, handler) } // Dex_ServiceDesc is the grpc.ServiceDesc for Dex service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Dex_ServiceDesc = grpc.ServiceDesc{ ServiceName: "api.Dex", HandlerType: (*DexServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "GetClient", Handler: _Dex_GetClient_Handler, }, { MethodName: "CreateClient", Handler: _Dex_CreateClient_Handler, }, { MethodName: "UpdateClient", Handler: _Dex_UpdateClient_Handler, }, { MethodName: "DeleteClient", Handler: _Dex_DeleteClient_Handler, }, { MethodName: "ListClients", Handler: _Dex_ListClients_Handler, }, { MethodName: "CreatePassword", Handler: _Dex_CreatePassword_Handler, }, { MethodName: "UpdatePassword", Handler: _Dex_UpdatePassword_Handler, }, { MethodName: "DeletePassword", Handler: _Dex_DeletePassword_Handler, }, { MethodName: "ListPasswords", Handler: _Dex_ListPasswords_Handler, }, { MethodName: "CreateConnector", Handler: _Dex_CreateConnector_Handler, }, { MethodName: "UpdateConnector", Handler: _Dex_UpdateConnector_Handler, }, { MethodName: "DeleteConnector", Handler: _Dex_DeleteConnector_Handler, }, { MethodName: "ListConnectors", Handler: _Dex_ListConnectors_Handler, }, { MethodName: "GetVersion", Handler: _Dex_GetVersion_Handler, }, { MethodName: "GetDiscovery", Handler: _Dex_GetDiscovery_Handler, }, { MethodName: "ListRefresh", Handler: _Dex_ListRefresh_Handler, }, { MethodName: "RevokeRefresh", Handler: _Dex_RevokeRefresh_Handler, }, { MethodName: "VerifyPassword", Handler: _Dex_VerifyPassword_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "api/v2/api.proto", } ================================================ FILE: api/v2/go.mod ================================================ module github.com/dexidp/dex/api/v2 go 1.25.0 require ( google.golang.org/grpc v1.79.3 google.golang.org/protobuf v1.36.11 ) require ( golang.org/x/net v0.52.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.35.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect ) ================================================ FILE: api/v2/go.sum ================================================ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w= google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= ================================================ FILE: cmd/dex/config.go ================================================ package main import ( "bytes" "encoding/base64" "encoding/json" "fmt" "log/slog" "net/http" "net/netip" "os" "strings" "golang.org/x/crypto/bcrypt" "github.com/dexidp/dex/pkg/featureflags" "github.com/dexidp/dex/server" "github.com/dexidp/dex/server/signer" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent" "github.com/dexidp/dex/storage/etcd" "github.com/dexidp/dex/storage/kubernetes" "github.com/dexidp/dex/storage/memory" "github.com/dexidp/dex/storage/sql" ) func configUnmarshaller(b []byte, v interface{}) error { if !featureflags.ConfigDisallowUnknownFields.Enabled() { return json.Unmarshal(b, v) } dec := json.NewDecoder(bytes.NewReader(b)) dec.DisallowUnknownFields() return dec.Decode(v) } // Config is the config format for the main application. type Config struct { Issuer string `json:"issuer"` Storage Storage `json:"storage"` Web Web `json:"web"` Telemetry Telemetry `json:"telemetry"` OAuth2 OAuth2 `json:"oauth2"` GRPC GRPC `json:"grpc"` Expiry Expiry `json:"expiry"` Logger Logger `json:"logger"` Frontend server.WebConfig `json:"frontend"` // Signer configuration controls signing of JWT tokens issued by Dex. Signer Signer `json:"signer"` // StaticConnectors are user defined connectors specified in the ConfigMap // Write operations, like updating a connector, will fail. StaticConnectors []Connector `json:"connectors"` // StaticClients cause the server to use this list of clients rather than // querying the storage. Write operations, like creating a client, will fail. StaticClients []storage.Client `json:"staticClients"` // If enabled, the server will maintain a list of passwords which can be used // to identify a user. EnablePasswordDB bool `json:"enablePasswordDB"` // StaticPasswords cause the server use this list of passwords rather than // querying the storage. Cannot be specified without enabling a passwords // database. StaticPasswords []password `json:"staticPasswords"` // Sessions holds authentication session configuration. // Requires DEX_SESSIONS_ENABLED=true feature flag. Sessions *Sessions `json:"sessions"` // MFA holds multi-factor authentication configuration. MFA MFAConfig `json:"mfa"` } // MFAConfig holds multi-factor authentication settings. type MFAConfig struct { // Authenticators defines MFA providers available for clients to reference. Authenticators []MFAAuthenticator `json:"authenticators"` // DefaultMFAChain is the default ordered list of authenticator IDs applied // to clients that don't specify their own mfaChain. Empty means no MFA by default. DefaultMFAChain []string `json:"defaultMFAChain"` } // Validate the configuration func (c Config) Validate() error { // Fast checks. Perform these first for a more responsive CLI. checks := []struct { bad bool errMsg string }{ {c.Issuer == "", "no issuer specified in config file"}, {!c.EnablePasswordDB && len(c.StaticPasswords) != 0, "cannot specify static passwords without enabling password db"}, {c.Storage.Config == nil, "no storage supplied in config file"}, {c.Web.HTTP == "" && c.Web.HTTPS == "", "must supply a HTTP/HTTPS address to listen on"}, {c.Web.HTTPS != "" && c.Web.TLSCert == "", "no cert specified for HTTPS"}, {c.Web.HTTPS != "" && c.Web.TLSKey == "", "no private key specified for HTTPS"}, {c.Web.TLSMinVersion != "" && c.Web.TLSMinVersion != "1.2" && c.Web.TLSMinVersion != "1.3", "supported TLS versions are: 1.2, 1.3"}, {c.Web.TLSMaxVersion != "" && c.Web.TLSMaxVersion != "1.2" && c.Web.TLSMaxVersion != "1.3", "supported TLS versions are: 1.2, 1.3"}, {c.Web.TLSMaxVersion != "" && c.Web.TLSMinVersion != "" && c.Web.TLSMinVersion > c.Web.TLSMaxVersion, "TLSMinVersion greater than TLSMaxVersion"}, {c.GRPC.TLSCert != "" && c.GRPC.Addr == "", "no address specified for gRPC"}, {c.GRPC.TLSKey != "" && c.GRPC.Addr == "", "no address specified for gRPC"}, {(c.GRPC.TLSCert == "") != (c.GRPC.TLSKey == ""), "must specific both a gRPC TLS cert and key"}, {c.GRPC.TLSCert == "" && c.GRPC.TLSClientCA != "", "cannot specify gRPC TLS client CA without a gRPC TLS cert"}, {c.GRPC.TLSMinVersion != "" && c.GRPC.TLSMinVersion != "1.2" && c.GRPC.TLSMinVersion != "1.3", "supported TLS versions are: 1.2, 1.3"}, {c.GRPC.TLSMaxVersion != "" && c.GRPC.TLSMaxVersion != "1.2" && c.GRPC.TLSMaxVersion != "1.3", "supported TLS versions are: 1.2, 1.3"}, {c.GRPC.TLSMaxVersion != "" && c.GRPC.TLSMinVersion != "" && c.GRPC.TLSMinVersion > c.GRPC.TLSMaxVersion, "TLSMinVersion greater than TLSMaxVersion"}, } var checkErrors []string for _, check := range checks { if check.bad { checkErrors = append(checkErrors, check.errMsg) } } if len(checkErrors) != 0 { return fmt.Errorf("invalid Config:\n\t-\t%s", strings.Join(checkErrors, "\n\t-\t")) } if c.Sessions != nil && !featureflags.SessionsEnabled.Enabled() { return fmt.Errorf("sessions config requires sessions to be enabled (DEX_SESSIONS_ENABLED=true)") } if err := c.validateMFA(); err != nil { return err } return nil } func (c Config) validateMFA() error { mfa := c.MFA if len(mfa.Authenticators) == 0 && len(mfa.DefaultMFAChain) == 0 { return nil } if !featureflags.SessionsEnabled.Enabled() { return fmt.Errorf("mfa requires sessions to be enabled (DEX_SESSIONS_ENABLED=true)") } knownTypes := map[string]bool{"TOTP": true} ids := make(map[string]bool, len(mfa.Authenticators)) for _, auth := range mfa.Authenticators { if auth.ID == "" { return fmt.Errorf("mfa.authenticators: authenticator must have an id") } if ids[auth.ID] { return fmt.Errorf("mfa.authenticators: duplicate authenticator id %q", auth.ID) } ids[auth.ID] = true if !knownTypes[auth.Type] { return fmt.Errorf("mfa.authenticators: unknown type %q for authenticator %q", auth.Type, auth.ID) } } for _, authID := range mfa.DefaultMFAChain { if !ids[authID] { return fmt.Errorf("mfa.defaultMFAChain: references unknown authenticator %q", authID) } } for _, client := range c.StaticClients { for _, authID := range client.MFAChain { if !ids[authID] { return fmt.Errorf("staticClients: client %q references unknown MFA authenticator %q", client.ID, authID) } } } return nil } type password storage.Password func (p *password) UnmarshalJSON(b []byte) error { var data struct { Email string `json:"email"` Username string `json:"username"` Name string `json:"name"` PreferredUsername string `json:"preferredUsername"` EmailVerified *bool `json:"emailVerified"` UserID string `json:"userID"` Hash string `json:"hash"` HashFromEnv string `json:"hashFromEnv"` Groups []string `json:"groups"` } if err := configUnmarshaller(b, &data); err != nil { return err } *p = password(storage.Password{ Email: data.Email, Username: data.Username, Name: data.Name, PreferredUsername: data.PreferredUsername, EmailVerified: data.EmailVerified, UserID: data.UserID, Groups: data.Groups, }) if len(data.Hash) == 0 && len(data.HashFromEnv) > 0 { data.Hash = os.Getenv(data.HashFromEnv) } if len(data.Hash) == 0 { return fmt.Errorf("no password hash provided") } // If this value is a valid bcrypt, use it. _, bcryptErr := bcrypt.Cost([]byte(data.Hash)) if bcryptErr == nil { p.Hash = []byte(data.Hash) return nil } // For backwards compatibility try to base64 decode this value. hashBytes, err := base64.StdEncoding.DecodeString(data.Hash) if err != nil { return fmt.Errorf("malformed bcrypt hash: %v", bcryptErr) } if _, err := bcrypt.Cost(hashBytes); err != nil { return fmt.Errorf("malformed bcrypt hash: %v", err) } p.Hash = hashBytes return nil } // OAuth2 describes enabled OAuth2 extensions. type OAuth2 struct { // list of allowed grant types, // defaults to all supported types GrantTypes []string `json:"grantTypes"` ResponseTypes []string `json:"responseTypes"` // If specified, do not prompt the user to approve client authorization. The // act of logging in implies authorization. SkipApprovalScreen bool `json:"skipApprovalScreen"` // If specified, show the connector selection screen even if there's only one AlwaysShowLoginScreen bool `json:"alwaysShowLoginScreen"` // This is the connector that can be used for password grant PasswordConnector string `json:"passwordConnector"` // PKCE configuration PKCE PKCE `json:"pkce"` } // PKCE holds the PKCE (Proof Key for Code Exchange) configuration. type PKCE struct { // If true, PKCE is required for all authorization code flows. Enforce bool `json:"enforce"` // Supported code challenge methods. Defaults to ["S256", "plain"]. CodeChallengeMethodsSupported []string `json:"codeChallengeMethodsSupported"` } // Web is the config format for the HTTP server. type Web struct { HTTP string `json:"http"` HTTPS string `json:"https"` Headers Headers `json:"headers"` TLSCert string `json:"tlsCert"` TLSKey string `json:"tlsKey"` TLSMinVersion string `json:"tlsMinVersion"` TLSMaxVersion string `json:"tlsMaxVersion"` AllowedOrigins []string `json:"allowedOrigins"` AllowedHeaders []string `json:"allowedHeaders"` ClientRemoteIP ClientRemoteIP `json:"clientRemoteIP"` } type ClientRemoteIP struct { Header string `json:"header"` TrustedProxies []string `json:"trustedProxies"` } func (cr *ClientRemoteIP) ParseTrustedProxies() ([]netip.Prefix, error) { if cr == nil { return nil, nil } trusted := make([]netip.Prefix, 0, len(cr.TrustedProxies)) for _, cidr := range cr.TrustedProxies { ipNet, err := netip.ParsePrefix(cidr) if err != nil { return nil, fmt.Errorf("failed to parse CIDR %q: %v", cidr, err) } trusted = append(trusted, ipNet) } return trusted, nil } type Headers struct { // Set the Content-Security-Policy header to HTTP responses. // Unset if blank. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy ContentSecurityPolicy string `json:"Content-Security-Policy"` // Set the X-Frame-Options header to HTTP responses. // Unset if blank. Accepted values are deny and sameorigin. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options XFrameOptions string `json:"X-Frame-Options"` // Set the X-Content-Type-Options header to HTTP responses. // Unset if blank. Accepted value is nosniff. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options XContentTypeOptions string `json:"X-Content-Type-Options"` // Set the X-XSS-Protection header to all responses. // Unset if blank. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection XXSSProtection string `json:"X-XSS-Protection"` // Set the Strict-Transport-Security header to HTTP responses. // Unset if blank. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security StrictTransportSecurity string `json:"Strict-Transport-Security"` } func (h *Headers) ToHTTPHeader() http.Header { if h == nil { return make(map[string][]string) } header := make(map[string][]string) if h.ContentSecurityPolicy != "" { header["Content-Security-Policy"] = []string{h.ContentSecurityPolicy} } if h.XFrameOptions != "" { header["X-Frame-Options"] = []string{h.XFrameOptions} } if h.XContentTypeOptions != "" { header["X-Content-Type-Options"] = []string{h.XContentTypeOptions} } if h.XXSSProtection != "" { header["X-XSS-Protection"] = []string{h.XXSSProtection} } if h.StrictTransportSecurity != "" { header["Strict-Transport-Security"] = []string{h.StrictTransportSecurity} } return header } // Telemetry is the config format for telemetry including the HTTP server config. type Telemetry struct { HTTP string `json:"http"` // EnableProfiling makes profiling endpoints available via web interface host:port/debug/pprof/ EnableProfiling bool `json:"enableProfiling"` } // GRPC is the config for the gRPC API. type GRPC struct { // The port to listen on. Addr string `json:"addr"` TLSCert string `json:"tlsCert"` TLSKey string `json:"tlsKey"` TLSClientCA string `json:"tlsClientCA"` TLSMinVersion string `json:"tlsMinVersion"` TLSMaxVersion string `json:"tlsMaxVersion"` Reflection bool `json:"reflection"` } // Storage holds app's storage configuration. type Storage struct { Type string `json:"type"` Config StorageConfig `json:"config"` } // StorageConfig is a configuration that can create a storage. type StorageConfig interface { Open(logger *slog.Logger) (storage.Storage, error) } var ( _ StorageConfig = (*etcd.Etcd)(nil) _ StorageConfig = (*kubernetes.Config)(nil) _ StorageConfig = (*memory.Config)(nil) _ StorageConfig = (*sql.SQLite3)(nil) _ StorageConfig = (*sql.Postgres)(nil) _ StorageConfig = (*sql.MySQL)(nil) _ StorageConfig = (*ent.SQLite3)(nil) _ StorageConfig = (*ent.Postgres)(nil) _ StorageConfig = (*ent.MySQL)(nil) ) func getORMBasedSQLStorage(normal, entBased func() StorageConfig) func() StorageConfig { return func() StorageConfig { if featureflags.EntEnabled.Enabled() { return entBased() } return normal() } } // Recursively expand environment variables in the map to avoid // issues with JSON special characters and escapes func expandEnvInMap(m map[string]interface{}) { for k, v := range m { switch vt := v.(type) { case string: m[k] = os.ExpandEnv(vt) case map[string]interface{}: expandEnvInMap(vt) case []interface{}: for i, item := range vt { if itemMap, ok := item.(map[string]interface{}); ok { expandEnvInMap(itemMap) } else if itemString, ok := item.(string); ok { vt[i] = os.ExpandEnv(itemString) } } } } } var storages = map[string]func() StorageConfig{ "etcd": func() StorageConfig { return new(etcd.Etcd) }, "kubernetes": func() StorageConfig { return new(kubernetes.Config) }, "memory": func() StorageConfig { return new(memory.Config) }, "sqlite3": getORMBasedSQLStorage(func() StorageConfig { return new(sql.SQLite3) }, func() StorageConfig { return new(ent.SQLite3) }), "postgres": getORMBasedSQLStorage(func() StorageConfig { return new(sql.Postgres) }, func() StorageConfig { return new(ent.Postgres) }), "mysql": getORMBasedSQLStorage(func() StorageConfig { return new(sql.MySQL) }, func() StorageConfig { return new(ent.MySQL) }), } // UnmarshalJSON allows Storage to implement the unmarshaler interface to // dynamically determine the type of the storage config. func (s *Storage) UnmarshalJSON(b []byte) error { var store struct { Type string `json:"type"` Config json.RawMessage `json:"config"` } if err := configUnmarshaller(b, &store); err != nil { return fmt.Errorf("parse storage: %v", err) } f, ok := storages[store.Type] if !ok { return fmt.Errorf("unknown storage type %q", store.Type) } storageConfig := f() if len(store.Config) != 0 { data := []byte(store.Config) if featureflags.ExpandEnv.Enabled() { var rawMap map[string]interface{} if err := configUnmarshaller(store.Config, &rawMap); err != nil { return fmt.Errorf("unmarshal config for env expansion: %v", err) } // Recursively expand environment variables in the map to avoid // issues with JSON special characters and escapes expandEnvInMap(rawMap) // Marshal the expanded map back to JSON expandedData, err := json.Marshal(rawMap) if err != nil { return fmt.Errorf("marshal expanded config: %v", err) } data = expandedData } if err := configUnmarshaller(data, storageConfig); err != nil { return fmt.Errorf("parse storage config: %v", err) } } *s = Storage{ Type: store.Type, Config: storageConfig, } return nil } // Signer holds app's signer configuration. type Signer struct { Type string `json:"type"` Config SignerConfig `json:"config"` } // SignerConfig is a configuration that can create a signer. type SignerConfig interface{} var ( _ SignerConfig = (*signer.LocalConfig)(nil) _ SignerConfig = (*signer.VaultConfig)(nil) ) var signerConfigs = map[string]func() SignerConfig{ "local": func() SignerConfig { return new(signer.LocalConfig) }, "vault": func() SignerConfig { return new(signer.VaultConfig) }, } // UnmarshalJSON allows Signer to implement the unmarshaler interface to // dynamically determine the type of the signer config. func (s *Signer) UnmarshalJSON(b []byte) error { var signerData struct { Type string `json:"type"` Config json.RawMessage `json:"config"` } if err := json.Unmarshal(b, &signerData); err != nil { return fmt.Errorf("parse signer: %v", err) } f, ok := signerConfigs[signerData.Type] if !ok { return fmt.Errorf("unknown signer type %q", signerData.Type) } signerConfig := f() if len(signerData.Config) != 0 { data := []byte(signerData.Config) if featureflags.ExpandEnv.Enabled() { var rawMap map[string]interface{} if err := json.Unmarshal(signerData.Config, &rawMap); err != nil { return fmt.Errorf("unmarshal config for env expansion: %v", err) } // Recursively expand environment variables in the map expandEnvInMap(rawMap) // Marshal the expanded map back to JSON expandedData, err := json.Marshal(rawMap) if err != nil { return fmt.Errorf("marshal expanded config: %v", err) } data = expandedData } if err := json.Unmarshal(data, signerConfig); err != nil { return fmt.Errorf("parse signer config: %v", err) } } *s = Signer{ Type: signerData.Type, Config: signerConfig, } return nil } // Connector is a magical type that can unmarshal YAML dynamically. The // Type field determines the connector type, which is then customized for Config. type Connector struct { Type string `json:"type"` Name string `json:"name"` ID string `json:"id"` Config server.ConnectorConfig `json:"config"` GrantTypes []string `json:"grantTypes"` } // UnmarshalJSON allows Connector to implement the unmarshaler interface to // dynamically determine the type of the connector config. func (c *Connector) UnmarshalJSON(b []byte) error { var conn struct { Type string `json:"type"` Name string `json:"name"` ID string `json:"id"` Config json.RawMessage `json:"config"` GrantTypes []string `json:"grantTypes"` } if err := configUnmarshaller(b, &conn); err != nil { return fmt.Errorf("parse connector: %v", err) } f, ok := server.ConnectorsConfig[conn.Type] if !ok { return fmt.Errorf("unknown connector type %q", conn.Type) } connConfig := f() if len(conn.Config) != 0 { data := []byte(conn.Config) if featureflags.ExpandEnv.Enabled() { var rawMap map[string]interface{} if err := configUnmarshaller(conn.Config, &rawMap); err != nil { return fmt.Errorf("unmarshal config for env expansion: %v", err) } // Recursively expand environment variables in the map to avoid // issues with JSON special characters and escapes expandEnvInMap(rawMap) // Marshal the expanded map back to JSON expandedData, err := json.Marshal(rawMap) if err != nil { return fmt.Errorf("marshal expanded config: %v", err) } data = expandedData } if err := configUnmarshaller(data, connConfig); err != nil { return fmt.Errorf("parse connector config: %v", err) } } *c = Connector{ Type: conn.Type, Name: conn.Name, ID: conn.ID, Config: connConfig, GrantTypes: conn.GrantTypes, } return nil } // ToStorageConnector converts an object to storage connector type. func ToStorageConnector(c Connector) (storage.Connector, error) { data, err := json.Marshal(c.Config) if err != nil { return storage.Connector{}, fmt.Errorf("failed to marshal connector config: %v", err) } return storage.Connector{ ID: c.ID, Type: c.Type, Name: c.Name, Config: data, GrantTypes: c.GrantTypes, }, nil } // Expiry holds configuration for the validity period of components. type Expiry struct { // SigningKeys defines the duration of time after which the SigningKeys will be rotated. SigningKeys string `json:"signingKeys"` // IdTokens defines the duration of time for which the IdTokens will be valid. IDTokens string `json:"idTokens"` // AuthRequests defines the duration of time for which the AuthRequests will be valid. AuthRequests string `json:"authRequests"` // DeviceRequests defines the duration of time for which the DeviceRequests will be valid. DeviceRequests string `json:"deviceRequests"` // RefreshTokens defines refresh tokens expiry policy RefreshTokens RefreshToken `json:"refreshTokens"` } // Logger holds configuration required to customize logging for dex. type Logger struct { // Level sets logging level severity. Level slog.Level `json:"level"` // Format specifies the format to be used for logging. Format string `json:"format"` // ExcludeFields specifies log attribute keys that should be dropped from all // log output. This is useful for suppressing PII fields like email, username, // preferred_username, or groups in environments subject to GDPR or similar // data-handling constraints. ExcludeFields []string `json:"excludeFields"` } type RefreshToken struct { DisableRotation bool `json:"disableRotation"` ReuseInterval string `json:"reuseInterval"` AbsoluteLifetime string `json:"absoluteLifetime"` ValidIfNotUsedFor string `json:"validIfNotUsedFor"` } // Sessions holds authentication session configuration. type Sessions struct { // CookieName is the name of the session cookie. Defaults to "dex_session". CookieName string `json:"cookieName"` // AbsoluteLifetime is the maximum session lifetime from creation. Defaults to "24h". AbsoluteLifetime string `json:"absoluteLifetime"` // ValidIfNotUsedFor is the idle timeout. Defaults to "1h". ValidIfNotUsedFor string `json:"validIfNotUsedFor"` // RememberMeCheckedByDefault controls the default state of the "remember me" checkbox. RememberMeCheckedByDefault *bool `json:"rememberMeCheckedByDefault"` } // MFAAuthenticator defines a multi-factor authentication provider. type MFAAuthenticator struct { ID string `json:"id"` Type string `json:"type"` Config json.RawMessage `json:"config"` // ConnectorTypes limits this authenticator to specific connector types (e.g., "ldap", "oidc", "saml"). // If empty, the authenticator applies to all connector types. ConnectorTypes []string `json:"connectorTypes"` } // TOTPConfig holds configuration for a TOTP authenticator. type TOTPConfig struct { // Issuer is the name of the service shown in the authenticator app. Issuer string `json:"issuer"` } ================================================ FILE: cmd/dex/config_test.go ================================================ package main import ( "encoding/json" "log/slog" "os" "testing" "github.com/ghodss/yaml" "github.com/kylelemons/godebug/pretty" "github.com/dexidp/dex/connector/mock" "github.com/dexidp/dex/connector/oidc" "github.com/dexidp/dex/server" "github.com/dexidp/dex/server/signer" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/sql" ) var _ = yaml.YAMLToJSON func boolPtr(v bool) *bool { return &v } func TestValidConfiguration(t *testing.T) { configuration := Config{ Issuer: "http://127.0.0.1:5556/dex", Storage: Storage{ Type: "sqlite3", Config: &sql.SQLite3{ File: "examples/dex.db", }, }, Web: Web{ HTTP: "127.0.0.1:5556", }, StaticConnectors: []Connector{ { Type: "mockCallback", ID: "mock", Name: "Example", Config: &mock.CallbackConfig{}, }, }, } if err := configuration.Validate(); err != nil { t.Fatalf("this configuration should have been valid: %v", err) } } func TestInvalidConfiguration(t *testing.T) { configuration := Config{} err := configuration.Validate() if err == nil { t.Fatal("this configuration should be invalid") } got := err.Error() wanted := `invalid Config: - no issuer specified in config file - no storage supplied in config file - must supply a HTTP/HTTPS address to listen on` if got != wanted { t.Fatalf("Expected error message to be %q, got %q", wanted, got) } } func TestUnmarshalConfig(t *testing.T) { rawConfig := []byte(` issuer: http://127.0.0.1:5556/dex storage: type: postgres config: host: 10.0.0.1 port: 65432 maxOpenConns: 5 maxIdleConns: 3 connMaxLifetime: 30 connectionTimeout: 3 web: https: 127.0.0.1:5556 tlsMinVersion: 1.3 tlsMaxVersion: 1.2 headers: Strict-Transport-Security: "max-age=31536000; includeSubDomains" frontend: dir: ./web extra: foo: bar staticClients: - id: example-app redirectURIs: - 'http://127.0.0.1:5555/callback' name: 'Example App' secret: ZXhhbXBsZS1hcHAtc2VjcmV0 oauth2: alwaysShowLoginScreen: true grantTypes: - refresh_token - "urn:ietf:params:oauth:grant-type:token-exchange" connectors: - type: mockCallback id: mock name: Example grantTypes: - authorization_code - "urn:ietf:params:oauth:grant-type:token-exchange" - type: oidc id: google name: Google config: issuer: https://accounts.google.com clientID: foo clientSecret: bar redirectURI: http://127.0.0.1:5556/dex/callback/google enablePasswordDB: true staticPasswords: - email: "admin@example.com" # bcrypt hash of the string "password" hash: "$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy" username: "admin" name: "Admin User" emailVerified: false preferredUsername: "admin-public" groups: - "team-a" - "team-a/admins" userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" - email: "foo@example.com" # base64'd value of the same bcrypt hash above. We want to be able to parse both of these hash: "JDJhJDEwJDMzRU1UMGNWWVZsUHk2V0FNQ0xzY2VMWWpXaHVIcGJ6NXl1Wnh1L0dBRmowM0o5THl0anV5" username: "foo" userID: "41331323-6f44-45e6-b3b9-2c4b60c02be5" expiry: signingKeys: "7h" idTokens: "25h" authRequests: "25h" deviceRequests: "10m" logger: level: "debug" format: "json" additionalFeatures: [ "ConnectorsCRUD" ] `) want := Config{ Issuer: "http://127.0.0.1:5556/dex", Storage: Storage{ Type: "postgres", Config: &sql.Postgres{ NetworkDB: sql.NetworkDB{ Host: "10.0.0.1", Port: 65432, MaxOpenConns: 5, MaxIdleConns: 3, ConnMaxLifetime: 30, ConnectionTimeout: 3, }, }, }, Web: Web{ HTTPS: "127.0.0.1:5556", TLSMinVersion: "1.3", TLSMaxVersion: "1.2", Headers: Headers{ StrictTransportSecurity: "max-age=31536000; includeSubDomains", }, }, Frontend: server.WebConfig{ Dir: "./web", Extra: map[string]string{ "foo": "bar", }, }, StaticClients: []storage.Client{ { ID: "example-app", Secret: "ZXhhbXBsZS1hcHAtc2VjcmV0", Name: "Example App", RedirectURIs: []string{ "http://127.0.0.1:5555/callback", }, }, }, OAuth2: OAuth2{ AlwaysShowLoginScreen: true, GrantTypes: []string{ "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange", }, }, StaticConnectors: []Connector{ { Type: "mockCallback", ID: "mock", Name: "Example", Config: &mock.CallbackConfig{}, GrantTypes: []string{ "authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", }, }, { Type: "oidc", ID: "google", Name: "Google", Config: &oidc.Config{ Issuer: "https://accounts.google.com", ClientID: "foo", ClientSecret: "bar", RedirectURI: "http://127.0.0.1:5556/dex/callback/google", }, }, }, EnablePasswordDB: true, StaticPasswords: []password{ { Email: "admin@example.com", Hash: []byte("$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"), Username: "admin", Name: "Admin User", EmailVerified: boolPtr(false), PreferredUsername: "admin-public", UserID: "08a8684b-db88-4b73-90a9-3cd1661f5466", Groups: []string{ "team-a", "team-a/admins", }, }, { Email: "foo@example.com", Hash: []byte("$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"), Username: "foo", UserID: "41331323-6f44-45e6-b3b9-2c4b60c02be5", }, }, Expiry: Expiry{ SigningKeys: "7h", IDTokens: "25h", AuthRequests: "25h", DeviceRequests: "10m", }, Logger: Logger{ Level: slog.LevelDebug, Format: "json", }, } var c Config if err := yaml.Unmarshal(rawConfig, &c); err != nil { t.Fatalf("failed to decode config: %v", err) } if diff := pretty.Compare(c, want); diff != "" { t.Errorf("got!=want: %s", diff) } } func TestUnmarshalConfigWithEnvNoExpand(t *testing.T) { // If the env variable DEX_EXPAND_ENV is set and has a "falsy" value, os.ExpandEnv is disabled. // ParseBool: "It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False." checkUnmarshalConfigWithEnv(t, "0", false) checkUnmarshalConfigWithEnv(t, "f", false) checkUnmarshalConfigWithEnv(t, "F", false) checkUnmarshalConfigWithEnv(t, "FALSE", false) checkUnmarshalConfigWithEnv(t, "false", false) checkUnmarshalConfigWithEnv(t, "False", false) os.Unsetenv("DEX_EXPAND_ENV") } func TestUnmarshalConfigWithEnvExpand(t *testing.T) { // If the env variable DEX_EXPAND_ENV is unset or has a "truthy" or unknown value, os.ExpandEnv is enabled. // ParseBool: "It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False." checkUnmarshalConfigWithEnv(t, "1", true) checkUnmarshalConfigWithEnv(t, "t", true) checkUnmarshalConfigWithEnv(t, "T", true) checkUnmarshalConfigWithEnv(t, "TRUE", true) checkUnmarshalConfigWithEnv(t, "true", true) checkUnmarshalConfigWithEnv(t, "True", true) // Values that can't be parsed as bool: checkUnmarshalConfigWithEnv(t, "UNSET", true) checkUnmarshalConfigWithEnv(t, "", true) checkUnmarshalConfigWithEnv(t, "whatever - true is default", true) os.Unsetenv("DEX_EXPAND_ENV") } func checkUnmarshalConfigWithEnv(t *testing.T, dexExpandEnv string, wantExpandEnv bool) { // For hashFromEnv: os.Setenv("DEX_FOO_USER_PASSWORD", "$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy") // For os.ExpandEnv ($VAR -> value_of_VAR): os.Setenv("DEX_FOO_POSTGRES_HOST", "10.0.0.1") os.Setenv("DEX_FOO_POSTGRES_PASSWORD", `psql"test\pass`) os.Setenv("DEX_FOO_OIDC_CLIENT_SECRET", `abc"def\ghi`) if dexExpandEnv != "UNSET" { os.Setenv("DEX_EXPAND_ENV", dexExpandEnv) } else { os.Unsetenv("DEX_EXPAND_ENV") } rawConfig := []byte(` issuer: http://127.0.0.1:5556/dex storage: type: postgres config: # Env variables are expanded in raw YAML source. # Single quotes work fine, as long as the env variable doesn't contain any. host: '$DEX_FOO_POSTGRES_HOST' password: '$DEX_FOO_POSTGRES_PASSWORD' port: 65432 maxOpenConns: 5 maxIdleConns: 3 connMaxLifetime: 30 connectionTimeout: 3 web: http: 127.0.0.1:5556 frontend: dir: ./web extra: foo: bar staticClients: - id: example-app redirectURIs: - 'http://127.0.0.1:5555/callback' name: 'Example App' secret: ZXhhbXBsZS1hcHAtc2VjcmV0 oauth2: alwaysShowLoginScreen: true connectors: - type: mockCallback id: mock name: Example - type: oidc id: google name: Google config: issuer: https://accounts.google.com clientID: foo # Env variables are expanded in raw YAML source. # Single quotes work fine, as long as the env variable doesn't contain any. clientSecret: '$DEX_FOO_OIDC_CLIENT_SECRET' redirectURI: http://127.0.0.1:5556/dex/callback/google enablePasswordDB: true staticPasswords: - email: "admin@example.com" # bcrypt hash of the string "password" hash: "$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy" username: "admin" userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" - email: "foo@example.com" hashFromEnv: "DEX_FOO_USER_PASSWORD" username: "foo" userID: "41331323-6f44-45e6-b3b9-2c4b60c02be5" expiry: signingKeys: "7h" idTokens: "25h" authRequests: "25h" logger: level: "debug" format: "json" `) // This is not a valid hostname. It's only used to check whether os.ExpandEnv was applied or not. wantPostgresHost := "$DEX_FOO_POSTGRES_HOST" wantPostgresPassword := "$DEX_FOO_POSTGRES_PASSWORD" wantOidcClientSecret := "$DEX_FOO_OIDC_CLIENT_SECRET" if wantExpandEnv { wantPostgresHost = "10.0.0.1" wantPostgresPassword = `psql"test\pass` wantOidcClientSecret = `abc"def\ghi` } want := Config{ Issuer: "http://127.0.0.1:5556/dex", Storage: Storage{ Type: "postgres", Config: &sql.Postgres{ NetworkDB: sql.NetworkDB{ Host: wantPostgresHost, Password: wantPostgresPassword, Port: 65432, MaxOpenConns: 5, MaxIdleConns: 3, ConnMaxLifetime: 30, ConnectionTimeout: 3, }, }, }, Web: Web{ HTTP: "127.0.0.1:5556", }, Frontend: server.WebConfig{ Dir: "./web", Extra: map[string]string{ "foo": "bar", }, }, StaticClients: []storage.Client{ { ID: "example-app", Secret: "ZXhhbXBsZS1hcHAtc2VjcmV0", Name: "Example App", RedirectURIs: []string{ "http://127.0.0.1:5555/callback", }, }, }, OAuth2: OAuth2{ AlwaysShowLoginScreen: true, }, StaticConnectors: []Connector{ { Type: "mockCallback", ID: "mock", Name: "Example", Config: &mock.CallbackConfig{}, }, { Type: "oidc", ID: "google", Name: "Google", Config: &oidc.Config{ Issuer: "https://accounts.google.com", ClientID: "foo", ClientSecret: wantOidcClientSecret, RedirectURI: "http://127.0.0.1:5556/dex/callback/google", }, }, }, EnablePasswordDB: true, StaticPasswords: []password{ { Email: "admin@example.com", Hash: []byte("$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"), Username: "admin", UserID: "08a8684b-db88-4b73-90a9-3cd1661f5466", }, { Email: "foo@example.com", Hash: []byte("$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"), Username: "foo", UserID: "41331323-6f44-45e6-b3b9-2c4b60c02be5", }, }, Expiry: Expiry{ SigningKeys: "7h", IDTokens: "25h", AuthRequests: "25h", }, Logger: Logger{ Level: slog.LevelDebug, Format: "json", }, } var c Config if err := yaml.Unmarshal(rawConfig, &c); err != nil { t.Fatalf("failed to decode config: %v", err) } if diff := pretty.Compare(c, want); diff != "" { t.Errorf("got!=want: %s", diff) } } func TestSignerConfigUnmarshal(t *testing.T) { tests := []struct { name string config string wantErr bool check func(*Config) error }{ { name: "local signer with rotation period", config: ` issuer: http://127.0.0.1:5556/dex storage: type: memory web: http: 0.0.0.0:5556 signer: type: local config: keysRotationPeriod: 6h enablePasswordDB: true `, wantErr: false, check: func(c *Config) error { if c.Signer.Type != "local" { t.Errorf("expected signer type 'local', got %q", c.Signer.Type) } if localConfig, ok := c.Signer.Config.(*signer.LocalConfig); !ok { t.Error("expected LocalConfig") } else if localConfig.KeysRotationPeriod != "6h" { t.Errorf("expected keys rotation period '6h', got %q", localConfig.KeysRotationPeriod) } return nil }, }, { name: "vault signer", config: ` issuer: http://127.0.0.1:5556/dex storage: type: memory web: http: 0.0.0.0:5556 signer: type: vault config: addr: http://localhost:8200 token: test-token keyName: test-key enablePasswordDB: true `, wantErr: false, check: func(c *Config) error { if c.Signer.Type != "vault" { t.Errorf("expected signer type 'vault', got %q", c.Signer.Type) } if vaultConfig, ok := c.Signer.Config.(*signer.VaultConfig); !ok { t.Error("expected VaultConfig") } else { if vaultConfig.Addr != "http://localhost:8200" { t.Errorf("expected addr 'http://localhost:8200', got %q", vaultConfig.Addr) } if vaultConfig.Token != "test-token" { t.Errorf("expected token 'test-token', got %q", vaultConfig.Token) } if vaultConfig.KeyName != "test-key" { t.Errorf("expected keyName 'test-key', got %q", vaultConfig.KeyName) } } return nil }, }, { name: "default to local when no signer specified", config: ` issuer: http://127.0.0.1:5556/dex storage: type: memory web: http: 0.0.0.0:5556 enablePasswordDB: true `, wantErr: false, check: func(c *Config) error { if c.Signer.Type != "" { t.Errorf("expected signer type '', got %q", c.Signer.Type) } return nil }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var c Config data, err := yaml.YAMLToJSON([]byte(tt.config)) if err != nil { t.Fatalf("failed to convert yaml to json: %v", err) } err = json.Unmarshal(data, &c) if (err != nil) != tt.wantErr { t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr) return } if err == nil && tt.check != nil { if err := tt.check(&c); err != nil { t.Errorf("check failed: %v", err) } } }) } } ================================================ FILE: cmd/dex/excluding_handler.go ================================================ package main import ( "context" "log/slog" ) // excludingHandler is an slog.Handler wrapper that drops log attributes // whose keys match a configured set. This allows PII fields like email, // username, or groups to be redacted at the logger level rather than // requiring per-callsite suppression logic. type excludingHandler struct { inner slog.Handler exclude map[string]bool } func newExcludingHandler(inner slog.Handler, fields []string) slog.Handler { if len(fields) == 0 { return inner } m := make(map[string]bool, len(fields)) for _, f := range fields { m[f] = true } return &excludingHandler{inner: inner, exclude: m} } func (h *excludingHandler) Enabled(ctx context.Context, level slog.Level) bool { return h.inner.Enabled(ctx, level) } func (h *excludingHandler) Handle(ctx context.Context, record slog.Record) error { // Rebuild the record without excluded attributes. filtered := slog.NewRecord(record.Time, record.Level, record.Message, record.PC) record.Attrs(func(a slog.Attr) bool { if !h.exclude[a.Key] { filtered.AddAttrs(a) } return true }) return h.inner.Handle(ctx, filtered) } func (h *excludingHandler) WithAttrs(attrs []slog.Attr) slog.Handler { var kept []slog.Attr for _, a := range attrs { if !h.exclude[a.Key] { kept = append(kept, a) } } return &excludingHandler{inner: h.inner.WithAttrs(kept), exclude: h.exclude} } func (h *excludingHandler) WithGroup(name string) slog.Handler { return &excludingHandler{inner: h.inner.WithGroup(name), exclude: h.exclude} } ================================================ FILE: cmd/dex/excluding_handler_test.go ================================================ package main import ( "bytes" "context" "encoding/json" "log/slog" "testing" ) func TestExcludingHandler(t *testing.T) { tests := []struct { name string exclude []string logAttrs []slog.Attr wantKeys []string absentKeys []string }{ { name: "no exclusions", exclude: nil, logAttrs: []slog.Attr{ slog.String("email", "user@example.com"), slog.String("connector_id", "github"), }, wantKeys: []string{"email", "connector_id"}, }, { name: "exclude email", exclude: []string{"email"}, logAttrs: []slog.Attr{ slog.String("email", "user@example.com"), slog.String("connector_id", "github"), }, wantKeys: []string{"connector_id"}, absentKeys: []string{"email"}, }, { name: "exclude multiple fields", exclude: []string{"email", "username", "groups"}, logAttrs: []slog.Attr{ slog.String("email", "user@example.com"), slog.String("username", "johndoe"), slog.String("connector_id", "github"), slog.Any("groups", []string{"admin"}), }, wantKeys: []string{"connector_id"}, absentKeys: []string{"email", "username", "groups"}, }, { name: "exclude non-existent field is harmless", exclude: []string{"nonexistent"}, logAttrs: []slog.Attr{ slog.String("email", "user@example.com"), }, wantKeys: []string{"email"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var buf bytes.Buffer inner := slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo}) handler := newExcludingHandler(inner, tt.exclude) logger := slog.New(handler) attrs := make([]any, 0, len(tt.logAttrs)*2) for _, a := range tt.logAttrs { attrs = append(attrs, a) } logger.Info("test message", attrs...) var result map[string]any if err := json.Unmarshal(buf.Bytes(), &result); err != nil { t.Fatalf("failed to parse log output: %v", err) } for _, key := range tt.wantKeys { if _, ok := result[key]; !ok { t.Errorf("expected key %q in log output", key) } } for _, key := range tt.absentKeys { if _, ok := result[key]; ok { t.Errorf("expected key %q to be absent from log output", key) } } }) } } func TestExcludingHandlerWithAttrs(t *testing.T) { var buf bytes.Buffer inner := slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo}) handler := newExcludingHandler(inner, []string{"email"}) logger := slog.New(handler) // Pre-bind an excluded attr via With child := logger.With("email", "user@example.com", "connector_id", "github") child.Info("login successful") var result map[string]any if err := json.Unmarshal(buf.Bytes(), &result); err != nil { t.Fatalf("failed to parse log output: %v", err) } if _, ok := result["email"]; ok { t.Error("expected email to be excluded from WithAttrs output") } if _, ok := result["connector_id"]; !ok { t.Error("expected connector_id to be present") } } func TestExcludingHandlerEnabled(t *testing.T) { inner := slog.NewJSONHandler(&bytes.Buffer{}, &slog.HandlerOptions{Level: slog.LevelWarn}) handler := newExcludingHandler(inner, []string{"email"}) if handler.Enabled(context.Background(), slog.LevelInfo) { t.Error("expected Info to be disabled when handler level is Warn") } if !handler.Enabled(context.Background(), slog.LevelWarn) { t.Error("expected Warn to be enabled") } } func TestExcludingHandlerNilFields(t *testing.T) { var buf bytes.Buffer inner := slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo}) // With nil/empty fields, should return the inner handler directly handler := newExcludingHandler(inner, nil) if _, ok := handler.(*excludingHandler); ok { t.Error("expected nil fields to return inner handler directly, not wrap it") } handler = newExcludingHandler(inner, []string{}) if _, ok := handler.(*excludingHandler); ok { t.Error("expected empty fields to return inner handler directly, not wrap it") } } ================================================ FILE: cmd/dex/logger.go ================================================ package main import ( "context" "fmt" "log/slog" "os" "strings" "github.com/dexidp/dex/server" ) var logFormats = []string{"json", "text"} func newLogger(level slog.Level, format string, excludeFields []string) (*slog.Logger, error) { var handler slog.Handler switch strings.ToLower(format) { case "", "text": handler = slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ Level: level, }) case "json": handler = slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ Level: level, }) default: return nil, fmt.Errorf("log format is not one of the supported values (%s): %s", strings.Join(logFormats, ", "), format) } handler = newExcludingHandler(handler, excludeFields) return slog.New(newRequestContextHandler(handler)), nil } var _ slog.Handler = requestContextHandler{} type requestContextHandler struct { handler slog.Handler } func newRequestContextHandler(handler slog.Handler) slog.Handler { return requestContextHandler{ handler: handler, } } func (h requestContextHandler) Enabled(ctx context.Context, level slog.Level) bool { return h.handler.Enabled(ctx, level) } func (h requestContextHandler) Handle(ctx context.Context, record slog.Record) error { if v, ok := ctx.Value(server.RequestKeyRemoteIP).(string); ok { record.AddAttrs(slog.String(string(server.RequestKeyRemoteIP), v)) } if v, ok := ctx.Value(server.RequestKeyRequestID).(string); ok { record.AddAttrs(slog.String(string(server.RequestKeyRequestID), v)) } return h.handler.Handle(ctx, record) } func (h requestContextHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return requestContextHandler{h.handler.WithAttrs(attrs)} } func (h requestContextHandler) WithGroup(name string) slog.Handler { return requestContextHandler{h.handler.WithGroup(name)} } ================================================ FILE: cmd/dex/main.go ================================================ package main import ( "fmt" "os" "github.com/spf13/cobra" ) func commandRoot() *cobra.Command { rootCmd := &cobra.Command{ Use: "dex", Run: func(cmd *cobra.Command, args []string) { cmd.Help() os.Exit(2) }, } rootCmd.AddCommand(commandServe()) rootCmd.AddCommand(commandVersion()) return rootCmd } func main() { if err := commandRoot().Execute(); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(2) } } ================================================ FILE: cmd/dex/serve.go ================================================ package main import ( "context" "crypto/tls" "crypto/x509" "encoding/json" "errors" "fmt" "log/slog" "net" "net/http" "net/http/pprof" "os" "os/signal" "path/filepath" "runtime" "strings" "sync/atomic" "syscall" "time" gosundheit "github.com/AppsFlyer/go-sundheit" "github.com/AppsFlyer/go-sundheit/checks" gosundheithttp "github.com/AppsFlyer/go-sundheit/http" "github.com/fsnotify/fsnotify" "github.com/ghodss/yaml" grpcprometheus "github.com/grpc-ecosystem/go-grpc-prometheus" "github.com/oklog/run" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/spf13/cobra" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" "github.com/dexidp/dex/api/v2" "github.com/dexidp/dex/pkg/featureflags" "github.com/dexidp/dex/server" "github.com/dexidp/dex/server/signer" "github.com/dexidp/dex/storage" ) type serveOptions struct { // Config file path config string // Flags webHTTPAddr string webHTTPSAddr string telemetryAddr string grpcAddr string } var buildInfo = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "build_info", Namespace: "dex", Help: "A metric with a constant '1' value labeled by version from which Dex was built.", }, []string{"version", "go_version", "platform"}, ) func commandServe() *cobra.Command { options := serveOptions{} cmd := &cobra.Command{ Use: "serve [flags] [config file]", Short: "Launch Dex", Example: "dex serve config.yaml", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true cmd.SilenceErrors = true options.config = args[0] return runServe(options) }, } flags := cmd.Flags() flags.StringVar(&options.webHTTPAddr, "web-http-addr", "", "Web HTTP address") flags.StringVar(&options.webHTTPSAddr, "web-https-addr", "", "Web HTTPS address") flags.StringVar(&options.telemetryAddr, "telemetry-addr", "", "Telemetry address") flags.StringVar(&options.grpcAddr, "grpc-addr", "", "gRPC API address") return cmd } func runServe(options serveOptions) error { configFile := options.config configData, err := os.ReadFile(configFile) if err != nil { return fmt.Errorf("failed to read config file %s: %v", configFile, err) } var c Config jsonConfigData, err := yaml.YAMLToJSON(configData) if err != nil { return fmt.Errorf("error parse config file %s: %v", configFile, err) } if err := configUnmarshaller(jsonConfigData, &c); err != nil { return fmt.Errorf("error unmarshalling config file %s: %v", configFile, err) } applyConfigOverrides(options, &c) logger, err := newLogger(c.Logger.Level, c.Logger.Format, c.Logger.ExcludeFields) if err != nil { return fmt.Errorf("invalid config: %v", err) } logger.Info( "Version info", "dex_version", version, slog.Group("go", "version", runtime.Version(), "os", runtime.GOOS, "arch", runtime.GOARCH, ), ) if c.Logger.Level != slog.LevelInfo { logger.Info("config using log level", "level", c.Logger.Level) } if err := c.Validate(); err != nil { return err } logger.Info("config issuer", "issuer", c.Issuer) prometheusRegistry := prometheus.NewRegistry() prometheusRegistry.MustRegister(buildInfo) recordBuildInfo() err = prometheusRegistry.Register(collectors.NewGoCollector()) if err != nil { return fmt.Errorf("failed to register Go runtime metrics: %v", err) } err = prometheusRegistry.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})) if err != nil { return fmt.Errorf("failed to register process metrics: %v", err) } grpcMetrics := grpcprometheus.NewServerMetrics() err = prometheusRegistry.Register(grpcMetrics) if err != nil { return fmt.Errorf("failed to register gRPC server metrics: %v", err) } var grpcOptions []grpc.ServerOption allowedTLSCiphers := []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, tls.TLS_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, } allowedTLSVersions := map[string]int{ "1.2": tls.VersionTLS12, "1.3": tls.VersionTLS13, } if c.GRPC.TLSCert != "" { tlsMinVersion := tls.VersionTLS12 if c.GRPC.TLSMinVersion != "" { tlsMinVersion = allowedTLSVersions[c.GRPC.TLSMinVersion] } tlsMaxVersion := 0 // default for max is whatever Go defaults to if c.GRPC.TLSMaxVersion != "" { tlsMaxVersion = allowedTLSVersions[c.GRPC.TLSMaxVersion] } baseTLSConfig := &tls.Config{ MinVersion: uint16(tlsMinVersion), MaxVersion: uint16(tlsMaxVersion), CipherSuites: allowedTLSCiphers, PreferServerCipherSuites: true, } tlsConfig, err := newTLSReloader(logger, c.GRPC.TLSCert, c.GRPC.TLSKey, c.GRPC.TLSClientCA, baseTLSConfig) if err != nil { return fmt.Errorf("invalid config: get gRPC TLS: %v", err) } if c.GRPC.TLSClientCA != "" { // Only add metrics if client auth is enabled grpcOptions = append(grpcOptions, grpc.StreamInterceptor(grpcMetrics.StreamServerInterceptor()), grpc.UnaryInterceptor(grpcMetrics.UnaryServerInterceptor()), ) } grpcOptions = append(grpcOptions, grpc.Creds(credentials.NewTLS(tlsConfig))) } s, err := c.Storage.Config.Open(logger) if err != nil { return fmt.Errorf("failed to initialize storage: %v", err) } defer s.Close() logger.Info("config storage", "storage_type", c.Storage.Type) if len(c.StaticClients) > 0 { for i, client := range c.StaticClients { if client.Name == "" { return fmt.Errorf("invalid config: Name field is required for a client") } if client.ID == "" && client.IDEnv == "" { return fmt.Errorf("invalid config: ID or IDEnv field is required for a client") } if client.IDEnv != "" { if client.ID != "" { return fmt.Errorf("invalid config: ID and IDEnv fields are exclusive for client %q", client.ID) } c.StaticClients[i].ID = os.Getenv(client.IDEnv) } if client.Secret == "" && client.SecretEnv == "" && !client.Public { return fmt.Errorf("invalid config: Secret or SecretEnv field is required for client %q", client.ID) } if client.SecretEnv != "" { if client.Secret != "" { return fmt.Errorf("invalid config: Secret and SecretEnv fields are exclusive for client %q", client.ID) } c.StaticClients[i].Secret = os.Getenv(client.SecretEnv) } logger.Info("config static client", "client_name", client.Name) } s = storage.WithStaticClients(s, c.StaticClients) } if len(c.StaticPasswords) > 0 { passwords := make([]storage.Password, len(c.StaticPasswords)) for i, p := range c.StaticPasswords { passwords[i] = storage.Password(p) } s = storage.WithStaticPasswords(s, passwords, logger) } storageConnectors := make([]storage.Connector, len(c.StaticConnectors)) for i, c := range c.StaticConnectors { if c.ID == "" || c.Name == "" || c.Type == "" { return fmt.Errorf("invalid config: ID, Type and Name fields are required for a connector") } if c.Config == nil { return fmt.Errorf("invalid config: no config field for connector %q", c.ID) } for _, gt := range c.GrantTypes { if !server.ConnectorGrantTypes[gt] { return fmt.Errorf("invalid config: unknown grant type %q for connector %q", gt, c.ID) } } logger.Info("config connector", "connector_id", c.ID) // convert to a storage connector object conn, err := ToStorageConnector(c) if err != nil { return fmt.Errorf("failed to initialize storage connectors: %v", err) } storageConnectors[i] = conn } if c.EnablePasswordDB { storageConnectors = append(storageConnectors, storage.Connector{ ID: server.LocalConnector, Name: "Email", Type: server.LocalConnector, }) logger.Info("config connector: local passwords enabled") } s = storage.WithStaticConnectors(s, storageConnectors) if len(c.OAuth2.ResponseTypes) > 0 { logger.Info("config response types accepted", "response_types", c.OAuth2.ResponseTypes) } if c.OAuth2.SkipApprovalScreen { logger.Info("config skipping approval screen") } if c.OAuth2.PasswordConnector != "" { logger.Info("config using password grant connector", "password_connector", c.OAuth2.PasswordConnector) } if len(c.Web.AllowedOrigins) > 0 { logger.Info("config allowed origins", "origins", c.Web.AllowedOrigins) } if featureflags.ContinueOnConnectorFailure.Enabled() { logger.Info("continue on connector failure feature flag enabled") } // explicitly convert to UTC. now := func() time.Time { return time.Now().UTC() } healthChecker := gosundheit.New() // Parse expiry durations idTokensValidFor := 24 * time.Hour // default if c.Expiry.IDTokens != "" { var err error idTokensValidFor, err = time.ParseDuration(c.Expiry.IDTokens) if err != nil { return fmt.Errorf("invalid config value %q for id token expiry: %v", c.Expiry.IDTokens, err) } logger.Info("config id tokens", "valid_for", idTokensValidFor) } // Create signer var signerInstance signer.Signer switch c.Signer.Type { case "vault": vaultConfig, ok := c.Signer.Config.(*signer.VaultConfig) if !ok { return fmt.Errorf("invalid vault signer config") } signerInstance, err = vaultConfig.Open(context.Background()) if err != nil { return fmt.Errorf("failed to open vault signer: %v", err) } logger.Info("signer configured", "type", "vault") case "local": localConfig, ok := c.Signer.Config.(*signer.LocalConfig) if !ok { return fmt.Errorf("invalid local signer config") } if localConfig.KeysRotationPeriod == "" { return fmt.Errorf("failed to open local signer: signer.config.keysRotationPeriod must be specified") } if c.Expiry.SigningKeys != "" { logger.Warn("both expiry.signingKeys and signer.config.keysRotationPeriod specified, using signer.config.keysRotationPeriod") } signerInstance, err = localConfig.Open(context.Background(), s, idTokensValidFor, now, logger) if err != nil { return fmt.Errorf("failed to open local signer: %v", err) } logger.Info("signer configured", "type", "local", "keys_rotation_period", localConfig.KeysRotationPeriod) case "": // Default to local signer // Handle deprecated expiry.signingKeys configuration if c.Expiry.SigningKeys != "" { logger.Warn("config expiry.signingKeys will be removed in a future release", "use_instead", "signer.config.keysRotationPeriod", "current_value", c.Expiry.SigningKeys, "deprecated", true) } else { c.Expiry.SigningKeys = "6h" } localConfig := signer.LocalConfig{KeysRotationPeriod: c.Expiry.SigningKeys} signerInstance, err = localConfig.Open(context.Background(), s, idTokensValidFor, now, logger) if err != nil { return fmt.Errorf("failed to open local signer: %v", err) } logger.Info("signer configured", "type", "local", "keys_rotation_period", localConfig.KeysRotationPeriod) default: return fmt.Errorf("unknown signer type %q", c.Signer.Type) } serverConfig := server.Config{ AllowedGrantTypes: c.OAuth2.GrantTypes, SupportedResponseTypes: c.OAuth2.ResponseTypes, SkipApprovalScreen: c.OAuth2.SkipApprovalScreen, AlwaysShowLoginScreen: c.OAuth2.AlwaysShowLoginScreen, PasswordConnector: c.OAuth2.PasswordConnector, PKCE: server.PKCEConfig{ Enforce: c.OAuth2.PKCE.Enforce, CodeChallengeMethodsSupported: c.OAuth2.PKCE.CodeChallengeMethodsSupported, }, Headers: c.Web.Headers.ToHTTPHeader(), AllowedOrigins: c.Web.AllowedOrigins, AllowedHeaders: c.Web.AllowedHeaders, Issuer: c.Issuer, Storage: s, Web: c.Frontend, Logger: logger, Now: now, PrometheusRegistry: prometheusRegistry, HealthChecker: healthChecker, ContinueOnConnectorFailure: featureflags.ContinueOnConnectorFailure.Enabled(), Signer: signerInstance, IDTokensValidFor: idTokensValidFor, MFAProviders: buildMFAProviders(c.MFA.Authenticators, logger), DefaultMFAChain: c.MFA.DefaultMFAChain, } if c.Expiry.AuthRequests != "" { authRequests, err := time.ParseDuration(c.Expiry.AuthRequests) if err != nil { return fmt.Errorf("invalid config value %q for auth request expiry: %v", c.Expiry.AuthRequests, err) } logger.Info("config auth requests", "valid_for", authRequests) serverConfig.AuthRequestsValidFor = authRequests } if c.Expiry.DeviceRequests != "" { deviceRequests, err := time.ParseDuration(c.Expiry.DeviceRequests) if err != nil { return fmt.Errorf("invalid config value %q for device request expiry: %v", c.Expiry.DeviceRequests, err) } logger.Info("config device requests", "valid_for", deviceRequests) serverConfig.DeviceRequestsValidFor = deviceRequests } refreshTokenPolicy, err := server.NewRefreshTokenPolicy( logger, c.Expiry.RefreshTokens.DisableRotation, c.Expiry.RefreshTokens.ValidIfNotUsedFor, c.Expiry.RefreshTokens.AbsoluteLifetime, c.Expiry.RefreshTokens.ReuseInterval, ) if err != nil { return fmt.Errorf("invalid refresh token expiration policy config: %v", err) } serverConfig.RefreshTokenPolicy = refreshTokenPolicy if featureflags.SessionsEnabled.Enabled() { sessionConfig, err := parseSessionConfig(c.Sessions) if err != nil { return fmt.Errorf("invalid session config: %v", err) } serverConfig.SessionConfig = sessionConfig logger.Info("config sessions", "cookie_name", sessionConfig.CookieName, "absolute_lifetime", sessionConfig.AbsoluteLifetime, "valid_if_not_used_for", sessionConfig.ValidIfNotUsedFor, ) } serverConfig.RealIPHeader = c.Web.ClientRemoteIP.Header serverConfig.TrustedRealIPCIDRs, err = c.Web.ClientRemoteIP.ParseTrustedProxies() if err != nil { return fmt.Errorf("failed to parse client remote IP settings: %v", err) } serv, err := server.NewServer(context.Background(), serverConfig) if err != nil { return fmt.Errorf("failed to initialize server: %v", err) } telemetryRouter := http.NewServeMux() telemetryRouter.Handle("/metrics", promhttp.HandlerFor(prometheusRegistry, promhttp.HandlerOpts{})) // Configure health checker { handler := gosundheithttp.HandleHealthJSON(healthChecker) telemetryRouter.Handle("/healthz", handler) // Kubernetes style health checks telemetryRouter.HandleFunc("/healthz/live", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("ok")) }) telemetryRouter.Handle("/healthz/ready", handler) } healthChecker.RegisterCheck( &checks.CustomCheck{ CheckName: "storage", CheckFunc: storage.NewCustomHealthCheckFunc(serverConfig.Storage, serverConfig.Now), }, gosundheit.ExecutionPeriod(15*time.Second), gosundheit.InitiallyPassing(true), ) var group run.Group // Set up telemetry server if c.Telemetry.HTTP != "" { const name = "telemetry" logger.Info("listening on", "server", name, "address", c.Telemetry.HTTP) l, err := net.Listen("tcp", c.Telemetry.HTTP) if err != nil { return fmt.Errorf("listening (%s) on %s: %v", name, c.Telemetry.HTTP, err) } if c.Telemetry.EnableProfiling { pprofHandler(telemetryRouter) } server := &http.Server{ Handler: telemetryRouter, } defer server.Close() group.Add(func() error { return server.Serve(l) }, func(err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() logger.Debug("starting graceful shutdown", "server", name) if err := server.Shutdown(ctx); err != nil { logger.Error("graceful shutdown", "server", name, "err", err) } }) } // Set up http server if c.Web.HTTP != "" { const name = "http" logger.Info("listening on", "server", name, "address", c.Web.HTTP) l, err := net.Listen("tcp", c.Web.HTTP) if err != nil { return fmt.Errorf("listening (%s) on %s: %v", name, c.Web.HTTP, err) } server := &http.Server{ Handler: serv, } defer server.Close() group.Add(func() error { return server.Serve(l) }, func(err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() logger.Debug("starting graceful shutdown", "server", name) if err := server.Shutdown(ctx); err != nil { logger.Error("graceful shutdown", "server", name, "err", err) } }) } // Set up https server if c.Web.HTTPS != "" { const name = "https" logger.Info("listening on", "server", name, "address", c.Web.HTTPS) l, err := net.Listen("tcp", c.Web.HTTPS) if err != nil { return fmt.Errorf("listening (%s) on %s: %v", name, c.Web.HTTPS, err) } tlsMinVersion := tls.VersionTLS12 if c.Web.TLSMinVersion != "" { tlsMinVersion = allowedTLSVersions[c.Web.TLSMinVersion] } tlsMaxVersion := 0 // default for max is whatever Go defaults to if c.Web.TLSMaxVersion != "" { tlsMaxVersion = allowedTLSVersions[c.Web.TLSMaxVersion] } baseTLSConfig := &tls.Config{ MinVersion: uint16(tlsMinVersion), MaxVersion: uint16(tlsMaxVersion), CipherSuites: allowedTLSCiphers, PreferServerCipherSuites: true, } tlsConfig, err := newTLSReloader(logger, c.Web.TLSCert, c.Web.TLSKey, "", baseTLSConfig) if err != nil { return fmt.Errorf("invalid config: get HTTP TLS: %v", err) } server := &http.Server{ Handler: serv, TLSConfig: tlsConfig, } defer server.Close() group.Add(func() error { return server.ServeTLS(l, "", "") }, func(err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() logger.Debug("starting graceful shutdown", "server", name) if err := server.Shutdown(ctx); err != nil { logger.Error("graceful shutdown", "server", name, "err", err) } }) } // Set up grpc server if c.GRPC.Addr != "" { logger.Info("listening on", "server", "grpc", "address", c.GRPC.Addr) grpcListener, err := net.Listen("tcp", c.GRPC.Addr) if err != nil { return fmt.Errorf("listening (grpc) on %s: %w", c.GRPC.Addr, err) } grpcSrv := grpc.NewServer(grpcOptions...) api.RegisterDexServer(grpcSrv, server.NewAPI(serverConfig.Storage, logger, version, serv)) grpcMetrics.InitializeMetrics(grpcSrv) if c.GRPC.Reflection { logger.Info("enabling reflection in grpc service") reflection.Register(grpcSrv) } group.Add(func() error { return grpcSrv.Serve(grpcListener) }, func(err error) { logger.Debug("starting graceful shutdown", "server", "grpc") grpcSrv.GracefulStop() }) } group.Add(run.SignalHandler(context.Background(), os.Interrupt, syscall.SIGTERM)) if err := group.Run(); err != nil { if _, ok := err.(run.SignalError); !ok { return fmt.Errorf("run groups: %w", err) } logger.Info("shutdown now", "err", err) } return nil } func applyConfigOverrides(options serveOptions, config *Config) { if options.webHTTPAddr != "" { config.Web.HTTP = options.webHTTPAddr } if options.webHTTPSAddr != "" { config.Web.HTTPS = options.webHTTPSAddr } if options.telemetryAddr != "" { config.Telemetry.HTTP = options.telemetryAddr } if options.grpcAddr != "" { config.GRPC.Addr = options.grpcAddr } if config.Frontend.Dir == "" { config.Frontend.Dir = os.Getenv("DEX_FRONTEND_DIR") } if len(config.OAuth2.GrantTypes) == 0 { config.OAuth2.GrantTypes = []string{ "authorization_code", "implicit", "password", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code", "urn:ietf:params:oauth:grant-type:token-exchange", } if featureflags.ClientCredentialGrantEnabledByDefault.Enabled() { config.OAuth2.GrantTypes = append(config.OAuth2.GrantTypes, "client_credentials") } } } func pprofHandler(router *http.ServeMux) { router.HandleFunc("/debug/pprof/", pprof.Index) router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) router.HandleFunc("/debug/pprof/profile", pprof.Profile) router.HandleFunc("/debug/pprof/symbol", pprof.Symbol) router.HandleFunc("/debug/pprof/trace", pprof.Trace) } // newTLSReloader returns a [tls.Config] with GetCertificate or GetConfigForClient set // to reload certificates from the given paths on SIGHUP or on file creates (atomic update via rename). func newTLSReloader(logger *slog.Logger, certFile, keyFile, caFile string, baseConfig *tls.Config) (*tls.Config, error) { // trigger reload on channel sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGHUP) // files to watch watchFiles := map[string]struct{}{ certFile: {}, keyFile: {}, } if caFile != "" { watchFiles[caFile] = struct{}{} } watchDirs := make(map[string]struct{}) // dedupe dirs for f := range watchFiles { dir := filepath.Dir(f) if !strings.HasPrefix(f, dir) { // normalize name to have ./ prefix if only a local path was provided // can't pass "" to watcher.Add watchFiles[dir+string(filepath.Separator)+f] = struct{}{} } watchDirs[dir] = struct{}{} } // trigger reload on file change watcher, err := fsnotify.NewWatcher() if err != nil { return nil, fmt.Errorf("create watcher for TLS reloader: %v", err) } // recommended by fsnotify: watch the dir to handle renames // https://pkg.go.dev/github.com/fsnotify/fsnotify#hdr-Watching_files for dir := range watchDirs { logger.Debug("watching dir", "dir", dir) err := watcher.Add(dir) if err != nil { return nil, fmt.Errorf("watch dir for TLS reloader: %v", err) } } // load once outside the goroutine so we can return an error on misconfig initialConfig, err := loadTLSConfig(certFile, keyFile, caFile, baseConfig) if err != nil { return nil, fmt.Errorf("load TLS config: %v", err) } // stored version of current tls config ptr := &atomic.Pointer[tls.Config]{} ptr.Store(initialConfig) // start background worker to reload certs go func() { loop: for { select { case sig := <-sigc: logger.Debug("reloading cert from signal", "signal", sig) case evt := <-watcher.Events: if _, ok := watchFiles[evt.Name]; !ok || !evt.Has(fsnotify.Create) { continue loop } logger.Debug("reloading cert from fsnotify", "event", evt.Name, "operation", evt.Op.String()) case err := <-watcher.Errors: logger.Error("TLS reloader watch", "err", err) } loaded, err := loadTLSConfig(certFile, keyFile, caFile, baseConfig) if err != nil { logger.Error("reload TLS config", "err", err) } ptr.Store(loaded) } }() // https://pkg.go.dev/crypto/tls#baseConfig // Server configurations must set one of Certificates, GetCertificate or GetConfigForClient. if caFile != "" { // grpc will use this via tls.Server for mTLS initialConfig.GetConfigForClient = func(chi *tls.ClientHelloInfo) (*tls.Config, error) { return ptr.Load(), nil } } else { // net/http only uses Certificates or GetCertificate initialConfig.GetCertificate = func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { return &ptr.Load().Certificates[0], nil } } return initialConfig, nil } // loadTLSConfig loads the given file paths into a [tls.Config] func loadTLSConfig(certFile, keyFile, caFile string, baseConfig *tls.Config) (*tls.Config, error) { cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, fmt.Errorf("loading TLS keypair: %v", err) } loadedConfig := baseConfig.Clone() // copy loadedConfig.Certificates = []tls.Certificate{cert} if caFile != "" { cPool := x509.NewCertPool() clientCert, err := os.ReadFile(caFile) if err != nil { return nil, fmt.Errorf("reading from client CA file: %v", err) } if !cPool.AppendCertsFromPEM(clientCert) { return nil, errors.New("failed to parse client CA") } loadedConfig.ClientAuth = tls.RequireAndVerifyClientCert loadedConfig.ClientCAs = cPool } return loadedConfig, nil } // recordBuildInfo publishes information about Dex version and runtime info through an info metric (gauge). func recordBuildInfo() { buildInfo.WithLabelValues(version, runtime.Version(), fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)).Set(1) } func parseSessionConfig(s *Sessions) (*server.SessionConfig, error) { sc := &server.SessionConfig{ CookieName: "dex_session", AbsoluteLifetime: 24 * time.Hour, ValidIfNotUsedFor: 1 * time.Hour, RememberMeCheckedByDefault: true, } if s != nil { if s.CookieName != "" { sc.CookieName = s.CookieName } if s.AbsoluteLifetime != "" { d, err := time.ParseDuration(s.AbsoluteLifetime) if err != nil { return nil, fmt.Errorf("invalid absoluteLifetime %q: %v", s.AbsoluteLifetime, err) } sc.AbsoluteLifetime = d } if s.ValidIfNotUsedFor != "" { d, err := time.ParseDuration(s.ValidIfNotUsedFor) if err != nil { return nil, fmt.Errorf("invalid validIfNotUsedFor %q: %v", s.ValidIfNotUsedFor, err) } sc.ValidIfNotUsedFor = d } if s.RememberMeCheckedByDefault != nil { sc.RememberMeCheckedByDefault = *s.RememberMeCheckedByDefault } } if sc.AbsoluteLifetime <= 0 { return nil, fmt.Errorf("absoluteLifetime must be positive, got %v", sc.AbsoluteLifetime) } if sc.ValidIfNotUsedFor <= 0 { return nil, fmt.Errorf("validIfNotUsedFor must be positive, got %v", sc.ValidIfNotUsedFor) } if sc.ValidIfNotUsedFor > sc.AbsoluteLifetime { return nil, fmt.Errorf("validIfNotUsedFor (%v) must not exceed absoluteLifetime (%v)", sc.ValidIfNotUsedFor, sc.AbsoluteLifetime) } return sc, nil } func buildMFAProviders(authenticators []MFAAuthenticator, logger *slog.Logger) map[string]server.MFAProvider { if len(authenticators) == 0 { return nil } providers := make(map[string]server.MFAProvider, len(authenticators)) for _, auth := range authenticators { switch auth.Type { case "TOTP": var cfg TOTPConfig if err := json.Unmarshal(auth.Config, &cfg); err != nil { logger.Error("failed to parse TOTP config", "id", auth.ID, "err", err) continue } providers[auth.ID] = server.NewTOTPProvider(cfg.Issuer, auth.ConnectorTypes) logger.Info("MFA authenticator configured", "id", auth.ID, "type", auth.Type) default: logger.Error("unknown MFA authenticator type, skipping", "id", auth.ID, "type", auth.Type) } } return providers } ================================================ FILE: cmd/dex/serve_test.go ================================================ package main import ( "log/slog" "testing" "github.com/stretchr/testify/require" ) func TestNewLogger(t *testing.T) { t.Run("JSON", func(t *testing.T) { logger, err := newLogger(slog.LevelInfo, "json", nil) require.NoError(t, err) require.NotEqual(t, (*slog.Logger)(nil), logger) }) t.Run("Text", func(t *testing.T) { logger, err := newLogger(slog.LevelError, "text", nil) require.NoError(t, err) require.NotEqual(t, (*slog.Logger)(nil), logger) }) t.Run("Unknown", func(t *testing.T) { logger, err := newLogger(slog.LevelError, "gofmt", nil) require.Error(t, err) require.Equal(t, "log format is not one of the supported values (json, text): gofmt", err.Error()) require.Equal(t, (*slog.Logger)(nil), logger) }) } ================================================ FILE: cmd/dex/version.go ================================================ package main import ( "fmt" "runtime" "github.com/spf13/cobra" ) var version = "DEV" func commandVersion() *cobra.Command { return &cobra.Command{ Use: "version", Short: "Print the version and exit", Run: func(_ *cobra.Command, _ []string) { fmt.Printf( "Dex Version: %s\nGo Version: %s\nGo OS/ARCH: %s %s\n", version, runtime.Version(), runtime.GOOS, runtime.GOARCH, ) }, } } ================================================ FILE: cmd/docker-entrypoint/main.go ================================================ // Package main provides a utility program to launch the Dex container process with an optional // templating step (provided by gomplate). // // This was originally written as a shell script, but we rewrote it as a Go program so that it could // run as a raw binary in a distroless container. package main import ( "fmt" "os" "os/exec" "strings" "syscall" ) func main() { // Note that this docker-entrypoint program is args[0], and it is provided with the true process // args. args := os.Args[1:] if len(args) == 0 { fmt.Println("error: no args passed to entrypoint") os.Exit(1) } if err := run(args, realExec, realWhich, realGomplate); err != nil { fmt.Println("error:", err.Error()) os.Exit(1) } } func realExec(args ...string) error { argv0, err := exec.LookPath(args[0]) if err != nil { return fmt.Errorf("cannot lookup path for command %s: %w", args[0], err) } if err := syscall.Exec(argv0, args, os.Environ()); err != nil { return fmt.Errorf("cannot exec command %s (%q): %w", args, argv0, err) } return nil } func realWhich(path string) string { fullPath, err := exec.LookPath(path) if err != nil { return "" } return fullPath } func realGomplate(path string) (string, error) { tmpFile, err := os.CreateTemp("/tmp", "dex.config.yaml-*") if err != nil { return "", fmt.Errorf("cannot create temp file: %w", err) } cmd := exec.Command("gomplate", "-f", path, "-o", tmpFile.Name()) // TODO(nabokihms): Workaround to run gomplate from a non-root directory in distroless images // gomplate tries to access CWD on start, see: https://github.com/hairyhenderson/gomplate/pull/2202 cmd.Dir = "/etc/dex" output, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("error executing gomplate: %w, (output: %q)", err, string(output)) } return tmpFile.Name(), nil } func run(args []string, execFunc func(...string) error, whichFunc func(string) string, gomplateFunc func(string) (string, error)) error { if args[0] != "dex" && args[0] != whichFunc("dex") { return execFunc(args...) } if args[1] != "serve" { return execFunc(args...) } newArgs := []string{} for _, tplCandidate := range args { if hasSuffixes(tplCandidate, ".tpl", ".tmpl", ".yaml") { fileName, err := gomplateFunc(tplCandidate) if err != nil { return err } newArgs = append(newArgs, fileName) } else { newArgs = append(newArgs, tplCandidate) } } return execFunc(newArgs...) } func hasSuffixes(s string, suffixes ...string) bool { for _, suffix := range suffixes { if strings.HasSuffix(s, suffix) { return true } } return false } ================================================ FILE: cmd/docker-entrypoint/main_test.go ================================================ package main import ( "strings" "testing" ) type execArgs struct { gomplate bool argPrefixes []string } func TestRun(t *testing.T) { tests := []struct { name string args []string execReturns error whichReturns string wantExecArgs execArgs wantErr error }{ { name: "executable not dex", args: []string{"tuna", "fish"}, wantExecArgs: execArgs{gomplate: false, argPrefixes: []string{"tuna", "fish"}}, }, { name: "executable is full path to dex", args: []string{"/usr/local/bin/dex", "marshmallow", "zelda"}, whichReturns: "/usr/local/bin/dex", wantExecArgs: execArgs{gomplate: false, argPrefixes: []string{"/usr/local/bin/dex", "marshmallow", "zelda"}}, }, { name: "command is not serve", args: []string{"dex", "marshmallow", "zelda"}, wantExecArgs: execArgs{gomplate: false, argPrefixes: []string{"dex", "marshmallow", "zelda"}}, }, { name: "no templates", args: []string{"dex", "serve", "config.yaml.not-a-template"}, wantExecArgs: execArgs{gomplate: false, argPrefixes: []string{"dex", "serve", "config.yaml.not-a-template"}}, }, { name: "no templates", args: []string{"dex", "serve", "config.yaml.not-a-template"}, wantExecArgs: execArgs{gomplate: false, argPrefixes: []string{"dex", "serve", "config.yaml.not-a-template"}}, }, { name: ".tpl template", args: []string{"dex", "serve", "config.tpl"}, wantExecArgs: execArgs{gomplate: true, argPrefixes: []string{"dex", "serve", "/tmp/dex.config.yaml-"}}, }, { name: ".tmpl template", args: []string{"dex", "serve", "config.tmpl"}, wantExecArgs: execArgs{gomplate: true, argPrefixes: []string{"dex", "serve", "/tmp/dex.config.yaml-"}}, }, { name: ".yaml template", args: []string{"dex", "serve", "some/path/config.yaml"}, wantExecArgs: execArgs{gomplate: true, argPrefixes: []string{"dex", "serve", "/tmp/dex.config.yaml-"}}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { var gotExecArgs []string var runsGomplate bool fakeExec := func(args ...string) error { gotExecArgs = append(args, gotExecArgs...) return test.execReturns } fakeWhich := func(_ string) string { return test.whichReturns } fakeGomplate := func(file string) (string, error) { runsGomplate = true return "/tmp/dex.config.yaml-", nil } gotErr := run(test.args, fakeExec, fakeWhich, fakeGomplate) if (test.wantErr == nil) != (gotErr == nil) { t.Errorf("wanted error %s, got %s", test.wantErr, gotErr) } if !execArgsMatch(test.wantExecArgs, runsGomplate, gotExecArgs) { t.Errorf("wanted exec args %+v (running gomplate: %+v), got %+v (running gomplate: %+v)", test.wantExecArgs.argPrefixes, test.wantExecArgs.gomplate, gotExecArgs, runsGomplate) } }) } } func execArgsMatch(wantExecArgs execArgs, gomplate bool, gotExecArgs []string) bool { if wantExecArgs.gomplate != gomplate { return false } for i := range wantExecArgs.argPrefixes { if !strings.HasPrefix(gotExecArgs[i], wantExecArgs.argPrefixes[i]) { return false } } return true } ================================================ FILE: config.dev.yaml ================================================ issuer: http://127.0.0.1:5556/dex storage: type: sqlite3 config: file: var/sqlite/dex.db web: http: 127.0.0.1:5556 telemetry: http: 127.0.0.1:5558 grpc: addr: 127.0.0.1:5557 staticClients: - id: example-app redirectURIs: - 'http://127.0.0.1:5555/callback' name: 'Example App' secret: ZXhhbXBsZS1hcHAtc2VjcmV0 connectors: - type: mockCallback id: mock name: Example enablePasswordDB: true staticPasswords: - email: "admin@example.com" hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" username: "admin" name: "Admin User" emailVerified: true preferredUsername: "admin" groups: - "team-a" - "team-a/admins" userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" ================================================ FILE: config.docker.yaml ================================================ {{- /* NOTE: This configuration file is an example and exists only for development purposes. */ -}} {{- /* To find more about gomplate formatting, please visit its documentation site - https://docs.gomplate.ca/ */ -}} issuer: {{ getenv "DEX_ISSUER" "http://127.0.0.1:5556/dex" }} storage: type: sqlite3 config: file: {{ getenv "DEX_STORAGE_SQLITE3_CONFIG_FILE" "/var/dex/dex.db" }} web: {{- if getenv "DEX_WEB_HTTPS" "" }} https: {{ .Env.DEX_WEB_HTTPS }} tlsKey: {{ getenv "DEX_WEB_TLS_KEY" | required "$DEX_WEB_TLS_KEY in case of web.https is enabled" }} tlsCert: {{ getenv "DEX_WEB_TLS_CERT" | required "$DEX_WEB_TLS_CERT in case of web.https is enabled" }} {{- end }} http: {{ getenv "DEX_WEB_HTTP" "0.0.0.0:5556" }} {{- if getenv "DEX_TELEMETRY_HTTP" }} telemetry: http: {{ .Env.DEX_TELEMETRY_HTTP }} {{- end }} expiry: deviceRequests: {{ getenv "DEX_EXPIRY_DEVICE_REQUESTS" "5m" }} signingKeys: {{ getenv "DEX_EXPIRY_SIGNING_KEYS" "6h" }} idTokens: {{ getenv "DEX_EXPIRY_ID_TOKENS" "24h" }} authRequests: {{ getenv "DEX_EXPIRY_AUTH_REQUESTS" "24h" }} logger: level: {{ getenv "DEX_LOG_LEVEL" "info" }} format: {{ getenv "DEX_LOG_FORMAT" "text" }} oauth2: responseTypes: {{ getenv "DEX_OAUTH2_RESPONSE_TYPES" "[code]" }} skipApprovalScreen: {{ getenv "DEX_OAUTH2_SKIP_APPROVAL_SCREEN" "false" }} alwaysShowLoginScreen: {{ getenv "DEX_OAUTH2_ALWAYS_SHOW_LOGIN_SCREEN" "false" }} {{- if getenv "DEX_OAUTH2_PASSWORD_CONNECTOR" "" }} passwordConnector: {{ .Env.DEX_OAUTH2_PASSWORD_CONNECTOR }} {{- end }} enablePasswordDB: {{ getenv "DEX_ENABLE_PASSWORD_DB" "true" }} connectors: {{- if getenv "DEX_CONNECTORS_ENABLE_MOCK" }} - type: mockCallback id: mock name: Example {{- end }} ================================================ FILE: config.yaml.dist ================================================ # The base path of Dex and the external name of the OpenID Connect service. # This is the canonical URL that all clients MUST use to refer to Dex. If a # path is provided, Dex's HTTP service will listen at a non-root URL. issuer: http://127.0.0.1:5556/dex # The storage configuration determines where Dex stores its state. # Supported options include: # - SQL flavors # - key-value stores (eg. etcd) # - Kubernetes Custom Resources # # See the documentation (https://dexidp.io/docs/storage/) for further information. storage: type: memory # type: sqlite3 # config: # file: /var/dex/dex.db # type: mysql # config: # host: 127.0.0.1 # port: 3306 # database: dex # user: mysql # password: mysql # ssl: # mode: "false" # type: postgres # config: # host: 127.0.0.1 # port: 5432 # database: dex # user: postgres # password: postgres # ssl: # mode: disable # type: etcd # config: # endpoints: # - http://127.0.0.1:2379 # namespace: dex/ # type: kubernetes # config: # kubeConfigFile: $HOME/.kube/config # HTTP service configuration web: http: 127.0.0.1:5556 # Uncomment to enable HTTPS endpoint. # https: 127.0.0.1:5554 # tlsCert: /etc/dex/tls.crt # tlsKey: /etc/dex/tls.key # tlsMinVersion: 1.2 # tlsMaxVersion: 1.3 # Dex UI configuration # frontend: # issuer: dex # logoURL: theme/logo.png # dir: "" # theme: light # Telemetry configuration # telemetry: # http: 127.0.0.1:5558 # logger: # level: "debug" # format: "text" # can also be "json" # # Drop these attribute keys from all log output (useful for GDPR/PII suppression). # # excludeFields: [email, username, preferred_username, groups] # gRPC API configuration # Uncomment this block to enable the gRPC API. # See the documentation (https://dexidp.io/docs/api/) for further information. # grpc: # addr: 127.0.0.1:5557 # tlsCert: examples/grpc-client/server.crt # tlsKey: examples/grpc-client/server.key # tlsClientCA: examples/grpc-client/ca.crt # Expiration configuration for tokens, signing keys, etc. # expiry: # deviceRequests: "5m" # signingKeys: "6h" # idTokens: "24h" # refreshTokens: # disableRotation: false # reuseInterval: "3s" # validIfNotUsedFor: "2160h" # 90 days # absoluteLifetime: "3960h" # 165 days # OAuth2 configuration # oauth2: # # use ["code", "token", "id_token"] to enable implicit flow for web-only clients # responseTypes: [ "code" ] # also allowed are "token" and "id_token" # # # By default, Dex will ask for approval to share data with application # # (approval for sharing data from connected IdP to Dex is separate process on IdP) # skipApprovalScreen: false # # # If only one authentication method is enabled, the default behavior is to # # go directly to it. For connected IdPs, this redirects the browser away # # from application to upstream provider such as the Google login page # alwaysShowLoginScreen: false # # # Uncomment to use a specific connector for password grants # passwordConnector: local # # # PKCE (Proof Key for Code Exchange) configuration # pkce: # # If true, PKCE is required for all authorization code flows (OAuth 2.1). # enforce: false # # Supported code challenge methods. Defaults to ["S256", "plain"]. # codeChallengeMethodsSupported: ["S256", "plain"] # Static clients registered in Dex by default. # # Alternatively, clients may be added through the gRPC API. # staticClients: # - id: example-app # redirectURIs: # - 'http://127.0.0.1:5555/callback' # name: 'Example App' # secret: ZXhhbXBsZS1hcHAtc2VjcmV0 # # # Example using environment variables # # These fields are mutually exclusive with id and secret respectively. # - idEnv: DEX_CLIENT_ID # secretEnv: DEX_CLIENT_SECRET # redirectURIs: # - 'https://app.example.com/callback' # name: 'Production App' # # # Example of a public client (no secret required) # - id: example-device-client # redirectURIs: # - /device/callback # name: 'Static Client for Device Flow' # public: true # # # Example of a client restricted to specific connectors # - id: restricted-client # secret: restricted-client-secret # redirectURIs: # - 'https://app.example.com/callback' # name: 'Restricted Client' # allowedConnectors: # - github # - google # Connectors are used to authenticate users against upstream identity providers. # # See the documentation (https://dexidp.io/docs/connectors/) for further information. # connectors: [] # Enable the password database. # # It's a "virtual" connector (identity provider) that stores # login credentials in Dex's store. enablePasswordDB: true # If this option isn't chosen users may be added through the gRPC API. # A static list of passwords for the password connector. # # Alternatively, passwords my be added/updated through the gRPC API. # staticPasswords: # - email: "user@example.com" # # bcrypt hash of the string "password" # hash: "$2a$10$examplehash..." # username: "user-login" # # Optional. Maps to OIDC "name" claim. Defaults to username. # name: "User Full Name" # # Optional. Maps to OIDC "email_verified" claim. Defaults to true. # emailVerified: true # # Optional. Maps to OIDC "preferred_username" claim. # preferredUsername: "user-public" # # Optional. Maps to OIDC "groups" claim (when 'groups' scope is requested). # groups: # - "team-a" # - "team-a/admins" # userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" ================================================ FILE: connector/atlassiancrowd/atlassiancrowd.go ================================================ // Package atlassiancrowd provides authentication strategies using Atlassian Crowd. package atlassiancrowd import ( "bytes" "context" "encoding/json" "fmt" "io" "log/slog" "net" "net/http" "strings" "time" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/groups" ) // Config holds configuration options for Atlassian Crowd connector. // Crowd connectors require executing two queries, the first to find // the user based on the username and password given to the connector. // The second to use the user entry to search for groups. // // An example config: // // type: atlassian-crowd // config: // baseURL: https://crowd.example.com/context // clientID: applogin // clientSecret: appP4$$w0rd // # users can be restricted by a list of groups // groups: // - admin // # Prompt for username field // usernamePrompt: Login // preferredUsernameField: name type Config struct { BaseURL string `json:"baseURL"` ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` Groups []string `json:"groups"` // PreferredUsernameField allows users to set the field to any of the // following values: "key", "name" or "email". // If unset, the preferred_username field will remain empty. PreferredUsernameField string `json:"preferredUsernameField"` // UsernamePrompt allows users to override the username attribute (displayed // in the username/password prompt). If unset, the handler will use. // "Username". UsernamePrompt string `json:"usernamePrompt"` } type crowdUser struct { Key string Name string Active bool Email string } type crowdGroups struct { Groups []struct { Name string } `json:"groups"` } type crowdAuthentication struct { Token string User struct { Name string } `json:"user"` CreatedDate uint64 `json:"created-date"` ExpiryDate uint64 `json:"expiry-date"` } type crowdAuthenticationError struct { Reason string Message string } // Open returns a strategy for logging in through Atlassian Crowd func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { if c.BaseURL == "" { return nil, fmt.Errorf("crowd: no baseURL provided for crowd connector") } return &crowdConnector{Config: *c, logger: logger.With(slog.Group("connector", "type", "atlassiancrowd", "id", id))}, nil } var ( _ connector.PasswordConnector = (*crowdConnector)(nil) _ connector.RefreshConnector = (*crowdConnector)(nil) ) type crowdConnector struct { Config logger *slog.Logger } type refreshData struct { Username string `json:"username"` } func (c *crowdConnector) Login(ctx context.Context, s connector.Scopes, username, password string) (ident connector.Identity, validPass bool, err error) { // make this check to avoid empty passwords. if password == "" { return connector.Identity{}, false, nil } // We want to return a different error if the user's password is incorrect vs // if there was an error. var incorrectPass bool var user crowdUser client := c.crowdAPIClient() if incorrectPass, err = c.authenticateWithPassword(ctx, client, username, password); err != nil { return connector.Identity{}, false, err } if incorrectPass { return connector.Identity{}, false, nil } if user, err = c.user(ctx, client, username); err != nil { return connector.Identity{}, false, err } ident = c.identityFromCrowdUser(user) if s.Groups { userGroups, err := c.getGroups(ctx, client, s.Groups, ident.Username) if err != nil { return connector.Identity{}, false, fmt.Errorf("crowd: failed to query groups: %v", err) } ident.Groups = userGroups } if s.OfflineAccess { refresh := refreshData{Username: username} // Encode entry for following up requests such as the groups query and refresh attempts. if ident.ConnectorData, err = json.Marshal(refresh); err != nil { return connector.Identity{}, false, fmt.Errorf("crowd: marshal refresh data: %v", err) } } return ident, true, nil } func (c *crowdConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) { var data refreshData if err := json.Unmarshal(ident.ConnectorData, &data); err != nil { return ident, fmt.Errorf("crowd: failed to unmarshal internal data: %v", err) } var user crowdUser client := c.crowdAPIClient() user, err := c.user(ctx, client, data.Username) if err != nil { return ident, fmt.Errorf("crowd: get user %q: %v", data.Username, err) } newIdent := c.identityFromCrowdUser(user) newIdent.ConnectorData = ident.ConnectorData // If user exists, authenticate it to prolong sso session. err = c.authenticateUser(ctx, client, data.Username) if err != nil { return ident, fmt.Errorf("crowd: authenticate user: %v", err) } if s.Groups { userGroups, err := c.getGroups(ctx, client, s.Groups, newIdent.Username) if err != nil { return connector.Identity{}, fmt.Errorf("crowd: failed to query groups: %v", err) } newIdent.Groups = userGroups } return newIdent, nil } func (c *crowdConnector) Prompt() string { return c.UsernamePrompt } func (c *crowdConnector) crowdAPIClient() *http.Client { return &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, }, } } // authenticateWithPassword creates a new session for user and validates a password with Crowd API func (c *crowdConnector) authenticateWithPassword(ctx context.Context, client *http.Client, username string, password string) (invalidPass bool, err error) { req, err := c.crowdUserManagementRequest(ctx, "POST", "/session", struct { Username string `json:"username"` Password string `json:"password"` }{Username: username, Password: password}, ) if err != nil { return false, fmt.Errorf("crowd: new auth pass api request %v", err) } resp, err := client.Do(req) if err != nil { return false, fmt.Errorf("crowd: api request %v", err) } defer resp.Body.Close() body, err := c.validateCrowdResponse(resp) if err != nil { return false, err } if resp.StatusCode != http.StatusCreated { var authError crowdAuthenticationError if err := json.Unmarshal(body, &authError); err != nil { return false, fmt.Errorf("unmarshal auth pass response: %d %v %q", resp.StatusCode, err, string(body)) } if authError.Reason == "INVALID_USER_AUTHENTICATION" { return true, nil } return false, fmt.Errorf("%s: %s", resp.Status, authError.Message) } var authResponse crowdAuthentication if err := json.Unmarshal(body, &authResponse); err != nil { return false, fmt.Errorf("decode auth response: %v", err) } return false, nil } // authenticateUser creates a new session for user without password validations with Crowd API func (c *crowdConnector) authenticateUser(ctx context.Context, client *http.Client, username string) error { req, err := c.crowdUserManagementRequest(ctx, "POST", "/session?validate-password=false", struct { Username string `json:"username"` }{Username: username}, ) if err != nil { return fmt.Errorf("crowd: new auth api request %v", err) } resp, err := client.Do(req) if err != nil { return fmt.Errorf("crowd: api request %v", err) } defer resp.Body.Close() body, err := c.validateCrowdResponse(resp) if err != nil { return err } if resp.StatusCode != http.StatusCreated { return fmt.Errorf("%s: %s", resp.Status, body) } var authResponse crowdAuthentication if err := json.Unmarshal(body, &authResponse); err != nil { return fmt.Errorf("decode auth response: %v", err) } return nil } // user retrieves user info from Crowd API func (c *crowdConnector) user(ctx context.Context, client *http.Client, username string) (crowdUser, error) { var user crowdUser req, err := c.crowdUserManagementRequest(ctx, "GET", fmt.Sprintf("/user?username=%s", username), nil, ) if err != nil { return user, fmt.Errorf("crowd: new user api request %v", err) } resp, err := client.Do(req) if err != nil { return user, fmt.Errorf("crowd: api request %v", err) } defer resp.Body.Close() body, err := c.validateCrowdResponse(resp) if err != nil { return user, err } if resp.StatusCode != http.StatusOK { return user, fmt.Errorf("%s: %s", resp.Status, body) } if err := json.Unmarshal(body, &user); err != nil { return user, fmt.Errorf("failed to decode response: %v", err) } return user, nil } // groups retrieves groups from Crowd API func (c *crowdConnector) groups(ctx context.Context, client *http.Client, username string) (userGroups []string, err error) { var crowdGroups crowdGroups req, err := c.crowdUserManagementRequest(ctx, "GET", fmt.Sprintf("/user/group/nested?username=%s", username), nil, ) if err != nil { return nil, fmt.Errorf("crowd: new groups api request %v", err) } resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("crowd: api request %v", err) } defer resp.Body.Close() body, err := c.validateCrowdResponse(resp) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("%s: %s", resp.Status, body) } if err := json.Unmarshal(body, &crowdGroups); err != nil { return nil, fmt.Errorf("failed to decode response: %v", err) } for _, group := range crowdGroups.Groups { userGroups = append(userGroups, group.Name) } return userGroups, nil } // identityFromCrowdUser converts crowdUser to Identity func (c *crowdConnector) identityFromCrowdUser(user crowdUser) connector.Identity { identity := connector.Identity{ Username: user.Name, UserID: user.Key, Email: user.Email, EmailVerified: true, } switch c.PreferredUsernameField { case "key": identity.PreferredUsername = user.Key case "name": identity.PreferredUsername = user.Name case "email": identity.PreferredUsername = user.Email default: if c.PreferredUsernameField != "" { c.logger.Warn("preferred_username left empty. Invalid crowd field mapped to preferred_username", "field", c.PreferredUsernameField) } } return identity } // getGroups retrieves a list of user's groups and filters it func (c *crowdConnector) getGroups(ctx context.Context, client *http.Client, groupScope bool, userLogin string) ([]string, error) { crowdGroups, err := c.groups(ctx, client, userLogin) if err != nil { return nil, err } if len(c.Groups) > 0 { filteredGroups := groups.Filter(crowdGroups, c.Groups) if len(filteredGroups) == 0 { return nil, fmt.Errorf("crowd: user %q is not in any of the required groups", userLogin) } return filteredGroups, nil } else if groupScope { return crowdGroups, nil } return nil, nil } // crowdUserManagementRequest create a http.Request with basic auth, json payload and Accept header func (c *crowdConnector) crowdUserManagementRequest(ctx context.Context, method string, apiURL string, jsonPayload interface{}) (*http.Request, error) { var body io.Reader if jsonPayload != nil { jsonData, err := json.Marshal(jsonPayload) if err != nil { return nil, fmt.Errorf("crowd: marshal API json payload: %v", err) } body = bytes.NewReader(jsonData) } req, err := http.NewRequest(method, fmt.Sprintf("%s/rest/usermanagement/1%s", c.BaseURL, apiURL), body) if err != nil { return nil, fmt.Errorf("new API req: %v", err) } req = req.WithContext(ctx) // Crowd API requires a basic auth req.SetBasicAuth(c.ClientID, c.ClientSecret) req.Header.Set("Accept", "application/json") if jsonPayload != nil { req.Header.Set("Content-type", "application/json") } return req, nil } // validateCrowdResponse validates unique not JSON responses from API func (c *crowdConnector) validateCrowdResponse(resp *http.Response) ([]byte, error) { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("crowd: read user body: %v", err) } if resp.StatusCode == http.StatusForbidden && strings.Contains(string(body), "The server understood the request but refuses to authorize it.") { c.logger.Debug("crowd response validation failed", "response", string(body)) return nil, fmt.Errorf("dex is forbidden from making requests to the Atlassian Crowd application by URL %q", c.BaseURL) } if resp.StatusCode == http.StatusUnauthorized && string(body) == "Application failed to authenticate" { c.logger.Debug("crowd response validation failed", "response", string(body)) return nil, fmt.Errorf("dex failed to authenticate Crowd Application with ID %q", c.ClientID) } return body, nil } ================================================ FILE: connector/atlassiancrowd/atlassiancrowd_test.go ================================================ // Package atlassiancrowd provides authentication strategies using Atlassian Crowd. package atlassiancrowd import ( "context" "crypto/tls" "encoding/json" "fmt" "log/slog" "net/http" "net/http/httptest" "reflect" "testing" ) func TestUserGroups(t *testing.T) { s := newTestServer(map[string]TestServerResponse{ "/rest/usermanagement/1/user/group/nested?username=testuser": { Body: crowdGroups{Groups: []struct{ Name string }{{Name: "group1"}, {Name: "group2"}}}, Code: 200, }, }) defer s.Close() c := newTestCrowdConnector(s.URL) groups, err := c.getGroups(context.Background(), newClient(), true, "testuser") expectNil(t, err) expectEquals(t, groups, []string{"group1", "group2"}) } func TestUserGroupsWithFiltering(t *testing.T) { s := newTestServer(map[string]TestServerResponse{ "/rest/usermanagement/1/user/group/nested?username=testuser": { Body: crowdGroups{Groups: []struct{ Name string }{{Name: "group1"}, {Name: "group2"}}}, Code: 200, }, }) defer s.Close() c := newTestCrowdConnector(s.URL) c.Groups = []string{"group1"} groups, err := c.getGroups(context.Background(), newClient(), true, "testuser") expectNil(t, err) expectEquals(t, groups, []string{"group1"}) } func TestUserLoginFlow(t *testing.T) { s := newTestServer(map[string]TestServerResponse{ "/rest/usermanagement/1/session?validate-password=false": { Body: crowdAuthentication{}, Code: 201, }, "/rest/usermanagement/1/user?username=testuser": { Body: crowdUser{Active: true, Name: "testuser", Email: "testuser@example.com"}, Code: 200, }, "/rest/usermanagement/1/user?username=testuser2": { Body: `The server understood the request but refuses to authorize it.`, Code: 403, }, }) defer s.Close() c := newTestCrowdConnector(s.URL) user, err := c.user(context.Background(), newClient(), "testuser") expectNil(t, err) expectEquals(t, user.Name, "testuser") expectEquals(t, user.Email, "testuser@example.com") err = c.authenticateUser(context.Background(), newClient(), "testuser") expectNil(t, err) _, err = c.user(context.Background(), newClient(), "testuser2") expectEquals(t, err, fmt.Errorf("dex is forbidden from making requests to the Atlassian Crowd application by URL %q", s.URL)) } func TestUserPassword(t *testing.T) { s := newTestServer(map[string]TestServerResponse{ "/rest/usermanagement/1/session": { Body: crowdAuthenticationError{Reason: "INVALID_USER_AUTHENTICATION", Message: "test"}, Code: 401, }, "/rest/usermanagement/1/session?validate-password=false": { Body: crowdAuthentication{}, Code: 201, }, }) defer s.Close() c := newTestCrowdConnector(s.URL) invalidPassword, err := c.authenticateWithPassword(context.Background(), newClient(), "testuser", "testpassword") expectNil(t, err) expectEquals(t, invalidPassword, true) err = c.authenticateUser(context.Background(), newClient(), "testuser") expectNil(t, err) } func TestIdentityFromCrowdUser(t *testing.T) { user := crowdUser{ Key: "12345", Name: "testuser", Active: true, Email: "testuser@example.com", } c := newTestCrowdConnector("/") // Sanity checks expectEquals(t, user.Name, "testuser") expectEquals(t, user.Email, "testuser@example.com") // Test unconfigured behavior i := c.identityFromCrowdUser(user) expectEquals(t, i.UserID, "12345") expectEquals(t, i.Username, "testuser") expectEquals(t, i.Email, "testuser@example.com") expectEquals(t, i.EmailVerified, true) // Test for various PreferredUsernameField settings // unset expectEquals(t, i.PreferredUsername, "") c.Config.PreferredUsernameField = "key" i = c.identityFromCrowdUser(user) expectEquals(t, i.PreferredUsername, "12345") c.Config.PreferredUsernameField = "name" i = c.identityFromCrowdUser(user) expectEquals(t, i.PreferredUsername, "testuser") c.Config.PreferredUsernameField = "email" i = c.identityFromCrowdUser(user) expectEquals(t, i.PreferredUsername, "testuser@example.com") c.Config.PreferredUsernameField = "invalidstring" i = c.identityFromCrowdUser(user) expectEquals(t, i.PreferredUsername, "") } type TestServerResponse struct { Body interface{} Code int } func newTestCrowdConnector(baseURL string) crowdConnector { connector := crowdConnector{} connector.BaseURL = baseURL connector.logger = slog.New(slog.DiscardHandler) return connector } func newTestServer(responses map[string]TestServerResponse) *httptest.Server { s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { response := responses[r.RequestURI] w.Header().Add("Content-Type", "application/json") w.WriteHeader(response.Code) json.NewEncoder(w).Encode(response.Body) })) return s } func newClient() *http.Client { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } return &http.Client{Transport: tr} } func expectNil(t *testing.T, a interface{}) { if a != nil { t.Errorf("Expected %+v to equal nil", a) } } func expectEquals(t *testing.T, a interface{}, b interface{}) { if !reflect.DeepEqual(a, b) { t.Errorf("Expected %+v to equal %+v", a, b) } } ================================================ FILE: connector/authproxy/authproxy.go ================================================ // Package authproxy implements a connector which relies on external // authentication (e.g. mod_auth in Apache2) and returns an identity with the // HTTP header X-Remote-User as verified email. package authproxy import ( "fmt" "log/slog" "net/http" "net/url" "strings" "github.com/dexidp/dex/connector" ) // Config holds the configuration parameters for a connector which returns an // identity with the HTTP header X-Remote-User as verified email, // X-Remote-Group and configured staticGroups as user's group. // Headers retrieved to fetch user's email and group can be configured // with userHeader and groupHeader. type Config struct { UserIDHeader string `json:"userIDHeader"` UserHeader string `json:"userHeader"` UserNameHeader string `json:"userNameHeader"` EmailHeader string `json:"emailHeader"` GroupHeader string `json:"groupHeader"` GroupHeaderSeparator string `json:"groupHeaderSeparator"` Groups []string `json:"staticGroups"` } // Open returns an authentication strategy which requires no user interaction. func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { userIDHeader := c.UserIDHeader if userIDHeader == "" { userIDHeader = "X-Remote-User-Id" } userHeader := c.UserHeader if userHeader == "" { userHeader = "X-Remote-User" } userNameHeader := c.UserNameHeader if userNameHeader == "" { userNameHeader = "X-Remote-User-Name" } emailHeader := c.EmailHeader if emailHeader == "" { emailHeader = "X-Remote-User-Email" } groupHeader := c.GroupHeader if groupHeader == "" { groupHeader = "X-Remote-Group" } groupHeaderSeparator := c.GroupHeaderSeparator if groupHeaderSeparator == "" { groupHeaderSeparator = "," } return &callback{ userIDHeader: userIDHeader, userHeader: userHeader, userNameHeader: userNameHeader, emailHeader: emailHeader, groupHeader: groupHeader, groupHeaderSeparator: groupHeaderSeparator, groups: c.Groups, logger: logger.With(slog.Group("connector", "type", "authproxy", "id", id)), pathSuffix: "/" + id, }, nil } var _ connector.CallbackConnector = (*callback)(nil) // Callback is a connector which returns an identity with the HTTP header // X-Remote-User as verified email. type callback struct { userIDHeader string userNameHeader string userHeader string emailHeader string groupHeader string groupHeaderSeparator string groups []string logger *slog.Logger pathSuffix string } // LoginURL returns the URL to redirect the user to login with. func (m *callback) LoginURL(s connector.Scopes, callbackURL, state string) (string, []byte, error) { u, err := url.Parse(callbackURL) if err != nil { return "", nil, fmt.Errorf("failed to parse callbackURL %q: %v", callbackURL, err) } u.Path += m.pathSuffix v := u.Query() v.Set("state", state) u.RawQuery = v.Encode() return u.String(), nil, nil } // HandleCallback parses the request and returns the user's identity func (m *callback) HandleCallback(s connector.Scopes, _ []byte, r *http.Request) (connector.Identity, error) { remoteUser := r.Header.Get(m.userHeader) if remoteUser == "" { return connector.Identity{}, fmt.Errorf("required HTTP header %s is not set", m.userHeader) } remoteUserName := r.Header.Get(m.userNameHeader) if remoteUserName == "" { remoteUserName = remoteUser } remoteUserID := r.Header.Get(m.userIDHeader) if remoteUserID == "" { remoteUserID = remoteUser } remoteUserEmail := r.Header.Get(m.emailHeader) if remoteUserEmail == "" { remoteUserEmail = remoteUser } groups := m.groups headerGroup := r.Header.Get(m.groupHeader) if headerGroup != "" { splitheaderGroup := strings.Split(headerGroup, m.groupHeaderSeparator) for i, v := range splitheaderGroup { splitheaderGroup[i] = strings.TrimSpace(v) } groups = append(splitheaderGroup, groups...) } return connector.Identity{ UserID: remoteUserID, Username: remoteUser, PreferredUsername: remoteUserName, Email: remoteUserEmail, EmailVerified: true, Groups: groups, }, nil } ================================================ FILE: connector/authproxy/authproxy_test.go ================================================ package authproxy import ( "log/slog" "net/http" "reflect" "testing" "github.com/dexidp/dex/connector" ) const ( testEmail = "testuser@example.com" testGroup1 = "group1" testGroup2 = "group2" testGroup3 = "group 3" testGroup4 = "group 4" testStaticGroup1 = "static1" testStaticGroup2 = "static 2" testUsername = "Test User" testPreferredUsername = "testuser" testUserID = "1234567890" ) var logger = slog.New(slog.DiscardHandler) func TestUser(t *testing.T) { config := Config{} conn, _ := config.Open("test", logger) callback := conn.(*callback) req, err := http.NewRequest("GET", "/", nil) expectNil(t, err) req.Header = map[string][]string{ "X-Remote-User": {testUsername}, } ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req) expectNil(t, err) // If not specified, the userID and email should fall back to the remote user expectEquals(t, ident.UserID, testUsername) expectEquals(t, ident.PreferredUsername, testUsername) expectEquals(t, ident.Username, testUsername) expectEquals(t, ident.Email, testUsername) expectEquals(t, len(ident.Groups), 0) } func TestExtraHeaders(t *testing.T) { config := Config{} conn, _ := config.Open("test", logger) callback := conn.(*callback) req, err := http.NewRequest("GET", "/", nil) expectNil(t, err) req.Header = map[string][]string{ "X-Remote-User-Id": {testUserID}, "X-Remote-User": {testUsername}, "X-Remote-User-Name": {testPreferredUsername}, "X-Remote-User-Email": {testEmail}, } ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req) expectNil(t, err) expectEquals(t, ident.UserID, testUserID) expectEquals(t, ident.PreferredUsername, testPreferredUsername) expectEquals(t, ident.Username, testUsername) expectEquals(t, ident.Email, testEmail) expectEquals(t, len(ident.Groups), 0) } func TestSingleGroup(t *testing.T) { config := Config{} conn, _ := config.Open("test", logger) callback := conn.(*callback) req, err := http.NewRequest("GET", "/", nil) expectNil(t, err) req.Header = map[string][]string{ "X-Remote-User": {testEmail}, "X-Remote-Group": {testGroup1}, } ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req) expectNil(t, err) expectEquals(t, ident.UserID, testEmail) expectEquals(t, len(ident.Groups), 1) expectEquals(t, ident.Groups[0], testGroup1) } func TestMultipleGroup(t *testing.T) { config := Config{} conn, _ := config.Open("test", logger) callback := conn.(*callback) req, err := http.NewRequest("GET", "/", nil) expectNil(t, err) req.Header = map[string][]string{ "X-Remote-User": {testEmail}, "X-Remote-Group": {testGroup1 + ", " + testGroup2 + ", " + testGroup3 + ", " + testGroup4}, } ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req) expectNil(t, err) expectEquals(t, ident.UserID, testEmail) expectEquals(t, len(ident.Groups), 4) expectEquals(t, ident.Groups[0], testGroup1) expectEquals(t, ident.Groups[1], testGroup2) expectEquals(t, ident.Groups[2], testGroup3) expectEquals(t, ident.Groups[3], testGroup4) } func TestMultipleGroupWithCustomSeparator(t *testing.T) { config := Config{ GroupHeaderSeparator: ";", } conn, _ := config.Open("test", logger) callback := conn.(*callback) req, err := http.NewRequest("GET", "/", nil) expectNil(t, err) req.Header = map[string][]string{ "X-Remote-User": {testEmail}, "X-Remote-Group": {testGroup1 + ";" + testGroup2 + ";" + testGroup3 + ";" + testGroup4}, } ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req) expectNil(t, err) expectEquals(t, ident.UserID, testEmail) expectEquals(t, len(ident.Groups), 4) expectEquals(t, ident.Groups[0], testGroup1) expectEquals(t, ident.Groups[1], testGroup2) expectEquals(t, ident.Groups[2], testGroup3) expectEquals(t, ident.Groups[3], testGroup4) } func TestStaticGroup(t *testing.T) { config := Config{ Groups: []string{"static1", "static 2"}, } conn, _ := config.Open("test", logger) callback := conn.(*callback) req, err := http.NewRequest("GET", "/", nil) expectNil(t, err) req.Header = map[string][]string{ "X-Remote-User": {testEmail}, "X-Remote-Group": {testGroup1 + ", " + testGroup2 + ", " + testGroup3 + ", " + testGroup4}, } ident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req) expectNil(t, err) expectEquals(t, ident.UserID, testEmail) expectEquals(t, len(ident.Groups), 6) expectEquals(t, ident.Groups[0], testGroup1) expectEquals(t, ident.Groups[1], testGroup2) expectEquals(t, ident.Groups[2], testGroup3) expectEquals(t, ident.Groups[3], testGroup4) expectEquals(t, ident.Groups[4], testStaticGroup1) expectEquals(t, ident.Groups[5], testStaticGroup2) } func expectNil(t *testing.T, a interface{}) { if a != nil { t.Errorf("Expected %+v to equal nil", a) } } func expectEquals(t *testing.T, a interface{}, b interface{}) { if !reflect.DeepEqual(a, b) { t.Errorf("Expected %+v to equal %+v", a, b) } } ================================================ FILE: connector/bitbucketcloud/bitbucketcloud.go ================================================ // Package bitbucketcloud provides authentication strategies using Bitbucket Cloud. package bitbucketcloud import ( "context" "encoding/json" "errors" "fmt" "io" "log/slog" "net/http" "sync" "time" "golang.org/x/oauth2" "golang.org/x/oauth2/bitbucket" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/groups" ) const ( apiURL = "https://api.bitbucket.org/2.0" // Switch to API v2.0 when the Atlassian platform services are fully available in Bitbucket legacyAPIURL = "https://api.bitbucket.org/1.0" // Bitbucket requires this scope to access '/user' API endpoints. scopeAccount = "account" // Bitbucket requires this scope to access '/user/emails' API endpoints. scopeEmail = "email" // Bitbucket requires this scope to access '/teams' API endpoints // which are used when a client includes the 'groups' scope. scopeTeams = "team" ) // Config holds configuration options for Bitbucket logins. type Config struct { ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` RedirectURI string `json:"redirectURI"` Teams []string `json:"teams"` IncludeTeamGroups bool `json:"includeTeamGroups,omitempty"` } // Open returns a strategy for logging in through Bitbucket. func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { b := bitbucketConnector{ redirectURI: c.RedirectURI, teams: c.Teams, clientID: c.ClientID, clientSecret: c.ClientSecret, includeTeamGroups: c.IncludeTeamGroups, apiURL: apiURL, legacyAPIURL: legacyAPIURL, logger: logger.With(slog.Group("connector", "type", "bitbucketcloud", "id", id)), } return &b, nil } type connectorData struct { AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` Expiry time.Time `json:"expiry"` } var ( _ connector.CallbackConnector = (*bitbucketConnector)(nil) _ connector.RefreshConnector = (*bitbucketConnector)(nil) ) type bitbucketConnector struct { redirectURI string teams []string clientID string clientSecret string logger *slog.Logger apiURL string legacyAPIURL string // the following are used only for tests hostName string httpClient *http.Client includeTeamGroups bool } // groupsRequired returns whether dex requires Bitbucket's 'team' scope. func (b *bitbucketConnector) groupsRequired(groupScope bool) bool { return len(b.teams) > 0 || groupScope } func (b *bitbucketConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config { bitbucketScopes := []string{scopeAccount, scopeEmail} if b.groupsRequired(scopes.Groups) { bitbucketScopes = append(bitbucketScopes, scopeTeams) } endpoint := bitbucket.Endpoint if b.hostName != "" { endpoint = oauth2.Endpoint{ AuthURL: "https://" + b.hostName + "/site/oauth2/authorize", TokenURL: "https://" + b.hostName + "/site/oauth2/access_token", } } return &oauth2.Config{ ClientID: b.clientID, ClientSecret: b.clientSecret, Endpoint: endpoint, Scopes: bitbucketScopes, } } func (b *bitbucketConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) { if b.redirectURI != callbackURL { return "", nil, fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, b.redirectURI) } return b.oauth2Config(scopes).AuthCodeURL(state), nil, nil } type oauth2Error struct { error string errorDescription string } func (e *oauth2Error) Error() string { if e.errorDescription == "" { return e.error } return e.error + ": " + e.errorDescription } func (b *bitbucketConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, &oauth2Error{errType, q.Get("error_description")} } oauth2Config := b.oauth2Config(s) ctx := r.Context() if b.httpClient != nil { ctx = context.WithValue(r.Context(), oauth2.HTTPClient, b.httpClient) } token, err := oauth2Config.Exchange(ctx, q.Get("code")) if err != nil { return identity, fmt.Errorf("bitbucket: failed to get token: %v", err) } client := oauth2Config.Client(ctx, token) user, err := b.user(ctx, client) if err != nil { return identity, fmt.Errorf("bitbucket: get user: %v", err) } identity = connector.Identity{ UserID: user.UUID, Username: user.Username, Email: user.Email, EmailVerified: true, } if b.groupsRequired(s.Groups) { groups, err := b.getGroups(ctx, client, s.Groups, user.Username) if err != nil { return identity, err } identity.Groups = groups } if s.OfflineAccess { data := connectorData{ AccessToken: token.AccessToken, RefreshToken: token.RefreshToken, Expiry: token.Expiry, } connData, err := json.Marshal(data) if err != nil { return identity, fmt.Errorf("bitbucket: marshal connector data: %v", err) } identity.ConnectorData = connData } return identity, nil } // Refreshing tokens // https://github.com/golang/oauth2/issues/84#issuecomment-332860871 type tokenNotifyFunc func(*oauth2.Token) error // notifyRefreshTokenSource is essentially `oauth2.ReuseTokenSource` with `TokenNotifyFunc` added. type notifyRefreshTokenSource struct { new oauth2.TokenSource mu sync.Mutex // guards t t *oauth2.Token f tokenNotifyFunc // called when token refreshed so new refresh token can be persisted } // Token returns the current token if it's still valid, else will // refresh the current token (using r.Context for HTTP client // information) and return the new one. func (s *notifyRefreshTokenSource) Token() (*oauth2.Token, error) { s.mu.Lock() defer s.mu.Unlock() if s.t.Valid() { return s.t, nil } t, err := s.new.Token() if err != nil { return nil, err } s.t = t return t, s.f(t) } func (b *bitbucketConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { if len(identity.ConnectorData) == 0 { return identity, errors.New("bitbucket: no upstream access token found") } var data connectorData if err := json.Unmarshal(identity.ConnectorData, &data); err != nil { return identity, fmt.Errorf("bitbucket: unmarshal access token: %v", err) } tok := &oauth2.Token{ AccessToken: data.AccessToken, RefreshToken: data.RefreshToken, Expiry: data.Expiry, } client := oauth2.NewClient(ctx, ¬ifyRefreshTokenSource{ new: b.oauth2Config(s).TokenSource(ctx, tok), t: tok, f: func(tok *oauth2.Token) error { data := connectorData{ AccessToken: tok.AccessToken, RefreshToken: tok.RefreshToken, Expiry: tok.Expiry, } connData, err := json.Marshal(data) if err != nil { return fmt.Errorf("bitbucket: marshal connector data: %v", err) } identity.ConnectorData = connData return nil }, }) user, err := b.user(ctx, client) if err != nil { return identity, fmt.Errorf("bitbucket: get user: %v", err) } identity.Username = user.Username identity.Email = user.Email if b.groupsRequired(s.Groups) { groups, err := b.getGroups(ctx, client, s.Groups, user.Username) if err != nil { return identity, err } identity.Groups = groups } return identity, nil } // Bitbucket pagination wrapper type pagedResponse struct { Size int `json:"size"` Page int `json:"page"` PageLen int `json:"pagelen"` Next *string `json:"next"` Previous *string `json:"previous"` } // user holds Bitbucket user information (relevant to dex) as defined by // https://developer.atlassian.com/bitbucket/api/2/reference/resource/user type user struct { Username string `json:"username"` UUID string `json:"uuid"` Email string `json:"email"` } // user queries the Bitbucket API for profile information using the provided client. // // The HTTP client is expected to be constructed by the golang.org/x/oauth2 package, // which inserts a bearer token as part of the request. func (b *bitbucketConnector) user(ctx context.Context, client *http.Client) (user, error) { // https://developer.atlassian.com/bitbucket/api/2/reference/resource/user var ( u user err error ) if err = get(ctx, client, b.apiURL+"/user", &u); err != nil { return user{}, err } if u.Email, err = b.userEmail(ctx, client); err != nil { return user{}, err } return u, nil } // userEmail holds Bitbucket user email information as defined by // https://developer.atlassian.com/bitbucket/api/2/reference/resource/user/emails type userEmail struct { IsPrimary bool `json:"is_primary"` IsConfirmed bool `json:"is_confirmed"` Email string `json:"email"` } type userEmailResponse struct { pagedResponse Values []userEmail } // userEmail returns the users primary, confirmed email // // The HTTP client is expected to be constructed by the golang.org/x/oauth2 package, // which inserts a bearer token as part of the request. func (b *bitbucketConnector) userEmail(ctx context.Context, client *http.Client) (string, error) { apiURL := b.apiURL + "/user/emails" for { // https://developer.atlassian.com/bitbucket/api/2/reference/resource/user/emails var response userEmailResponse if err := get(ctx, client, apiURL, &response); err != nil { return "", err } for _, email := range response.Values { if email.IsConfirmed && email.IsPrimary { return email.Email, nil } } if response.Next == nil { break } } return "", errors.New("bitbucket: user has no confirmed, primary email") } // getGroups retrieves Bitbucket teams a user is in, if any. func (b *bitbucketConnector) getGroups(ctx context.Context, client *http.Client, groupScope bool, userLogin string) ([]string, error) { bitbucketTeams, err := b.userWorkspaces(ctx, client) if err != nil { return nil, err } if len(b.teams) > 0 { filteredTeams := groups.Filter(bitbucketTeams, b.teams) if len(filteredTeams) == 0 { return nil, fmt.Errorf("bitbucket: user %q is not in any of the required teams", userLogin) } return filteredTeams, nil } else if groupScope { return bitbucketTeams, nil } return nil, nil } type workspaceSlug struct { Slug string `json:"slug"` } type workspace struct { Workspace workspaceSlug `json:"workspace"` } type userWorkspacesResponse struct { pagedResponse Values []workspace `json:"values"` } func (b *bitbucketConnector) userWorkspaces(ctx context.Context, client *http.Client) ([]string, error) { var teams []string apiURL := b.apiURL + "/user/permissions/workspaces" for { // https://developer.atlassian.com/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-get var response userWorkspacesResponse if err := get(ctx, client, apiURL, &response); err != nil { return nil, fmt.Errorf("bitbucket: get user teams: %v", err) } for _, value := range response.Values { teams = append(teams, value.Workspace.Slug) } if response.Next == nil { break } } if b.includeTeamGroups { for _, team := range teams { teamGroups, err := b.userTeamGroups(ctx, client, team) if err != nil { return nil, fmt.Errorf("bitbucket: %v", err) } teams = append(teams, teamGroups...) } } return teams, nil } type group struct { Slug string `json:"slug"` } func (b *bitbucketConnector) userTeamGroups(ctx context.Context, client *http.Client, teamName string) ([]string, error) { apiURL := b.legacyAPIURL + "/groups/" + teamName var response []group if err := get(ctx, client, apiURL, &response); err != nil { return nil, fmt.Errorf("get user team %q groups: %v", teamName, err) } teamGroups := make([]string, 0, len(response)) for _, group := range response { teamGroups = append(teamGroups, teamName+"/"+group.Slug) } return teamGroups, nil } // get creates a "GET `apiURL`" request with context, sends the request using // the client, and decodes the resulting response body into v. // Any errors encountered when building requests, sending requests, and // reading and decoding response data are returned. func get(ctx context.Context, client *http.Client, apiURL string, v interface{}) error { req, err := http.NewRequest("GET", apiURL, nil) if err != nil { return fmt.Errorf("bitbucket: new req: %v", err) } req = req.WithContext(ctx) resp, err := client.Do(req) if err != nil { return fmt.Errorf("bitbucket: get URL %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("bitbucket: read body: %s: %v", resp.Status, err) } return fmt.Errorf("%s: %s", resp.Status, body) } if err := json.NewDecoder(resp.Body).Decode(v); err != nil { return fmt.Errorf("bitbucket: failed to decode response: %v", err) } return nil } ================================================ FILE: connector/bitbucketcloud/bitbucketcloud_test.go ================================================ package bitbucketcloud import ( "context" "crypto/tls" "encoding/json" "net/http" "net/http/httptest" "net/url" "reflect" "testing" "github.com/dexidp/dex/connector" ) func TestUserGroups(t *testing.T) { teamsResponse := userWorkspacesResponse{ pagedResponse: pagedResponse{ Size: 3, Page: 1, PageLen: 10, }, Values: []workspace{ {Workspace: workspaceSlug{Slug: "team-1"}}, {Workspace: workspaceSlug{Slug: "team-2"}}, {Workspace: workspaceSlug{Slug: "team-3"}}, }, } s := newTestServer(map[string]interface{}{ "/user/permissions/workspaces": teamsResponse, "/groups/team-1": []group{{Slug: "administrators"}, {Slug: "members"}}, "/groups/team-2": []group{{Slug: "everyone"}}, "/groups/team-3": []group{}, }) connector := bitbucketConnector{apiURL: s.URL, legacyAPIURL: s.URL} groups, err := connector.userWorkspaces(context.Background(), newClient()) expectNil(t, err) expectEquals(t, groups, []string{ "team-1", "team-2", "team-3", }) connector.includeTeamGroups = true groups, err = connector.userWorkspaces(context.Background(), newClient()) expectNil(t, err) expectEquals(t, groups, []string{ "team-1", "team-2", "team-3", "team-1/administrators", "team-1/members", "team-2/everyone", }) s.Close() } func TestUserWithoutTeams(t *testing.T) { s := newTestServer(map[string]interface{}{ "/user/permissions/workspaces": userWorkspacesResponse{}, }) connector := bitbucketConnector{apiURL: s.URL} groups, err := connector.userWorkspaces(context.Background(), newClient()) expectNil(t, err) expectEquals(t, len(groups), 0) s.Close() } func TestUsernameIncludedInFederatedIdentity(t *testing.T) { s := newTestServer(map[string]interface{}{ "/user": user{Username: "some-login"}, "/user/emails": userEmailResponse{ pagedResponse: pagedResponse{ Size: 1, Page: 1, PageLen: 10, }, Values: []userEmail{{ Email: "some@email.com", IsConfirmed: true, IsPrimary: true, }}, }, "/site/oauth2/access_token": map[string]interface{}{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", "expires_in": "30", }, }) hostURL, err := url.Parse(s.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) bitbucketConnector := bitbucketConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: newClient()} identity, err := bitbucketConnector.HandleCallback(connector.Scopes{}, nil, req) expectNil(t, err) expectEquals(t, identity.Username, "some-login") s.Close() } func newTestServer(responses map[string]interface{}) *httptest.Server { return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(responses[r.URL.String()]) })) } func newClient() *http.Client { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } return &http.Client{Transport: tr} } func expectNil(t *testing.T, a interface{}) { if a != nil { t.Fatalf("Expected %+v to equal nil", a) } } func expectEquals(t *testing.T, a interface{}, b interface{}) { if !reflect.DeepEqual(a, b) { t.Fatalf("Expected %+v to equal %+v", a, b) } } ================================================ FILE: connector/connector.go ================================================ // Package connector defines interfaces for federated identity strategies. package connector import ( "context" "fmt" "net/http" ) // UserNotInRequiredGroupsError is returned by a connector when a user // successfully authenticates but is not a member of any of the required groups. // The server will respond with HTTP 403 Forbidden instead of 500. type UserNotInRequiredGroupsError struct { UserID string Groups []string } func (e *UserNotInRequiredGroupsError) Error() string { return fmt.Sprintf("user %q is not in any of the required groups %v", e.UserID, e.Groups) } // Connector is a mechanism for federating login to a remote identity service. // // Implementations are expected to implement either the PasswordConnector or // CallbackConnector interface. type Connector interface{} // Scopes represents additional data requested by the clients about the end user. type Scopes struct { // The client has requested a refresh token from the server. OfflineAccess bool // The client has requested group information about the end user. Groups bool } // Identity represents the ID Token claims supported by the server. type Identity struct { UserID string Username string PreferredUsername string Email string EmailVerified bool Groups []string // ConnectorData holds data used by the connector for subsequent requests after initial // authentication, such as access tokens for upstream provides. // // This data is never shared with end users, OAuth clients, or through the API. ConnectorData []byte } // PasswordConnector is an interface implemented by connectors which take a // username and password. // Prompt() is used to inform the handler what to display in the password // template. If this returns an empty string, it'll default to "Username". type PasswordConnector interface { Prompt() string Login(ctx context.Context, s Scopes, username, password string) (identity Identity, validPassword bool, err error) } // CallbackConnector is an interface implemented by connectors which use an OAuth // style redirect flow to determine user information. type CallbackConnector interface { // The initial URL to redirect the user to. // // OAuth2 implementations should request different scopes from the upstream // identity provider based on the scopes requested by the downstream client. // For example, if the downstream client requests a refresh token from the // server, the connector should also request a token from the provider. // // Many identity providers have arbitrary restrictions on refresh tokens. For // example Google only allows a single refresh token per client/user/scopes // combination, and wont return a refresh token even if offline access is // requested if one has already been issues. There's no good general answer // for these kind of restrictions, and may require this package to become more // aware of the global set of user/connector interactions. LoginURL(s Scopes, callbackURL, state string) (string, []byte, error) // Handle the callback to the server and return an identity. HandleCallback(s Scopes, connData []byte, r *http.Request) (identity Identity, err error) } // SAMLConnector represents SAML connectors which implement the HTTP POST binding. // // RelayState is handled by the server. // // See: https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf // "3.5 HTTP POST Binding" type SAMLConnector interface { // POSTData returns an encoded SAML request and SSO URL for the server to // render a POST form with. // // POSTData should encode the provided request ID in the returned serialized // SAML request. POSTData(s Scopes, requestID string) (ssoURL, samlRequest string, err error) // HandlePOST decodes, verifies, and maps attributes from the SAML response. // It passes the expected value of the "InResponseTo" response field, which // the connector must ensure matches the response value. // // See: https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf // "3.2.2 Complex Type StatusResponseType" HandlePOST(s Scopes, samlResponse, inResponseTo string) (identity Identity, err error) } // RefreshConnector is a connector that can update the client claims. type RefreshConnector interface { // Refresh is called when a client attempts to claim a refresh token. The // connector should attempt to update the identity object to reflect any // changes since the token was last refreshed. Refresh(ctx context.Context, s Scopes, identity Identity) (Identity, error) } type TokenIdentityConnector interface { TokenIdentity(ctx context.Context, subjectTokenType, subjectToken string) (Identity, error) } ================================================ FILE: connector/gitea/gitea.go ================================================ // Package gitea provides authentication strategies using Gitea. package gitea import ( "context" "encoding/json" "errors" "fmt" "io" "log/slog" "net/http" "strconv" "sync" "time" "golang.org/x/oauth2" "github.com/dexidp/dex/connector" ) // Config holds configuration options for gitea logins. type Config struct { BaseURL string `json:"baseURL"` ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` RedirectURI string `json:"redirectURI"` Orgs []Org `json:"orgs"` LoadAllGroups bool `json:"loadAllGroups"` UseLoginAsID bool `json:"useLoginAsID"` } // Org holds org-team filters, in which teams are optional. type Org struct { // Organization name in gitea (not slug, full name). Only users in this gitea // organization can authenticate. Name string `json:"name"` // Names of teams in a gitea organization. A user will be able to // authenticate if they are members of at least one of these teams. Users // in the organization can authenticate if this field is omitted from the // config file. Teams []string `json:"teams,omitempty"` } type giteaUser struct { ID int `json:"id"` Name string `json:"full_name"` Username string `json:"login"` Email string `json:"email"` IsAdmin bool `json:"is_admin"` } // Open returns a strategy for logging in through Gitea func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { if c.BaseURL == "" { c.BaseURL = "https://gitea.com" } return &giteaConnector{ baseURL: c.BaseURL, redirectURI: c.RedirectURI, orgs: c.Orgs, clientID: c.ClientID, clientSecret: c.ClientSecret, logger: logger.With(slog.Group("connector", "type", "gitea", "id", id)), loadAllGroups: c.LoadAllGroups, useLoginAsID: c.UseLoginAsID, }, nil } type connectorData struct { AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` Expiry time.Time `json:"expiry"` } var ( _ connector.CallbackConnector = (*giteaConnector)(nil) _ connector.RefreshConnector = (*giteaConnector)(nil) ) type giteaConnector struct { baseURL string redirectURI string orgs []Org clientID string clientSecret string logger *slog.Logger httpClient *http.Client // if set to true and no orgs are configured then connector loads all user claims (all orgs and team) loadAllGroups bool // if set to true will use the user's handle rather than their numeric id as the ID useLoginAsID bool } func (c *giteaConnector) oauth2Config(_ connector.Scopes) *oauth2.Config { giteaEndpoint := oauth2.Endpoint{AuthURL: c.baseURL + "/login/oauth/authorize", TokenURL: c.baseURL + "/login/oauth/access_token"} return &oauth2.Config{ ClientID: c.clientID, ClientSecret: c.clientSecret, Endpoint: giteaEndpoint, RedirectURL: c.redirectURI, } } func (c *giteaConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) { if c.redirectURI != callbackURL { return "", nil, fmt.Errorf("expected callback URL %q did not match the URL in the config %q", c.redirectURI, callbackURL) } return c.oauth2Config(scopes).AuthCodeURL(state), nil, nil } type oauth2Error struct { error string errorDescription string } func (e *oauth2Error) Error() string { if e.errorDescription == "" { return e.error } return e.error + ": " + e.errorDescription } func (c *giteaConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, &oauth2Error{errType, q.Get("error_description")} } oauth2Config := c.oauth2Config(s) ctx := r.Context() if c.httpClient != nil { ctx = context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient) } token, err := oauth2Config.Exchange(ctx, q.Get("code")) if err != nil { return identity, fmt.Errorf("gitea: failed to get token: %v", err) } client := oauth2Config.Client(ctx, token) user, err := c.user(ctx, client) if err != nil { return identity, fmt.Errorf("gitea: get user: %v", err) } username := user.Name if username == "" { username = user.Email } identity = connector.Identity{ UserID: strconv.Itoa(user.ID), Username: username, PreferredUsername: user.Username, Email: user.Email, EmailVerified: true, } if c.useLoginAsID { identity.UserID = user.Username } // Only set identity.Groups if 'orgs', 'org', or 'groups' scope are specified. if c.groupsRequired() { groups, err := c.getGroups(ctx, client) if err != nil { return identity, err } identity.Groups = groups } if s.OfflineAccess { data := connectorData{ AccessToken: token.AccessToken, RefreshToken: token.RefreshToken, Expiry: token.Expiry, } connData, err := json.Marshal(data) if err != nil { return identity, fmt.Errorf("gitea: marshal connector data: %v", err) } identity.ConnectorData = connData } return identity, nil } // Refreshing tokens // https://github.com/golang/oauth2/issues/84#issuecomment-332860871 type tokenNotifyFunc func(*oauth2.Token) error // notifyRefreshTokenSource is essentially `oauth2.ReuseTokenSource` with `TokenNotifyFunc` added. type notifyRefreshTokenSource struct { new oauth2.TokenSource mu sync.Mutex // guards t t *oauth2.Token f tokenNotifyFunc // called when token refreshed so new refresh token can be persisted } // Token returns the current token if it's still valid, else will // refresh the current token (using r.Context for HTTP client // information) and return the new one. func (s *notifyRefreshTokenSource) Token() (*oauth2.Token, error) { s.mu.Lock() defer s.mu.Unlock() if s.t.Valid() { return s.t, nil } t, err := s.new.Token() if err != nil { return nil, err } s.t = t return t, s.f(t) } func (c *giteaConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) { if len(ident.ConnectorData) == 0 { return ident, errors.New("gitea: no upstream access token found") } var data connectorData if err := json.Unmarshal(ident.ConnectorData, &data); err != nil { return ident, fmt.Errorf("gitea: unmarshal access token: %v", err) } tok := &oauth2.Token{ AccessToken: data.AccessToken, RefreshToken: data.RefreshToken, Expiry: data.Expiry, } client := oauth2.NewClient(ctx, ¬ifyRefreshTokenSource{ new: c.oauth2Config(s).TokenSource(ctx, tok), t: tok, f: func(tok *oauth2.Token) error { data := connectorData{ AccessToken: tok.AccessToken, RefreshToken: tok.RefreshToken, Expiry: tok.Expiry, } connData, err := json.Marshal(data) if err != nil { return fmt.Errorf("gitea: marshal connector data: %v", err) } ident.ConnectorData = connData return nil }, }) user, err := c.user(ctx, client) if err != nil { return ident, fmt.Errorf("gitea: get user: %v", err) } username := user.Name if username == "" { username = user.Email } ident.Username = username ident.PreferredUsername = user.Username ident.Email = user.Email // Only set identity.Groups if 'orgs', 'org', or 'groups' scope are specified. if c.groupsRequired() { groups, err := c.getGroups(ctx, client) if err != nil { return ident, err } ident.Groups = groups } return ident, nil } // getGroups retrieves Gitea orgs and teams a user is in, if any. func (c *giteaConnector) getGroups(ctx context.Context, client *http.Client) ([]string, error) { if len(c.orgs) > 0 { return c.groupsForOrgs(ctx, client) } else if c.loadAllGroups { return c.userGroups(ctx, client) } return nil, nil } // formatTeamName returns unique team name. // Orgs might have the same team names. To make team name unique it should be prefixed with the org name. func formatTeamName(org string, team string) string { return fmt.Sprintf("%s:%s", org, team) } // groupsForOrgs returns list of groups that user belongs to in approved list func (c *giteaConnector) groupsForOrgs(ctx context.Context, client *http.Client) ([]string, error) { groups, err := c.userGroups(ctx, client) if err != nil { return groups, err } keys := make(map[string]bool) for _, o := range c.orgs { keys[o.Name] = true if o.Teams != nil { for _, t := range o.Teams { keys[formatTeamName(o.Name, t)] = true } } } atLeastOne := false filteredGroups := make([]string, 0) for _, g := range groups { if _, value := keys[g]; value { filteredGroups = append(filteredGroups, g) atLeastOne = true } } if !atLeastOne { return []string{}, fmt.Errorf("gitea: User does not belong to any of the approved groups") } return filteredGroups, nil } type organization struct { ID int64 `json:"id"` Name string `json:"username"` } type team struct { ID int64 `json:"id"` Name string `json:"name"` Organization *organization `json:"organization"` } func (c *giteaConnector) userGroups(ctx context.Context, client *http.Client) ([]string, error) { apiURL := c.baseURL + "/api/v1/user/teams" groups := make([]string, 0) page := 1 limit := 20 for { var teams []team req, err := http.NewRequest("GET", fmt.Sprintf("%s?page=%d&limit=%d", apiURL, page, limit), nil) if err != nil { return groups, fmt.Errorf("gitea: new req: %v", err) } req = req.WithContext(ctx) resp, err := client.Do(req) if err != nil { return groups, fmt.Errorf("gitea: get URL %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return groups, fmt.Errorf("gitea: read body: %v", err) } return groups, fmt.Errorf("%s: %s", resp.Status, body) } if err := json.NewDecoder(resp.Body).Decode(&teams); err != nil { return groups, fmt.Errorf("failed to decode response: %v", err) } if len(teams) == 0 { break } for _, t := range teams { groups = append(groups, t.Organization.Name) groups = append(groups, formatTeamName(t.Organization.Name, t.Name)) } page++ } // remove duplicate slice variables keys := make(map[string]struct{}) list := []string{} for _, group := range groups { if _, exists := keys[group]; !exists { keys[group] = struct{}{} list = append(list, group) } } groups = list return groups, nil } // user queries the Gitea API for profile information using the provided client. The HTTP // client is expected to be constructed by the golang.org/x/oauth2 package, which inserts // a bearer token as part of the request. func (c *giteaConnector) user(ctx context.Context, client *http.Client) (giteaUser, error) { var u giteaUser req, err := http.NewRequest("GET", c.baseURL+"/api/v1/user", nil) if err != nil { return u, fmt.Errorf("gitea: new req: %v", err) } req = req.WithContext(ctx) resp, err := client.Do(req) if err != nil { return u, fmt.Errorf("gitea: get URL %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return u, fmt.Errorf("gitea: read body: %v", err) } return u, fmt.Errorf("%s: %s", resp.Status, body) } if err := json.NewDecoder(resp.Body).Decode(&u); err != nil { return u, fmt.Errorf("failed to decode response: %v", err) } return u, nil } // groupsRequired returns whether dex needs to request groups from Gitea. func (c *giteaConnector) groupsRequired() bool { return len(c.orgs) > 0 || c.loadAllGroups } ================================================ FILE: connector/gitea/gitea_test.go ================================================ package gitea import ( "crypto/tls" "encoding/json" "net/http" "net/http/httptest" "net/url" "reflect" "testing" "github.com/dexidp/dex/connector" ) // tests that the email is used as their username when they have no username set func TestUsernameIncludedInFederatedIdentity(t *testing.T) { s := newTestServer(map[string]interface{}{ "/api/v1/user": giteaUser{Email: "some@email.com", ID: 12345678}, "/login/oauth/access_token": map[string]interface{}{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", "expires_in": "30", }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) c := giteaConnector{baseURL: s.URL, httpClient: newClient()} identity, err := c.HandleCallback(connector.Scopes{}, nil, req) expectNil(t, err) expectEquals(t, identity.Username, "some@email.com") expectEquals(t, identity.UserID, "12345678") c = giteaConnector{baseURL: s.URL, httpClient: newClient()} identity, err = c.HandleCallback(connector.Scopes{}, nil, req) expectNil(t, err) expectEquals(t, identity.Username, "some@email.com") expectEquals(t, identity.UserID, "12345678") } func newTestServer(responses map[string]interface{}) *httptest.Server { return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { response := responses[r.RequestURI] w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(response) })) } func newClient() *http.Client { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } return &http.Client{Transport: tr} } func expectNil(t *testing.T, a interface{}) { if a != nil { t.Errorf("Expected %+v to equal nil", a) } } func expectEquals(t *testing.T, a interface{}, b interface{}) { if !reflect.DeepEqual(a, b) { t.Errorf("Expected %+v to equal %+v", a, b) } } ================================================ FILE: connector/github/github.go ================================================ // Package github provides authentication strategies using GitHub. package github import ( "context" "encoding/json" "errors" "fmt" "io" "log/slog" "net/http" "regexp" "strconv" "strings" "golang.org/x/oauth2" "golang.org/x/oauth2/github" "github.com/dexidp/dex/connector" groups_pkg "github.com/dexidp/dex/pkg/groups" "github.com/dexidp/dex/pkg/httpclient" ) const ( apiURL = "https://api.github.com" // GitHub requires this scope to access '/user' and '/user/emails' API endpoints. scopeEmail = "user:email" // GitHub requires this scope to access '/user/teams' and '/orgs' API endpoints // which are used when a client includes the 'groups' scope. scopeOrgs = "read:org" // githubAPIVersion pins the GitHub REST API version used in requests. githubAPIVersion = "2022-11-28" ) // Pagination URL patterns // https://developer.github.com/v3/#pagination var ( reNext = regexp.MustCompile("<([^>]+)>; rel=\"next\"") reLast = regexp.MustCompile("<([^>]+)>; rel=\"last\"") ) // Config holds configuration options for github logins. type Config struct { ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` RedirectURI string `json:"redirectURI"` Org string `json:"org"` Orgs []Org `json:"orgs"` HostName string `json:"hostName"` RootCA string `json:"rootCA"` TeamNameField string `json:"teamNameField"` LoadAllGroups bool `json:"loadAllGroups"` UseLoginAsID bool `json:"useLoginAsID"` PreferredEmailDomain string `json:"preferredEmailDomain"` } // Org holds org-team filters, in which teams are optional. type Org struct { // Organization name in github (not slug, full name). Only users in this github // organization can authenticate. Name string `json:"name"` // Names of teams in a github organization. A user will be able to // authenticate if they are members of at least one of these teams. Users // in the organization can authenticate if this field is omitted from the // config file. Teams []string `json:"teams,omitempty"` } // Open returns a strategy for logging in through GitHub. func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { if c.Org != "" { // Return error if both 'org' and 'orgs' fields are used. if len(c.Orgs) > 0 { return nil, errors.New("github: cannot use both 'org' and 'orgs' fields simultaneously") } logger.Warn("github: legacy field 'org' being used. Switch to the newer 'orgs' field structure") } g := githubConnector{ redirectURI: c.RedirectURI, org: c.Org, orgs: c.Orgs, clientID: c.ClientID, clientSecret: c.ClientSecret, apiURL: apiURL, logger: logger.With(slog.Group("connector", "type", "github", "id", id)), useLoginAsID: c.UseLoginAsID, preferredEmailDomain: c.PreferredEmailDomain, } if c.HostName != "" { // ensure this is a hostname and not a URL or path. if strings.Contains(c.HostName, "/") { return nil, errors.New("invalid hostname: hostname cannot contain `/`") } g.hostName = c.HostName g.apiURL = "https://" + c.HostName + "/api/v3" } if c.RootCA != "" { if c.HostName == "" { return nil, errors.New("invalid connector config: Host name field required for a root certificate file") } g.rootCA = c.RootCA var err error if g.httpClient, err = httpclient.NewHTTPClient([]string{g.rootCA}, false); err != nil { return nil, fmt.Errorf("failed to create HTTP client: %v", err) } } g.loadAllGroups = c.LoadAllGroups switch c.TeamNameField { case "name", "slug", "both", "": g.teamNameField = c.TeamNameField default: return nil, fmt.Errorf("invalid connector config: unsupported team name field value `%s`", c.TeamNameField) } if c.PreferredEmailDomain != "" { if strings.HasSuffix(c.PreferredEmailDomain, "*") { return nil, errors.New("invalid PreferredEmailDomain: glob pattern cannot end with \"*\"") } } return &g, nil } type connectorData struct { // GitHub's OAuth2 tokens never expire. We don't need a refresh token. AccessToken string `json:"accessToken"` } var ( _ connector.CallbackConnector = (*githubConnector)(nil) _ connector.RefreshConnector = (*githubConnector)(nil) ) type githubConnector struct { redirectURI string org string orgs []Org clientID string clientSecret string logger *slog.Logger // apiURL defaults to "https://api.github.com" apiURL string // hostName of the GitHub enterprise account. hostName string // Used to support untrusted/self-signed CA certs. rootCA string // HTTP Client that trusts the custom declared rootCA cert. httpClient *http.Client // optional choice between 'name' (default) or 'slug' teamNameField string // if set to true and no orgs are configured then connector loads all user claims (all orgs and team) loadAllGroups bool // if set to true will use the user's handle rather than their numeric id as the ID useLoginAsID bool // the domain to be preferred among the user's emails. e.g. "github.com" preferredEmailDomain string } // groupsRequired returns whether dex requires GitHub's 'read:org' scope. Dex // needs 'read:org' if 'orgs' or 'org' fields are populated in a config file. // Clients can require 'groups' scope without setting 'orgs'/'org'. func (c *githubConnector) groupsRequired(groupScope bool) bool { return len(c.orgs) > 0 || c.org != "" || groupScope } func (c *githubConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config { // 'read:org' scope is required by the GitHub API, and thus for dex to ensure // a user is a member of orgs and teams provided in configs. githubScopes := []string{scopeEmail} if c.groupsRequired(scopes.Groups) { githubScopes = append(githubScopes, scopeOrgs) } endpoint := github.Endpoint // case when it is a GitHub Enterprise account. if c.hostName != "" { endpoint = oauth2.Endpoint{ AuthURL: "https://" + c.hostName + "/login/oauth/authorize", TokenURL: "https://" + c.hostName + "/login/oauth/access_token", } } return &oauth2.Config{ ClientID: c.clientID, ClientSecret: c.clientSecret, Endpoint: endpoint, Scopes: githubScopes, RedirectURL: c.redirectURI, } } func (c *githubConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) { if c.redirectURI != callbackURL { return "", nil, fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI) } return c.oauth2Config(scopes).AuthCodeURL(state), nil, nil } type oauth2Error struct { error string errorDescription string } func (e *oauth2Error) Error() string { if e.errorDescription == "" { return e.error } return e.error + ": " + e.errorDescription } func (c *githubConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, &oauth2Error{errType, q.Get("error_description")} } oauth2Config := c.oauth2Config(s) ctx := r.Context() // GitHub Enterprise account if c.httpClient != nil { ctx = context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient) } token, err := oauth2Config.Exchange(ctx, q.Get("code")) if err != nil { return identity, fmt.Errorf("github: failed to get token: %v", err) } client := oauth2Config.Client(ctx, token) user, err := c.user(ctx, client) if err != nil { return identity, fmt.Errorf("github: get user: %v", err) } username := user.Name if username == "" { username = user.Login } identity = connector.Identity{ UserID: strconv.Itoa(user.ID), Username: username, PreferredUsername: user.Login, Email: user.Email, EmailVerified: true, } if c.useLoginAsID { identity.UserID = user.Login } // Only set identity.Groups if 'orgs', 'org', or 'groups' scope are specified. if c.groupsRequired(s.Groups) { groups, err := c.getGroups(ctx, client, s.Groups, user.Login) if err != nil { return identity, err } identity.Groups = groups } if s.OfflineAccess { data := connectorData{AccessToken: token.AccessToken} connData, err := json.Marshal(data) if err != nil { return identity, fmt.Errorf("marshal connector data: %v", err) } identity.ConnectorData = connData } return identity, nil } func (c *githubConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { if len(identity.ConnectorData) == 0 { return identity, errors.New("no upstream access token found") } var data connectorData if err := json.Unmarshal(identity.ConnectorData, &data); err != nil { return identity, fmt.Errorf("github: unmarshal access token: %v", err) } client := c.oauth2Config(s).Client(ctx, &oauth2.Token{AccessToken: data.AccessToken}) user, err := c.user(ctx, client) if err != nil { return identity, fmt.Errorf("github: get user: %v", err) } username := user.Name if username == "" { username = user.Login } identity.Username = username identity.PreferredUsername = user.Login identity.Email = user.Email // Only set identity.Groups if 'orgs', 'org', or 'groups' scope are specified. if c.groupsRequired(s.Groups) { groups, err := c.getGroups(ctx, client, s.Groups, user.Login) if err != nil { return identity, err } identity.Groups = groups } return identity, nil } // getGroups retrieves GitHub orgs and teams a user is in, if any. func (c *githubConnector) getGroups(ctx context.Context, client *http.Client, groupScope bool, userLogin string) ([]string, error) { switch { case len(c.orgs) > 0: return c.groupsForOrgs(ctx, client, userLogin) case c.org != "": return c.teamsForOrg(ctx, client, c.org) case groupScope && c.loadAllGroups: return c.userGroups(ctx, client) } return nil, nil } // formatTeamName returns unique team name. // Orgs might have the same team names. To make team name unique it should be prefixed with the org name. func formatTeamName(org string, team string) string { return fmt.Sprintf("%s:%s", org, team) } // groupsForOrgs enforces org and team constraints on user authorization // Cases in which user is authorized: // // N orgs, no teams: user is member of at least 1 org // N orgs, M teams per org: user is member of any team from at least 1 org // N-1 orgs, M teams per org, 1 org with no teams: user is member of any team // // from at least 1 org, or member of org with no teams func (c *githubConnector) groupsForOrgs(ctx context.Context, client *http.Client, userName string) ([]string, error) { groups := make([]string, 0) var inOrgNoTeams bool for _, org := range c.orgs { inOrg, err := c.userInOrg(ctx, client, userName, org.Name) if err != nil { return nil, err } if !inOrg { continue } teams, err := c.teamsForOrg(ctx, client, org.Name) if err != nil { return nil, err } // User is in at least one org. User is authorized if no teams are specified // in config; include all teams in claim. Otherwise filter out teams not in // 'teams' list in config. if len(org.Teams) == 0 { inOrgNoTeams = true } else if teams = groups_pkg.Filter(teams, org.Teams); len(teams) == 0 { c.logger.Info("user in org but no teams", "user", userName, "org", org.Name) } for _, teamName := range teams { groups = append(groups, formatTeamName(org.Name, teamName)) } } if inOrgNoTeams || len(groups) > 0 { return groups, nil } return groups, fmt.Errorf("github: user %q not in required orgs or teams", userName) } func (c *githubConnector) userGroups(ctx context.Context, client *http.Client) ([]string, error) { orgs, err := c.userOrgs(ctx, client) if err != nil { return nil, err } orgTeams, err := c.userOrgTeams(ctx, client) if err != nil { return nil, err } groups := make([]string, 0) for _, o := range orgs { groups = append(groups, o) if teams, ok := orgTeams[o]; ok { for _, t := range teams { groups = append(groups, formatTeamName(o, t)) } } } return groups, nil } // userOrgs retrieves list of current user orgs func (c *githubConnector) userOrgs(ctx context.Context, client *http.Client) ([]string, error) { groups := make([]string, 0) apiURL := c.apiURL + "/user/orgs" for { // https://developer.github.com/v3/orgs/#list-your-organizations var ( orgs []org err error ) if apiURL, err = get(ctx, client, apiURL, &orgs); err != nil { return nil, fmt.Errorf("github: get orgs: %v", err) } for _, o := range orgs { groups = append(groups, o.Login) } if apiURL == "" { break } } return groups, nil } // userOrgTeams retrieves teams which current user belongs to. // Method returns a map where key is an org name and value list of teams under the org. func (c *githubConnector) userOrgTeams(ctx context.Context, client *http.Client) (map[string][]string, error) { groups := make(map[string][]string) apiURL := c.apiURL + "/user/teams" for { // https://developer.github.com/v3/orgs/teams/#list-user-teams var ( teams []team err error ) if apiURL, err = get(ctx, client, apiURL, &teams); err != nil { return nil, fmt.Errorf("github: get teams: %v", err) } for _, t := range teams { groups[t.Org.Login] = append(groups[t.Org.Login], c.teamGroupClaims(t)...) } if apiURL == "" { break } } return groups, nil } // get creates a "GET `apiURL`" request with context, sends the request using // the client, and decodes the resulting response body into v. A pagination URL // is returned if one exists. Any errors encountered when building requests, // sending requests, and reading and decoding response data are returned. func get(ctx context.Context, client *http.Client, apiURL string, v interface{}) (string, error) { req, err := http.NewRequest("GET", apiURL, nil) if err != nil { return "", fmt.Errorf("github: new req: %v", err) } req = req.WithContext(ctx) req.Header.Set("X-GitHub-Api-Version", githubAPIVersion) resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("github: get URL %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("github: read body: %v", err) } return "", fmt.Errorf("%s: %s", resp.Status, body) } if err := json.NewDecoder(resp.Body).Decode(v); err != nil { return "", fmt.Errorf("failed to decode response: %v", err) } return getPagination(apiURL, resp), nil } // getPagination checks the "Link" header field for "next" or "last" pagination URLs, // and returns "next" page URL or empty string to indicate that there are no more pages. // Non empty next pages' URL is returned if both "last" and "next" URLs are found and next page // URL is not equal to last. // // https://developer.github.com/v3/#pagination func getPagination(apiURL string, resp *http.Response) string { if resp == nil { return "" } links := resp.Header.Get("Link") if len(reLast.FindStringSubmatch(links)) > 1 { lastPageURL := reLast.FindStringSubmatch(links)[1] if apiURL == lastPageURL { return "" } } else { return "" } if len(reNext.FindStringSubmatch(links)) > 1 { return reNext.FindStringSubmatch(links)[1] } return "" } // user holds GitHub user information (relevant to dex) as defined by // https://developer.github.com/v3/users/#response-with-public-profile-information type user struct { Name string `json:"name"` Login string `json:"login"` ID int `json:"id"` Email string `json:"email"` } // user queries the GitHub API for profile information using the provided client. // // The HTTP client is expected to be constructed by the golang.org/x/oauth2 package, // which inserts a bearer token as part of the request. func (c *githubConnector) user(ctx context.Context, client *http.Client) (user, error) { // https://developer.github.com/v3/users/#get-the-authenticated-user var u user if _, err := get(ctx, client, c.apiURL+"/user", &u); err != nil { return u, err } // Only public user emails are returned by 'GET /user'. // If a user has no public email, we must retrieve private emails explicitly. // If preferredEmailDomain is set, we always need to retrieve all emails. if u.Email == "" || c.preferredEmailDomain != "" { var err error if u.Email, err = c.userEmail(ctx, client); err != nil { return u, err } } return u, nil } // userEmail holds GitHub user email information as defined by // https://developer.github.com/v3/users/emails/#response type userEmail struct { Email string `json:"email"` Verified bool `json:"verified"` Primary bool `json:"primary"` Visibility string `json:"visibility"` } // userEmail queries the GitHub API for a users' email information using the // provided client. Only returns the users' verified, primary email (private or // public). // // The HTTP client is expected to be constructed by the golang.org/x/oauth2 package, // which inserts a bearer token as part of the request. func (c *githubConnector) userEmail(ctx context.Context, client *http.Client) (string, error) { var ( primaryEmail userEmail preferredEmails []userEmail ) apiURL := c.apiURL + "/user/emails" for { // https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user var ( emails []userEmail err error ) if apiURL, err = get(ctx, client, apiURL, &emails); err != nil { return "", err } for _, email := range emails { /* if GitHub Enterprise, set email.Verified to true This change being made because GitHub Enterprise does not support email verification. CircleCI indicated that GitHub advised them not to check for verified emails (https://circleci.com/enterprise/changelog/#1-47-1). In addition, GitHub Enterprise support replied to a support ticket with "There is no way to verify an email address in GitHub Enterprise." */ if c.hostName != "" { email.Verified = true } if email.Verified && email.Primary { primaryEmail = email } if c.preferredEmailDomain != "" { _, domainPart, ok := strings.Cut(email.Email, "@") if !ok { return "", errors.New("github: invalid format email is detected") } if email.Verified && c.isPreferredEmailDomain(domainPart) { preferredEmails = append(preferredEmails, email) } } } if apiURL == "" { break } } if len(preferredEmails) > 0 { return preferredEmails[0].Email, nil } if primaryEmail.Email != "" { return primaryEmail.Email, nil } return "", errors.New("github: user has no verified, primary email or preferred-domain email") } // isPreferredEmailDomain checks the domain is matching with preferredEmailDomain. func (c *githubConnector) isPreferredEmailDomain(domain string) bool { if domain == c.preferredEmailDomain { return true } preferredDomainParts := strings.Split(c.preferredEmailDomain, ".") domainParts := strings.Split(domain, ".") if len(preferredDomainParts) != len(domainParts) { return false } for i, v := range preferredDomainParts { if domainParts[i] != v && v != "*" { return false } } return true } // userInOrg queries the GitHub API for a users' org membership. // // The HTTP passed client is expected to be constructed by the golang.org/x/oauth2 package, // which inserts a bearer token as part of the request. func (c *githubConnector) userInOrg(ctx context.Context, client *http.Client, userName, orgName string) (bool, error) { // requester == user, so GET-ing this endpoint should return 404/302 if user // is not a member // // https://developer.github.com/v3/orgs/members/#check-membership apiURL := fmt.Sprintf("%s/orgs/%s/members/%s", c.apiURL, orgName, userName) req, err := http.NewRequest("GET", apiURL, nil) if err != nil { return false, fmt.Errorf("github: new req: %v", err) } req = req.WithContext(ctx) req.Header.Set("X-GitHub-Api-Version", githubAPIVersion) resp, err := client.Do(req) if err != nil { return false, fmt.Errorf("github: get teams: %v", err) } defer resp.Body.Close() switch resp.StatusCode { case http.StatusNoContent: case http.StatusFound, http.StatusNotFound: c.logger.Info("user not in org or application not authorized to read org data", "user", userName, "org", orgName) default: err = fmt.Errorf("github: unexpected return status: %q", resp.Status) } // 204 if user is a member return resp.StatusCode == http.StatusNoContent, err } // teams holds GitHub a users' team information as defined by // https://developer.github.com/v3/orgs/teams/#response-12 type team struct { Name string `json:"name"` Org org `json:"organization"` Slug string `json:"slug"` } type org struct { Login string `json:"login"` } // teamsForOrg queries the GitHub API for team membership within a specific organization. // // The HTTP passed client is expected to be constructed by the golang.org/x/oauth2 package, // which inserts a bearer token as part of the request. func (c *githubConnector) teamsForOrg(ctx context.Context, client *http.Client, orgName string) ([]string, error) { apiURL, groups := c.apiURL+"/user/teams", []string{} for { // https://developer.github.com/v3/orgs/teams/#list-user-teams var ( teams []team err error ) if apiURL, err = get(ctx, client, apiURL, &teams); err != nil { return nil, fmt.Errorf("github: get teams: %v", err) } for _, t := range teams { if t.Org.Login == orgName { groups = append(groups, c.teamGroupClaims(t)...) } } if apiURL == "" { break } } return groups, nil } // teamGroupClaims returns team slug if 'teamNameField' option is set to // 'slug', returns the slug *and* name if set to 'both', otherwise returns team // name. func (c *githubConnector) teamGroupClaims(t team) []string { switch c.teamNameField { case "both": return []string{t.Name, t.Slug} case "slug": return []string{t.Slug} default: return []string{t.Name} } } ================================================ FILE: connector/github/github_test.go ================================================ package github import ( "context" "crypto/tls" "encoding/json" "errors" "fmt" "log/slog" "net/http" "net/http/httptest" "net/url" "reflect" "strings" "testing" "github.com/dexidp/dex/connector" ) type testResponse struct { data interface{} nextLink string lastLink string } func TestUserGroups(t *testing.T) { s := newTestServer(map[string]testResponse{ "/user/orgs": { data: []org{{Login: "org-1"}, {Login: "org-2"}}, nextLink: "/user/orgs?since=2", lastLink: "/user/orgs?since=2", }, "/user/orgs?since=2": {data: []org{{Login: "org-3"}}}, "/user/teams": { data: []team{ {Name: "team-1", Org: org{Login: "org-1"}}, {Name: "team-2", Org: org{Login: "org-1"}}, }, nextLink: "/user/teams?since=2", lastLink: "/user/teams?since=2", }, "/user/teams?since=2": { data: []team{ {Name: "team-3", Org: org{Login: "org-1"}}, {Name: "team-4", Org: org{Login: "org-2"}}, }, nextLink: "/user/teams?since=2", lastLink: "/user/teams?since=2", }, }) defer s.Close() c := githubConnector{apiURL: s.URL} groups, err := c.userGroups(context.Background(), newClient()) expectNil(t, err) expectEquals(t, groups, []string{ "org-1", "org-1:team-1", "org-1:team-2", "org-1:team-3", "org-2", "org-2:team-4", "org-3", }) } func TestUserGroupsWithoutOrgs(t *testing.T) { s := newTestServer(map[string]testResponse{ "/user/orgs": {data: []org{}}, "/user/teams": {data: []team{}}, }) defer s.Close() c := githubConnector{apiURL: s.URL} groups, err := c.userGroups(context.Background(), newClient()) expectNil(t, err) expectEquals(t, len(groups), 0) } func TestUserGroupsWithTeamNameFieldConfig(t *testing.T) { s := newTestServer(map[string]testResponse{ "/user/orgs": { data: []org{{Login: "org-1"}}, }, "/user/teams": { data: []team{ {Name: "Team 1", Slug: "team-1", Org: org{Login: "org-1"}}, }, }, }) defer s.Close() c := githubConnector{apiURL: s.URL, teamNameField: "slug"} groups, err := c.userGroups(context.Background(), newClient()) expectNil(t, err) expectEquals(t, groups, []string{ "org-1", "org-1:team-1", }) } func TestUserGroupsWithTeamNameAndSlugFieldConfig(t *testing.T) { s := newTestServer(map[string]testResponse{ "/user/orgs": { data: []org{{Login: "org-1"}}, }, "/user/teams": { data: []team{ {Name: "Team 1", Slug: "team-1", Org: org{Login: "org-1"}}, }, }, }) defer s.Close() c := githubConnector{apiURL: s.URL, teamNameField: "both"} groups, err := c.userGroups(context.Background(), newClient()) expectNil(t, err) expectEquals(t, groups, []string{ "org-1", "org-1:Team 1", "org-1:team-1", }) } // tests that the users login is used as their username when they have no username set func TestUsernameIncludedInFederatedIdentity(t *testing.T) { s := newTestServer(map[string]testResponse{ "/user": {data: user{Login: "some-login", ID: 12345678}}, "/user/emails": {data: []userEmail{{ Email: "some@email.com", Verified: true, Primary: true, }}}, "/login/oauth/access_token": {data: map[string]interface{}{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", "expires_in": "30", }}, "/user/orgs": { data: []org{{Login: "org-1"}}, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) c := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: newClient()} identity, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req) expectNil(t, err) expectEquals(t, identity.Username, "some-login") expectEquals(t, identity.UserID, "12345678") expectEquals(t, 0, len(identity.Groups)) c = githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: newClient(), loadAllGroups: true} identity, err = c.HandleCallback(connector.Scopes{Groups: true}, nil, req) expectNil(t, err) expectEquals(t, identity.Username, "some-login") expectEquals(t, identity.UserID, "12345678") expectEquals(t, identity.Groups, []string{"org-1"}) } func TestLoginUsedAsIDWhenConfigured(t *testing.T) { s := newTestServer(map[string]testResponse{ "/user": {data: user{Login: "some-login", ID: 12345678, Name: "Joe Bloggs"}}, "/user/emails": {data: []userEmail{{ Email: "some@email.com", Verified: true, Primary: true, }}}, "/login/oauth/access_token": {data: map[string]interface{}{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", "expires_in": "30", }}, "/user/orgs": { data: []org{{Login: "org-1"}}, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) c := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: newClient(), useLoginAsID: true} identity, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req) expectNil(t, err) expectEquals(t, identity.UserID, "some-login") expectEquals(t, identity.Username, "Joe Bloggs") } func TestPreferredEmailDomainConfigured(t *testing.T) { ctx := context.Background() s := newTestServer(map[string]testResponse{ "/user": {data: user{Login: "some-login", ID: 12345678, Name: "Joe Bloggs"}}, "/user/emails": { data: []userEmail{ { Email: "some@email.com", Verified: true, Primary: true, }, { Email: "another@email.com", Verified: true, Primary: false, }, { Email: "some@preferred-domain.com", Verified: true, Primary: false, }, { Email: "another@preferred-domain.com", Verified: true, Primary: false, }, }, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) client := newClient() c := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: client, preferredEmailDomain: "preferred-domain.com"} u, err := c.user(ctx, client) expectNil(t, err) expectEquals(t, u.Email, "some@preferred-domain.com") } func TestPreferredEmailDomainConfiguredWithGlob(t *testing.T) { ctx := context.Background() s := newTestServer(map[string]testResponse{ "/user": {data: user{Login: "some-login", ID: 12345678, Name: "Joe Bloggs"}}, "/user/emails": { data: []userEmail{ { Email: "some@email.com", Verified: true, Primary: true, }, { Email: "another@email.com", Verified: true, Primary: false, }, { Email: "some@another.preferred-domain.com", Verified: true, Primary: false, }, { Email: "some@sub-domain.preferred-domain.co", Verified: true, Primary: false, }, }, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) client := newClient() c := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: client, preferredEmailDomain: "*.preferred-domain.co"} u, err := c.user(ctx, client) expectNil(t, err) expectEquals(t, u.Email, "some@sub-domain.preferred-domain.co") } func TestPreferredEmailDomainConfigured_UserHasNoPreferredDomainEmail(t *testing.T) { ctx := context.Background() s := newTestServer(map[string]testResponse{ "/user": {data: user{Login: "some-login", ID: 12345678, Name: "Joe Bloggs"}}, "/user/emails": { data: []userEmail{ { Email: "some@email.com", Verified: true, Primary: true, }, { Email: "another@email.com", Verified: true, Primary: false, }, }, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) client := newClient() c := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: client, preferredEmailDomain: "preferred-domain.com"} u, err := c.user(ctx, client) expectNil(t, err) expectEquals(t, u.Email, "some@email.com") } func TestPreferredEmailDomainNotConfigured(t *testing.T) { ctx := context.Background() s := newTestServer(map[string]testResponse{ "/user": {data: user{Login: "some-login", ID: 12345678, Name: "Joe Bloggs"}}, "/user/emails": { data: []userEmail{ { Email: "some@email.com", Verified: true, Primary: true, }, { Email: "another@email.com", Verified: true, Primary: false, }, { Email: "some@preferred-domain.com", Verified: true, Primary: false, }, }, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) client := newClient() c := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: client} u, err := c.user(ctx, client) expectNil(t, err) expectEquals(t, u.Email, "some@email.com") } func TestPreferredEmailDomainConfigured_Error_BothPrimaryAndPreferredDomainEmailNotFound(t *testing.T) { ctx := context.Background() s := newTestServer(map[string]testResponse{ "/user": {data: user{Login: "some-login", ID: 12345678, Name: "Joe Bloggs"}}, "/user/emails": { data: []userEmail{ { Email: "some@email.com", Verified: true, Primary: false, }, { Email: "another@email.com", Verified: true, Primary: false, }, { Email: "some@preferred-domain.com", Verified: true, Primary: false, }, }, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) client := newClient() c := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: client, preferredEmailDomain: "foo.bar"} _, err = c.user(ctx, client) expectNotNil(t, err, "Email not found error") expectEquals(t, err.Error(), "github: user has no verified, primary email or preferred-domain email") } func Test_isPreferredEmailDomain(t *testing.T) { client := newClient() tests := []struct { preferredEmailDomain string email string expected bool }{ { preferredEmailDomain: "example.com", email: "test@example.com", expected: true, }, { preferredEmailDomain: "example.com", email: "test@another.com", expected: false, }, { preferredEmailDomain: "*.example.com", email: "test@my.example.com", expected: true, }, { preferredEmailDomain: "*.example.com", email: "test@my.another.com", expected: false, }, { preferredEmailDomain: "*.example.com", email: "test@my.domain.example.com", expected: false, }, { preferredEmailDomain: "*.example.com", email: "test@sub.domain.com", expected: false, }, { preferredEmailDomain: "*.*.example.com", email: "test@sub.my.example.com", expected: true, }, { preferredEmailDomain: "*.*.example.com", email: "test@a.my.google.com", expected: false, }, } for _, test := range tests { t.Run(test.preferredEmailDomain, func(t *testing.T) { c := githubConnector{apiURL: "apiURL", hostName: "github.com", httpClient: client, preferredEmailDomain: test.preferredEmailDomain} _, domainPart, _ := strings.Cut(test.email, "@") res := c.isPreferredEmailDomain(domainPart) expectEquals(t, res, test.expected) }) } } func Test_Open_PreferredDomainConfig(t *testing.T) { log := slog.New(slog.DiscardHandler) tests := []struct { preferredEmailDomain string email string expected error }{ { preferredEmailDomain: "example.com", expected: nil, }, { preferredEmailDomain: "*.example.com", expected: nil, }, { preferredEmailDomain: "*.*.example.com", expected: nil, }, { preferredEmailDomain: "example.*", expected: errors.New("invalid PreferredEmailDomain: glob pattern cannot end with \"*\""), }, } for _, test := range tests { t.Run(test.preferredEmailDomain, func(t *testing.T) { c := Config{ PreferredEmailDomain: test.preferredEmailDomain, } _, err := c.Open("id", log) expectEquals(t, err, test.expected) }) } } func TestGetSendsAPIVersionHeader(t *testing.T) { var gotHeader string s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotHeader = r.Header.Get("X-GitHub-Api-Version") w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode([]org{}) })) defer s.Close() var result []org _, err := get(context.Background(), newClient(), s.URL+"/user/orgs", &result) expectNil(t, err) expectEquals(t, gotHeader, githubAPIVersion) } func newTestServer(responses map[string]testResponse) *httptest.Server { var s *httptest.Server s = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { response := responses[r.RequestURI] linkParts := make([]string, 0) if response.nextLink != "" { linkParts = append(linkParts, fmt.Sprintf("<%s%s>; rel=\"next\"", s.URL, response.nextLink)) } if response.lastLink != "" { linkParts = append(linkParts, fmt.Sprintf("<%s%s>; rel=\"last\"", s.URL, response.lastLink)) } if len(linkParts) > 0 { w.Header().Add("Link", strings.Join(linkParts, ", ")) } w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(response.data) })) return s } func newClient() *http.Client { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } return &http.Client{Transport: tr} } func expectNil(t *testing.T, a interface{}) { if a != nil { t.Errorf("Expected %+v to equal nil", a) } } func expectNotNil(t *testing.T, a interface{}, msg string) { if a == nil { t.Errorf("Expected %+v to not to be nil", msg) } } func expectEquals(t *testing.T, a interface{}, b interface{}) { if !reflect.DeepEqual(a, b) { t.Errorf("Expected %+v to equal %+v", a, b) } } ================================================ FILE: connector/gitlab/gitlab.go ================================================ // Package gitlab provides authentication strategies using GitLab. package gitlab import ( "context" "encoding/json" "errors" "fmt" "io" "log/slog" "net/http" "strconv" "strings" "time" "golang.org/x/oauth2" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/groups" "github.com/dexidp/dex/pkg/httpclient" ) const ( // read operations of the /api/v4/user endpoint scopeUser = "read_user" // used to retrieve groups from /oauth/userinfo // https://docs.gitlab.com/ee/integration/openid_connect_provider.html scopeOpenID = "openid" ) // Config holds configuration options for gitlab logins. type Config struct { BaseURL string `json:"baseURL"` ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` RedirectURI string `json:"redirectURI"` Groups []string `json:"groups"` UseLoginAsID bool `json:"useLoginAsID"` GetGroupsPermission bool `json:"getGroupsPermission"` RootCAData []byte `json:"rootCAData,omitempty"` } type gitlabUser struct { ID int Name string Username string State string Email string IsAdmin bool } // Open returns a strategy for logging in through GitLab. func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { if c.BaseURL == "" { c.BaseURL = "https://gitlab.com" } var httpClient *http.Client if len(c.RootCAData) > 0 { var err error httpClient, err = httpclient.NewHTTPClient([]string{string(c.RootCAData)}, false) if err != nil { // Keep backward-compatible error semantics for invalid PEM input. if strings.Contains(err.Error(), "not in PEM format") { return nil, fmt.Errorf("gitlab: invalid rootCAData") } return nil, fmt.Errorf("gitlab: failed to create HTTP client: %v", err) } httpClient.Timeout = 30 * time.Second } return &gitlabConnector{ baseURL: c.BaseURL, redirectURI: c.RedirectURI, clientID: c.ClientID, clientSecret: c.ClientSecret, logger: logger.With(slog.Group("connector", "type", "gitlab", "id", id)), groups: c.Groups, useLoginAsID: c.UseLoginAsID, getGroupsPermission: c.GetGroupsPermission, httpClient: httpClient, }, nil } type connectorData struct { // Support GitLab's Access Tokens and Refresh tokens. AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` } var ( _ connector.CallbackConnector = (*gitlabConnector)(nil) _ connector.RefreshConnector = (*gitlabConnector)(nil) _ connector.TokenIdentityConnector = (*gitlabConnector)(nil) ) type gitlabConnector struct { baseURL string redirectURI string groups []string clientID string clientSecret string logger *slog.Logger httpClient *http.Client // if set to true will use the user's handle rather than their numeric id as the ID useLoginAsID bool // if set to true permissions will be added to list of groups getGroupsPermission bool } func (c *gitlabConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config { gitlabScopes := []string{scopeUser} if c.groupsRequired(scopes.Groups) { gitlabScopes = []string{scopeUser, scopeOpenID} } gitlabEndpoint := oauth2.Endpoint{AuthURL: c.baseURL + "/oauth/authorize", TokenURL: c.baseURL + "/oauth/token"} return &oauth2.Config{ ClientID: c.clientID, ClientSecret: c.clientSecret, Endpoint: gitlabEndpoint, Scopes: gitlabScopes, RedirectURL: c.redirectURI, } } func (c *gitlabConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) { if c.redirectURI != callbackURL { return "", nil, fmt.Errorf("expected callback URL %q did not match the URL in the config %q", c.redirectURI, callbackURL) } return c.oauth2Config(scopes).AuthCodeURL(state), nil, nil } type oauth2Error struct { error string errorDescription string } func (e *oauth2Error) Error() string { if e.errorDescription == "" { return e.error } return e.error + ": " + e.errorDescription } func (c *gitlabConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, &oauth2Error{errType, q.Get("error_description")} } oauth2Config := c.oauth2Config(s) ctx := r.Context() if c.httpClient != nil { ctx = context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient) } token, err := oauth2Config.Exchange(ctx, q.Get("code")) if err != nil { return identity, fmt.Errorf("gitlab: failed to get token: %v", err) } return c.identity(ctx, s, token) } func (c *gitlabConnector) identity(ctx context.Context, s connector.Scopes, token *oauth2.Token) (identity connector.Identity, err error) { oauth2Config := c.oauth2Config(s) client := oauth2Config.Client(ctx, token) user, err := c.user(ctx, client) if err != nil { return identity, fmt.Errorf("gitlab: get user: %v", err) } username := user.Name if username == "" { username = user.Email } identity = connector.Identity{ UserID: strconv.Itoa(user.ID), Username: username, PreferredUsername: user.Username, Email: user.Email, EmailVerified: true, } if c.useLoginAsID { identity.UserID = user.Username } if c.groupsRequired(s.Groups) { groups, err := c.getGroups(ctx, client, s.Groups, user.Username) if err != nil { return identity, fmt.Errorf("gitlab: get groups: %v", err) } identity.Groups = groups } if s.OfflineAccess { data := connectorData{RefreshToken: token.RefreshToken, AccessToken: token.AccessToken} connData, err := json.Marshal(data) if err != nil { return identity, fmt.Errorf("gitlab: marshal connector data: %v", err) } identity.ConnectorData = connData } return identity, nil } func (c *gitlabConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) { var data connectorData if err := json.Unmarshal(ident.ConnectorData, &data); err != nil { return ident, fmt.Errorf("gitlab: unmarshal connector data: %v", err) } oauth2Config := c.oauth2Config(s) if c.httpClient != nil { ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient) } switch { case data.RefreshToken != "": { t := &oauth2.Token{ RefreshToken: data.RefreshToken, Expiry: time.Now().Add(-time.Hour), } token, err := oauth2Config.TokenSource(ctx, t).Token() if err != nil { return ident, fmt.Errorf("gitlab: failed to get refresh token: %v", err) } return c.identity(ctx, s, token) } case data.AccessToken != "": { token := &oauth2.Token{ AccessToken: data.AccessToken, } return c.identity(ctx, s, token) } default: return ident, errors.New("no refresh or access token found") } } // TokenIdentity is used for token exchange, verifying a GitLab access token // and returning the associated user identity. This enables direct authentication // with Dex using an existing GitLab token without going through the OAuth flow. // // Note: The connector decides whether to fetch groups based on its configuration // (groups filter, getGroupsPermission), not on the scopes from the token exchange request. // The server will then decide whether to include groups in the final token based on // the requested scopes. This matches the behavior of other connectors (e.g., OIDC). func (c *gitlabConnector) TokenIdentity(ctx context.Context, _, subjectToken string) (connector.Identity, error) { if c.httpClient != nil { ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient) } token := &oauth2.Token{ AccessToken: subjectToken, TokenType: "Bearer", // GitLab tokens are typically Bearer tokens even if the type is not explicitly provided. } // For token exchange, we determine if groups should be fetched based on connector configuration. // If the connector has groups filter or getGroupsPermission enabled, we fetch groups. scopes := connector.Scopes{ // Scopes are not provided in token exchange, so we request groups every time and return only if configured. Groups: true, } return c.identity(ctx, scopes, token) } func (c *gitlabConnector) groupsRequired(groupScope bool) bool { return len(c.groups) > 0 || groupScope } // user queries the GitLab API for profile information using the provided client. The HTTP // client is expected to be constructed by the golang.org/x/oauth2 package, which inserts // a bearer token as part of the request. func (c *gitlabConnector) user(ctx context.Context, client *http.Client) (gitlabUser, error) { var u gitlabUser req, err := http.NewRequest("GET", c.baseURL+"/api/v4/user", nil) if err != nil { return u, fmt.Errorf("gitlab: new req: %v", err) } req = req.WithContext(ctx) resp, err := client.Do(req) if err != nil { return u, fmt.Errorf("gitlab: get URL %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return u, fmt.Errorf("gitlab: read body: %v", err) } return u, fmt.Errorf("%s: %s", resp.Status, body) } if err := json.NewDecoder(resp.Body).Decode(&u); err != nil { return u, fmt.Errorf("failed to decode response: %v", err) } return u, nil } type userInfo struct { Groups []string `json:"groups"` OwnerPermission []string `json:"https://gitlab.org/claims/groups/owner"` MaintainerPermission []string `json:"https://gitlab.org/claims/groups/maintainer"` DeveloperPermission []string `json:"https://gitlab.org/claims/groups/developer"` } // userGroups queries the GitLab API for group membership. // // The HTTP passed client is expected to be constructed by the golang.org/x/oauth2 package, // which inserts a bearer token as part of the request. func (c *gitlabConnector) userGroups(ctx context.Context, client *http.Client) ([]string, error) { req, err := http.NewRequest("GET", c.baseURL+"/oauth/userinfo", nil) if err != nil { return nil, fmt.Errorf("gitlab: new req: %v", err) } req = req.WithContext(ctx) resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("gitlab: get URL %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("gitlab: read body: %v", err) } return nil, fmt.Errorf("%s: %s", resp.Status, body) } var u userInfo if err := json.NewDecoder(resp.Body).Decode(&u); err != nil { return nil, fmt.Errorf("failed to decode response: %v", err) } if c.getGroupsPermission { groups := c.setGroupsPermission(u) return groups, nil } return u.Groups, nil } func (c *gitlabConnector) setGroupsPermission(u userInfo) []string { groups := u.Groups L1: for _, g := range groups { for _, op := range u.OwnerPermission { if g == op { groups = append(groups, fmt.Sprintf("%s:owner", g)) continue L1 } if len(g) > len(op) { if g[0:len(op)] == op && string(g[len(op)]) == "/" { groups = append(groups, fmt.Sprintf("%s:owner", g)) continue L1 } } } for _, mp := range u.MaintainerPermission { if g == mp { groups = append(groups, fmt.Sprintf("%s:maintainer", g)) continue L1 } if len(g) > len(mp) { if g[0:len(mp)] == mp && string(g[len(mp)]) == "/" { groups = append(groups, fmt.Sprintf("%s:maintainer", g)) continue L1 } } } for _, dp := range u.DeveloperPermission { if g == dp { groups = append(groups, fmt.Sprintf("%s:developer", g)) continue L1 } if len(g) > len(dp) { if g[0:len(dp)] == dp && string(g[len(dp)]) == "/" { groups = append(groups, fmt.Sprintf("%s:developer", g)) continue L1 } } } } return groups } func (c *gitlabConnector) getGroups(ctx context.Context, client *http.Client, groupScope bool, userLogin string) ([]string, error) { gitlabGroups, err := c.userGroups(ctx, client) if err != nil { return nil, err } if len(c.groups) > 0 { filteredGroups := groups.Filter(gitlabGroups, c.groups) if len(filteredGroups) == 0 { return nil, fmt.Errorf("gitlab: user %q is not in any of the required groups", userLogin) } return filteredGroups, nil } else if groupScope { return gitlabGroups, nil } return nil, nil } ================================================ FILE: connector/gitlab/gitlab_test.go ================================================ package gitlab import ( "context" "crypto/tls" "encoding/json" "fmt" "io" "log/slog" "net/http" "net/http/httptest" "net/url" "os" "reflect" "strings" "testing" "time" "github.com/dexidp/dex/connector" ) func readValidRootCAData(t *testing.T) []byte { t.Helper() b, err := os.ReadFile("testdata/rootCA.pem") if err != nil { t.Fatalf("failed to read rootCA.pem testdata: %v", err) } return b } func newLocalHTTPSTestServer(t *testing.T, handler http.Handler) *httptest.Server { t.Helper() ts := httptest.NewUnstartedServer(handler) cert, err := tls.LoadX509KeyPair("testdata/server.crt", "testdata/server.key") if err != nil { t.Fatalf("failed to load TLS test cert/key: %v", err) } ts.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} ts.StartTLS() return ts } func TestOpenWithRootCADataCreatesHTTPClient(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) cfg := &Config{ RootCAData: readValidRootCAData(t), } conn, err := cfg.Open("test", logger) if err != nil { t.Fatalf("expected nil error, got %v", err) } gc, ok := conn.(*gitlabConnector) if !ok { t.Fatalf("expected *gitlabConnector, got %T", conn) } if gc.httpClient == nil { t.Fatalf("expected httpClient to be non-nil") } if gc.httpClient.Timeout != 30*time.Second { t.Fatalf("expected httpClient timeout %v, got %v", 30*time.Second, gc.httpClient.Timeout) } tr, ok := gc.httpClient.Transport.(*http.Transport) if !ok { t.Fatalf("expected transport to be *http.Transport, got %T", gc.httpClient.Transport) } // ProxyFromEnvironment is expected to be enabled (non-nil proxy func). if tr.Proxy == nil { t.Fatalf("expected transport.Proxy to be set (ProxyFromEnvironment)") } if tr.TLSClientConfig == nil || tr.TLSClientConfig.RootCAs == nil { t.Fatalf("expected transport TLS root CAs to be configured") } } func TestOpenWithInvalidRootCADataReturnsError(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) cfg := &Config{ RootCAData: []byte("not a pem"), } _, err := cfg.Open("test", logger) if err == nil { t.Fatalf("expected error, got nil") } if !strings.Contains(err.Error(), "invalid rootCAData") { t.Fatalf("expected error to contain %q, got %q", "invalid rootCAData", err.Error()) } } func TestHandleCallbackCustomRootCADataEnablesTLSRequests(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) ts := newLocalHTTPSTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") switch r.URL.Path { case "/oauth/token": // oauth2.Exchange expects an access token in response. fmt.Fprint(w, `{"access_token":"abc","token_type":"bearer","expires_in":30}`) case "/api/v4/user": json.NewEncoder(w).Encode(gitlabUser{Email: "some@email.com", ID: 12345678}) default: http.NotFound(w, r) } })) defer ts.Close() cfg := &Config{ BaseURL: ts.URL, ClientID: "client-id", ClientSecret: "client-secret", RedirectURI: "https://example.invalid/callback", RootCAData: readValidRootCAData(t), } conn, err := cfg.Open("test", logger) if err != nil { t.Fatalf("Open() error: %v", err) } hostURL, err := url.Parse(ts.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String()+"?code=testcode", nil) expectNil(t, err) identity, err := conn.(connector.CallbackConnector).HandleCallback(connector.Scopes{Groups: false}, nil, req) if err != nil { t.Fatalf("HandleCallback() error: %v", err) } if identity.Email != "some@email.com" || identity.UserID != "12345678" { t.Fatalf("unexpected identity: %#v", identity) } } func TestHandleCallbackWithoutRootCADataFailsTLS(t *testing.T) { logger := slog.New(slog.NewTextHandler(io.Discard, nil)) ts := newLocalHTTPSTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") switch r.URL.Path { case "/oauth/token": fmt.Fprint(w, `{"access_token":"abc","token_type":"bearer","expires_in":30}`) case "/api/v4/user": json.NewEncoder(w).Encode(gitlabUser{Email: "some@email.com", ID: 12345678}) default: http.NotFound(w, r) } })) defer ts.Close() cfg := &Config{ BaseURL: ts.URL, ClientID: "client-id", ClientSecret: "client-secret", RedirectURI: "https://example.invalid/callback", // RootCAData intentionally omitted: should fail TLS verification against our custom server cert. } conn, err := cfg.Open("test", logger) if err != nil { t.Fatalf("Open() error: %v", err) } hostURL, err := url.Parse(ts.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String()+"?code=testcode", nil) expectNil(t, err) _, err = conn.(connector.CallbackConnector).HandleCallback(connector.Scopes{Groups: false}, nil, req) if err == nil { t.Fatalf("expected TLS error, got nil") } } func TestUserGroups(t *testing.T) { s := newTestServer(map[string]interface{}{ "/oauth/userinfo": userInfo{ Groups: []string{"team-1", "team-2"}, }, }) defer s.Close() c := gitlabConnector{baseURL: s.URL} groups, err := c.getGroups(context.Background(), newClient(), true, "joebloggs") expectNil(t, err) expectEquals(t, groups, []string{ "team-1", "team-2", }) } func TestUserGroupsWithFiltering(t *testing.T) { s := newTestServer(map[string]interface{}{ "/oauth/userinfo": userInfo{ Groups: []string{"team-1", "team-2"}, }, }) defer s.Close() c := gitlabConnector{baseURL: s.URL, groups: []string{"team-1"}} groups, err := c.getGroups(context.Background(), newClient(), true, "joebloggs") expectNil(t, err) expectEquals(t, groups, []string{ "team-1", }) } func TestUserGroupsWithoutOrgs(t *testing.T) { s := newTestServer(map[string]interface{}{ "/oauth/userinfo": userInfo{ Groups: []string{}, }, }) defer s.Close() c := gitlabConnector{baseURL: s.URL} groups, err := c.getGroups(context.Background(), newClient(), true, "joebloggs") expectNil(t, err) expectEquals(t, len(groups), 0) } // tests that the email is used as their username when they have no username set func TestUsernameIncludedInFederatedIdentity(t *testing.T) { s := newTestServer(map[string]interface{}{ "/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678}, "/oauth/token": map[string]interface{}{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", "expires_in": "30", }, "/oauth/userinfo": userInfo{ Groups: []string{"team-1"}, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) c := gitlabConnector{baseURL: s.URL, httpClient: newClient()} identity, err := c.HandleCallback(connector.Scopes{Groups: false}, nil, req) expectNil(t, err) expectEquals(t, identity.Username, "some@email.com") expectEquals(t, identity.UserID, "12345678") expectEquals(t, 0, len(identity.Groups)) c = gitlabConnector{baseURL: s.URL, httpClient: newClient()} identity, err = c.HandleCallback(connector.Scopes{Groups: true}, nil, req) expectNil(t, err) expectEquals(t, identity.Username, "some@email.com") expectEquals(t, identity.UserID, "12345678") expectEquals(t, identity.Groups, []string{"team-1"}) } func TestLoginUsedAsIDWhenConfigured(t *testing.T) { s := newTestServer(map[string]interface{}{ "/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs", Username: "joebloggs"}, "/oauth/token": map[string]interface{}{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", "expires_in": "30", }, "/oauth/userinfo": userInfo{ Groups: []string{"team-1"}, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) c := gitlabConnector{baseURL: s.URL, httpClient: newClient(), useLoginAsID: true} identity, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req) expectNil(t, err) expectEquals(t, identity.UserID, "joebloggs") expectEquals(t, identity.Username, "Joe Bloggs") } func TestLoginWithTeamWhitelisted(t *testing.T) { s := newTestServer(map[string]interface{}{ "/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs"}, "/oauth/token": map[string]interface{}{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", "expires_in": "30", }, "/oauth/userinfo": userInfo{ Groups: []string{"team-1"}, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) c := gitlabConnector{baseURL: s.URL, httpClient: newClient(), groups: []string{"team-1"}} identity, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req) expectNil(t, err) expectEquals(t, identity.UserID, "12345678") expectEquals(t, identity.Username, "Joe Bloggs") } func TestLoginWithTeamNonWhitelisted(t *testing.T) { s := newTestServer(map[string]interface{}{ "/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs", Username: "joebloggs"}, "/oauth/token": map[string]interface{}{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", "expires_in": "30", }, "/oauth/userinfo": userInfo{ Groups: []string{"team-1"}, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) c := gitlabConnector{baseURL: s.URL, httpClient: newClient(), groups: []string{"team-2"}} _, err = c.HandleCallback(connector.Scopes{Groups: true}, nil, req) expectNotNil(t, err, "HandleCallback error") expectEquals(t, err.Error(), "gitlab: get groups: gitlab: user \"joebloggs\" is not in any of the required groups") } func TestRefresh(t *testing.T) { s := newTestServer(map[string]interface{}{ "/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678}, "/oauth/token": map[string]interface{}{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", "refresh_token": "oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC", "expires_in": "30", }, "/oauth/userinfo": userInfo{ Groups: []string{"team-1"}, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) c := gitlabConnector{baseURL: s.URL, httpClient: newClient()} expectedConnectorData, err := json.Marshal(connectorData{ RefreshToken: "oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC", AccessToken: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", }) expectNil(t, err) identity, err := c.HandleCallback(connector.Scopes{OfflineAccess: true}, nil, req) expectNil(t, err) expectEquals(t, identity.Username, "some@email.com") expectEquals(t, identity.UserID, "12345678") expectEquals(t, identity.ConnectorData, expectedConnectorData) identity, err = c.Refresh(context.Background(), connector.Scopes{OfflineAccess: true}, identity) expectNil(t, err) expectEquals(t, identity.Username, "some@email.com") expectEquals(t, identity.UserID, "12345678") expectEquals(t, identity.ConnectorData, expectedConnectorData) } func TestRefreshWithEmptyConnectorData(t *testing.T) { s := newTestServer(map[string]interface{}{ "/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678}, "/oauth/token": map[string]interface{}{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", "refresh_token": "oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC", "expires_in": "30", }, "/oauth/userinfo": userInfo{ Groups: []string{"team-1"}, }, }) defer s.Close() emptyConnectorData, err := json.Marshal(connectorData{ RefreshToken: "", AccessToken: "", }) expectNil(t, err) c := gitlabConnector{baseURL: s.URL, httpClient: newClient()} emptyIdentity := connector.Identity{ConnectorData: emptyConnectorData} identity, err := c.Refresh(context.Background(), connector.Scopes{OfflineAccess: true}, emptyIdentity) expectNotNil(t, err, "Refresh error") expectEquals(t, emptyIdentity, identity) } func TestGroupsWithPermission(t *testing.T) { s := newTestServer(map[string]interface{}{ "/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs", Username: "joebloggs"}, "/oauth/token": map[string]interface{}{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", "expires_in": "30", }, "/oauth/userinfo": userInfo{ Groups: []string{"ops", "dev", "ops-test", "ops/project", "dev/project1", "dev/project2"}, OwnerPermission: []string{"ops"}, DeveloperPermission: []string{"dev"}, MaintainerPermission: []string{"dev/project1"}, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) c := gitlabConnector{baseURL: s.URL, httpClient: newClient(), getGroupsPermission: true} identity, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req) expectNil(t, err) expectEquals(t, identity.Groups, []string{ "ops", "dev", "ops-test", "ops/project", "dev/project1", "dev/project2", "ops:owner", "dev:developer", "ops/project:owner", "dev/project1:maintainer", "dev/project2:developer", }) } func newTestServer(responses map[string]interface{}) *httptest.Server { return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { response := responses[r.RequestURI] w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(response) })) } func newClient() *http.Client { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } return &http.Client{Transport: tr} } func expectNil(t *testing.T, a interface{}) { if a != nil { t.Errorf("Expected %+v to equal nil", a) } } func expectNotNil(t *testing.T, a interface{}, msg string) { if a == nil { t.Errorf("Expected %+v to not to be nil", msg) } } func expectEquals(t *testing.T, a interface{}, b interface{}) { if !reflect.DeepEqual(a, b) { t.Errorf("Expected %+v to equal %+v", a, b) } } func TestTokenIdentity(t *testing.T) { // Note: These tests verify that the connector returns groups based on its configuration. // The actual inclusion of groups in the final Dex token depends on the 'groups' scope // in the token exchange request, which is handled by the Dex server, not the connector. tests := []struct { name string userInfo userInfo groups []string getGroupsPermission bool useLoginAsID bool expectUserID string expectGroups []string }{ { name: "without groups config", expectUserID: "12345678", expectGroups: nil, }, { name: "with groups filter", userInfo: userInfo{ Groups: []string{"team-1", "team-2"}, }, groups: []string{"team-1"}, expectUserID: "12345678", expectGroups: []string{"team-1"}, }, { name: "with groups permission", userInfo: userInfo{ Groups: []string{"ops", "dev"}, OwnerPermission: []string{"ops"}, DeveloperPermission: []string{"dev"}, MaintainerPermission: []string{}, }, getGroupsPermission: true, expectUserID: "12345678", expectGroups: []string{"ops", "dev", "ops:owner", "dev:developer"}, }, { name: "with useLoginAsID", useLoginAsID: true, expectUserID: "joebloggs", expectGroups: nil, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { responses := map[string]interface{}{ "/api/v4/user": gitlabUser{ Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs", Username: "joebloggs", }, "/oauth/userinfo": tc.userInfo, } s := newTestServer(responses) defer s.Close() c := gitlabConnector{ baseURL: s.URL, httpClient: newClient(), groups: tc.groups, getGroupsPermission: tc.getGroupsPermission, useLoginAsID: tc.useLoginAsID, } accessToken := "test-access-token" ctx := context.Background() identity, err := c.TokenIdentity(ctx, "urn:ietf:params:oauth:token-type:access_token", accessToken) expectNil(t, err) expectEquals(t, identity.UserID, tc.expectUserID) expectEquals(t, identity.Username, "Joe Bloggs") expectEquals(t, identity.PreferredUsername, "joebloggs") expectEquals(t, identity.Email, "some@email.com") expectEquals(t, identity.EmailVerified, true) expectEquals(t, identity.Groups, tc.expectGroups) }) } } ================================================ FILE: connector/gitlab/testdata/rootCA.pem ================================================ -----BEGIN CERTIFICATE----- MIID1jCCAr4CCQCG4JBeSi6cDjANBgkqhkiG9w0BAQsFADCBrDELMAkGA1UEBhMC VVMxFDASBgNVBAgMC1JhbmRvbVN0YXRlMRMwEQYDVQQHDApSYW5kb21DaXR5MRsw GQYDVQQKDBJSYW5kb21Pcmdhbml6YXRpb24xHzAdBgNVBAsMFlJhbmRvbU9yZ2Fu aXphdGlvblVuaXQxIDAeBgkqhkiG9w0BCQEWEWhlbGxvQGV4YW1wbGUuY29tMRIw EAYDVQQDDAlsb2NhbGhvc3QwHhcNMjIxMDA3MjIwNjQwWhcNMzIxMDA0MjIwNjQw WjCBrDELMAkGA1UEBhMCVVMxFDASBgNVBAgMC1JhbmRvbVN0YXRlMRMwEQYDVQQH DApSYW5kb21DaXR5MRswGQYDVQQKDBJSYW5kb21Pcmdhbml6YXRpb24xHzAdBgNV BAsMFlJhbmRvbU9yZ2FuaXphdGlvblVuaXQxIDAeBgkqhkiG9w0BCQEWEWhlbGxv QGV4YW1wbGUuY29tMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDh0HlpAKMKYyxbvW70XRY2bVNiNdAFninug1P4FDAJ z8xnbFzk17FLY7zqdtGTDmPDJ8AAxIwpGv2zYWW5VMeqKWfvyuD5dSCauY1Pdmug uZbpAvoJrx1sw+TL61ByVmy8x3ccB4LLKuzil/vAzUDJQkPsfTECVUPV+yiGSDuO EEVR9X6rZUwx2expXm8Wtb/a88FbPVI09b9eb4iWfLvGD2eNAtw8w21W0X7sQ8Hq zEPqquMEL4qPnNDdtk592uHvLLrd1uH8qH7c1JyA76T7H3YeUCNEi+PnLgqtsZmX sKY62HnLt8/LAClVsN9lFYkKEjU9V+U7IN2cL6+EwtsdAgMBAAEwDQYJKoZIhvcN AQELBQADggEBAN6g0qit/3R2X+KdR0LgRXF/h4qQFgcV6cxnhRAmLIDNJlxKSHqN IE5+bxzCbkblzGfr/jNPqW0s+yaN4CyMgKNYSzkLBPE4FF+19Uv+dyYfFms3mDJ7 0rGjS5bCscThWhpaSw20LcwQcr/+X+/fGzJ01dVFK1UOjBKg4d4dMwxklbIkZqIq siRW0GMy26mgVZ/BSjeh5kEjs6h6H3cJsGl7xYT+BI7wnxHwGeT9tkBgiyT5FwaS vtdZkBpQ9q8f7FwsEm3woLHdWuOnrtUtVpY/oc6WFGdROQdGzjSk0D3kHs9YhueC GSzZKrqX+TSIgpPrLYNHX4uxlo5TAwP/5GM= -----END CERTIFICATE----- ================================================ FILE: connector/gitlab/testdata/server.crt ================================================ -----BEGIN CERTIFICATE----- MIIE5TCCA82gAwIBAgIJAMGzXwBRpkG7MA0GCSqGSIb3DQEBCwUAMIGsMQswCQYD VQQGEwJVUzEUMBIGA1UECAwLUmFuZG9tU3RhdGUxEzARBgNVBAcMClJhbmRvbUNp dHkxGzAZBgNVBAoMElJhbmRvbU9yZ2FuaXphdGlvbjEfMB0GA1UECwwWUmFuZG9t T3JnYW5pemF0aW9uVW5pdDEgMB4GCSqGSIb3DQEJARYRaGVsbG9AZXhhbXBsZS5j b20xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMjEwMDcyMjA3MDhaFw0zMjEwMDQy MjA3MDhaMIGsMQswCQYDVQQGEwJVUzEUMBIGA1UECAwLUmFuZG9tU3RhdGUxEzAR BgNVBAcMClJhbmRvbUNpdHkxGzAZBgNVBAoMElJhbmRvbU9yZ2FuaXphdGlvbjEf MB0GA1UECwwWUmFuZG9tT3JnYW5pemF0aW9uVW5pdDEgMB4GCSqGSIb3DQEJARYR aGVsbG9AZXhhbXBsZS5jb20xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAMuKdpXP87Q7Kg3iafXzvBuVIyV1K5UmMYiN koztkC5XrCzHaQRS/CoIb7/nUqmtAxx7RL0jzhZ93zBN4HY/Zcnrd9tXoPPxi0mG ZZWfFU6nN8nOkMHWzEbHVBmhxpfGtwmLcajQ4HrK1TZwJUn6GqclHQRy/gjxkiw5 KPqzfVOVlA6ht4KdKstKazQkWZ5gdWT4d8yrEy/IT4oaW05xALBMQ7YGjkzWKsSF 6ygXI7xqF9rg9jCnUsPYg4f8ut3N0c00KjsfKOOj2dF/ZyjedQ5c0u4hHmxSo3Ka 0ZTmIrMfbVXgGjxRG2HZXLpPvQKoCf/fOX8Irdr+lahFVKASxN0CAwEAAaOCAQYw ggECMIHLBgNVHSMEgcMwgcChgbKkga8wgawxCzAJBgNVBAYTAlVTMRQwEgYDVQQI DAtSYW5kb21TdGF0ZTETMBEGA1UEBwwKUmFuZG9tQ2l0eTEbMBkGA1UECgwSUmFu ZG9tT3JnYW5pemF0aW9uMR8wHQYDVQQLDBZSYW5kb21Pcmdhbml6YXRpb25Vbml0 MSAwHgYJKoZIhvcNAQkBFhFoZWxsb0BleGFtcGxlLmNvbTESMBAGA1UEAwwJbG9j YWxob3N0ggkAhuCQXkounA4wCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwGgYDVR0R BBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCWmh5ebpkm v2B1yQgarSCSSkLZ5DZSAJjrPgW2IJqCW2q2D1HworbW1Yn5jqrM9FKGnJfjCyve zBB5AOlGp+0bsZGgMRMCavgv4QhTThXUoJqqHcfEu4wHndcgrqSadxmV5aisSR4u gXnjW43o3akby+h1K40RR3vVkpzPaoC3/bgk7WVpfpPiP32E24a01gETozRb/of/ ATN3JBe0xh+e63CrPX1sago5+u3UETIoOr0fW8M/gU9GApmJiFAXwHag6j54hLCG 23EtVDwmlarG8Pj+i0yru8s22QqzAJi5E0OwR4aB8tqicLKYBVfzyLCOielIBUrK OkuFKp+VjxQX -----END CERTIFICATE----- ================================================ FILE: connector/gitlab/testdata/server.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDLinaVz/O0OyoN 4mn187wblSMldSuVJjGIjZKM7ZAuV6wsx2kEUvwqCG+/51KprQMce0S9I84Wfd8w TeB2P2XJ63fbV6Dz8YtJhmWVnxVOpzfJzpDB1sxGx1QZocaXxrcJi3Go0OB6ytU2 cCVJ+hqnJR0Ecv4I8ZIsOSj6s31TlZQOobeCnSrLSms0JFmeYHVk+HfMqxMvyE+K GltOcQCwTEO2Bo5M1irEhesoFyO8ahfa4PYwp1LD2IOH/LrdzdHNNCo7Hyjjo9nR f2co3nUOXNLuIR5sUqNymtGU5iKzH21V4Bo8URth2Vy6T70CqAn/3zl/CK3a/pWo RVSgEsTdAgMBAAECggEAU6cxu7q+54kVbKVsdThaTF/MFR4F7oPHAd9lpuQQSOuh iLngMHXGy6OyAgYZlEDWMYN8KdwoXFgZPaoUIaVGuWk8Vnq6XOgeHfbNk2PRhwT0 yc1K80/Lnx9XMj2p+EEkgxi7eu12BSGN5ZTLzo6rG50GQwjb3WMjd2d6rybL0GjC wg2arcBk3sSMYmvZOqlAsaQmtgwkJhvhVkVfEQSD3VKF7g0dh/h3LIPyM0Ff4M67 KpLMPPwzUJ/0Z4ewAP06mMKUA86R93M+dWs2eh1oBGnRkVQdhCJLXJpuGHZ6BTiB Ry0AeorHfnVXPbtpUeAq6m5/BBl6qX0ooB08BIFwAQKBgQDqJpTZS/ZzqL6Kcs14 MyFu+7DungSxQ5oK9ju7EFSosanSk4UEa/lw992kM6nsIMwgSVQgba5zKcVMeSmk AVbpznegQD1BYCwOGwbGvkJ8jbhPy+WLbbRjWT/E6AItZgUK+fyTIcNvSehcQqsT fhgWsK7ueZCmLQfVhK1AxtvY3QKBgQDeiKuo8plsH/7IxDn7KVHBOHKPC2ZPzg03 i7La6zomiRckwwPnhicRSYsjtfCCW6Ms+uzjTEItgFM+5PdrXheeku+z/sExRtZu emqPqDomixlXDRQ6RN3gnBSk4RU+ROB1u1uBLWXqRz8Gp2zJGRxhHfYt2zefBv4w /cIuPC3cAQKBgD2UsAkGJWb9tj8LOmama+CYaUwYWvuT3+uKHuNvxBQpxZQQICet jgjb53rL66Cib4z+PBXbQsoe7jjSlNUBVS5gkq2et31+IZgEG6AhYbMIQrUZ1uD4 lTybuF289vWhoynj3T2E37VhJq89CWky/HrbNOabKiPKLAlHv5kNs7wxAoGBANEJ XQbU7J2O6Iy7FyQBSlTQq3wHX1Iz4mJ9DcNrFzK/sEfOEMrZT7WDefpPm984KW3F P+S766ZGVuxLtMbcmh9RM23HLr8VJbSdtZ/AjO9L1r/Y/1lE+49TzmibLpNRq++r 0WbkuEl8J44ek6fLuMbZmDi3JeZycTCgDlnUGdgBAoGAYdliovtURZCm46t1uE3F idCLCXCccjkt1hcNGNjck/b0trHA7wOEqICIguoWDlEBTc0PDvHEq6PfKyqptGkj AgaZTMF/aZiGqlT7VRpBuzxM/uV5xzCg+i2ViaW/p3xq0z2PRljVZiEfe5aWcjiM ouTtnC3TgmcjhTgGmb48QQE= -----END PRIVATE KEY----- ================================================ FILE: connector/google/google.go ================================================ // Package google implements logging in through Google's OpenID Connect provider. package google import ( "context" "errors" "fmt" "log/slog" "net/http" "os" "strings" "time" "cloud.google.com/go/compute/metadata" "github.com/coreos/go-oidc/v3/oidc" "golang.org/x/exp/slices" "golang.org/x/oauth2" "golang.org/x/oauth2/google" admin "google.golang.org/api/admin/directory/v1" "google.golang.org/api/impersonate" "google.golang.org/api/option" "github.com/dexidp/dex/connector" pkg_groups "github.com/dexidp/dex/pkg/groups" ) const ( issuerURL = "https://accounts.google.com" wildcardDomainToAdminEmail = "*" ) // Config holds configuration options for Google logins. type Config struct { ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` RedirectURI string `json:"redirectURI"` Scopes []string `json:"scopes"` // defaults to "profile" and "email" // Optional list of whitelisted domains // If this field is nonempty, only users from a listed domain will be allowed to log in HostedDomains []string `json:"hostedDomains"` // Optional list of whitelisted groups // If this field is nonempty, only users from a listed group will be allowed to log in Groups []string `json:"groups"` // Optional path to service account json // If nonempty, and groups claim is made, will use authentication from file to // check groups with the admin directory api ServiceAccountFilePath string `json:"serviceAccountFilePath"` // Deprecated: Use DomainToAdminEmail AdminEmail string // Required if ServiceAccountFilePath // The map workspace domain to email of a GSuite super user which the service account will impersonate // when listing groups DomainToAdminEmail map[string]string // If this field is true, fetch direct group membership and transitive group membership FetchTransitiveGroupMembership bool `json:"fetchTransitiveGroupMembership"` // Optional value for the prompt parameter, defaults to consent when offline_access // scope is requested PromptType *string `json:"promptType"` } // Open returns a connector which can be used to login users through Google. func (c *Config) Open(id string, logger *slog.Logger) (conn connector.Connector, err error) { logger = logger.With(slog.Group("connector", "type", "google", "id", id)) if c.AdminEmail != "" { logger.Warn(`use "domainToAdminEmail.*" option instead of "adminEmail"`, "deprecated", true) if c.DomainToAdminEmail == nil { c.DomainToAdminEmail = make(map[string]string) } c.DomainToAdminEmail[wildcardDomainToAdminEmail] = c.AdminEmail } ctx, cancel := context.WithCancel(context.Background()) provider, err := oidc.NewProvider(ctx, issuerURL) if err != nil { cancel() return nil, fmt.Errorf("failed to get provider: %v", err) } scopes := []string{oidc.ScopeOpenID} if len(c.Scopes) > 0 { scopes = append(scopes, c.Scopes...) } else { scopes = append(scopes, "profile", "email") } adminSrv := make(map[string]*admin.Service) // We know impersonation is required when using a service account credential // TODO: or is it? if len(c.DomainToAdminEmail) == 0 && c.ServiceAccountFilePath != "" { cancel() return nil, fmt.Errorf("directory service requires the domainToAdminEmail option to be configured") } if (len(c.DomainToAdminEmail) > 0) || slices.Contains(scopes, "groups") { for domain, adminEmail := range c.DomainToAdminEmail { srv, err := createDirectoryService(c.ServiceAccountFilePath, adminEmail, logger) if err != nil { cancel() return nil, fmt.Errorf("could not create directory service: %v", err) } adminSrv[domain] = srv } } promptType := "consent" if c.PromptType != nil { promptType = *c.PromptType } clientID := c.ClientID return &googleConnector{ redirectURI: c.RedirectURI, oauth2Config: &oauth2.Config{ ClientID: clientID, ClientSecret: c.ClientSecret, Endpoint: provider.Endpoint(), Scopes: scopes, RedirectURL: c.RedirectURI, }, verifier: provider.Verifier( &oidc.Config{ClientID: clientID}, ), logger: logger, cancel: cancel, hostedDomains: c.HostedDomains, groups: c.Groups, serviceAccountFilePath: c.ServiceAccountFilePath, domainToAdminEmail: c.DomainToAdminEmail, fetchTransitiveGroupMembership: c.FetchTransitiveGroupMembership, adminSrv: adminSrv, promptType: promptType, }, nil } var ( _ connector.CallbackConnector = (*googleConnector)(nil) _ connector.RefreshConnector = (*googleConnector)(nil) ) type googleConnector struct { redirectURI string oauth2Config *oauth2.Config verifier *oidc.IDTokenVerifier cancel context.CancelFunc logger *slog.Logger hostedDomains []string groups []string serviceAccountFilePath string domainToAdminEmail map[string]string fetchTransitiveGroupMembership bool adminSrv map[string]*admin.Service promptType string } func (c *googleConnector) Close() error { c.cancel() return nil } func (c *googleConnector) LoginURL(s connector.Scopes, callbackURL, state string) (string, []byte, error) { if c.redirectURI != callbackURL { return "", nil, fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI) } var opts []oauth2.AuthCodeOption if len(c.hostedDomains) > 0 { preferredDomain := c.hostedDomains[0] if len(c.hostedDomains) > 1 { preferredDomain = "*" } opts = append(opts, oauth2.SetAuthURLParam("hd", preferredDomain)) } if s.OfflineAccess { opts = append(opts, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", c.promptType)) } return c.oauth2Config.AuthCodeURL(state, opts...), nil, nil } type oauth2Error struct { error string errorDescription string } func (e *oauth2Error) Error() string { if e.errorDescription == "" { return e.error } return e.error + ": " + e.errorDescription } func (c *googleConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, &oauth2Error{errType, q.Get("error_description")} } token, err := c.oauth2Config.Exchange(r.Context(), q.Get("code")) if err != nil { return identity, fmt.Errorf("google: failed to get token: %v", err) } return c.createIdentity(r.Context(), identity, s, token) } func (c *googleConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { t := &oauth2.Token{ RefreshToken: string(identity.ConnectorData), Expiry: time.Now().Add(-time.Hour), } token, err := c.oauth2Config.TokenSource(ctx, t).Token() if err != nil { return identity, fmt.Errorf("google: failed to get token: %v", err) } return c.createIdentity(ctx, identity, s, token) } func (c *googleConnector) createIdentity(ctx context.Context, identity connector.Identity, s connector.Scopes, token *oauth2.Token) (connector.Identity, error) { rawIDToken, ok := token.Extra("id_token").(string) if !ok { return identity, errors.New("google: no id_token in token response") } idToken, err := c.verifier.Verify(ctx, rawIDToken) if err != nil { return identity, fmt.Errorf("google: failed to verify ID Token: %v", err) } var claims struct { Username string `json:"name"` Email string `json:"email"` EmailVerified bool `json:"email_verified"` HostedDomain string `json:"hd"` } if err := idToken.Claims(&claims); err != nil { return identity, fmt.Errorf("oidc: failed to decode claims: %v", err) } if len(c.hostedDomains) > 0 { found := false for _, domain := range c.hostedDomains { if claims.HostedDomain == domain { found = true break } } if !found { return identity, fmt.Errorf("oidc: unexpected hd claim %v", claims.HostedDomain) } } var groups []string if s.Groups && len(c.adminSrv) > 0 { checkedGroups := make(map[string]struct{}) groups, err = c.getGroups(claims.Email, c.fetchTransitiveGroupMembership, checkedGroups) if err != nil { return identity, fmt.Errorf("google: could not retrieve groups: %v", err) } if len(c.groups) > 0 { groups = pkg_groups.Filter(groups, c.groups) if len(groups) == 0 { return identity, fmt.Errorf("google: user %q is not in any of the required groups", claims.Username) } } } identity = connector.Identity{ UserID: idToken.Subject, Username: claims.Username, Email: claims.Email, EmailVerified: claims.EmailVerified, ConnectorData: []byte(token.RefreshToken), Groups: groups, } return identity, nil } // getGroups creates a connection to the admin directory service and lists // all groups the user is a member of func (c *googleConnector) getGroups(email string, fetchTransitiveGroupMembership bool, checkedGroups map[string]struct{}) ([]string, error) { var userGroups []string var err error groupsList := &admin.Groups{} domain := c.extractDomainFromEmail(email) adminSrv, err := c.findAdminService(domain) if err != nil { return nil, err } for { groupsList, err = adminSrv.Groups.List(). UserKey(email).PageToken(groupsList.NextPageToken).Do() if err != nil { return nil, fmt.Errorf("could not list groups: %v", err) } for _, group := range groupsList.Groups { if _, exists := checkedGroups[group.Email]; exists { continue } checkedGroups[group.Email] = struct{}{} // TODO (joelspeed): Make desired group key configurable userGroups = append(userGroups, group.Email) if !fetchTransitiveGroupMembership { continue } // getGroups takes a user's email/alias as well as a group's email/alias transitiveGroups, err := c.getGroups(group.Email, fetchTransitiveGroupMembership, checkedGroups) if err != nil { return nil, fmt.Errorf("could not list transitive groups: %v", err) } userGroups = append(userGroups, transitiveGroups...) } if groupsList.NextPageToken == "" { break } } return userGroups, nil } func (c *googleConnector) findAdminService(domain string) (*admin.Service, error) { adminSrv, ok := c.adminSrv[domain] if !ok { adminSrv, ok = c.adminSrv[wildcardDomainToAdminEmail] c.logger.Debug("using wildcard admin email to fetch groups", "admin_email", c.domainToAdminEmail[wildcardDomainToAdminEmail]) } if !ok { return nil, fmt.Errorf("unable to find super admin email, domainToAdminEmail for domain: %s not set, %s is also empty", domain, wildcardDomainToAdminEmail) } return adminSrv, nil } // extracts the domain name from an email input. If the email is valid, it returns the domain name after the "@" symbol. // However, in the case of a broken or invalid email, it returns a wildcard symbol. func (c *googleConnector) extractDomainFromEmail(email string) string { at := strings.LastIndex(email, "@") if at >= 0 { _, domain := email[:at], email[at+1:] return domain } return wildcardDomainToAdminEmail } // getCredentialsFromFilePath reads and returns the service account credentials from the file at the provided path. // If an error occurs during the read, it is returned. func getCredentialsFromFilePath(serviceAccountFilePath string) ([]byte, error) { jsonCredentials, err := os.ReadFile(serviceAccountFilePath) if err != nil { return nil, fmt.Errorf("error reading credentials from file: %v", err) } return jsonCredentials, nil } // getCredentialsFromDefault retrieves the application's default credentials. // If the default credential is empty, it attempts to create a new service with metadata credentials. // If successful, it returns the service and nil error. // If unsuccessful, it returns the error and a nil service. func getCredentialsFromDefault(ctx context.Context, email string, logger *slog.Logger) ([]byte, *admin.Service, error) { credential, err := google.FindDefaultCredentials(ctx) if err != nil { return nil, nil, fmt.Errorf("failed to fetch application default credentials: %w", err) } if credential.JSON == nil { logger.Info("JSON is empty, using flow for GCE") service, err := createServiceWithMetadataServer(ctx, email, logger) if err != nil { return nil, nil, err } return nil, service, nil } return credential.JSON, nil, nil } // createServiceWithMetadataServer creates a new service using metadata server. // If an error occurs during the process, it is returned along with a nil service. func createServiceWithMetadataServer(ctx context.Context, adminEmail string, logger *slog.Logger) (*admin.Service, error) { serviceAccountEmail, err := metadata.EmailWithContext(ctx, "default") logger.Info("discovered serviceAccountEmail", "email", serviceAccountEmail) if err != nil { return nil, fmt.Errorf("unable to get service account email from metadata server: %v", err) } config := impersonate.CredentialsConfig{ TargetPrincipal: serviceAccountEmail, Scopes: []string{admin.AdminDirectoryGroupReadonlyScope}, Lifetime: 0, Subject: adminEmail, } tokenSource, err := impersonate.CredentialsTokenSource(ctx, config) if err != nil { return nil, fmt.Errorf("unable to impersonate with %s, error: %v", adminEmail, err) } return admin.NewService(ctx, option.WithHTTPClient(oauth2.NewClient(ctx, tokenSource))) } // createDirectoryService sets up super user impersonation and creates an admin client for calling // the google admin api. If no serviceAccountFilePath is defined, the application default credential // is used. func createDirectoryService(serviceAccountFilePath, email string, logger *slog.Logger) (service *admin.Service, err error) { var jsonCredentials []byte ctx := context.Background() if serviceAccountFilePath == "" { logger.Warn("the application default credential is used since the service account file path is not used") jsonCredentials, service, err = getCredentialsFromDefault(ctx, email, logger) if err != nil { return } if service != nil { return } } else { jsonCredentials, err = getCredentialsFromFilePath(serviceAccountFilePath) if err != nil { return } } config, err := google.JWTConfigFromJSON(jsonCredentials, admin.AdminDirectoryGroupReadonlyScope) if err != nil { return nil, fmt.Errorf("unable to parse client secret file to config: %v", err) } // Only attempt impersonation when there is a user configured if email != "" { config.Subject = email } return admin.NewService(ctx, option.WithHTTPClient(config.Client(ctx))) } ================================================ FILE: connector/google/google_test.go ================================================ package google import ( "context" "encoding/json" "fmt" "log/slog" "net/http" "net/http/httptest" "net/url" "os" "strings" "testing" "github.com/stretchr/testify/assert" admin "google.golang.org/api/admin/directory/v1" "google.golang.org/api/option" "github.com/dexidp/dex/connector" ) var ( // groups_0 // ┌───────┤ // groups_2 groups_1 // │ ├────────┐ // └── user_1 user_2 testGroups = map[string][]*admin.Group{ "user_1@dexidp.com": {{Email: "groups_2@dexidp.com"}, {Email: "groups_1@dexidp.com"}}, "user_2@dexidp.com": {{Email: "groups_1@dexidp.com"}}, "groups_1@dexidp.com": {{Email: "groups_0@dexidp.com"}}, "groups_2@dexidp.com": {{Email: "groups_0@dexidp.com"}}, "groups_0@dexidp.com": {}, } callCounter = make(map[string]int) ) func testSetup() *httptest.Server { mux := http.NewServeMux() mux.HandleFunc("/admin/directory/v1/groups/", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") userKey := r.URL.Query().Get("userKey") if groups, ok := testGroups[userKey]; ok { json.NewEncoder(w).Encode(admin.Groups{Groups: groups}) callCounter[userKey]++ } }) return httptest.NewServer(mux) } func newConnector(config *Config) (*googleConnector, error) { log := slog.New(slog.DiscardHandler) conn, err := config.Open("id", log) if err != nil { return nil, err } googleConn, ok := conn.(*googleConnector) if !ok { return nil, fmt.Errorf("failed to convert to googleConnector") } return googleConn, nil } func tempServiceAccountKey() (string, error) { fd, err := os.CreateTemp("", "google_service_account_key") if err != nil { return "", err } defer fd.Close() err = json.NewEncoder(fd).Encode(map[string]string{ "type": "service_account", "project_id": "sample-project", "private_key_id": "sample-key-id", "private_key": "-----BEGIN PRIVATE KEY-----\nsample-key\n-----END PRIVATE KEY-----\n", "client_id": "sample-client-id", "client_x509_cert_url": "localhost", }) return fd.Name(), err } func TestOpen(t *testing.T) { ts := testSetup() defer ts.Close() type testCase struct { config *Config expectedErr string // string to set in GOOGLE_APPLICATION_CREDENTIALS. As local development environments can // already contain ADC, test cases will be built upon this setting this env variable adc string } serviceAccountFilePath, err := tempServiceAccountKey() assert.Nil(t, err) for name, reference := range map[string]testCase{ "missing_admin_email": { config: &Config{ ClientID: "testClient", ClientSecret: "testSecret", RedirectURI: ts.URL + "/callback", Scopes: []string{"openid", "groups"}, ServiceAccountFilePath: serviceAccountFilePath, }, expectedErr: "requires the domainToAdminEmail", }, "service_account_key_not_found": { config: &Config{ ClientID: "testClient", ClientSecret: "testSecret", RedirectURI: ts.URL + "/callback", Scopes: []string{"openid", "groups"}, DomainToAdminEmail: map[string]string{"*": "foo@bar.com"}, ServiceAccountFilePath: "not_found.json", }, expectedErr: "error reading credentials", }, "service_account_key_valid": { config: &Config{ ClientID: "testClient", ClientSecret: "testSecret", RedirectURI: ts.URL + "/callback", Scopes: []string{"openid", "groups"}, DomainToAdminEmail: map[string]string{"bar.com": "foo@bar.com"}, ServiceAccountFilePath: serviceAccountFilePath, }, expectedErr: "", }, "adc": { config: &Config{ ClientID: "testClient", ClientSecret: "testSecret", RedirectURI: ts.URL + "/callback", Scopes: []string{"openid", "groups"}, DomainToAdminEmail: map[string]string{"*": "foo@bar.com"}, }, adc: serviceAccountFilePath, expectedErr: "", }, "adc_priority": { config: &Config{ ClientID: "testClient", ClientSecret: "testSecret", RedirectURI: ts.URL + "/callback", Scopes: []string{"openid", "groups"}, DomainToAdminEmail: map[string]string{"*": "foo@bar.com"}, ServiceAccountFilePath: serviceAccountFilePath, }, adc: "/dev/null", expectedErr: "", }, } { reference := reference t.Run(name, func(t *testing.T) { assert := assert.New(t) os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", reference.adc) conn, err := newConnector(reference.config) if reference.expectedErr == "" { assert.Nil(err) assert.NotNil(conn) } else { assert.ErrorContains(err, reference.expectedErr) } }) } } func TestGetGroups(t *testing.T) { ts := testSetup() defer ts.Close() serviceAccountFilePath, err := tempServiceAccountKey() assert.Nil(t, err) os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", serviceAccountFilePath) conn, err := newConnector(&Config{ ClientID: "testClient", ClientSecret: "testSecret", RedirectURI: ts.URL + "/callback", Scopes: []string{"openid", "groups"}, DomainToAdminEmail: map[string]string{"*": "admin@dexidp.com"}, }) assert.Nil(t, err) conn.adminSrv[wildcardDomainToAdminEmail], err = admin.NewService(context.Background(), option.WithoutAuthentication(), option.WithEndpoint(ts.URL)) assert.Nil(t, err) type testCase struct { userKey string fetchTransitiveGroupMembership bool shouldErr bool expectedGroups []string } for name, testCase := range map[string]testCase{ "user1_non_transitive_lookup": { userKey: "user_1@dexidp.com", fetchTransitiveGroupMembership: false, shouldErr: false, expectedGroups: []string{"groups_1@dexidp.com", "groups_2@dexidp.com"}, }, "user1_transitive_lookup": { userKey: "user_1@dexidp.com", fetchTransitiveGroupMembership: true, shouldErr: false, expectedGroups: []string{"groups_0@dexidp.com", "groups_1@dexidp.com", "groups_2@dexidp.com"}, }, "user2_non_transitive_lookup": { userKey: "user_2@dexidp.com", fetchTransitiveGroupMembership: false, shouldErr: false, expectedGroups: []string{"groups_1@dexidp.com"}, }, "user2_transitive_lookup": { userKey: "user_2@dexidp.com", fetchTransitiveGroupMembership: true, shouldErr: false, expectedGroups: []string{"groups_0@dexidp.com", "groups_1@dexidp.com"}, }, } { testCase := testCase callCounter = map[string]int{} t.Run(name, func(t *testing.T) { assert := assert.New(t) lookup := make(map[string]struct{}) groups, err := conn.getGroups(testCase.userKey, testCase.fetchTransitiveGroupMembership, lookup) if testCase.shouldErr { assert.NotNil(err) } else { assert.Nil(err) } assert.ElementsMatch(testCase.expectedGroups, groups) t.Logf("[%s] Amount of API calls per userKey: %+v\n", t.Name(), callCounter) }) } } func TestDomainToAdminEmailConfig(t *testing.T) { ts := testSetup() defer ts.Close() serviceAccountFilePath, err := tempServiceAccountKey() assert.Nil(t, err) os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", serviceAccountFilePath) conn, err := newConnector(&Config{ ClientID: "testClient", ClientSecret: "testSecret", RedirectURI: ts.URL + "/callback", Scopes: []string{"openid", "groups"}, DomainToAdminEmail: map[string]string{"dexidp.com": "admin@dexidp.com"}, }) assert.Nil(t, err) conn.adminSrv["dexidp.com"], err = admin.NewService(context.Background(), option.WithoutAuthentication(), option.WithEndpoint(ts.URL)) assert.Nil(t, err) type testCase struct { userKey string expectedErr string } for name, testCase := range map[string]testCase{ "correct_user_request": { userKey: "user_1@dexidp.com", expectedErr: "", }, "wrong_user_request": { userKey: "user_1@foo.bar", expectedErr: "unable to find super admin email", }, "wrong_connector_response": { userKey: "user_1_foo.bar", expectedErr: "unable to find super admin email", }, } { testCase := testCase callCounter = map[string]int{} t.Run(name, func(t *testing.T) { assert := assert.New(t) lookup := make(map[string]struct{}) _, err := conn.getGroups(testCase.userKey, true, lookup) if testCase.expectedErr != "" { assert.ErrorContains(err, testCase.expectedErr) } else { assert.Nil(err) } t.Logf("[%s] Amount of API calls per userKey: %+v\n", t.Name(), callCounter) }) } } var gceMetadataFlags = map[string]bool{ "failOnEmailRequest": false, } func mockGCEMetadataServer() *httptest.Server { mux := http.NewServeMux() mux.HandleFunc("/computeMetadata/v1/instance/service-accounts/default/email", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") if gceMetadataFlags["failOnEmailRequest"] { w.WriteHeader(http.StatusBadRequest) } json.NewEncoder(w).Encode("my-service-account@example-project.iam.gserviceaccount.com") }) mux.HandleFunc("/computeMetadata/v1/instance/service-accounts/default/token", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(struct { AccessToken string `json:"access_token"` ExpiresInSec int `json:"expires_in"` TokenType string `json:"token_type"` }{ AccessToken: "my-example.token", ExpiresInSec: 3600, TokenType: "Bearer", }) }) return httptest.NewServer(mux) } func TestGCEWorkloadIdentity(t *testing.T) { ts := testSetup() defer ts.Close() metadataServer := mockGCEMetadataServer() defer metadataServer.Close() metadataServerHost := strings.Replace(metadataServer.URL, "http://", "", 1) os.Setenv("GCE_METADATA_HOST", metadataServerHost) os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "") os.Setenv("HOME", "/tmp") gceMetadataFlags["failOnEmailRequest"] = true _, err := newConnector(&Config{ ClientID: "testClient", ClientSecret: "testSecret", RedirectURI: ts.URL + "/callback", Scopes: []string{"openid", "groups"}, DomainToAdminEmail: map[string]string{"dexidp.com": "admin@dexidp.com"}, }) assert.Error(t, err) gceMetadataFlags["failOnEmailRequest"] = false conn, err := newConnector(&Config{ ClientID: "testClient", ClientSecret: "testSecret", RedirectURI: ts.URL + "/callback", Scopes: []string{"openid", "groups"}, DomainToAdminEmail: map[string]string{"dexidp.com": "admin@dexidp.com"}, }) assert.Nil(t, err) conn.adminSrv["dexidp.com"], err = admin.NewService(context.Background(), option.WithoutAuthentication(), option.WithEndpoint(ts.URL)) assert.Nil(t, err) type testCase struct { userKey string expectedErr string } for name, testCase := range map[string]testCase{ "correct_user_request": { userKey: "user_1@dexidp.com", expectedErr: "", }, "wrong_user_request": { userKey: "user_1@foo.bar", expectedErr: "unable to find super admin email", }, "wrong_connector_response": { userKey: "user_1_foo.bar", expectedErr: "unable to find super admin email", }, } { t.Run(name, func(t *testing.T) { assert := assert.New(t) lookup := make(map[string]struct{}) _, err := conn.getGroups(testCase.userKey, true, lookup) if testCase.expectedErr != "" { assert.ErrorContains(err, testCase.expectedErr) } else { assert.Nil(err) } }) } } func TestPromptTypeConfig(t *testing.T) { promptTypeLogin := "login" cases := []struct { name string promptType *string expectedPromptTypeValue string }{ { name: "prompt type is nil", promptType: nil, expectedPromptTypeValue: "consent", }, { name: "prompt type is empty", promptType: new(string), expectedPromptTypeValue: "", }, { name: "prompt type is set", promptType: &promptTypeLogin, expectedPromptTypeValue: "login", }, } ts := testSetup() defer ts.Close() serviceAccountFilePath, err := tempServiceAccountKey() assert.Nil(t, err) os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", serviceAccountFilePath) for _, test := range cases { t.Run(test.name, func(t *testing.T) { conn, err := newConnector(&Config{ ClientID: "testClient", ClientSecret: "testSecret", RedirectURI: ts.URL + "/callback", Scopes: []string{"openid", "groups", "offline_access"}, DomainToAdminEmail: map[string]string{"dexidp.com": "admin@dexidp.com"}, PromptType: test.promptType, }) assert.Nil(t, err) assert.Equal(t, test.expectedPromptTypeValue, conn.promptType) loginURL, _, err := conn.LoginURL(connector.Scopes{OfflineAccess: true}, ts.URL+"/callback", "state") assert.Nil(t, err) urlp, err := url.Parse(loginURL) assert.Nil(t, err) assert.Equal(t, test.expectedPromptTypeValue, urlp.Query().Get("prompt")) }) } } ================================================ FILE: connector/keystone/keystone.go ================================================ // Package keystone provides authentication strategy using Keystone. package keystone import ( "bytes" "context" "encoding/json" "fmt" "io" "log/slog" "net/http" "github.com/google/uuid" "github.com/dexidp/dex/connector" ) var ( _ connector.PasswordConnector = (*conn)(nil) _ connector.RefreshConnector = (*conn)(nil) ) type conn struct { Domain domainKeystone Host string AdminUsername string AdminPassword string client *http.Client Logger *slog.Logger } type userKeystone struct { Domain domainKeystone `json:"domain"` ID string `json:"id"` Name string `json:"name"` } type domainKeystone struct { ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` } // Config holds the configuration parameters for Keystone connector. // Keystone should expose API v3 // An example config: // // connectors: // type: keystone // id: keystone // name: Keystone // config: // keystoneHost: http://example:5000 // domain: default // keystoneUsername: demo // keystonePassword: DEMO_PASS type Config struct { Domain string `json:"domain"` Host string `json:"keystoneHost"` AdminUsername string `json:"keystoneUsername"` AdminPassword string `json:"keystonePassword"` } type loginRequestData struct { auth `json:"auth"` } type auth struct { Identity identity `json:"identity"` } type identity struct { Methods []string `json:"methods"` Password password `json:"password"` } type password struct { User user `json:"user"` } type user struct { Name string `json:"name"` Domain domainKeystone `json:"domain"` Password string `json:"password"` } type token struct { User userKeystone `json:"user"` } type tokenResponse struct { Token token `json:"token"` } type group struct { ID string `json:"id"` Name string `json:"name"` } type groupsResponse struct { Groups []group `json:"groups"` } type userResponse struct { User struct { Name string `json:"name"` Email string `json:"email"` ID string `json:"id"` } `json:"user"` } // Open returns an authentication strategy using Keystone. func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { _, err := uuid.Parse(c.Domain) var domain domainKeystone // check if the supplied domain is a UUID or the special "default" value // which is treated as an ID and not a name if err == nil || c.Domain == "default" { domain = domainKeystone{ ID: c.Domain, } } else { domain = domainKeystone{ Name: c.Domain, } } return &conn{ Domain: domain, Host: c.Host, AdminUsername: c.AdminUsername, AdminPassword: c.AdminPassword, Logger: logger.With(slog.Group("connector", "type", "keystone", "id", id)), client: http.DefaultClient, }, nil } func (p *conn) Close() error { return nil } func (p *conn) Login(ctx context.Context, scopes connector.Scopes, username, password string) (identity connector.Identity, validPassword bool, err error) { resp, err := p.getTokenResponse(ctx, username, password) if err != nil { return identity, false, fmt.Errorf("keystone: error %v", err) } if resp.StatusCode/100 != 2 { return identity, false, fmt.Errorf("keystone login: error %v", resp.StatusCode) } if resp.StatusCode != 201 { return identity, false, nil } token := resp.Header.Get("X-Subject-Token") data, err := io.ReadAll(resp.Body) if err != nil { return identity, false, err } defer resp.Body.Close() tokenResp := new(tokenResponse) err = json.Unmarshal(data, &tokenResp) if err != nil { return identity, false, fmt.Errorf("keystone: invalid token response: %v", err) } if scopes.Groups { groups, err := p.getUserGroups(ctx, tokenResp.Token.User.ID, token) if err != nil { return identity, false, err } identity.Groups = groups } identity.Username = username identity.UserID = tokenResp.Token.User.ID user, err := p.getUser(ctx, tokenResp.Token.User.ID, token) if err != nil { return identity, false, err } if user.User.Email != "" { identity.Email = user.User.Email identity.EmailVerified = true } return identity, true, nil } func (p *conn) Prompt() string { return "username" } func (p *conn) Refresh( ctx context.Context, scopes connector.Scopes, identity connector.Identity, ) (connector.Identity, error) { token, err := p.getAdminToken(ctx) if err != nil { return identity, fmt.Errorf("keystone: failed to obtain admin token: %v", err) } ok, err := p.checkIfUserExists(ctx, identity.UserID, token) if err != nil { return identity, err } if !ok { return identity, fmt.Errorf("keystone: user %q does not exist", identity.UserID) } if scopes.Groups { groups, err := p.getUserGroups(ctx, identity.UserID, token) if err != nil { return identity, err } identity.Groups = groups } return identity, nil } func (p *conn) getTokenResponse(ctx context.Context, username, pass string) (response *http.Response, err error) { jsonData := loginRequestData{ auth: auth{ Identity: identity{ Methods: []string{"password"}, Password: password{ User: user{ Name: username, Domain: p.Domain, Password: pass, }, }, }, }, } jsonValue, err := json.Marshal(jsonData) if err != nil { return nil, err } // https://developer.openstack.org/api-ref/identity/v3/#password-authentication-with-unscoped-authorization authTokenURL := p.Host + "/v3/auth/tokens/" req, err := http.NewRequest("POST", authTokenURL, bytes.NewBuffer(jsonValue)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") req = req.WithContext(ctx) return p.client.Do(req) } func (p *conn) getAdminToken(ctx context.Context) (string, error) { resp, err := p.getTokenResponse(ctx, p.AdminUsername, p.AdminPassword) if err != nil { return "", err } defer resp.Body.Close() token := resp.Header.Get("X-Subject-Token") return token, nil } func (p *conn) checkIfUserExists(ctx context.Context, userID string, token string) (bool, error) { user, err := p.getUser(ctx, userID, token) return user != nil, err } func (p *conn) getUser(ctx context.Context, userID string, token string) (*userResponse, error) { // https://developer.openstack.org/api-ref/identity/v3/#show-user-details userURL := p.Host + "/v3/users/" + userID req, err := http.NewRequest("GET", userURL, nil) if err != nil { return nil, err } req.Header.Set("X-Auth-Token", token) req = req.WithContext(ctx) resp, err := p.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, err } data, err := io.ReadAll(resp.Body) if err != nil { return nil, err } user := userResponse{} err = json.Unmarshal(data, &user) if err != nil { return nil, err } return &user, nil } func (p *conn) getUserGroups(ctx context.Context, userID string, token string) ([]string, error) { // https://developer.openstack.org/api-ref/identity/v3/#list-groups-to-which-a-user-belongs groupsURL := p.Host + "/v3/users/" + userID + "/groups" req, err := http.NewRequest("GET", groupsURL, nil) if err != nil { return nil, err } req.Header.Set("X-Auth-Token", token) req = req.WithContext(ctx) resp, err := p.client.Do(req) if err != nil { p.Logger.Error("error while fetching user groups", "user_id", userID, "err", err) return nil, err } data, err := io.ReadAll(resp.Body) if err != nil { return nil, err } defer resp.Body.Close() groupsResp := new(groupsResponse) err = json.Unmarshal(data, &groupsResp) if err != nil { return nil, err } groups := make([]string, len(groupsResp.Groups)) for i, group := range groupsResp.Groups { groups[i] = group.Name } return groups, nil } ================================================ FILE: connector/keystone/keystone_test.go ================================================ package keystone import ( "bytes" "context" "encoding/json" "io" "net/http" "os" "reflect" "strings" "testing" "github.com/dexidp/dex/connector" ) const ( invalidPass = "WRONG_PASS" testUser = "test_user" testPass = "test_pass" testEmail = "test@example.com" testGroup = "test_group" testDomainAltName = "altdomain" testDomainID = "default" testDomainName = "Default" ) var ( keystoneURL = "" keystoneAdminURL = "" adminUser = "" adminPass = "" authTokenURL = "" usersURL = "" groupsURL = "" domainsURL = "" ) type userReq struct { Name string `json:"name"` Email string `json:"email"` Enabled bool `json:"enabled"` Password string `json:"password"` Roles []string `json:"roles"` DomainID string `json:"domain_id,omitempty"` } type domainResponse struct { Domain domainKeystone `json:"domain"` } type domainsResponse struct { Domains []domainKeystone `json:"domains"` } type groupResponse struct { Group struct { ID string `json:"id"` } `json:"group"` } func getAdminToken(t *testing.T, adminName, adminPass string) (token, id string) { t.Helper() jsonData := loginRequestData{ auth: auth{ Identity: identity{ Methods: []string{"password"}, Password: password{ User: user{ Name: adminName, Domain: domainKeystone{ID: testDomainID}, Password: adminPass, }, }, }, }, } body, err := json.Marshal(jsonData) if err != nil { t.Fatal(err) } req, err := http.NewRequest("POST", authTokenURL, bytes.NewBuffer(body)) if err != nil { t.Fatalf("keystone: failed to obtain admin token: %v\n", err) } req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } token = resp.Header.Get("X-Subject-Token") data, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } defer resp.Body.Close() tokenResp := new(tokenResponse) err = json.Unmarshal(data, &tokenResp) if err != nil { t.Fatal(err) } return token, tokenResp.Token.User.ID } func getOrCreateDomain(t *testing.T, token, domainName string) string { t.Helper() domainSearchURL := domainsURL + "?name=" + domainName reqGet, err := http.NewRequest("GET", domainSearchURL, nil) if err != nil { t.Fatal(err) } reqGet.Header.Set("X-Auth-Token", token) reqGet.Header.Add("Content-Type", "application/json") respGet, err := http.DefaultClient.Do(reqGet) if err != nil { t.Fatal(err) } dataGet, err := io.ReadAll(respGet.Body) if err != nil { t.Fatal(err) } defer respGet.Body.Close() domainsResp := new(domainsResponse) err = json.Unmarshal(dataGet, &domainsResp) if err != nil { t.Fatal(err) } if len(domainsResp.Domains) >= 1 { return domainsResp.Domains[0].ID } createDomainData := map[string]interface{}{ "domain": map[string]interface{}{ "name": domainName, "enabled": true, }, } body, err := json.Marshal(createDomainData) if err != nil { t.Fatal(err) } req, err := http.NewRequest("POST", domainsURL, bytes.NewBuffer(body)) if err != nil { t.Fatal(err) } req.Header.Set("X-Auth-Token", token) req.Header.Add("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } if resp.StatusCode != 201 { t.Fatalf("failed to create domain %s", domainName) } data, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } defer resp.Body.Close() domainResp := new(domainResponse) err = json.Unmarshal(data, &domainResp) if err != nil { t.Fatal(err) } return domainResp.Domain.ID } func createUser(t *testing.T, token, domainID, userName, userEmail, userPass string) string { t.Helper() createUserData := map[string]interface{}{ "user": userReq{ DomainID: domainID, Name: userName, Email: userEmail, Enabled: true, Password: userPass, Roles: []string{"admin"}, }, } body, err := json.Marshal(createUserData) if err != nil { t.Fatal(err) } req, err := http.NewRequest("POST", usersURL, bytes.NewBuffer(body)) if err != nil { t.Fatal(err) } req.Header.Set("X-Auth-Token", token) req.Header.Add("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } data, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } defer resp.Body.Close() userResp := new(userResponse) err = json.Unmarshal(data, &userResp) if err != nil { t.Fatal(err) } return userResp.User.ID } // delete group or user func deleteResource(t *testing.T, token, id, uri string) { t.Helper() deleteURI := uri + id req, err := http.NewRequest("DELETE", deleteURI, nil) if err != nil { t.Fatalf("error: %v", err) } req.Header.Set("X-Auth-Token", token) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("error: %v", err) } defer resp.Body.Close() } func createGroup(t *testing.T, token, description, name string) string { t.Helper() createGroupData := map[string]interface{}{ "group": map[string]interface{}{ "name": name, "description": description, }, } body, err := json.Marshal(createGroupData) if err != nil { t.Fatal(err) } req, err := http.NewRequest("POST", groupsURL, bytes.NewBuffer(body)) if err != nil { t.Fatal(err) } req.Header.Set("X-Auth-Token", token) req.Header.Add("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } data, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } defer resp.Body.Close() groupResp := new(groupResponse) err = json.Unmarshal(data, &groupResp) if err != nil { t.Fatal(err) } return groupResp.Group.ID } func addUserToGroup(t *testing.T, token, groupID, userID string) error { t.Helper() uri := groupsURL + groupID + "/users/" + userID req, err := http.NewRequest("PUT", uri, nil) if err != nil { return err } req.Header.Set("X-Auth-Token", token) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("error: %v", err) } defer resp.Body.Close() return nil } func TestIncorrectCredentialsLogin(t *testing.T) { setupVariables(t) c := conn{ client: http.DefaultClient, Host: keystoneURL, Domain: domainKeystone{ID: testDomainID}, AdminUsername: adminUser, AdminPassword: adminPass, } s := connector.Scopes{OfflineAccess: true, Groups: true} _, validPW, err := c.Login(context.Background(), s, adminUser, invalidPass) if validPW { t.Fatal("Incorrect password check") } if err == nil { t.Fatal("Error should be returned when invalid password is provided") } if !strings.Contains(err.Error(), "401") { t.Fatal("Unrecognized error, expecting 401") } } func TestValidUserLogin(t *testing.T) { setupVariables(t) token, _ := getAdminToken(t, adminUser, adminPass) type tUser struct { createDomain bool domain domainKeystone username string email string password string } type expect struct { username string email string verifiedEmail bool } tests := []struct { name string input tUser expected expect }{ { name: "test with email address", input: tUser{ createDomain: false, domain: domainKeystone{ID: testDomainID}, username: testUser, email: testEmail, password: testPass, }, expected: expect{ username: testUser, email: testEmail, verifiedEmail: true, }, }, { name: "test without email address", input: tUser{ createDomain: false, domain: domainKeystone{ID: testDomainID}, username: testUser, email: "", password: testPass, }, expected: expect{ username: testUser, email: "", verifiedEmail: false, }, }, { name: "test with default domain Name", input: tUser{ createDomain: false, domain: domainKeystone{Name: testDomainName}, username: testUser, email: testEmail, password: testPass, }, expected: expect{ username: testUser, email: testEmail, verifiedEmail: true, }, }, { name: "test with custom domain Name", input: tUser{ createDomain: true, domain: domainKeystone{Name: testDomainAltName}, username: testUser, email: testEmail, password: testPass, }, expected: expect{ username: testUser, email: testEmail, verifiedEmail: true, }, }, { name: "test with custom domain ID", input: tUser{ createDomain: true, domain: domainKeystone{}, username: testUser, email: testEmail, password: testPass, }, expected: expect{ username: testUser, email: testEmail, verifiedEmail: true, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { domainID := "" if tt.input.createDomain == true { domainID = getOrCreateDomain(t, token, testDomainAltName) t.Logf("getOrCreateDomain ID: %s\n", domainID) // if there was nothing set then use the dynamically generated domain ID if tt.input.domain.ID == "" && tt.input.domain.Name == "" { tt.input.domain.ID = domainID } } userID := createUser(t, token, domainID, tt.input.username, tt.input.email, tt.input.password) defer deleteResource(t, token, userID, usersURL) c := conn{ client: http.DefaultClient, Host: keystoneURL, Domain: tt.input.domain, AdminUsername: adminUser, AdminPassword: adminPass, } s := connector.Scopes{OfflineAccess: true, Groups: true} identity, validPW, err := c.Login(context.Background(), s, tt.input.username, tt.input.password) if err != nil { t.Fatalf("Login failed for user %s: %v", tt.input.username, err.Error()) } t.Log(identity) if identity.Username != tt.expected.username { t.Fatalf("Invalid user. Got: %v. Wanted: %v", identity.Username, tt.expected.username) } if identity.UserID == "" { t.Fatalf("Didn't get any UserID back") } if identity.Email != tt.expected.email { t.Fatalf("Invalid email. Got: %v. Wanted: %v", identity.Email, tt.expected.email) } if identity.EmailVerified != tt.expected.verifiedEmail { t.Fatalf("Invalid verifiedEmail. Got: %v. Wanted: %v", identity.EmailVerified, tt.expected.verifiedEmail) } if !validPW { t.Fatal("Valid password was not accepted") } }) } } func TestUseRefreshToken(t *testing.T) { setupVariables(t) token, adminID := getAdminToken(t, adminUser, adminPass) groupID := createGroup(t, token, "Test group description", testGroup) addUserToGroup(t, token, groupID, adminID) defer deleteResource(t, token, groupID, groupsURL) c := conn{ client: http.DefaultClient, Host: keystoneURL, Domain: domainKeystone{ID: testDomainID}, AdminUsername: adminUser, AdminPassword: adminPass, } s := connector.Scopes{OfflineAccess: true, Groups: true} identityLogin, _, err := c.Login(context.Background(), s, adminUser, adminPass) if err != nil { t.Fatal(err.Error()) } identityRefresh, err := c.Refresh(context.Background(), s, identityLogin) if err != nil { t.Fatal(err.Error()) } expectEquals(t, 1, len(identityRefresh.Groups)) expectEquals(t, testGroup, identityRefresh.Groups[0]) } func TestUseRefreshTokenUserDeleted(t *testing.T) { setupVariables(t) token, _ := getAdminToken(t, adminUser, adminPass) userID := createUser(t, token, "", testUser, testEmail, testPass) c := conn{ client: http.DefaultClient, Host: keystoneURL, Domain: domainKeystone{ID: testDomainID}, AdminUsername: adminUser, AdminPassword: adminPass, } s := connector.Scopes{OfflineAccess: true, Groups: true} identityLogin, _, err := c.Login(context.Background(), s, testUser, testPass) if err != nil { t.Fatal(err.Error()) } _, err = c.Refresh(context.Background(), s, identityLogin) if err != nil { t.Fatal(err.Error()) } deleteResource(t, token, userID, usersURL) _, err = c.Refresh(context.Background(), s, identityLogin) if !strings.Contains(err.Error(), "does not exist") { t.Errorf("unexpected error: %s", err.Error()) } } func TestUseRefreshTokenGroupsChanged(t *testing.T) { setupVariables(t) token, _ := getAdminToken(t, adminUser, adminPass) userID := createUser(t, token, "", testUser, testEmail, testPass) defer deleteResource(t, token, userID, usersURL) c := conn{ client: http.DefaultClient, Host: keystoneURL, Domain: domainKeystone{ID: testDomainID}, AdminUsername: adminUser, AdminPassword: adminPass, } s := connector.Scopes{OfflineAccess: true, Groups: true} identityLogin, _, err := c.Login(context.Background(), s, testUser, testPass) if err != nil { t.Fatal(err.Error()) } identityRefresh, err := c.Refresh(context.Background(), s, identityLogin) if err != nil { t.Fatal(err.Error()) } expectEquals(t, 0, len(identityRefresh.Groups)) groupID := createGroup(t, token, "Test group", testGroup) addUserToGroup(t, token, groupID, userID) defer deleteResource(t, token, groupID, groupsURL) identityRefresh, err = c.Refresh(context.Background(), s, identityLogin) if err != nil { t.Fatal(err.Error()) } expectEquals(t, 1, len(identityRefresh.Groups)) } func TestNoGroupsInScope(t *testing.T) { setupVariables(t) token, _ := getAdminToken(t, adminUser, adminPass) userID := createUser(t, token, "", testUser, testEmail, testPass) defer deleteResource(t, token, userID, usersURL) c := conn{ client: http.DefaultClient, Host: keystoneURL, Domain: domainKeystone{ID: testDomainID}, AdminUsername: adminUser, AdminPassword: adminPass, } s := connector.Scopes{OfflineAccess: true, Groups: false} groupID := createGroup(t, token, "Test group", testGroup) addUserToGroup(t, token, groupID, userID) defer deleteResource(t, token, groupID, groupsURL) identityLogin, _, err := c.Login(context.Background(), s, testUser, testPass) if err != nil { t.Fatal(err.Error()) } expectEquals(t, 0, len(identityLogin.Groups)) identityRefresh, err := c.Refresh(context.Background(), s, identityLogin) if err != nil { t.Fatal(err.Error()) } expectEquals(t, 0, len(identityRefresh.Groups)) } func setupVariables(t *testing.T) { keystoneURLEnv := "DEX_KEYSTONE_URL" keystoneAdminURLEnv := "DEX_KEYSTONE_ADMIN_URL" keystoneAdminUserEnv := "DEX_KEYSTONE_ADMIN_USER" keystoneAdminPassEnv := "DEX_KEYSTONE_ADMIN_PASS" keystoneURL = os.Getenv(keystoneURLEnv) if keystoneURL == "" { t.Skipf("variable %q not set, skipping keystone connector tests\n", keystoneURLEnv) return } keystoneAdminURL = os.Getenv(keystoneAdminURLEnv) if keystoneAdminURL == "" { t.Skipf("variable %q not set, skipping keystone connector tests\n", keystoneAdminURLEnv) return } adminUser = os.Getenv(keystoneAdminUserEnv) if adminUser == "" { t.Skipf("variable %q not set, skipping keystone connector tests\n", keystoneAdminUserEnv) return } adminPass = os.Getenv(keystoneAdminPassEnv) if adminPass == "" { t.Skipf("variable %q not set, skipping keystone connector tests\n", keystoneAdminPassEnv) return } authTokenURL = keystoneURL + "/v3/auth/tokens/" usersURL = keystoneAdminURL + "/v3/users/" groupsURL = keystoneAdminURL + "/v3/groups/" domainsURL = keystoneAdminURL + "/v3/domains/" } func expectEquals(t *testing.T, a interface{}, b interface{}) { if !reflect.DeepEqual(a, b) { t.Errorf("Expected %v to be equal %v", a, b) } } ================================================ FILE: connector/ldap/gen-certs.sh ================================================ #!/bin/bash -e # Stolen from the coreos/matchbox repo. echo " [req] req_extensions = v3_req distinguished_name = req_distinguished_name [req_distinguished_name] [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names [alt_names] DNS.101 = localhost " > openssl.config openssl genrsa -out testdata/ca.key 2048 openssl genrsa -out testdata/server.key 2048 openssl req \ -x509 -new -nodes \ -key testdata/ca.key \ -days 10000 -out testdata/ca.crt \ -subj "/CN=ldap-tests" openssl req \ -new \ -key testdata/server.key \ -out testdata/server.csr \ -subj "/CN=localhost" \ -config openssl.config openssl x509 -req \ -in testdata/server.csr \ -CA testdata/ca.crt \ -CAkey testdata/ca.key \ -CAcreateserial \ -out testdata/server.crt \ -days 10000 \ -extensions v3_req \ -extfile openssl.config rm testdata/server.csr rm testdata/ca.srl rm openssl.config ================================================ FILE: connector/ldap/ldap.go ================================================ // Package ldap implements strategies for authenticating using the LDAP protocol. package ldap import ( "context" "crypto/tls" "crypto/x509" "encoding/json" "fmt" "log/slog" "net" "net/url" "os" "strings" "github.com/go-ldap/ldap/v3" "github.com/dexidp/dex/connector" ) // Config holds the configuration parameters for the LDAP connector. The LDAP // connectors require executing two queries, the first to find the user based on // the username and password given to the connector. The second to use the user // entry to search for groups. // // An example config: // // type: ldap // config: // host: ldap.example.com:636 // # The following field is required if using port 389. // # insecureNoSSL: true // rootCA: /etc/dex/ldap.ca // bindDN: uid=serviceaccount,cn=users,dc=example,dc=com // bindPW: password // userSearch: // # Would translate to the query "(&(objectClass=person)(|(uid=)(mail=)))" // baseDN: cn=users,dc=example,dc=com // filter: "(objectClass=person)" // username: // - uid // - mail // idAttr: uid // emailAttr: mail // nameAttr: name // preferredUsernameAttr: uid // groupSearch: // # Would translate to the separate query per user matcher pair and aggregate results into a single group list: // # "(&(|(objectClass=posixGroup)(objectClass=groupOfNames))(memberUid=))" // # "(&(|(objectClass=posixGroup)(objectClass=groupOfNames))(member=))" // baseDN: cn=groups,dc=example,dc=com // filter: "(|(objectClass=posixGroup)(objectClass=groupOfNames))" // userMatchers: // - userAttr: uid // groupAttr: memberUid // # Use if full DN is needed and not available as any other attribute // # Will only work if "DN" attribute does not exist in the record: // - userAttr: DN // groupAttr: member // nameAttr: name // // UsernameAttributes represents one or more LDAP attributes to match against // the username input. It supports unmarshaling from both a single string // (e.g. "uid") and a list of strings (e.g. ["uid", "mail"]). type UsernameAttributes []string func (u *UsernameAttributes) UnmarshalJSON(data []byte) error { var arr []string if err := json.Unmarshal(data, &arr); err == nil { *u = arr return nil } var s string if err := json.Unmarshal(data, &s); err != nil { return fmt.Errorf("username must be a string or list of strings") } if s != "" { *u = UsernameAttributes{s} } return nil } // UserMatcher holds information about user and group matching. type UserMatcher struct { UserAttr string `json:"userAttr"` GroupAttr string `json:"groupAttr"` // Look for parent groups RecursionGroupAttr string `json:"recursionGroupAttr"` } // Config holds configuration options for LDAP logins. type Config struct { // The host and optional port of the LDAP server. If port isn't supplied, it will be // guessed based on the TLS configuration. 389 or 636. Host string `json:"host"` // Required if LDAP host does not use TLS. InsecureNoSSL bool `json:"insecureNoSSL"` // Don't verify the CA. InsecureSkipVerify bool `json:"insecureSkipVerify"` // Connect to the insecure port then issue a StartTLS command to negotiate a // secure connection. If unsupplied secure connections will use the LDAPS // protocol. StartTLS bool `json:"startTLS"` // Path to a trusted root certificate file. RootCA string `json:"rootCA"` // Path to a client cert file generated by rootCA. ClientCert string `json:"clientCert"` // Path to a client private key file generated by rootCA. ClientKey string `json:"clientKey"` // Base64 encoded PEM data containing root CAs. RootCAData []byte `json:"rootCAData"` // BindDN and BindPW for an application service account. The connector uses these // credentials to search for users and groups. BindDN string `json:"bindDN"` BindPW string `json:"bindPW"` // UsernamePrompt allows users to override the username attribute (displayed // in the username/password prompt). If unset, the handler will use // "Username". UsernamePrompt string `json:"usernamePrompt"` // User entry search configuration. UserSearch struct { // BaseDN to start the search from. For example "cn=users,dc=example,dc=com" BaseDN string `json:"baseDN"` // Optional filter to apply when searching the directory. For example "(objectClass=person)" Filter string `json:"filter"` // Attribute(s) to match against the inputted username. Accepts a single string // or a list of strings. When multiple attributes are specified, an OR filter is // constructed: "(|(=)(=))". Username UsernameAttributes `json:"username"` // Can either be: // * "sub" - search the whole sub tree // * "one" - only search one level Scope string `json:"scope"` // A mapping of attributes on the user entry to claims. IDAttr string `json:"idAttr"` // Defaults to "uid" EmailAttr string `json:"emailAttr"` // Defaults to "mail" NameAttr string `json:"nameAttr"` // No default. PreferredUsernameAttrAttr string `json:"preferredUsernameAttr"` // No default. // If this is set, the email claim of the id token will be constructed from the idAttr and // value of emailSuffix. This should not include the @ character. EmailSuffix string `json:"emailSuffix"` // No default. } `json:"userSearch"` // Group search configuration. GroupSearch struct { // BaseDN to start the search from. For example "cn=groups,dc=example,dc=com" BaseDN string `json:"baseDN"` // Optional filter to apply when searching the directory. For example "(objectClass=posixGroup)" Filter string `json:"filter"` Scope string `json:"scope"` // Defaults to "sub" // DEPRECATED config options. Those are left for backward compatibility. // See "UserMatchers" below for the current group to user matching implementation // TODO: should be eventually removed from the code UserAttr string `json:"userAttr"` GroupAttr string `json:"groupAttr"` RecursionGroupAttr string `json:"recursionGroupAttr"` // Array of the field pairs used to match a user to a group. // See the "UserMatcher" struct for the exact field names // // Each pair adds an additional requirement to the filter that an attribute in the group // match the user's attribute value. For example that the "members" attribute of // a group matches the "uid" of the user. The exact filter being added is: // // (userMatchers[n].=userMatchers[n].) // UserMatchers []UserMatcher `json:"userMatchers"` // The attribute of the group that represents its name. NameAttr string `json:"nameAttr"` } `json:"groupSearch"` } func scopeString(i int) string { switch i { case ldap.ScopeBaseObject: return "base" case ldap.ScopeSingleLevel: return "one" case ldap.ScopeWholeSubtree: return "sub" default: return "" } } func parseScope(s string) (int, bool) { // NOTE(ericchiang): ScopeBaseObject doesn't really make sense for us because we // never know the user's or group's DN. switch s { case "", "sub": return ldap.ScopeWholeSubtree, true case "one": return ldap.ScopeSingleLevel, true } return 0, false } // Build a list of group attr name to user attr value matchers. // Function exists here to allow backward compatibility between old and new // group to user matching implementations. // See "Config.GroupSearch.UserMatchers" comments for the details func userMatchers(c *Config, logger *slog.Logger) []UserMatcher { if len(c.GroupSearch.UserMatchers) > 0 && c.GroupSearch.UserMatchers[0].UserAttr != "" { return c.GroupSearch.UserMatchers } if c.GroupSearch.UserAttr != "" || c.GroupSearch.GroupAttr != "" { logger.Warn(`use "groupSearch.userMatchers" option instead of "userAttr/groupAttr" fields`, "deprecated", true) } return []UserMatcher{ { UserAttr: c.GroupSearch.UserAttr, GroupAttr: c.GroupSearch.GroupAttr, RecursionGroupAttr: c.GroupSearch.RecursionGroupAttr, }, } } // Open returns an authentication strategy using LDAP. func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { logger = logger.With(slog.Group("connector", "type", "ldap", "id", id)) conn, err := c.OpenConnector(logger) if err != nil { return nil, err } return connector.Connector(conn), nil } type refreshData struct { Username string `json:"username"` Entry ldap.Entry `json:"entry"` } // OpenConnector is the same as Open but returns a type with all implemented connector interfaces. func (c *Config) OpenConnector(logger *slog.Logger) (interface { connector.Connector connector.PasswordConnector connector.RefreshConnector }, error, ) { return c.openConnector(logger) } func (c *Config) openConnector(logger *slog.Logger) (*ldapConnector, error) { requiredFields := []struct { name string val string }{ {"host", c.Host}, {"userSearch.baseDN", c.UserSearch.BaseDN}, } for _, field := range requiredFields { if field.val == "" { return nil, fmt.Errorf("ldap: missing required field %q", field.name) } } if len(c.UserSearch.Username) == 0 { return nil, fmt.Errorf("ldap: missing required field %q", "userSearch.username") } var ( host string err error ) if host, _, err = net.SplitHostPort(c.Host); err != nil { host = c.Host if c.InsecureNoSSL { c.Host += ":389" } else { c.Host += ":636" } } tlsConfig := &tls.Config{ServerName: host, InsecureSkipVerify: c.InsecureSkipVerify} if c.RootCA != "" || len(c.RootCAData) != 0 { data := c.RootCAData if len(data) == 0 { var err error if data, err = os.ReadFile(c.RootCA); err != nil { return nil, fmt.Errorf("ldap: read ca file: %v", err) } } rootCAs := x509.NewCertPool() if !rootCAs.AppendCertsFromPEM(data) { return nil, fmt.Errorf("ldap: no certs found in ca file") } tlsConfig.RootCAs = rootCAs } if c.ClientKey != "" && c.ClientCert != "" { cert, err := tls.LoadX509KeyPair(c.ClientCert, c.ClientKey) if err != nil { return nil, fmt.Errorf("ldap: load client cert failed: %v", err) } tlsConfig.Certificates = append(tlsConfig.Certificates, cert) } userSearchScope, ok := parseScope(c.UserSearch.Scope) if !ok { return nil, fmt.Errorf("userSearch.Scope unknown value %q", c.UserSearch.Scope) } groupSearchScope, ok := parseScope(c.GroupSearch.Scope) if !ok { return nil, fmt.Errorf("groupSearch.Scope unknown value %q", c.GroupSearch.Scope) } // TODO(nabokihms): remove it after deleting deprecated groupSearch options c.GroupSearch.UserMatchers = userMatchers(c, logger) return &ldapConnector{*c, userSearchScope, groupSearchScope, tlsConfig, c.UserSearch.Username, logger}, nil } var ( _ connector.PasswordConnector = (*ldapConnector)(nil) _ connector.RefreshConnector = (*ldapConnector)(nil) ) type ldapConnector struct { Config userSearchScope int groupSearchScope int tlsConfig *tls.Config usernameAttrs []string logger *slog.Logger } // do initializes a connection to the LDAP directory and passes it to the // provided function. It then performs appropriate teardown or reuse before // returning. func (c *ldapConnector) do(_ context.Context, f func(c *ldap.Conn) error) error { // TODO(ericchiang): support context here var ( conn *ldap.Conn err error ) switch { case c.InsecureNoSSL: u := url.URL{Scheme: "ldap", Host: c.Host} conn, err = ldap.DialURL(u.String()) case c.StartTLS: u := url.URL{Scheme: "ldap", Host: c.Host} conn, err = ldap.DialURL(u.String()) if err != nil { return fmt.Errorf("failed to connect: %v", err) } if err := conn.StartTLS(c.tlsConfig); err != nil { return fmt.Errorf("start TLS failed: %v", err) } default: u := url.URL{Scheme: "ldaps", Host: c.Host} conn, err = ldap.DialURL(u.String(), ldap.DialWithTLSConfig(c.tlsConfig)) } if err != nil { return fmt.Errorf("failed to connect: %v", err) } defer conn.Close() // If bindDN and bindPW are empty this will default to an anonymous bind. if c.BindDN == "" && c.BindPW == "" { if err := conn.UnauthenticatedBind(""); err != nil { return fmt.Errorf("ldap: initial anonymous bind failed: %v", err) } } else if err := conn.Bind(c.BindDN, c.BindPW); err != nil { return fmt.Errorf("ldap: initial bind for user %q failed: %v", c.BindDN, err) } return f(conn) } func (c *ldapConnector) getAttrs(e ldap.Entry, name string) []string { for _, a := range e.Attributes { if a.Name != name { continue } return a.Values } if strings.ToLower(name) == "dn" { return []string{e.DN} } c.logger.Debug("attribute is not fround in entry", "attribute", name) return nil } func (c *ldapConnector) getAttr(e ldap.Entry, name string) string { if a := c.getAttrs(e, name); len(a) > 0 { return a[0] } return "" } func (c *ldapConnector) identityFromEntry(user ldap.Entry) (ident connector.Identity, err error) { // If we're missing any attributes, such as email or ID, we want to report // an error rather than continuing. missing := []string{} // Fill the identity struct using the attributes from the user entry. if ident.UserID = c.getAttr(user, c.UserSearch.IDAttr); ident.UserID == "" { missing = append(missing, c.UserSearch.IDAttr) } if c.UserSearch.NameAttr != "" { if ident.Username = c.getAttr(user, c.UserSearch.NameAttr); ident.Username == "" { missing = append(missing, c.UserSearch.NameAttr) } } if c.UserSearch.PreferredUsernameAttrAttr != "" { if ident.PreferredUsername = c.getAttr(user, c.UserSearch.PreferredUsernameAttrAttr); ident.PreferredUsername == "" { missing = append(missing, c.UserSearch.PreferredUsernameAttrAttr) } } if c.UserSearch.EmailSuffix != "" { ident.Email = ident.Username + "@" + c.UserSearch.EmailSuffix } else if ident.Email = c.getAttr(user, c.UserSearch.EmailAttr); ident.Email == "" { missing = append(missing, c.UserSearch.EmailAttr) } // TODO(ericchiang): Let this value be set from an attribute. ident.EmailVerified = true if len(missing) != 0 { err := fmt.Errorf("ldap: entry %q missing following required attribute(s): %q", user.DN, missing) return connector.Identity{}, err } return ident, nil } func (c *ldapConnector) userEntry(conn *ldap.Conn, username string) (user ldap.Entry, found bool, err error) { var filter string escapedUsername := ldap.EscapeFilter(username) attrFilters := make([]string, 0, len(c.usernameAttrs)) for _, attr := range c.usernameAttrs { attrFilters = append(attrFilters, fmt.Sprintf("(%s=%s)", attr, escapedUsername)) } if len(attrFilters) == 1 { filter = attrFilters[0] // Skip OR wrapper for single attribute } else { filter = fmt.Sprintf("(|%s)", strings.Join(attrFilters, "")) } if c.UserSearch.Filter != "" { filter = fmt.Sprintf("(&%s%s)", c.UserSearch.Filter, filter) } // Initial search. req := &ldap.SearchRequest{ BaseDN: c.UserSearch.BaseDN, Filter: filter, Scope: c.userSearchScope, // We only need to search for these specific requests. Attributes: []string{ c.UserSearch.IDAttr, c.UserSearch.EmailAttr, // TODO(ericchiang): what if this contains duplicate values? }, } req.Attributes = append(req.Attributes, c.usernameAttrs...) for _, matcher := range c.GroupSearch.UserMatchers { req.Attributes = append(req.Attributes, matcher.UserAttr) } if c.UserSearch.NameAttr != "" { req.Attributes = append(req.Attributes, c.UserSearch.NameAttr) } if c.UserSearch.PreferredUsernameAttrAttr != "" { req.Attributes = append(req.Attributes, c.UserSearch.PreferredUsernameAttrAttr) } c.logger.Info("performing ldap search", "base_dn", req.BaseDN, "scope", scopeString(req.Scope), "filter", req.Filter) resp, err := conn.Search(req) if err != nil { return ldap.Entry{}, false, fmt.Errorf("ldap: search with filter %q failed: %v", req.Filter, err) } switch n := len(resp.Entries); n { case 0: c.logger.Error("no results returned for filter", "filter", filter) return ldap.Entry{}, false, nil case 1: user = *resp.Entries[0] c.logger.Info("username mapped to entry", "username", username, "user_dn", user.DN) return user, true, nil default: return ldap.Entry{}, false, fmt.Errorf("ldap: filter returned multiple (%d) results: %q", n, filter) } } func (c *ldapConnector) Login(ctx context.Context, s connector.Scopes, username, password string) (ident connector.Identity, validPass bool, err error) { // make this check to avoid unauthenticated bind to the LDAP server. if password == "" { return connector.Identity{}, false, nil } var ( // We want to return a different error if the user's password is incorrect vs // if there was an error. incorrectPass = false user ldap.Entry ) username = ldap.EscapeFilter(username) err = c.do(ctx, func(conn *ldap.Conn) error { entry, found, err := c.userEntry(conn, username) if err != nil { return err } if !found { incorrectPass = true return nil } user = entry // Try to authenticate as the distinguished name. if err := conn.Bind(user.DN, password); err != nil { // Detect a bad password through the LDAP error code. if ldapErr, ok := err.(*ldap.Error); ok { switch ldapErr.ResultCode { case ldap.LDAPResultInvalidCredentials: c.logger.Error("invalid password for user", "user_dn", user.DN) incorrectPass = true return nil case ldap.LDAPResultConstraintViolation: c.logger.Error("constraint violation for user", "user_dn", user.DN, "err", ldapErr.Error()) incorrectPass = true return nil } } // will also catch all ldap.Error without a case statement above return fmt.Errorf("ldap: failed to bind as dn %q: %v", user.DN, err) } return nil }) if err != nil { return connector.Identity{}, false, err } if incorrectPass { return connector.Identity{}, false, nil } if ident, err = c.identityFromEntry(user); err != nil { return connector.Identity{}, false, err } if s.Groups { groups, err := c.groups(ctx, user) if err != nil { return connector.Identity{}, false, fmt.Errorf("ldap: failed to query groups: %v", err) } ident.Groups = groups } if s.OfflineAccess { refresh := refreshData{ Username: username, Entry: user, } // Encode entry for follow up requests such as the groups query and // refresh attempts. if ident.ConnectorData, err = json.Marshal(refresh); err != nil { return connector.Identity{}, false, fmt.Errorf("ldap: marshal entry: %v", err) } } return ident, true, nil } func (c *ldapConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) { var data refreshData if err := json.Unmarshal(ident.ConnectorData, &data); err != nil { return ident, fmt.Errorf("ldap: failed to unmarshal internal data: %v", err) } var user ldap.Entry err := c.do(ctx, func(conn *ldap.Conn) error { entry, found, err := c.userEntry(conn, data.Username) if err != nil { return err } if !found { return fmt.Errorf("ldap: user not found %q", data.Username) } user = entry return nil }) if err != nil { return ident, err } if user.DN != data.Entry.DN { return ident, fmt.Errorf("ldap: refresh for username %q expected DN %q got %q", data.Username, data.Entry.DN, user.DN) } newIdent, err := c.identityFromEntry(user) if err != nil { return ident, err } newIdent.ConnectorData = ident.ConnectorData if s.Groups { groups, err := c.groups(ctx, user) if err != nil { return connector.Identity{}, fmt.Errorf("ldap: failed to query groups: %v", err) } newIdent.Groups = groups } return newIdent, nil } func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string, error) { if c.GroupSearch.BaseDN == "" { c.logger.Debug("No groups returned because no groups baseDN has been configured.", "base_dn", c.getAttr(user, c.UserSearch.NameAttr)) return nil, nil } var groupNames []string for _, matcher := range c.GroupSearch.UserMatchers { // Initial Search var groups []*ldap.Entry for _, attr := range c.getAttrs(user, matcher.UserAttr) { obtained, filter, err := c.queryGroups(ctx, matcher.GroupAttr, attr) if err != nil { return nil, err } gotGroups := len(obtained) != 0 if !gotGroups { // TODO(ericchiang): Is this going to spam the logs? c.logger.Error("ldap: groups search returned no groups", "filter", filter) } groups = append(groups, obtained...) } // If RecursionGroupAttr is not set, convert direct groups into names and return if matcher.RecursionGroupAttr == "" { for _, group := range groups { name := c.getAttr(*group, c.GroupSearch.NameAttr) if name == "" { return nil, fmt.Errorf( "ldap: group entity %q missing required attribute %q", group.DN, c.GroupSearch.NameAttr, ) } groupNames = append(groupNames, name) } continue } // Recursive Search c.logger.Info("Recursive group search enabled", "groupAttr", matcher.GroupAttr, "recursionAttr", matcher.RecursionGroupAttr) for { var nextLevel []*ldap.Entry for _, group := range groups { name := c.getAttr(*group, c.GroupSearch.NameAttr) if name == "" { return nil, fmt.Errorf("ldap: group entity %q missing required attribute %q", group.DN, c.GroupSearch.NameAttr) } // Prevent duplicates and circular references. duplicate := false for _, existingName := range groupNames { if name == existingName { c.logger.Debug("Found duplicate group", "name", name) duplicate = true break } } if duplicate { continue } groupNames = append(groupNames, name) // Search for parent groups using the group's DN. parents, filter, err := c.queryGroups(ctx, matcher.RecursionGroupAttr, group.DN) if err != nil { return nil, err } if len(parents) == 0 { c.logger.Debug("No parent groups found", "filter", filter) } else { nextLevel = append(nextLevel, parents...) } } if len(nextLevel) == 0 { break } groups = nextLevel } } return groupNames, nil } func (c *ldapConnector) queryGroups(ctx context.Context, memberAttr, dn string) ([]*ldap.Entry, string, error) { filter := fmt.Sprintf("(%s=%s)", memberAttr, ldap.EscapeFilter(dn)) if c.GroupSearch.Filter != "" { filter = fmt.Sprintf("(&%s%s)", c.GroupSearch.Filter, filter) } req := &ldap.SearchRequest{ BaseDN: c.GroupSearch.BaseDN, Filter: filter, Scope: c.groupSearchScope, Attributes: []string{c.GroupSearch.NameAttr}, } var entries []*ldap.Entry if err := c.do(ctx, func(conn *ldap.Conn) error { c.logger.Info( "performing ldap search", "base_dn", req.BaseDN, "scope", scopeString(req.Scope), "filter", req.Filter, ) resp, err := conn.Search(req) if err != nil { if ldapErr, ok := err.(*ldap.Error); ok && ldapErr.ResultCode == ldap.LDAPResultNoSuchObject { c.logger.Info("LDAP search returned no groups", "filter", filter) return nil } return fmt.Errorf("ldap: search failed: %v", err) } entries = append(entries, resp.Entries...) return nil }); err != nil { return nil, filter, err } return entries, filter, nil } func (c *ldapConnector) Prompt() string { return c.UsernamePrompt } ================================================ FILE: connector/ldap/ldap_test.go ================================================ package ldap import ( "context" "fmt" "log/slog" "os" "testing" "github.com/kylelemons/godebug/pretty" "github.com/dexidp/dex/connector" ) // connectionMethod indicates how the test should connect to the LDAP server. type connectionMethod int32 const ( connectStartTLS connectionMethod = iota connectLDAPS connectLDAP connectInsecureSkipVerify ) // subtest is a login test against a given schema. type subtest struct { // Name of the sub-test. name string // Password credentials, and if the connector should request // groups as well. username string password string groups bool // Expected result of the login. wantErr bool wantBadPW bool want connector.Identity } func TestQuery(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=People,ou=TestQuery,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailAttr = "mail" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn"} tests := []subtest{ { name: "validpassword", username: "jane", password: "foo", want: connector.Identity{ UserID: "cn=jane,ou=People,ou=TestQuery,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, }, }, { name: "validpassword2", username: "john", password: "bar", want: connector.Identity{ UserID: "cn=john,ou=People,ou=TestQuery,dc=example,dc=org", Username: "john", Email: "johndoe@example.com", EmailVerified: true, }, }, { name: "invalidpassword", username: "jane", password: "badpassword", wantBadPW: true, }, { name: "invaliduser", username: "idontexist", password: "foo", wantBadPW: true, // Want invalid password, not a query error. }, { name: "invalid wildcard username", username: "a*", // wildcard query is not allowed password: "foo", wantBadPW: true, // Want invalid password, not a query error. }, { name: "invalid wildcard password", username: "john", password: "*", // wildcard password is not allowed wantBadPW: true, // Want invalid password, not a query error. }, } runTests(t, connectLDAP, c, tests) } func TestQueryWithEmailSuffix(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=People,ou=TestQueryWithEmailSuffix,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailSuffix = "test.example.com" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn"} tests := []subtest{ { name: "ignoremailattr", username: "jane", password: "foo", want: connector.Identity{ UserID: "cn=jane,ou=People,ou=TestQueryWithEmailSuffix,dc=example,dc=org", Username: "jane", Email: "jane@test.example.com", EmailVerified: true, }, }, { name: "nomailattr", username: "john", password: "bar", want: connector.Identity{ UserID: "cn=john,ou=People,ou=TestQueryWithEmailSuffix,dc=example,dc=org", Username: "john", Email: "john@test.example.com", EmailVerified: true, }, }, } runTests(t, connectLDAP, c, tests) } func TestUserFilter(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=TestUserFilter,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailAttr = "mail" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn"} c.UserSearch.Filter = "(ou:dn:=Seattle)" tests := []subtest{ { name: "validpassword", username: "jane", password: "foo", want: connector.Identity{ UserID: "cn=jane,ou=People,ou=Seattle,ou=TestUserFilter,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, }, }, { name: "validpassword2", username: "john", password: "bar", want: connector.Identity{ UserID: "cn=john,ou=People,ou=Seattle,ou=TestUserFilter,dc=example,dc=org", Username: "john", Email: "johndoe@example.com", EmailVerified: true, }, }, { name: "invalidpassword", username: "jane", password: "badpassword", wantBadPW: true, }, { name: "invaliduser", username: "idontexist", password: "foo", wantBadPW: true, // Want invalid password, not a query error. }, } runTests(t, connectLDAP, c, tests) } func TestUsernameWithMultipleAttributes(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=TestUsernameWithMultipleAttributes,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailAttr = "mail" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn", "mail"} c.UserSearch.Filter = "(ou:dn:=Seattle)" tests := []subtest{ { name: "cn", username: "jane", password: "foo", want: connector.Identity{ UserID: "cn=jane,ou=People,ou=Seattle,ou=TestUsernameWithMultipleAttributes,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, }, }, { name: "mail", username: "janedoe@example.com", password: "foo", want: connector.Identity{ UserID: "cn=jane,ou=People,ou=Seattle,ou=TestUsernameWithMultipleAttributes,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, }, }, } runTests(t, connectLDAP, c, tests) } func TestGroupQuery(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=People,ou=TestGroupQuery,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailAttr = "mail" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn"} c.GroupSearch.BaseDN = "ou=Groups,ou=TestGroupQuery,dc=example,dc=org" c.GroupSearch.UserMatchers = []UserMatcher{ { UserAttr: "DN", GroupAttr: "member", }, } c.GroupSearch.NameAttr = "cn" tests := []subtest{ { name: "validpassword", username: "jane", password: "foo", groups: true, want: connector.Identity{ UserID: "cn=jane,ou=People,ou=TestGroupQuery,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, Groups: []string{"admins", "developers"}, }, }, { name: "validpassword2", username: "john", password: "bar", groups: true, want: connector.Identity{ UserID: "cn=john,ou=People,ou=TestGroupQuery,dc=example,dc=org", Username: "john", Email: "johndoe@example.com", EmailVerified: true, Groups: []string{"admins"}, }, }, } runTests(t, connectLDAP, c, tests) } func TestGroupsOnUserEntity(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=People,ou=TestGroupsOnUserEntity,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailAttr = "mail" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn"} c.GroupSearch.BaseDN = "ou=Groups,ou=TestGroupsOnUserEntity,dc=example,dc=org" c.GroupSearch.UserMatchers = []UserMatcher{ { UserAttr: "departmentNumber", GroupAttr: "gidNumber", }, } c.GroupSearch.NameAttr = "cn" tests := []subtest{ { name: "validpassword", username: "jane", password: "foo", groups: true, want: connector.Identity{ UserID: "cn=jane,ou=People,ou=TestGroupsOnUserEntity,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, Groups: []string{"admins", "developers"}, }, }, { name: "validpassword2", username: "john", password: "bar", groups: true, want: connector.Identity{ UserID: "cn=john,ou=People,ou=TestGroupsOnUserEntity,dc=example,dc=org", Username: "john", Email: "johndoe@example.com", EmailVerified: true, Groups: []string{"admins", "designers"}, }, }, } runTests(t, connectLDAP, c, tests) } func TestGroupFilter(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=People,ou=TestGroupFilter,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailAttr = "mail" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn"} c.GroupSearch.BaseDN = "ou=TestGroupFilter,dc=example,dc=org" c.GroupSearch.UserMatchers = []UserMatcher{ { UserAttr: "dn", GroupAttr: "member", }, } c.GroupSearch.NameAttr = "cn" c.GroupSearch.Filter = "(ou:dn:=Seattle)" // ignore other groups tests := []subtest{ { name: "validpassword", username: "jane", password: "foo", groups: true, want: connector.Identity{ UserID: "cn=jane,ou=People,ou=TestGroupFilter,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, Groups: []string{"admins", "developers"}, }, }, { name: "validpassword2", username: "john", password: "bar", groups: true, want: connector.Identity{ UserID: "cn=john,ou=People,ou=TestGroupFilter,dc=example,dc=org", Username: "john", Email: "johndoe@example.com", EmailVerified: true, Groups: []string{"admins"}, }, }, } runTests(t, connectLDAP, c, tests) } func TestGroupToUserMatchers(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailAttr = "mail" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn"} c.GroupSearch.BaseDN = "ou=TestGroupToUserMatchers,dc=example,dc=org" c.GroupSearch.UserMatchers = []UserMatcher{ { UserAttr: "DN", GroupAttr: "member", }, { UserAttr: "uid", GroupAttr: "memberUid", }, } c.GroupSearch.NameAttr = "cn" c.GroupSearch.Filter = "(|(objectClass=posixGroup)(objectClass=groupOfNames))" // search all group types tests := []subtest{ { name: "validpassword", username: "jane", password: "foo", groups: true, want: connector.Identity{ UserID: "cn=jane,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, Groups: []string{"admins", "developers", "frontend"}, }, }, { name: "validpassword2", username: "john", password: "bar", groups: true, want: connector.Identity{ UserID: "cn=john,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org", Username: "john", Email: "johndoe@example.com", EmailVerified: true, Groups: []string{"admins", "qa", "logger"}, }, }, } runTests(t, connectLDAP, c, tests) } // Test deprecated group to user matching implementation // which was left for backward compatibility. // See "Config.GroupSearch.UserMatchers" comments for the details func TestDeprecatedGroupToUserMatcher(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailAttr = "mail" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn"} c.GroupSearch.BaseDN = "ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org" c.GroupSearch.UserAttr = "DN" c.GroupSearch.GroupAttr = "member" c.GroupSearch.NameAttr = "cn" c.GroupSearch.Filter = "(ou:dn:=Seattle)" // ignore other groups tests := []subtest{ { name: "validpassword", username: "jane", password: "foo", groups: true, want: connector.Identity{ UserID: "cn=jane,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, Groups: []string{"admins", "developers"}, }, }, { name: "validpassword2", username: "john", password: "bar", groups: true, want: connector.Identity{ UserID: "cn=john,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org", Username: "john", Email: "johndoe@example.com", EmailVerified: true, Groups: []string{"admins"}, }, }, } runTests(t, connectLDAP, c, tests) } func TestStartTLS(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=People,ou=TestStartTLS,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailAttr = "mail" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn"} tests := []subtest{ { name: "validpassword", username: "jane", password: "foo", want: connector.Identity{ UserID: "cn=jane,ou=People,ou=TestStartTLS,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, }, }, } runTests(t, connectStartTLS, c, tests) } func TestInsecureSkipVerify(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=People,ou=TestInsecureSkipVerify,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailAttr = "mail" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn"} tests := []subtest{ { name: "validpassword", username: "jane", password: "foo", want: connector.Identity{ UserID: "cn=jane,ou=People,ou=TestInsecureSkipVerify,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, }, }, } runTests(t, connectInsecureSkipVerify, c, tests) } func TestLDAPS(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=People,ou=TestLDAPS,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailAttr = "mail" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn"} tests := []subtest{ { name: "validpassword", username: "jane", password: "foo", want: connector.Identity{ UserID: "cn=jane,ou=People,ou=TestLDAPS,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, }, }, } runTests(t, connectLDAPS, c, tests) } func TestUsernamePrompt(t *testing.T) { tests := map[string]struct { config Config expected string }{ "with usernamePrompt unset it returns \"\"": { config: Config{}, expected: "", }, "with usernamePrompt set it returns that": { config: Config{UsernamePrompt: "Email address"}, expected: "Email address", }, } for n, d := range tests { t.Run(n, func(t *testing.T) { conn := &ldapConnector{Config: d.config} if actual := conn.Prompt(); actual != d.expected { t.Errorf("expected %v, got %v", d.expected, actual) } }) } } func TestUsernameAttributesUnmarshal(t *testing.T) { tests := []struct { name string json string want UsernameAttributes wantErr bool }{ {name: "single string", json: `"uid"`, want: UsernameAttributes{"uid"}}, {name: "array of strings", json: `["uid","mail"]`, want: UsernameAttributes{"uid", "mail"}}, {name: "single element array", json: `["cn"]`, want: UsernameAttributes{"cn"}}, {name: "empty string", json: `""`, want: nil}, {name: "invalid type", json: `123`, wantErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got UsernameAttributes err := got.UnmarshalJSON([]byte(tt.json)) if (err != nil) != tt.wantErr { t.Fatalf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } if !tt.wantErr { if diff := pretty.Compare(tt.want, got); diff != "" { t.Errorf("unexpected result: %s", diff) } } }) } } func TestNestedGroups(t *testing.T) { c := &Config{} c.UserSearch.BaseDN = "ou=People,ou=TestNestedGroups,dc=example,dc=org" c.UserSearch.NameAttr = "cn" c.UserSearch.EmailAttr = "mail" c.UserSearch.IDAttr = "DN" c.UserSearch.Username = UsernameAttributes{"cn"} c.GroupSearch.BaseDN = "ou=TestNestedGroups,dc=example,dc=org" c.GroupSearch.UserMatchers = []UserMatcher{ { UserAttr: "DN", GroupAttr: "member", // Enable Recursive Search RecursionGroupAttr: "member", }, } c.GroupSearch.NameAttr = "cn" tests := []subtest{ { name: "nestedgroups_jane", username: "jane", password: "foo", groups: true, want: connector.Identity{ UserID: "cn=jane,ou=People,ou=TestNestedGroups,dc=example,dc=org", Username: "jane", Email: "janedoe@example.com", EmailVerified: true, Groups: []string{"childGroup", "circularGroup1", "intermediateGroup", "circularGroup2", "parentGroup"}, }, }, { name: "nestedgroups_john", username: "john", password: "bar", groups: true, want: connector.Identity{ UserID: "cn=john,ou=People,ou=TestNestedGroups,dc=example,dc=org", Username: "john", Email: "johndoe@example.com", EmailVerified: true, Groups: []string{"circularGroup2", "intermediateGroup", "circularGroup1", "parentGroup"}, }, }, } runTests(t, connectLDAP, c, tests) } func getenv(key, defaultVal string) string { if val := os.Getenv(key); val != "" { return val } return defaultVal } // runTests runs a set of tests against an LDAP schema. // // The tests require LDAP to be running. // You can use the provided docker-compose file to setup an LDAP server. func runTests(t *testing.T, connMethod connectionMethod, config *Config, tests []subtest) { ldapHost := os.Getenv("DEX_LDAP_HOST") if ldapHost == "" { t.Skipf(`test environment variable "DEX_LDAP_HOST" not set, skipping`) } // Shallow copy. c := *config // We need to configure host parameters but don't want to overwrite user or // group search configuration. switch connMethod { case connectStartTLS: c.Host = fmt.Sprintf("%s:%s", ldapHost, getenv("DEX_LDAP_PORT", "389")) c.RootCA = "testdata/certs/ca.crt" c.StartTLS = true case connectLDAPS: c.Host = fmt.Sprintf("%s:%s", ldapHost, getenv("DEX_LDAP_TLS_PORT", "636")) c.RootCA = "testdata/certs/ca.crt" case connectInsecureSkipVerify: c.Host = fmt.Sprintf("%s:%s", ldapHost, getenv("DEX_LDAP_TLS_PORT", "636")) c.InsecureSkipVerify = true case connectLDAP: c.Host = fmt.Sprintf("%s:%s", ldapHost, getenv("DEX_LDAP_PORT", "389")) c.InsecureNoSSL = true } c.BindDN = "cn=admin,dc=example,dc=org" c.BindPW = "admin" l := slog.New(slog.DiscardHandler) conn, err := c.openConnector(l) if err != nil { t.Errorf("open connector: %v", err) } for _, test := range tests { if test.name == "" { t.Fatal("go a subtest with no name") } // Run the subtest. t.Run(test.name, func(t *testing.T) { s := connector.Scopes{OfflineAccess: true, Groups: test.groups} ident, validPW, err := conn.Login(context.Background(), s, test.username, test.password) if err != nil { if !test.wantErr { t.Fatalf("query failed: %v", err) } return } if test.wantErr { t.Fatalf("wanted query to fail") } if !validPW { if !test.wantBadPW { t.Fatalf("invalid password: %v", err) } return } if test.wantBadPW { t.Fatalf("wanted invalid password") } got := ident got.ConnectorData = nil if diff := pretty.Compare(test.want, got); diff != "" { t.Error(diff) return } // Verify that refresh tokens work. ident, err = conn.Refresh(context.Background(), s, ident) if err != nil { t.Errorf("refresh failed: %v", err) } got = ident got.ConnectorData = nil if diff := pretty.Compare(test.want, got); diff != "" { t.Errorf("after refresh: %s", diff) } }) } } ================================================ FILE: connector/ldap/testdata/certs/ca.crt ================================================ -----BEGIN CERTIFICATE----- MIIC/TCCAeWgAwIBAgIJAIrt+AlVUsXKMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV BAMMCmxkYXAtdGVzdHMwHhcNMTcwNDEyMjAxNzI5WhcNNDQwODI4MjAxNzI5WjAV MRMwEQYDVQQDDApsZGFwLXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAzKJkt2WsALUDA3tQsedx7UJKIxis05+dU5FbBxf/BMSch8gCNh/cWErH IDljWGwLKbc9UefIz3BzbcNBPLgLGMp7t9Pf9HCBNf7lShLZB2BEGpgpCpd0urox xTqMEfchssJj75HOZRweHfBDDHk8LMHQYUBn5qTiuMYvBUbPVq69argE/kt5yAEW COZzzx38a11iY0gtPjY4Tc9vICsLHhTssNn/1wf+GFNzSTHqijC7NKW0txUneFQJ h6LAmKV/uZC84W1tqMDZKKpABiTpB+JbDvwsb9eXJ6YG6TgbKcrXjLy4ogbIrIRA s2DqMih792mxusIl6lRf3hTtCdyodwIDAQABo1AwTjAdBgNVHQ4EFgQUnfj9sAq4 2xBbV4rf5FNvYaE2Bg0wHwYDVR0jBBgwFoAUnfj9sAq42xBbV4rf5FNvYaE2Bg0w DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAFGnBH1qpLJLvrLWKNI5w u8pFYO3RGqmfJ3BGf60MQxdUaTIUNQxPfPATbth7t8GRJwpWESRDlaXWq9fM9rkt fbmuqjAMGTFloNd9ra6e2F0CKjwZWcn/3eG/mVw/5d1Ku9Ow8luKrZuzNzVJd13r hoNc1wYXN0pHWkNiRUuR/E4fE/sn+tYOpJ4XYQvKAcSrNrq8m5O9VG5gLvlTeNno 6q9hBy+5XKYUdHlzbAGm9QL0e1R45Mu4qxcFluKEmzS1rXlLsLs4/pqHgreXlYgL f7K0cFvaJGnFRKaxa6Bpf1EPNtqSc/pQZh01Ww8CUu1xh2+5KufgJQjAHVG3a1ow dQ== -----END CERTIFICATE----- ================================================ FILE: connector/ldap/testdata/certs/ca.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAzKJkt2WsALUDA3tQsedx7UJKIxis05+dU5FbBxf/BMSch8gC Nh/cWErHIDljWGwLKbc9UefIz3BzbcNBPLgLGMp7t9Pf9HCBNf7lShLZB2BEGpgp Cpd0uroxxTqMEfchssJj75HOZRweHfBDDHk8LMHQYUBn5qTiuMYvBUbPVq69argE /kt5yAEWCOZzzx38a11iY0gtPjY4Tc9vICsLHhTssNn/1wf+GFNzSTHqijC7NKW0 txUneFQJh6LAmKV/uZC84W1tqMDZKKpABiTpB+JbDvwsb9eXJ6YG6TgbKcrXjLy4 ogbIrIRAs2DqMih792mxusIl6lRf3hTtCdyodwIDAQABAoIBAHQpEucQbe0Q058c VxhF+2PlJ1R441JV3ubbMkL6mibIvNpO7QJwX5I3EIX4Ta6Z1lRd0g82dcVbXgrG tbeT+aie+E/Hk++cFZzjDqFXxZ7sRHycN1/tzbNZknsU2wIvuQ9STYxmxjSbG3V/ N3BTOZdmhbVO7Cv/GTwuM+7Y3UWkc74HaXfAgo1UIO9MtqgqP3H1Tv6ZIeKzl+mP wrvei0eQe6jI4W6+vUOX3SlrlrMxMTLK/Ce2MP1pJx++m8Ga23+vtna+lkOWnwcD NmhYl4dL31sDcE6Hz/T6Wwfdlfyugw8vi3a3GEYGMIwy27CFf/ccYnWPOI3oIHDe RwlXLCECgYEA595xJmfUpwqgYY80pT3JG3+64NWJ7f/gH0Ey9fivZfnTegjkI2Kc Uf7+odCq9I1TFtx10M72N4pXT1uLzJtINYty4ZIfOLG7jSraVbOuf9AvMNCYw+cT Fcf/HGUJEE95TKYDrGfklOYFNs3ZCcKOCYJOWCuwki8Vm2vtJpV6gnkCgYEA4e5b DI+YworLjokY8eP4aOF5BMuiFdGkYDjVQZG45RwjJdLwBjaf+HA4pAuJAr2LWiLX cdKpk+3AlJ8UMLIM+hBP4hBqnrPaRTkEhTXpbUA1lvL9o0mVDFgNh90guu5TeJza sW7JLaStmAyCxYGxbW4LTjR8GX9DPOPmLs5ZRm8CgYAyFW5DaXIZksYJzLEGcE4c Tn7DSdy9N+PlXGPxlYHteQUg+wKsUgSKAZZmxXfn0w77hSs9qzar0IoDbjbIP1Jd nn12E+YCjQGCAJugn2s12HYZCTW2Oxd4QPbt3zUR/NiqocFxYA+TygueRuB2pzue +jKKAQXmzZzRMYLMLsWDoQKBgAnrCcoyX5VivG7ka9jqlhQcmdBxFAt7KYkj1ZDM Ud6U7qIRcYIEUd95JbNl4jzhj0WEtAqGIfWhgUvE9ADzQAiWQLt+1v9ii9lwGFe0 tyuZnwCiaCoL5+Qj1Ww6c95g6f8oe51AbMp5KTm8it0axWw1YX+sZCpGYPBCXO9/ FYI3AoGBAMacjjbPjjfOXxBRhRz1rEDTrIStDj5KM4fgslKVGysqpH/mw7gSC8SK qn0anL2s3SAe9PQpOzM3pFFRZx4XMOk4ojYRZtp3FjPFDRnYuYzkfkbU7eV04awO 6nrua8KNLNK+ir9iCi46tP6Zr3F81zWGUoVArVUgCRDbA9e0swB0 -----END RSA PRIVATE KEY----- ================================================ FILE: connector/ldap/testdata/certs/dhparam.pem ================================================ -----BEGIN DH PARAMETERS----- MIIBCAKCAQEAx5y2viJKOAAcDYSj55odZsbA7dkSQ9afEPd9uaCLOvRYKLJY1S1V C4m1eVfna8JndSLdsBGDQe4BlBTkEYMYR8CJHtUuBxeAucOH8KlF8rIHXXi71oex T7kPtJEDINQKOn06bHqNcn0a7ZMWP8jiQ708OYr5P+1T/N82QTAFpDuqK42ZnBqf 8qzQkkTN0UCktY2EWnFTbNIXcMKWQnYP8zt/CG3Q31b2bnQt2iLEa/DIF7RLNjfx 9wPQBBAqgWbLmWfdPpHsAPtQxtItb+GRbPs3aLm06CFKlQuteDoP+suo0EtglHcV V9Ynvdz0cdJCJ7EPyET6CtLMzc/Puup/AwIBAg== -----END DH PARAMETERS----- ================================================ FILE: connector/ldap/testdata/certs/ldap.crt ================================================ -----BEGIN CERTIFICATE----- MIIC3DCCAcSgAwIBAgIJANsmsx7hUWnHMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV BAMMCmxkYXAtdGVzdHMwHhcNMTcwNDEyMjAxNzI5WhcNNDQwODI4MjAxNzI5WjAU MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQDlWGC5X/TWgysEimM7n0hSkXRCITwAFxKG0C4EeppmL42DBcjQa0xrElRF h57EBZltbSfvTMDBZAyhx5oZKoETDfwy5jFzf4L4PazSkvfn4qWmCnrq4HNO5Vl7 GBsW93bljsh2nfvoKDX2vBpEUe0qrZzJtRHq0ytfd6zXZ9+WFMsmhD9poADrH4hB /UOV3uCJPybOoy/WsANQpSgJPD886zakmF+54XQ3tExKzFA1rR4HJbU26h99U5kH 346sV7/xKJLENQVIH1qsqyA1UPDZRWusABjdIPc9Racy0/MxTVE0k5lQbBvz9QSe HZvW+ct/aZX5tjxr9JlSY7tK2I9FAgMBAAGjMDAuMAkGA1UdEwQCMAAwCwYDVR0P BAQDAgXgMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEA RZp/fNjoQNaO6KW0Ay0aaPW6jPrcqGjzFgeIXaw/0UaWm5jhptWtjOAILV+afIrd 4cKDg65o4xRdQYYbqmutFMAO/DeyDyMi3IL60qk0osipPDIORx5Ai2ZBQvUsGtwV np9UwQGNO5AGeR9N5kndyldbpxaIJFhsKOV8uRSi+4PRbMH3G0kJIX6wwZU4Ri/k 3lWJQfqULH0vtMQCWSJuaYHxWYFq4AM+H/zpLwg1WG2eKVgSMWotxMRi5LOFSBbG XuOxAb0SNBcXl6kjRYbQyHBxIJMsB1lk64g7dTJqXuYFUwmIGL/vTr6PL6EKYk65 /aWO8cvwXOrYaf9umgcqvg== -----END CERTIFICATE----- ================================================ FILE: connector/ldap/testdata/certs/ldap.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA5VhguV/01oMrBIpjO59IUpF0QiE8ABcShtAuBHqaZi+NgwXI 0GtMaxJURYeexAWZbW0n70zAwWQMoceaGSqBEw38MuYxc3+C+D2s0pL35+Klpgp6 6uBzTuVZexgbFvd25Y7Idp376Cg19rwaRFHtKq2cybUR6tMrX3es12fflhTLJoQ/ aaAA6x+IQf1Dld7giT8mzqMv1rADUKUoCTw/POs2pJhfueF0N7RMSsxQNa0eByW1 NuoffVOZB9+OrFe/8SiSxDUFSB9arKsgNVDw2UVrrAAY3SD3PUWnMtPzMU1RNJOZ UGwb8/UEnh2b1vnLf2mV+bY8a/SZUmO7StiPRQIDAQABAoIBAQDHBbKqK4MkxB8I ia8jhk4UmPTyjjSrP1pscyv75wkltA5xrQtfEj32jKlkzRQRt2o1c4w8NbbwHAp6 OeSYAjKQfoplAS3YtMbK9XqMIc3QBPcK5/1S5gQqaw0DrR+VBpq/CvEbPm3kQUDT JNkGgLH3X0G4KNGrniT9a7UqGJIGgdBAr7bPESiDi9wuOwfhm/9TB8LOG8wB9cn4 NcUipvjOcRxMFkyYtq056ZfGeoK2ooFe0lHi4j8sWXfII789OqN0plecAg8NGZsl klSncpTObE6eTXo9Jncio3pftvszEctKssK7vuL6opajtppT6C5FnKLb6NIAOo7j CPk1BRPhAoGBAPf8TMTr+l8MHRuVXEx52E1dBH46ZB8bMfvwb7cZ31Fn0EEmygCj wP9eKZ8MKmHVBbU6CbxYQMICTTwRrw9H0tNoaZBwzWMz/JDHcACfsPKtfrX8T4UQ wmVwbLctdC1Cbaxn1jYeSLoLfSe8IGPDnLpsMCzpRcQIgPS+gO69zr8vAoGBAOzB 254TKd2OQPnvUvmAVYGRYyTu/+ShH9fZyDJYtjhQbuxt6eqh3poneWJOW+KPlqDd J0a8yv1pDXmCy5k1Oo8Nubt7cPI0y2z0nm5LvAaqPaFdUJs9nq9umH3svJh6du6Z +TZ6MDU/eyJRq7Mc5SQrssziJidS3cU21b560xvLAoGBAPYpZY9Ia7Uz0iUSY5eq j7Nj9VTT45UZKsnbRxnrvckSEyDJP1XZN3iG4Sv3KI8KpWrbHNTwif/Lxx0stKin dDjU+Y0e3FJwRXL19lE4M68B17kQp2MAWufU7KX8oclXmoS8YmBAOZMsWmU6ErDV eVt4j23VdaJ9inzoKhZTJcqTAoGAH9znJZsGo16lt/1ReWqgF1Ptt+bCYY6drnsM ylnODD4m74LLXFx0jOKLH4PUMeWJLBUXWBnIZ9pfid7kb7YOL3p1aJnwVWhtiDhT qhxfLbZznOfmFT5xwMJtm2Tk7NBueSYXuBExs7jbZX8AUJau7/NBmPlGkTxBxGzg z0XQa4kCgYBxYBXwFpLLjBO+bMMkoVOlMDj7feCOWP9CsnKQSHYqPbmmb+8mA7pN mIWfjSVynVe+Ncn0I5Uijbs9QDYqcfApJQ+iXeb+VGrg4QkLHHGd/5kIY28Evc6A KVyRIuiYNmgOXGpaFpMXSw718N4U7jWW7lqUxK2rvEupFhaL52oJFQ== -----END RSA PRIVATE KEY----- ================================================ FILE: connector/ldap/testdata/schema.ldif ================================================ dn: ou=TestQuery,dc=example,dc=org objectClass: organizationalUnit ou: TestQuery dn: ou=People,ou=TestQuery,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,ou=TestQuery,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo dn: cn=john,ou=People,ou=TestQuery,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: john mail: johndoe@example.com userpassword: bar ######################################################################## dn: ou=TestQueryWithEmailSuffix,dc=example,dc=org objectClass: organizationalUnit ou: TestQueryWithEmailSuffix dn: ou=People,ou=TestQueryWithEmailSuffix,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,ou=TestQueryWithEmailSuffix,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo dn: cn=john,ou=People,ou=TestQueryWithEmailSuffix,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: john userpassword: bar ######################################################################## dn: ou=TestUserFilter,dc=example,dc=org objectClass: organizationalUnit ou: TestUserFilter dn: ou=Seattle,ou=TestUserFilter,dc=example,dc=org objectClass: organizationalUnit ou: Seattle dn: ou=Portland,ou=TestUserFilter,dc=example,dc=org objectClass: organizationalUnit ou: Portland dn: ou=People,ou=Seattle,ou=TestUserFilter,dc=example,dc=org objectClass: organizationalUnit ou: People dn: ou=People,ou=Portland,ou=TestUserFilter,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,ou=Seattle,ou=TestUserFilter,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo dn: cn=jane,ou=People,ou=Portland,ou=TestUserFilter,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoefromportland@example.com userpassword: baz dn: cn=john,ou=People,ou=Seattle,ou=TestUserFilter,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: john mail: johndoe@example.com userpassword: bar ######################################################################## dn: ou=TestGroupQuery,dc=example,dc=org objectClass: organizationalUnit ou: TestGroupQuery dn: ou=People,ou=TestGroupQuery,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,ou=TestGroupQuery,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo dn: cn=john,ou=People,ou=TestGroupQuery,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: john mail: johndoe@example.com userpassword: bar # Group definitions. dn: ou=Groups,ou=TestGroupQuery,dc=example,dc=org objectClass: organizationalUnit ou: Groups dn: cn=admins,ou=Groups,ou=TestGroupQuery,dc=example,dc=org objectClass: groupOfNames cn: admins member: cn=john,ou=People,ou=TestGroupQuery,dc=example,dc=org member: cn=jane,ou=People,ou=TestGroupQuery,dc=example,dc=org dn: cn=developers,ou=Groups,ou=TestGroupQuery,dc=example,dc=org objectClass: groupOfNames cn: developers member: cn=jane,ou=People,ou=TestGroupQuery,dc=example,dc=org ######################################################################## dn: ou=TestGroupsOnUserEntity,dc=example,dc=org objectClass: organizationalUnit ou: TestGroupsOnUserEntity dn: ou=People,ou=TestGroupsOnUserEntity,dc=example,dc=org objectClass: organizationalUnit ou: People # Groups are enumerated as part of the user entity instead of the members being # a list on the group entity. dn: cn=jane,ou=People,ou=TestGroupsOnUserEntity,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo departmentNumber: 1000 departmentNumber: 1001 dn: cn=john,ou=People,ou=TestGroupsOnUserEntity,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: john mail: johndoe@example.com userpassword: bar departmentNumber: 1000 departmentNumber: 1002 # Group definitions. Notice that they don't have any "member" field. dn: ou=Groups,ou=TestGroupsOnUserEntity,dc=example,dc=org objectClass: organizationalUnit ou: Groups dn: cn=admins,ou=Groups,ou=TestGroupsOnUserEntity,dc=example,dc=org objectClass: posixGroup cn: admins gidNumber: 1000 dn: cn=developers,ou=Groups,ou=TestGroupsOnUserEntity,dc=example,dc=org objectClass: posixGroup cn: developers gidNumber: 1001 dn: cn=designers,ou=Groups,ou=TestGroupsOnUserEntity,dc=example,dc=org objectClass: posixGroup cn: designers gidNumber: 1002 ######################################################################## dn: ou=TestGroupFilter,dc=example,dc=org objectClass: organizationalUnit ou: TestGroupFilter dn: ou=People,ou=TestGroupFilter,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,ou=TestGroupFilter,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo dn: cn=john,ou=People,ou=TestGroupFilter,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: john mail: johndoe@example.com userpassword: bar # Group definitions. dn: ou=Seattle,ou=TestGroupFilter,dc=example,dc=org objectClass: organizationalUnit ou: Seattle dn: ou=Portland,ou=TestGroupFilter,dc=example,dc=org objectClass: organizationalUnit ou: Portland dn: ou=Groups,ou=Seattle,ou=TestGroupFilter,dc=example,dc=org objectClass: organizationalUnit ou: Groups dn: ou=Groups,ou=Portland,ou=TestGroupFilter,dc=example,dc=org objectClass: organizationalUnit ou: Groups dn: cn=qa,ou=Groups,ou=Portland,ou=TestGroupFilter,dc=example,dc=org objectClass: groupOfNames cn: qa member: cn=john,ou=People,ou=TestGroupFilter,dc=example,dc=org dn: cn=admins,ou=Groups,ou=Seattle,ou=TestGroupFilter,dc=example,dc=org objectClass: groupOfNames cn: admins member: cn=john,ou=People,ou=TestGroupFilter,dc=example,dc=org member: cn=jane,ou=People,ou=TestGroupFilter,dc=example,dc=org dn: cn=developers,ou=Groups,ou=Seattle,ou=TestGroupFilter,dc=example,dc=org objectClass: groupOfNames cn: developers member: cn=jane,ou=People,ou=TestGroupFilter,dc=example,dc=org ######################################################################## dn: ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: organizationalUnit ou: TestGroupToUserMatchers dn: ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane uid: janedoe mail: janedoe@example.com userpassword: foo dn: cn=john,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: john uid: johndoe mail: johndoe@example.com userpassword: bar # Group definitions. dn: ou=Seattle,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: organizationalUnit ou: Seattle dn: ou=Portland,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: organizationalUnit ou: Portland dn: ou=Groups,ou=Seattle,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: organizationalUnit ou: Groups dn: ou=UnixGroups,ou=Seattle,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: organizationalUnit ou: UnixGroups dn: ou=Groups,ou=Portland,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: organizationalUnit ou: Groups dn: ou=UnixGroups,ou=Portland,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: organizationalUnit ou: UnixGroups dn: cn=qa,ou=Groups,ou=Portland,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: groupOfNames cn: qa member: cn=john,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org dn: cn=logger,ou=UnixGroups,ou=Portland,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: posixGroup gidNumber: 1000 cn: logger memberUid: johndoe dn: cn=admins,ou=Groups,ou=Seattle,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: groupOfNames cn: admins member: cn=john,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org member: cn=jane,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org dn: cn=developers,ou=Groups,ou=Seattle,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: groupOfNames cn: developers member: cn=jane,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org dn: cn=frontend,ou=UnixGroups,ou=Seattle,ou=TestGroupToUserMatchers,dc=example,dc=org objectClass: posixGroup gidNumber: 1001 cn: frontend memberUid: janedoe ######################################################################## dn: ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org objectClass: organizationalUnit ou: TestDeprecatedGroupToUserMatcher dn: ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo dn: cn=john,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: john mail: johndoe@example.com userpassword: bar # Group definitions. dn: ou=Seattle,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org objectClass: organizationalUnit ou: Seattle dn: ou=Portland,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org objectClass: organizationalUnit ou: Portland dn: ou=Groups,ou=Seattle,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org objectClass: organizationalUnit ou: Groups dn: ou=Groups,ou=Portland,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org objectClass: organizationalUnit ou: Groups dn: cn=qa,ou=Groups,ou=Portland,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org objectClass: groupOfNames cn: qa member: cn=john,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org dn: cn=admins,ou=Groups,ou=Seattle,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org objectClass: groupOfNames cn: admins member: cn=john,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org member: cn=jane,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org dn: cn=developers,ou=Groups,ou=Seattle,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org objectClass: groupOfNames cn: developers member: cn=jane,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org ######################################################################## dn: ou=TestStartTLS,dc=example,dc=org objectClass: organizationalUnit ou: TestStartTLS dn: ou=People,ou=TestStartTLS,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,ou=TestStartTLS,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo ######################################################################## dn: ou=TestInsecureSkipVerify,dc=example,dc=org objectClass: organizationalUnit ou: TestInsecureSkipVerify dn: ou=People,ou=TestInsecureSkipVerify,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,ou=TestInsecureSkipVerify,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo ######################################################################## dn: ou=TestLDAPS,dc=example,dc=org objectClass: organizationalUnit ou: TestLDAPS dn: ou=People,ou=TestLDAPS,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,ou=TestLDAPS,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo ######################################################################## dn: ou=TestNestedGroups,dc=example,dc=org objectClass: organizationalUnit ou: TestNestedGroups dn: ou=People,ou=TestNestedGroups,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,ou=TestNestedGroups,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo dn: cn=john,ou=People,ou=TestNestedGroups,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: john mail: johndoe@example.com userpassword: bar # Group definitions. dn: ou=Groups,ou=TestNestedGroups,dc=example,dc=org objectClass: organizationalUnit ou: Groups dn: cn=childGroup,ou=Groups,ou=TestNestedGroups,dc=example,dc=org objectClass: groupOfNames cn: childGroup member: cn=jane,ou=People,ou=TestNestedGroups,dc=example,dc=org dn: cn=intermediateGroup,ou=Groups,ou=TestNestedGroups,dc=example,dc=org objectClass: groupOfNames cn: intermediateGroup member: cn=childGroup,ou=Groups,ou=TestNestedGroups,dc=example,dc=org member: cn=john,ou=People,ou=TestNestedGroups,dc=example,dc=org dn: cn=parentGroup,ou=Groups,ou=TestNestedGroups,dc=example,dc=org objectClass: groupOfNames cn: parentGroup member: cn=intermediateGroup,ou=Groups,ou=TestNestedGroups,dc=example,dc=org dn: cn=circularGroup1,ou=Groups,ou=TestNestedGroups,dc=example,dc=org objectClass: groupOfNames cn: circularGroup1 member: cn=circularGroup2,ou=Groups,ou=TestNestedGroups,dc=example,dc=org member: cn=jane,ou=People,ou=TestNestedGroups,dc=example,dc=org dn: cn=circularGroup2,ou=Groups,ou=TestNestedGroups,dc=example,dc=org objectClass: groupOfNames cn: circularGroup2 member: cn=circularGroup1,ou=Groups,ou=TestNestedGroups,dc=example,dc=org member: cn=john,ou=People,ou=TestNestedGroups,dc=example,dc=org ######################################################################## dn: ou=TestUsernameWithMultipleAttributes,dc=example,dc=org objectClass: organizationalUnit ou: TestUsernameWithMultipleAttributes dn: ou=Seattle,ou=TestUsernameWithMultipleAttributes,dc=example,dc=org objectClass: organizationalUnit ou: Seattle dn: ou=People,ou=Seattle,ou=TestUsernameWithMultipleAttributes,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,ou=Seattle,ou=TestUsernameWithMultipleAttributes,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo ================================================ FILE: connector/linkedin/linkedin.go ================================================ // Package linkedin provides authentication strategies using LinkedIn package linkedin import ( "context" "encoding/json" "fmt" "io" "log/slog" "net/http" "strings" "golang.org/x/oauth2" "github.com/dexidp/dex/connector" ) const ( apiURL = "https://api.linkedin.com/v2" authURL = "https://www.linkedin.com/oauth/v2/authorization" tokenURL = "https://www.linkedin.com/oauth/v2/accessToken" ) // Config holds configuration options for LinkedIn logins. type Config struct { ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` RedirectURI string `json:"redirectURI"` } // Open returns a strategy for logging in through LinkedIn func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { return &linkedInConnector{ oauth2Config: &oauth2.Config{ ClientID: c.ClientID, ClientSecret: c.ClientSecret, Endpoint: oauth2.Endpoint{ AuthURL: authURL, TokenURL: tokenURL, }, Scopes: []string{"r_liteprofile", "r_emailaddress"}, RedirectURL: c.RedirectURI, }, logger: logger.With(slog.Group("connector", "type", "linkedin", "id", id)), }, nil } type connectorData struct { AccessToken string `json:"accessToken"` } var ( _ connector.CallbackConnector = (*linkedInConnector)(nil) _ connector.RefreshConnector = (*linkedInConnector)(nil) ) type linkedInConnector struct { oauth2Config *oauth2.Config logger *slog.Logger } // LoginURL returns an access token request URL func (c *linkedInConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) { if c.oauth2Config.RedirectURL != callbackURL { return "", nil, fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.oauth2Config.RedirectURL) } return c.oauth2Config.AuthCodeURL(state), nil, nil } // HandleCallback handles HTTP redirect from LinkedIn func (c *linkedInConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, &oauth2Error{errType, q.Get("error_description")} } ctx := r.Context() token, err := c.oauth2Config.Exchange(ctx, q.Get("code")) if err != nil { return identity, fmt.Errorf("linkedin: get token: %v", err) } client := c.oauth2Config.Client(ctx, token) profile, err := c.profile(ctx, client) if err != nil { return identity, fmt.Errorf("linkedin: get profile: %v", err) } identity = connector.Identity{ UserID: profile.ID, Username: profile.fullname(), Email: profile.Email, EmailVerified: true, } if s.OfflineAccess { data := connectorData{AccessToken: token.AccessToken} connData, err := json.Marshal(data) if err != nil { return identity, fmt.Errorf("linkedin: marshal connector data: %v", err) } identity.ConnectorData = connData } return identity, nil } func (c *linkedInConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) { if len(ident.ConnectorData) == 0 { return ident, fmt.Errorf("linkedin: no upstream access token found") } var data connectorData if err := json.Unmarshal(ident.ConnectorData, &data); err != nil { return ident, fmt.Errorf("linkedin: unmarshal access token: %v", err) } client := c.oauth2Config.Client(ctx, &oauth2.Token{AccessToken: data.AccessToken}) profile, err := c.profile(ctx, client) if err != nil { return ident, fmt.Errorf("linkedin: get profile: %v", err) } ident.Username = profile.fullname() ident.Email = profile.Email return ident, nil } type profile struct { ID string `json:"id"` FirstName string `json:"localizedFirstName"` LastName string `json:"localizedLastName"` Email string `json:"emailAddress"` } type emailresp struct { Elements []struct { Handle struct { EmailAddress string `json:"emailAddress"` } `json:"handle~"` } `json:"elements"` } // fullname returns a full name of a person, or email if the resulting name is // empty func (p profile) fullname() string { fname := strings.TrimSpace(p.FirstName + " " + p.LastName) if fname == "" { return p.Email } return fname } func (c *linkedInConnector) primaryEmail(ctx context.Context, client *http.Client) (email string, err error) { req, err := http.NewRequest("GET", apiURL+"/emailAddress?q=members&projection=(elements*(handle~))", nil) if err != nil { return email, fmt.Errorf("new req: %v", err) } resp, err := client.Do(req.WithContext(ctx)) if err != nil { return email, fmt.Errorf("get URL %v", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return email, fmt.Errorf("read body: %v", err) } if resp.StatusCode != http.StatusOK { return email, fmt.Errorf("%s: %s", resp.Status, body) } var parsedResp emailresp err = json.Unmarshal(body, &parsedResp) if err == nil { for _, elem := range parsedResp.Elements { email = elem.Handle.EmailAddress } } if email == "" { err = fmt.Errorf("email is not set") } return email, err } func (c *linkedInConnector) profile(ctx context.Context, client *http.Client) (p profile, err error) { // https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api // https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/primary-contact-api // https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/migration-faq#how-do-i-retrieve-the-members-email-address req, err := http.NewRequest("GET", apiURL+"/me", nil) if err != nil { return p, fmt.Errorf("new req: %v", err) } resp, err := client.Do(req.WithContext(ctx)) if err != nil { return p, fmt.Errorf("get URL %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return p, fmt.Errorf("read body: %v", err) } return p, fmt.Errorf("%s: %s", resp.Status, body) } if err := json.NewDecoder(resp.Body).Decode(&p); err != nil { return p, fmt.Errorf("JSON decode: %v", err) } email, err := c.primaryEmail(ctx, client) if err != nil { return p, fmt.Errorf("fetching email: %v", err) } p.Email = email return p, err } type oauth2Error struct { error string errorDescription string } func (e *oauth2Error) Error() string { if e.errorDescription == "" { return e.error } return e.error + ": " + e.errorDescription } ================================================ FILE: connector/microsoft/microsoft.go ================================================ // Package microsoft provides authentication strategies using Microsoft. package microsoft import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "log/slog" "net/http" "strings" "sync" "time" "golang.org/x/oauth2" "github.com/dexidp/dex/connector" groups_pkg "github.com/dexidp/dex/pkg/groups" ) // GroupNameFormat represents the format of the group identifier // we use type of string instead of int because it's easier to // marshall/unmarshall type GroupNameFormat string // Possible values for GroupNameFormat const ( GroupID GroupNameFormat = "id" GroupName GroupNameFormat = "name" ) const ( // Microsoft requires this scope to access user's profile scopeUser = "user.read" // Microsoft requires this scope to list groups the user is a member of // and resolve their ids to groups names. scopeGroups = "directory.read.all" // Microsoft requires this scope to return a refresh token // see https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#offline_access scopeOfflineAccess = "offline_access" ) // Config holds configuration options for microsoft logins. type Config struct { ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` RedirectURI string `json:"redirectURI"` Tenant string `json:"tenant"` OnlySecurityGroups bool `json:"onlySecurityGroups"` Groups []string `json:"groups"` GroupNameFormat GroupNameFormat `json:"groupNameFormat"` UseGroupsAsWhitelist bool `json:"useGroupsAsWhitelist"` EmailToLowercase bool `json:"emailToLowercase"` APIURL string `json:"apiURL"` GraphURL string `json:"graphURL"` // PromptType is used for the prompt query parameter. // For valid values, see https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code. PromptType string `json:"promptType"` DomainHint string `json:"domainHint"` Scopes []string `json:"scopes"` // defaults to scopeUser (user.read) } // Open returns a strategy for logging in through Microsoft. func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { m := microsoftConnector{ apiURL: strings.TrimSuffix(c.APIURL, "/"), graphURL: strings.TrimSuffix(c.GraphURL, "/"), redirectURI: c.RedirectURI, clientID: c.ClientID, clientSecret: c.ClientSecret, tenant: c.Tenant, onlySecurityGroups: c.OnlySecurityGroups, groups: c.Groups, groupNameFormat: c.GroupNameFormat, useGroupsAsWhitelist: c.UseGroupsAsWhitelist, logger: logger.With(slog.Group("connector", "type", "microsoft", "id", id)), emailToLowercase: c.EmailToLowercase, promptType: c.PromptType, domainHint: c.DomainHint, scopes: c.Scopes, } if m.apiURL == "" { m.apiURL = "https://login.microsoftonline.com" } if m.graphURL == "" { m.graphURL = "https://graph.microsoft.com" } // By default allow logins from both personal and business/school // accounts. if m.tenant == "" { m.tenant = "common" } // By default, use group names switch m.groupNameFormat { case "": m.groupNameFormat = GroupName case GroupID, GroupName: default: return nil, fmt.Errorf("invalid groupNameFormat: %s", m.groupNameFormat) } return &m, nil } type connectorData struct { AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` Expiry time.Time `json:"expiry"` } var ( _ connector.CallbackConnector = (*microsoftConnector)(nil) _ connector.RefreshConnector = (*microsoftConnector)(nil) ) type microsoftConnector struct { apiURL string graphURL string redirectURI string clientID string clientSecret string tenant string onlySecurityGroups bool groupNameFormat GroupNameFormat groups []string useGroupsAsWhitelist bool logger *slog.Logger emailToLowercase bool promptType string domainHint string scopes []string } func (c *microsoftConnector) isOrgTenant() bool { return c.tenant != "common" && c.tenant != "consumers" && c.tenant != "organizations" } func (c *microsoftConnector) groupsRequired(groupScope bool) bool { return (len(c.groups) > 0 || groupScope) && c.isOrgTenant() } func (c *microsoftConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config { var microsoftScopes []string if len(c.scopes) > 0 { microsoftScopes = c.scopes } else { microsoftScopes = append(microsoftScopes, scopeUser) } if c.groupsRequired(scopes.Groups) { microsoftScopes = append(microsoftScopes, scopeGroups) } if scopes.OfflineAccess { microsoftScopes = append(microsoftScopes, scopeOfflineAccess) } return &oauth2.Config{ ClientID: c.clientID, ClientSecret: c.clientSecret, Endpoint: oauth2.Endpoint{ AuthURL: c.apiURL + "/" + c.tenant + "/oauth2/v2.0/authorize", TokenURL: c.apiURL + "/" + c.tenant + "/oauth2/v2.0/token", }, Scopes: microsoftScopes, RedirectURL: c.redirectURI, } } func (c *microsoftConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) { if c.redirectURI != callbackURL { return "", nil, fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI) } var options []oauth2.AuthCodeOption if c.promptType != "" { options = append(options, oauth2.SetAuthURLParam("prompt", c.promptType)) } if c.domainHint != "" { options = append(options, oauth2.SetAuthURLParam("domain_hint", c.domainHint)) } return c.oauth2Config(scopes).AuthCodeURL(state, options...), nil, nil } func (c *microsoftConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, &oauth2Error{errType, q.Get("error_description")} } oauth2Config := c.oauth2Config(s) ctx := r.Context() token, err := oauth2Config.Exchange(ctx, q.Get("code")) if err != nil { return identity, fmt.Errorf("microsoft: failed to get token: %v", err) } client := oauth2Config.Client(ctx, token) user, err := c.user(ctx, client) if err != nil { return identity, fmt.Errorf("microsoft: get user: %v", err) } if c.emailToLowercase { user.Email = strings.ToLower(user.Email) } identity = connector.Identity{ UserID: user.ID, Username: user.Name, Email: user.Email, EmailVerified: true, } if c.groupsRequired(s.Groups) { groups, err := c.getGroups(ctx, client, user.ID) if err != nil { return identity, fmt.Errorf("microsoft: get groups: %w", err) } identity.Groups = groups } if s.OfflineAccess { data := connectorData{ AccessToken: token.AccessToken, RefreshToken: token.RefreshToken, Expiry: token.Expiry, } connData, err := json.Marshal(data) if err != nil { return identity, fmt.Errorf("microsoft: marshal connector data: %v", err) } identity.ConnectorData = connData } return identity, nil } type tokenNotifyFunc func(*oauth2.Token) error // notifyRefreshTokenSource is essentially `oauth2.ReuseTokenSource` with `TokenNotifyFunc` added. type notifyRefreshTokenSource struct { new oauth2.TokenSource mu sync.Mutex // guards t t *oauth2.Token f tokenNotifyFunc // called when token refreshed so new refresh token can be persisted } // Token returns the current token if it's still valid, else will // refresh the current token (using r.Context for HTTP client // information) and return the new one. func (s *notifyRefreshTokenSource) Token() (*oauth2.Token, error) { s.mu.Lock() defer s.mu.Unlock() if s.t.Valid() { return s.t, nil } t, err := s.new.Token() if err != nil { return nil, err } s.t = t return t, s.f(t) } func (c *microsoftConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { if len(identity.ConnectorData) == 0 { return identity, errors.New("microsoft: no upstream access token found") } var data connectorData if err := json.Unmarshal(identity.ConnectorData, &data); err != nil { return identity, fmt.Errorf("microsoft: unmarshal access token: %v", err) } tok := &oauth2.Token{ AccessToken: data.AccessToken, RefreshToken: data.RefreshToken, Expiry: data.Expiry, } client := oauth2.NewClient(ctx, ¬ifyRefreshTokenSource{ new: c.oauth2Config(s).TokenSource(ctx, tok), t: tok, f: func(tok *oauth2.Token) error { data := connectorData{ AccessToken: tok.AccessToken, RefreshToken: tok.RefreshToken, Expiry: tok.Expiry, } connData, err := json.Marshal(data) if err != nil { return fmt.Errorf("microsoft: marshal connector data: %v", err) } identity.ConnectorData = connData return nil }, }) user, err := c.user(ctx, client) if err != nil { return identity, fmt.Errorf("microsoft: get user: %v", err) } identity.Username = user.Name identity.Email = user.Email if c.groupsRequired(s.Groups) { groups, err := c.getGroups(ctx, client, user.ID) if err != nil { return identity, fmt.Errorf("microsoft: get groups: %w", err) } identity.Groups = groups } return identity, nil } // https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/user // id - The unique identifier for the user. Inherited from // // directoryObject. Key. Not nullable. Read-only. // // displayName - The name displayed in the address book for the user. // // This is usually the combination of the user's first name, // middle initial and last name. This property is required // when a user is created and it cannot be cleared during // updates. Supports $filter and $orderby. // // userPrincipalName - The user principal name (UPN) of the user. // // The UPN is an Internet-style login name for the user // based on the Internet standard RFC 822. By convention, // this should map to the user's email name. The general // format is alias@domain, where domain must be present in // the tenant’s collection of verified domains. This // property is required when a user is created. The // verified domains for the tenant can be accessed from the // verifiedDomains property of organization. Supports // $filter and $orderby. type user struct { ID string `json:"id"` Name string `json:"displayName"` Email string `json:"userPrincipalName"` } func (c *microsoftConnector) user(ctx context.Context, client *http.Client) (u user, err error) { // https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_get req, err := http.NewRequest("GET", c.graphURL+"/v1.0/me?$select=id,displayName,userPrincipalName", nil) if err != nil { return u, fmt.Errorf("new req: %v", err) } resp, err := client.Do(req.WithContext(ctx)) if err != nil { return u, fmt.Errorf("get URL %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return u, newGraphError(resp.Body) } if err := json.NewDecoder(resp.Body).Decode(&u); err != nil { return u, fmt.Errorf("JSON decode: %v", err) } return u, err } // https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/group // displayName - The display name for the group. This property is required when // // a group is created and it cannot be cleared during updates. // Supports $filter and $orderby. type group struct { Name string `json:"displayName"` } func (c *microsoftConnector) getGroups(ctx context.Context, client *http.Client, userID string) ([]string, error) { userGroups, err := c.getGroupIDs(ctx, client) if err != nil { return nil, err } if c.groupNameFormat == GroupName { userGroups, err = c.getGroupNames(ctx, client, userGroups) if err != nil { return nil, err } } // ensure that the user is in at least one required group filteredGroups := groups_pkg.Filter(userGroups, c.groups) if len(c.groups) > 0 && len(filteredGroups) == 0 { return nil, &connector.UserNotInRequiredGroupsError{UserID: userID, Groups: c.groups} } else if c.useGroupsAsWhitelist { return filteredGroups, nil } return userGroups, nil } func (c *microsoftConnector) getGroupIDs(ctx context.Context, client *http.Client) (ids []string, err error) { // https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_getmembergroups in := &struct { SecurityEnabledOnly bool `json:"securityEnabledOnly"` }{c.onlySecurityGroups} reqURL := c.graphURL + "/v1.0/me/getMemberGroups" for { var out []string var next string next, err = c.post(ctx, client, reqURL, in, &out) if err != nil { return ids, err } ids = append(ids, out...) if next == "" { return } reqURL = next } } func (c *microsoftConnector) getGroupNames(ctx context.Context, client *http.Client, ids []string) (groups []string, err error) { if len(ids) == 0 { return } // https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/directoryobject_getbyids in := &struct { IDs []string `json:"ids"` Types []string `json:"types"` }{ids, []string{"group"}} reqURL := c.graphURL + "/v1.0/directoryObjects/getByIds" for { var out []group var next string next, err = c.post(ctx, client, reqURL, in, &out) if err != nil { return groups, err } for _, g := range out { groups = append(groups, g.Name) } if next == "" { return } reqURL = next } } func (c *microsoftConnector) post(ctx context.Context, client *http.Client, reqURL string, in interface{}, out interface{}) (string, error) { var payload bytes.Buffer err := json.NewEncoder(&payload).Encode(in) if err != nil { return "", fmt.Errorf("microsoft: JSON encode: %v", err) } req, err := http.NewRequest("POST", reqURL, &payload) if err != nil { return "", fmt.Errorf("new req: %v", err) } req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req.WithContext(ctx)) if err != nil { return "", fmt.Errorf("post URL %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", newGraphError(resp.Body) } var next string if err = json.NewDecoder(resp.Body).Decode(&struct { NextLink *string `json:"@odata.nextLink"` Value interface{} `json:"value"` }{&next, out}); err != nil { return "", fmt.Errorf("JSON decode: %v", err) } return next, nil } type graphError struct { Code string `json:"code"` Message string `json:"message"` } func (e *graphError) Error() string { return e.Code + ": " + e.Message } func newGraphError(r io.Reader) error { // https://developer.microsoft.com/en-us/graph/docs/concepts/errors var ge graphError if err := json.NewDecoder(r).Decode(&struct { Error *graphError `json:"error"` }{&ge}); err != nil { return fmt.Errorf("JSON error decode: %v", err) } return &ge } type oauth2Error struct { error string errorDescription string } func (e *oauth2Error) Error() string { if e.errorDescription == "" { return e.error } return e.error + ": " + e.errorDescription } ================================================ FILE: connector/microsoft/microsoft_test.go ================================================ package microsoft import ( "encoding/json" "errors" "fmt" "net/http" "net/http/httptest" "net/url" "os" "reflect" "testing" "github.com/dexidp/dex/connector" ) type testResponse struct { data interface{} } const ( tenant = "9b1c3439-a67e-4e92-bb0d-0571d44ca965" clientID = "a115ebf3-6020-4384-8eb1-c0c42e667b6f" ) var dummyToken = testResponse{data: map[string]interface{}{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9", "expires_in": "30", }} func TestLoginURL(t *testing.T) { testURL := "https://test.com" testState := "some-state" conn := microsoftConnector{ apiURL: testURL, graphURL: testURL, redirectURI: testURL, clientID: clientID, tenant: tenant, } loginURL, _, _ := conn.LoginURL(connector.Scopes{}, conn.redirectURI, testState) parsedLoginURL, _ := url.Parse(loginURL) queryParams := parsedLoginURL.Query() expectEquals(t, parsedLoginURL.Path, "/"+tenant+"/oauth2/v2.0/authorize") expectEquals(t, queryParams.Get("client_id"), clientID) expectEquals(t, queryParams.Get("redirect_uri"), testURL) expectEquals(t, queryParams.Get("response_type"), "code") expectEquals(t, queryParams.Get("scope"), "user.read") expectEquals(t, queryParams.Get("state"), testState) expectEquals(t, queryParams.Get("prompt"), "") expectEquals(t, queryParams.Get("domain_hint"), "") } func TestLoginURLWithOptions(t *testing.T) { testURL := "https://test.com" promptType := "consent" domainHint := "domain.hint" conn := microsoftConnector{ apiURL: testURL, graphURL: testURL, redirectURI: testURL, clientID: clientID, tenant: tenant, promptType: promptType, domainHint: domainHint, } loginURL, _, _ := conn.LoginURL(connector.Scopes{}, conn.redirectURI, "some-state") parsedLoginURL, _ := url.Parse(loginURL) queryParams := parsedLoginURL.Query() expectEquals(t, queryParams.Get("prompt"), promptType) expectEquals(t, queryParams.Get("domain_hint"), domainHint) } func TestUserIdentityFromGraphAPI(t *testing.T) { s := newTestServer(map[string]testResponse{ "/v1.0/me?$select=id,displayName,userPrincipalName": { data: user{ID: "S56767889", Name: "Jane Doe", Email: "jane.doe@example.com"}, }, "/" + tenant + "/oauth2/v2.0/token": dummyToken, }) defer s.Close() req, _ := http.NewRequest("GET", s.URL, nil) c := microsoftConnector{apiURL: s.URL, graphURL: s.URL, tenant: tenant} identity, err := c.HandleCallback(connector.Scopes{Groups: false}, nil, req) expectNil(t, err) expectEquals(t, identity.Username, "Jane Doe") expectEquals(t, identity.UserID, "S56767889") expectEquals(t, identity.PreferredUsername, "") expectEquals(t, identity.Email, "jane.doe@example.com") expectEquals(t, identity.EmailVerified, true) expectEquals(t, len(identity.Groups), 0) } func TestUserGroupsFromGraphAPI(t *testing.T) { s := newTestServer(map[string]testResponse{ "/v1.0/me?$select=id,displayName,userPrincipalName": {data: user{}}, "/v1.0/me/getMemberGroups": {data: map[string]interface{}{ "value": []string{"a", "b"}, }}, "/" + tenant + "/oauth2/v2.0/token": dummyToken, }) defer s.Close() req, _ := http.NewRequest("GET", s.URL, nil) c := microsoftConnector{apiURL: s.URL, graphURL: s.URL, tenant: tenant} identity, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req) expectNil(t, err) expectEquals(t, identity.Groups, []string{"a", "b"}) } func TestUserNotInRequiredGroupFromGraphAPI(t *testing.T) { s := newTestServer(map[string]testResponse{ "/v1.0/me?$select=id,displayName,userPrincipalName": { data: user{ID: "user-id-123", Name: "Jane Doe", Email: "jane.doe@example.com"}, }, // The user is a member of groups "c" and "d", but the connector only // allows group "a" — so the user should be denied. "/v1.0/me/getMemberGroups": {data: map[string]interface{}{ "value": []string{"c", "d"}, }}, "/" + tenant + "/oauth2/v2.0/token": dummyToken, }) defer s.Close() req, _ := http.NewRequest("GET", s.URL, nil) c := microsoftConnector{ apiURL: s.URL, graphURL: s.URL, tenant: tenant, groups: []string{"a"}, } _, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req) if err == nil { t.Fatal("expected error when user is not in any required group, got nil") } var groupsErr *connector.UserNotInRequiredGroupsError if !errors.As(err, &groupsErr) { t.Errorf("expected *connector.UserNotInRequiredGroupsError, got %T: %v", err, err) } } func newTestServer(responses map[string]testResponse) *httptest.Server { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { response, found := responses[r.RequestURI] if !found { fmt.Fprintf(os.Stderr, "Mock response for %q not found\n", r.RequestURI) http.NotFound(w, r) return } w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(response.data) })) return s } func expectNil(t *testing.T, a interface{}) { if a != nil { t.Errorf("Expected %+v to equal nil", a) } } func expectEquals(t *testing.T, a interface{}, b interface{}) { if !reflect.DeepEqual(a, b) { t.Errorf("Expected %+v to equal %+v", a, b) } } ================================================ FILE: connector/mock/connectortest.go ================================================ // Package mock implements connectors which help test various server components. package mock import ( "context" "errors" "fmt" "log/slog" "net/http" "net/url" "github.com/dexidp/dex/connector" ) // NewCallbackConnector returns a mock connector which requires no user interaction. It always returns // the same (fake) identity. func NewCallbackConnector(logger *slog.Logger) connector.Connector { return &Callback{ Identity: connector.Identity{ UserID: "0-385-28089-0", Username: "Kilgore Trout", Email: "kilgore@kilgore.trout", EmailVerified: true, Groups: []string{"authors"}, ConnectorData: connectorData, }, Logger: logger, } } var ( _ connector.CallbackConnector = &Callback{} _ connector.RefreshConnector = &Callback{} _ connector.TokenIdentityConnector = &Callback{} ) // Callback is a connector that requires no user interaction and always returns the same identity. type Callback struct { // The returned identity. Identity connector.Identity Logger *slog.Logger } // LoginURL returns the URL to redirect the user to login with. func (m *Callback) LoginURL(s connector.Scopes, callbackURL, state string) (string, []byte, error) { u, err := url.Parse(callbackURL) if err != nil { return "", nil, fmt.Errorf("failed to parse callbackURL %q: %v", callbackURL, err) } v := u.Query() v.Set("state", state) u.RawQuery = v.Encode() return u.String(), nil, nil } var connectorData = []byte("foobar") // HandleCallback parses the request and returns the user's identity func (m *Callback) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (connector.Identity, error) { return m.Identity, nil } // Refresh updates the identity during a refresh token request. func (m *Callback) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { return m.Identity, nil } func (m *Callback) TokenIdentity(ctx context.Context, subjectTokenType, subjectToken string) (connector.Identity, error) { return m.Identity, nil } // CallbackConfig holds the configuration parameters for a connector which requires no interaction. type CallbackConfig struct{} // Open returns an authentication strategy which requires no user interaction. func (c *CallbackConfig) Open(id string, logger *slog.Logger) (connector.Connector, error) { logger = logger.With(slog.Group("connector", "type", "callback", "id", id)) return NewCallbackConnector(logger), nil } // PasswordConfig holds the configuration for a mock connector which prompts for the supplied // username and password. type PasswordConfig struct { Username string `json:"username"` Password string `json:"password"` } // Open returns an authentication strategy which prompts for a predefined username and password. func (c *PasswordConfig) Open(id string, logger *slog.Logger) (connector.Connector, error) { if c.Username == "" { return nil, errors.New("no username supplied") } if c.Password == "" { return nil, errors.New("no password supplied") } return &passwordConnector{c.Username, c.Password, logger}, nil } var ( _ connector.PasswordConnector = passwordConnector{} _ connector.RefreshConnector = passwordConnector{} ) type passwordConnector struct { username string password string logger *slog.Logger } func (p passwordConnector) Close() error { return nil } func (p passwordConnector) Login(ctx context.Context, s connector.Scopes, username, password string) (identity connector.Identity, validPassword bool, err error) { if username == p.username && password == p.password { return connector.Identity{ UserID: "0-385-28089-0", Username: "Kilgore Trout", Email: "kilgore@kilgore.trout", EmailVerified: true, ConnectorData: []byte(`{"test": "true"}`), }, true, nil } return identity, false, nil } func (p passwordConnector) Prompt() string { return "" } func (p passwordConnector) Refresh(_ context.Context, _ connector.Scopes, identity connector.Identity) (connector.Identity, error) { return identity, nil } ================================================ FILE: connector/oauth/oauth.go ================================================ package oauth import ( "context" "encoding/base64" "encoding/json" "errors" "fmt" "log/slog" "net/http" "strings" "golang.org/x/oauth2" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/httpclient" ) var _ connector.CallbackConnector = (*oauthConnector)(nil) type oauthConnector struct { clientID string clientSecret string redirectURI string tokenURL string authorizationURL string userInfoURL string scopes []string userIDKey string userNameKey string preferredUsernameKey string emailKey string emailVerifiedKey string groupsKey string httpClient *http.Client logger *slog.Logger } type connectorData struct { AccessToken string } type Config struct { ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` RedirectURI string `json:"redirectURI"` TokenURL string `json:"tokenURL"` AuthorizationURL string `json:"authorizationURL"` UserInfoURL string `json:"userInfoURL"` Scopes []string `json:"scopes"` RootCAs []string `json:"rootCAs"` InsecureSkipVerify bool `json:"insecureSkipVerify"` UserIDKey string `json:"userIDKey"` // defaults to "id" ClaimMapping struct { UserNameKey string `json:"userNameKey"` // defaults to "user_name" PreferredUsernameKey string `json:"preferredUsernameKey"` // defaults to "preferred_username" GroupsKey string `json:"groupsKey"` // defaults to "groups" EmailKey string `json:"emailKey"` // defaults to "email" EmailVerifiedKey string `json:"emailVerifiedKey"` // defaults to "email_verified" } `json:"claimMapping"` } func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { var err error userIDKey := c.UserIDKey if userIDKey == "" { userIDKey = "id" } userNameKey := c.ClaimMapping.UserNameKey if userNameKey == "" { userNameKey = "user_name" } preferredUsernameKey := c.ClaimMapping.PreferredUsernameKey if preferredUsernameKey == "" { preferredUsernameKey = "preferred_username" } groupsKey := c.ClaimMapping.GroupsKey if groupsKey == "" { groupsKey = "groups" } emailKey := c.ClaimMapping.EmailKey if emailKey == "" { emailKey = "email" } emailVerifiedKey := c.ClaimMapping.EmailVerifiedKey if emailVerifiedKey == "" { emailVerifiedKey = "email_verified" } oauthConn := &oauthConnector{ clientID: c.ClientID, clientSecret: c.ClientSecret, tokenURL: c.TokenURL, authorizationURL: c.AuthorizationURL, userInfoURL: c.UserInfoURL, scopes: c.Scopes, redirectURI: c.RedirectURI, logger: logger.With(slog.Group("connector", "type", "oauth", "id", id)), userIDKey: userIDKey, userNameKey: userNameKey, preferredUsernameKey: preferredUsernameKey, groupsKey: groupsKey, emailKey: emailKey, emailVerifiedKey: emailVerifiedKey, } oauthConn.httpClient, err = httpclient.NewHTTPClient(c.RootCAs, c.InsecureSkipVerify) if err != nil { return nil, err } return oauthConn, err } func (c *oauthConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) { if c.redirectURI != callbackURL { return "", nil, fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI) } oauth2Config := &oauth2.Config{ ClientID: c.clientID, ClientSecret: c.clientSecret, Endpoint: oauth2.Endpoint{TokenURL: c.tokenURL, AuthURL: c.authorizationURL}, RedirectURL: c.redirectURI, Scopes: c.scopes, } return oauth2Config.AuthCodeURL(state), nil, nil } func (c *oauthConnector) HandleCallback(s connector.Scopes, _ []byte, r *http.Request) (identity connector.Identity, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, errors.New(q.Get("error_description")) } oauth2Config := &oauth2.Config{ ClientID: c.clientID, ClientSecret: c.clientSecret, Endpoint: oauth2.Endpoint{TokenURL: c.tokenURL, AuthURL: c.authorizationURL}, RedirectURL: c.redirectURI, Scopes: c.scopes, } ctx := context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient) token, err := oauth2Config.Exchange(ctx, q.Get("code")) if err != nil { return identity, fmt.Errorf("OAuth connector: failed to get token: %v", err) } client := oauth2.NewClient(ctx, oauth2.StaticTokenSource(token)) userInfoResp, err := client.Get(c.userInfoURL) if err != nil { return identity, fmt.Errorf("OAuth Connector: failed to execute request to userinfo: %v", err) } defer userInfoResp.Body.Close() if userInfoResp.StatusCode != http.StatusOK { return identity, fmt.Errorf("OAuth Connector: failed to execute request to userinfo: status %d", userInfoResp.StatusCode) } var userInfoResult map[string]interface{} err = json.NewDecoder(userInfoResp.Body).Decode(&userInfoResult) if err != nil { return identity, fmt.Errorf("OAuth Connector: failed to parse userinfo: %v", err) } userID, found := userInfoResult[c.userIDKey] if !found { return identity, fmt.Errorf("OAuth Connector: not found %v claim", c.userIDKey) } switch userID.(type) { case float64, int64, string: identity.UserID = fmt.Sprintf("%v", userID) default: return identity, fmt.Errorf("OAuth Connector: %v claim should be string or number, got %T", c.userIDKey, userID) } identity.Username, _ = userInfoResult[c.userNameKey].(string) identity.PreferredUsername, _ = userInfoResult[c.preferredUsernameKey].(string) identity.Email, _ = userInfoResult[c.emailKey].(string) identity.EmailVerified, _ = userInfoResult[c.emailVerifiedKey].(bool) if s.Groups { groups := map[string]struct{}{} c.addGroupsFromMap(groups, userInfoResult) c.addGroupsFromToken(groups, token.AccessToken) for groupName := range groups { identity.Groups = append(identity.Groups, groupName) } } if s.OfflineAccess { data := connectorData{AccessToken: token.AccessToken} connData, err := json.Marshal(data) if err != nil { return identity, fmt.Errorf("OAuth Connector: failed to parse connector data for offline access: %v", err) } identity.ConnectorData = connData } return identity, nil } func (c *oauthConnector) addGroupsFromMap(groups map[string]struct{}, result map[string]interface{}) error { groupsClaim, ok := result[c.groupsKey].([]interface{}) if !ok { return errors.New("cannot convert to slice") } for _, group := range groupsClaim { if groupString, ok := group.(string); ok { groups[groupString] = struct{}{} } if groupMap, ok := group.(map[string]interface{}); ok { if groupName, ok := groupMap["name"].(string); ok { groups[groupName] = struct{}{} } } } return nil } func (c *oauthConnector) addGroupsFromToken(groups map[string]struct{}, token string) error { parts := strings.Split(token, ".") if len(parts) < 2 { return errors.New("invalid token") } decoded, err := decode(parts[1]) if err != nil { return err } var claimsMap map[string]interface{} err = json.Unmarshal(decoded, &claimsMap) if err != nil { return err } return c.addGroupsFromMap(groups, claimsMap) } func decode(seg string) ([]byte, error) { if l := len(seg) % 4; l > 0 { seg += strings.Repeat("=", 4-l) } return base64.URLEncoding.DecodeString(seg) } ================================================ FILE: connector/oauth/oauth_test.go ================================================ package oauth import ( "crypto/rand" "crypto/rsa" "encoding/json" "errors" "fmt" "log/slog" "net/http" "net/http/httptest" "net/url" "sort" "testing" "github.com/go-jose/go-jose/v4" "github.com/stretchr/testify/assert" "github.com/dexidp/dex/connector" ) func TestOpen(t *testing.T) { tokenClaims := map[string]interface{}{} userInfoClaims := map[string]interface{}{} testServer := testSetup(t, tokenClaims, userInfoClaims) defer testServer.Close() conn := newConnector(t, testServer.URL) sort.Strings(conn.scopes) assert.Equal(t, conn.clientID, "testClient") assert.Equal(t, conn.clientSecret, "testSecret") assert.Equal(t, conn.redirectURI, testServer.URL+"/callback") assert.Equal(t, conn.tokenURL, testServer.URL+"/token") assert.Equal(t, conn.authorizationURL, testServer.URL+"/authorize") assert.Equal(t, conn.userInfoURL, testServer.URL+"/userinfo") assert.Equal(t, len(conn.scopes), 2) assert.Equal(t, conn.scopes[0], "groups") assert.Equal(t, conn.scopes[1], "openid") } func TestLoginURL(t *testing.T) { tokenClaims := map[string]interface{}{} userInfoClaims := map[string]interface{}{} testServer := testSetup(t, tokenClaims, userInfoClaims) defer testServer.Close() conn := newConnector(t, testServer.URL) loginURL, _, err := conn.LoginURL(connector.Scopes{}, conn.redirectURI, "some-state") assert.Equal(t, err, nil) expectedURL, err := url.Parse(testServer.URL + "/authorize") assert.Equal(t, err, nil) values := url.Values{} values.Add("client_id", "testClient") values.Add("redirect_uri", conn.redirectURI) values.Add("response_type", "code") values.Add("scope", "openid groups") values.Add("state", "some-state") expectedURL.RawQuery = values.Encode() assert.Equal(t, loginURL, expectedURL.String()) } func TestHandleCallBackForGroupsInUserInfo(t *testing.T) { tokenClaims := map[string]interface{}{} userInfoClaims := map[string]interface{}{ "name": "test-name", "user_id_key": "test-user-id", "user_name_key": "test-username", "preferred_username": "test-preferred-username", "mail": "mod_mail", "has_verified_email": false, "groups_key": []string{"admin-group", "user-group"}, } testServer := testSetup(t, tokenClaims, userInfoClaims) defer testServer.Close() conn := newConnector(t, testServer.URL) req := newRequestWithAuthCode(t, testServer.URL, "TestHandleCallBackForGroupsInUserInfo") identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, req) assert.Equal(t, err, nil) sort.Strings(identity.Groups) assert.Equal(t, len(identity.Groups), 2) assert.Equal(t, identity.Groups[0], "admin-group") assert.Equal(t, identity.Groups[1], "user-group") assert.Equal(t, identity.UserID, "test-user-id") assert.Equal(t, identity.Username, "test-username") assert.Equal(t, identity.PreferredUsername, "test-preferred-username") assert.Equal(t, identity.Email, "mod_mail") assert.Equal(t, identity.EmailVerified, false) } func TestHandleCallBackForGroupMapsInUserInfo(t *testing.T) { tokenClaims := map[string]interface{}{} userInfoClaims := map[string]interface{}{ "name": "test-name", "user_id_key": "test-user-id", "user_name_key": "test-username", "preferred_username": "test-preferred-username", "mail": "mod_mail", "has_verified_email": false, "groups_key": []interface{}{ map[string]string{"name": "admin-group", "id": "111"}, map[string]string{"name": "user-group", "id": "222"}, }, } testServer := testSetup(t, tokenClaims, userInfoClaims) defer testServer.Close() conn := newConnector(t, testServer.URL) req := newRequestWithAuthCode(t, testServer.URL, "TestHandleCallBackForGroupMapsInUserInfo") identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, req) assert.Equal(t, err, nil) sort.Strings(identity.Groups) assert.Equal(t, len(identity.Groups), 2) assert.Equal(t, identity.Groups[0], "admin-group") assert.Equal(t, identity.Groups[1], "user-group") assert.Equal(t, identity.UserID, "test-user-id") assert.Equal(t, identity.Username, "test-username") assert.Equal(t, identity.PreferredUsername, "test-preferred-username") assert.Equal(t, identity.Email, "mod_mail") assert.Equal(t, identity.EmailVerified, false) } func TestHandleCallBackForGroupsInToken(t *testing.T) { tokenClaims := map[string]interface{}{ "groups_key": []string{"test-group"}, } userInfoClaims := map[string]interface{}{ "name": "test-name", "user_id_key": "test-user-id", "user_name_key": "test-username", "preferred_username": "test-preferred-username", "email": "test-email", "email_verified": true, } testServer := testSetup(t, tokenClaims, userInfoClaims) defer testServer.Close() conn := newConnector(t, testServer.URL) req := newRequestWithAuthCode(t, testServer.URL, "TestHandleCallBackForGroupsInToken") identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, req) assert.Equal(t, err, nil) assert.Equal(t, len(identity.Groups), 1) assert.Equal(t, identity.Groups[0], "test-group") assert.Equal(t, identity.PreferredUsername, "test-preferred-username") assert.Equal(t, identity.UserID, "test-user-id") assert.Equal(t, identity.Username, "test-username") assert.Equal(t, identity.Email, "") assert.Equal(t, identity.EmailVerified, false) } func TestHandleCallbackForNumericUserID(t *testing.T) { tokenClaims := map[string]interface{}{} userInfoClaims := map[string]interface{}{ "name": "test-name", "user_id_key": 1000, "user_name_key": "test-username", "preferred_username": "test-preferred-username", "mail": "mod_mail", "has_verified_email": false, } testServer := testSetup(t, tokenClaims, userInfoClaims) defer testServer.Close() conn := newConnector(t, testServer.URL) req := newRequestWithAuthCode(t, testServer.URL, "TestHandleCallbackForNumericUserID") identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, req) assert.Equal(t, err, nil) assert.Equal(t, identity.UserID, "1000") assert.Equal(t, identity.Username, "test-username") assert.Equal(t, identity.PreferredUsername, "test-preferred-username") assert.Equal(t, identity.Email, "mod_mail") assert.Equal(t, identity.EmailVerified, false) } func testSetup(t *testing.T, tokenClaims map[string]interface{}, userInfoClaims map[string]interface{}) *httptest.Server { key, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { t.Fatal("Failed to generate rsa key", err) } jwk := jose.JSONWebKey{ Key: key, KeyID: "some-key", Algorithm: "RSA", } mux := http.NewServeMux() mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { token, err := newToken(&jwk, tokenClaims) if err != nil { t.Fatal("unable to generate token", err) } w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(&map[string]string{ "access_token": token, "id_token": token, "token_type": "Bearer", }) }) mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(userInfoClaims) }) return httptest.NewServer(mux) } func newToken(key *jose.JSONWebKey, claims map[string]interface{}) (string, error) { signingKey := jose.SigningKey{Key: key, Algorithm: jose.RS256} signer, err := jose.NewSigner(signingKey, &jose.SignerOptions{}) if err != nil { return "", fmt.Errorf("new signer: %v", err) } payload, err := json.Marshal(claims) if err != nil { return "", fmt.Errorf("marshaling claims: %v", err) } signature, err := signer.Sign(payload) if err != nil { return "", fmt.Errorf("signing payload: %v", err) } return signature.CompactSerialize() } func newConnector(t *testing.T, serverURL string) *oauthConnector { testConfig := Config{ ClientID: "testClient", ClientSecret: "testSecret", RedirectURI: serverURL + "/callback", TokenURL: serverURL + "/token", AuthorizationURL: serverURL + "/authorize", UserInfoURL: serverURL + "/userinfo", Scopes: []string{"openid", "groups"}, UserIDKey: "user_id_key", } testConfig.ClaimMapping.UserNameKey = "user_name_key" testConfig.ClaimMapping.GroupsKey = "groups_key" testConfig.ClaimMapping.EmailKey = "mail" testConfig.ClaimMapping.EmailVerifiedKey = "has_verified_email" log := slog.New(slog.DiscardHandler) conn, err := testConfig.Open("id", log) if err != nil { t.Fatal(err) } oauthConn, ok := conn.(*oauthConnector) if !ok { t.Fatal(errors.New("failed to convert to oauthConnector")) } return oauthConn } func newRequestWithAuthCode(t *testing.T, serverURL string, code string) *http.Request { req, err := http.NewRequest("GET", serverURL, nil) if err != nil { t.Fatal("failed to create request", err) } values := req.URL.Query() values.Add("code", code) req.URL.RawQuery = values.Encode() return req } ================================================ FILE: connector/oidc/oidc.go ================================================ // Package oidc implements logging in through OpenID Connect providers. package oidc import ( "context" "encoding/json" "errors" "fmt" "log/slog" "net/http" "net/url" "regexp" "strings" "time" "github.com/coreos/go-oidc/v3/oidc" "golang.org/x/oauth2" "github.com/dexidp/dex/connector" groups_pkg "github.com/dexidp/dex/pkg/groups" "github.com/dexidp/dex/pkg/httpclient" ) const ( codeChallengeMethodPlain = "plain" codeChallengeMethodS256 = "S256" ) func contains(arr []string, item string) bool { for _, itemFromArray := range arr { if itemFromArray == item { return true } } return false } // Config holds configuration options for OpenID Connect logins. type Config struct { Issuer string `json:"issuer"` // Some offspec providers like Azure, Oracle IDCS have oidc discovery url // different from issuer url which causes issuerValidation to fail // IssuerAlias provides a way to override the Issuer url // from the .well-known/openid-configuration issuer IssuerAlias string `json:"issuerAlias"` ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` RedirectURI string `json:"redirectURI"` // The section to override options discovered automatically from // the providers' discovery URL (.well-known/openid-configuration). ProviderDiscoveryOverrides ProviderDiscoveryOverrides `json:"providerDiscoveryOverrides"` // Causes client_secret to be passed as POST parameters instead of basic // auth. This is specifically "NOT RECOMMENDED" by the OAuth2 RFC, but some // providers require it. // // https://tools.ietf.org/html/rfc6749#section-2.3.1 BasicAuthUnsupported *bool `json:"basicAuthUnsupported"` Scopes []string `json:"scopes"` // defaults to "profile" and "email" // HostedDomains was an optional list of whitelisted domains when using the OIDC connector with Google. // Only users from a whitelisted domain were allowed to log in. // Support for this option was removed from the OIDC connector. // Consider switching to the Google connector which supports this option. // // Deprecated: will be removed in future releases. HostedDomains []string `json:"hostedDomains"` // Certificates for SSL validation RootCAs []string `json:"rootCAs"` // Override the value of email_verified to true in the returned claims InsecureSkipEmailVerified bool `json:"insecureSkipEmailVerified"` // InsecureEnableGroups enables groups claims. This is disabled by default until https://github.com/dexidp/dex/issues/1065 is resolved InsecureEnableGroups bool `json:"insecureEnableGroups"` AllowedGroups []string `json:"allowedGroups"` // AcrValues (Authentication Context Class Reference Values) that specifies the Authentication Context Class Values // within the Authentication Request that the Authorization Server is being requested to use for // processing requests from this Client, with the values appearing in order of preference. AcrValues []string `json:"acrValues"` // Disable certificate verification InsecureSkipVerify bool `json:"insecureSkipVerify"` // GetUserInfo uses the userinfo endpoint to get additional claims for // the token. This is especially useful where upstreams return "thin" // id tokens GetUserInfo bool `json:"getUserInfo"` UserIDKey string `json:"userIDKey"` UserNameKey string `json:"userNameKey"` // PromptType will be used for the prompt parameter (when offline_access, by default prompt=consent) PromptType *string `json:"promptType"` // PKCEChallenge specifies which PKCE algorithm will be used // If not setted it will be auto-detected the best-fit for the connector. PKCEChallenge string `json:"pkceChallenge"` // OverrideClaimMapping will be used to override the options defined in claimMappings. // i.e. if there are 'email' and `preferred_email` claims available, by default Dex will always use the `email` claim independent of the ClaimMapping.EmailKey. // This setting allows you to override the default behavior of Dex and enforce the mappings defined in `claimMapping`. OverrideClaimMapping bool `json:"overrideClaimMapping"` // defaults to false ClaimMapping struct { // Configurable key which contains the preferred username claims PreferredUsernameKey string `json:"preferred_username"` // defaults to "preferred_username" // Configurable key which contains the email claims EmailKey string `json:"email"` // defaults to "email" // Configurable key which contains the groups claims GroupsKey string `json:"groups"` // defaults to "groups" } `json:"claimMapping"` // ClaimMutations holds all claim mutations options ClaimMutations struct { NewGroupFromClaims []NewGroupFromClaims `json:"newGroupFromClaims"` FilterGroupClaims FilterGroupClaims `json:"filterGroupClaims"` ModifyGroupNames ModifyGroupNames `json:"modifyGroupNames"` } `json:"claimModifications"` } type ProviderDiscoveryOverrides struct { // TokenURL provides a way to user overwrite the Token URL // from the .well-known/openid-configuration token_endpoint TokenURL string `json:"tokenURL"` // AuthURL provides a way to user overwrite the Auth URL // from the .well-known/openid-configuration authorization_endpoint AuthURL string `json:"authURL"` // JWKSURL provides a way to user overwrite the JWKS URL // from the .well-known/openid-configuration jwks_uri JWKSURL string `json:"jwksURL"` } func (o *ProviderDiscoveryOverrides) Empty() bool { return o.TokenURL == "" && o.AuthURL == "" && o.JWKSURL == "" } func getProvider(ctx context.Context, issuer string, overrides ProviderDiscoveryOverrides) (*oidc.Provider, error) { provider, err := oidc.NewProvider(ctx, issuer) if err != nil { return nil, fmt.Errorf("failed to get provider: %v", err) } if overrides.Empty() { return provider, nil } v := &struct { Issuer string `json:"issuer"` AuthURL string `json:"authorization_endpoint"` TokenURL string `json:"token_endpoint"` DeviceAuthURL string `json:"device_authorization_endpoint"` JWKSURL string `json:"jwks_uri"` UserInfoURL string `json:"userinfo_endpoint"` Algorithms []string `json:"id_token_signing_alg_values_supported"` }{} if err := provider.Claims(v); err != nil { return nil, fmt.Errorf("failed to extract provider discovery claims: %v", err) } config := oidc.ProviderConfig{ IssuerURL: v.Issuer, AuthURL: v.AuthURL, TokenURL: v.TokenURL, DeviceAuthURL: v.DeviceAuthURL, JWKSURL: v.JWKSURL, UserInfoURL: v.UserInfoURL, Algorithms: v.Algorithms, } if overrides.TokenURL != "" { config.TokenURL = overrides.TokenURL } if overrides.AuthURL != "" { config.AuthURL = overrides.AuthURL } if overrides.JWKSURL != "" { config.JWKSURL = overrides.JWKSURL } return config.NewProvider(context.Background()), nil } // NewGroupFromClaims creates a new group from a list of claims and appends it to the list of existing groups. type NewGroupFromClaims struct { // List of claim to join together Claims []string `json:"claims"` // String to separate the claims Delimiter string `json:"delimiter"` // Should Dex remove the Delimiter string from claim values // This is done to keep resulting claim structure in full control of the Dex operator ClearDelimiter bool `json:"clearDelimiter"` // String to place before the first claim Prefix string `json:"prefix"` } // FilterGroupClaims is a regex filter for to keep only the matching groups. // This is useful when the groups list is too large to fit within an HTTP header. type FilterGroupClaims struct { GroupsFilter string `json:"groupsFilter"` } // ModifyGroupNames allows to modify the group claims by adding a prefix and/or suffix to each group. type ModifyGroupNames struct { Prefix string `json:"prefix"` Suffix string `json:"suffix"` } // Domains that don't support basic auth. golang.org/x/oauth2 has an internal // list, but it only matches specific URLs, not top level domains. var brokenAuthHeaderDomains = []string{ // See: https://github.com/dexidp/dex/issues/859 "okta.com", "oktapreview.com", } // connectorData stores information for sessions authenticated by this connector type connectorData struct { RefreshToken []byte } // Detect auth header provider issues for known providers. This lets users // avoid having to explicitly set "basicAuthUnsupported" in their config. // // Setting the config field always overrides values returned by this function. func knownBrokenAuthHeaderProvider(issuerURL string) bool { if u, err := url.Parse(issuerURL); err == nil { for _, host := range brokenAuthHeaderDomains { if u.Host == host || strings.HasSuffix(u.Host, "."+host) { return true } } } return false } // PKCEChallengeData is used to store info for PKCE Challenge method and verifier // in the connectorData type PKCEChallengeData struct { CodeChallenge string `json:"codeChallenge"` CodeChallengeMethod string `json:"codeChallengeMethod"` } // Returns an AuthCodeOption according to the provided codeChallengeMethod func getAuthCodeOptionForCodeChallenge(codeVerifier, codeChallengeMethod string) (oauth2.AuthCodeOption, error) { switch codeChallengeMethod { case codeChallengeMethodPlain: return oauth2.VerifierOption(codeVerifier), nil case codeChallengeMethodS256: return oauth2.S256ChallengeOption(codeVerifier), nil default: return nil, fmt.Errorf("unknown challenge method (%v)", codeChallengeMethod) } } // Open returns a connector which can be used to login users through an upstream // OpenID Connect provider. func (c *Config) Open(id string, logger *slog.Logger) (conn connector.Connector, err error) { if len(c.HostedDomains) > 0 { return nil, fmt.Errorf("support for the Hosted domains option had been deprecated and removed, consider switching to the Google connector") } httpClient, err := httpclient.NewHTTPClient(c.RootCAs, c.InsecureSkipVerify) if err != nil { return nil, err } bgctx, cancel := context.WithCancel(context.Background()) ctx := context.WithValue(bgctx, oauth2.HTTPClient, httpClient) if c.IssuerAlias != "" { ctx = oidc.InsecureIssuerURLContext(ctx, c.IssuerAlias) } provider, err := getProvider(ctx, c.Issuer, c.ProviderDiscoveryOverrides) if err != nil { cancel() return nil, err } if !c.ProviderDiscoveryOverrides.Empty() { logger.Warn("overrides for connector are set, this can be a vulnerability when not properly configured", "connector_id", id) } endpoint := provider.Endpoint() if c.BasicAuthUnsupported != nil { // Setting "basicAuthUnsupported" always overrides our detection. if *c.BasicAuthUnsupported { endpoint.AuthStyle = oauth2.AuthStyleInParams } } else if knownBrokenAuthHeaderProvider(c.Issuer) { endpoint.AuthStyle = oauth2.AuthStyleInParams } scopes := []string{oidc.ScopeOpenID} if len(c.Scopes) > 0 { scopes = append(scopes, c.Scopes...) } else { scopes = append(scopes, "profile", "email") } // PromptType should be "consent" by default, if not set promptType := "consent" if c.PromptType != nil { promptType = *c.PromptType } var groupsFilter *regexp.Regexp if c.ClaimMutations.FilterGroupClaims.GroupsFilter != "" { groupsFilter, err = regexp.Compile(c.ClaimMutations.FilterGroupClaims.GroupsFilter) if err != nil { logger.Warn("ignoring invalid", "invalid_regex", c.ClaimMutations.FilterGroupClaims.GroupsFilter, "connector_id", id) } } // Obtain CodeChallengeMethodsSupported from the provider var metadata struct { CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"` } if err := provider.Claims(&metadata); err != nil { logger.Warn("failed to parse provider metadata") } // if PKCEChallenge method has not been setted in the config, auto-detect the best fit if c.PKCEChallenge == "" { if contains(metadata.CodeChallengeMethodsSupported, codeChallengeMethodS256) { c.PKCEChallenge = codeChallengeMethodS256 } else if contains(metadata.CodeChallengeMethodsSupported, codeChallengeMethodPlain) { c.PKCEChallenge = codeChallengeMethodPlain } } else { // if PKCEChallenge method has been setted in the config, check if it is supported if !contains(metadata.CodeChallengeMethodsSupported, c.PKCEChallenge) { logger.Warn("provided PKCEChallenge method not supported by the connector") } } clientID := c.ClientID return &oidcConnector{ provider: provider, redirectURI: c.RedirectURI, oauth2Config: &oauth2.Config{ ClientID: clientID, ClientSecret: c.ClientSecret, Endpoint: endpoint, Scopes: scopes, RedirectURL: c.RedirectURI, }, verifier: provider.VerifierContext( ctx, // Pass our ctx with customized http.Client &oidc.Config{ClientID: clientID}, ), logger: logger.With(slog.Group("connector", "type", "oidc", "id", id)), cancel: cancel, httpClient: httpClient, insecureSkipEmailVerified: c.InsecureSkipEmailVerified, insecureEnableGroups: c.InsecureEnableGroups, allowedGroups: c.AllowedGroups, acrValues: c.AcrValues, getUserInfo: c.GetUserInfo, promptType: promptType, userIDKey: c.UserIDKey, userNameKey: c.UserNameKey, overrideClaimMapping: c.OverrideClaimMapping, preferredUsernameKey: c.ClaimMapping.PreferredUsernameKey, emailKey: c.ClaimMapping.EmailKey, groupsKey: c.ClaimMapping.GroupsKey, newGroupFromClaims: c.ClaimMutations.NewGroupFromClaims, groupsFilter: groupsFilter, groupsPrefix: c.ClaimMutations.ModifyGroupNames.Prefix, groupsSuffix: c.ClaimMutations.ModifyGroupNames.Suffix, pkceChallenge: c.PKCEChallenge, }, nil } var ( _ connector.CallbackConnector = (*oidcConnector)(nil) _ connector.RefreshConnector = (*oidcConnector)(nil) _ connector.TokenIdentityConnector = (*oidcConnector)(nil) ) type oidcConnector struct { provider *oidc.Provider redirectURI string oauth2Config *oauth2.Config verifier *oidc.IDTokenVerifier cancel context.CancelFunc logger *slog.Logger httpClient *http.Client insecureSkipEmailVerified bool insecureEnableGroups bool allowedGroups []string acrValues []string getUserInfo bool promptType string userIDKey string userNameKey string overrideClaimMapping bool preferredUsernameKey string emailKey string groupsKey string newGroupFromClaims []NewGroupFromClaims groupsFilter *regexp.Regexp groupsPrefix string groupsSuffix string pkceChallenge string } func (c *oidcConnector) Close() error { c.cancel() return nil } func (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string) (string, []byte, error) { if c.redirectURI != callbackURL { return "", nil, fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI) } var opts []oauth2.AuthCodeOption var connectorData []byte if len(c.acrValues) > 0 { acrValues := strings.Join(c.acrValues, " ") opts = append(opts, oauth2.SetAuthURLParam("acr_values", acrValues)) } if s.OfflineAccess { opts = append(opts, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", c.promptType)) } if c.pkceChallenge != "" { codeVerifier := oauth2.GenerateVerifier() authCodeOption, err := getAuthCodeOptionForCodeChallenge(codeVerifier, c.pkceChallenge) if err != nil { return "", nil, fmt.Errorf("oidc: failed to get PKCE AuthCodeOption for CodeChallenge: %v", err) } data := PKCEChallengeData{ CodeChallenge: codeVerifier, CodeChallengeMethod: c.pkceChallenge, } connectorData, err = json.Marshal(data) if err != nil { return "", nil, fmt.Errorf("oidc: failed to create PKCEChallenge data: %v", err) } opts = append(opts, authCodeOption) } return c.oauth2Config.AuthCodeURL(state, opts...), connectorData, nil } type oauth2Error struct { error string errorDescription string } func (e *oauth2Error) Error() string { if e.errorDescription == "" { return e.error } return e.error + ": " + e.errorDescription } type caller uint const ( createCaller caller = iota refreshCaller exchangeCaller ) func (c *oidcConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, &oauth2Error{errType, q.Get("error_description")} } ctx := context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient) var opts []oauth2.AuthCodeOption if c.pkceChallenge != "" { var data PKCEChallengeData if err := json.Unmarshal(connData, &data); err != nil { return identity, fmt.Errorf("oidc: failed to parse PKCEChallenge data: %v", err) } if data.CodeChallenge == "" { return identity, fmt.Errorf("oidc: invalid PKCE CodeChallenge") } opts = append(opts, oauth2.VerifierOption(data.CodeChallenge)) } token, err := c.oauth2Config.Exchange(ctx, q.Get("code"), opts...) if err != nil { return identity, fmt.Errorf("oidc: failed to get token: %v", err) } return c.createIdentity(ctx, identity, token, createCaller) } // Refresh is used to refresh a session with the refresh token provided by the IdP func (c *oidcConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { cd := connectorData{} err := json.Unmarshal(identity.ConnectorData, &cd) if err != nil { return identity, fmt.Errorf("oidc: failed to unmarshal connector data: %v", err) } ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient) t := &oauth2.Token{ RefreshToken: string(cd.RefreshToken), Expiry: time.Now().Add(-time.Hour), } token, err := c.oauth2Config.TokenSource(ctx, t).Token() if err != nil { return identity, fmt.Errorf("oidc: failed to get refresh token: %v", err) } return c.createIdentity(ctx, identity, token, refreshCaller) } func (c *oidcConnector) TokenIdentity(ctx context.Context, subjectTokenType, subjectToken string) (connector.Identity, error) { var identity connector.Identity ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient) token := &oauth2.Token{ AccessToken: subjectToken, TokenType: subjectTokenType, } return c.createIdentity(ctx, identity, token, exchangeCaller) } func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.Identity, token *oauth2.Token, caller caller) (connector.Identity, error) { var claims map[string]interface{} if rawIDToken, ok := token.Extra("id_token").(string); ok { idToken, err := c.verifier.Verify(ctx, rawIDToken) if err != nil { return identity, fmt.Errorf("oidc: failed to verify ID Token: %v", err) } if err := idToken.Claims(&claims); err != nil { return identity, fmt.Errorf("oidc: failed to decode claims: %v", err) } } else if caller == exchangeCaller { switch token.TokenType { case "urn:ietf:params:oauth:token-type:id_token": // Verify only works on ID tokens idToken, err := c.provider.Verifier(&oidc.Config{SkipClientIDCheck: true}).Verify(ctx, token.AccessToken) if err != nil { return identity, fmt.Errorf("oidc: failed to verify token: %v", err) } if err := idToken.Claims(&claims); err != nil { return identity, fmt.Errorf("oidc: failed to decode claims: %v", err) } case "urn:ietf:params:oauth:token-type:access_token": if !c.getUserInfo { return identity, fmt.Errorf("oidc: getUserInfo is required for access token exchange") } default: return identity, fmt.Errorf("unknown token type for token exchange: %s", token.TokenType) } } else if caller != refreshCaller { // ID tokens aren't mandatory in the reply when using a refresh_token grant return identity, errors.New("oidc: no id_token in token response") } // We immediately want to run getUserInfo if configured before we validate the claims. // For token exchanges with access tokens, this is how we verify the token. if c.getUserInfo { userInfo, err := c.provider.UserInfo(ctx, oauth2.StaticTokenSource(&oauth2.Token{ AccessToken: token.AccessToken, TokenType: "Bearer", // The UserInfo endpoint requires a bearer token as per RFC6750 })) if err != nil { return identity, fmt.Errorf("oidc: error loading userinfo: %v", err) } if err := userInfo.Claims(&claims); err != nil { return identity, fmt.Errorf("oidc: failed to decode userinfo claims: %v", err) } } const subjectClaimKey = "sub" subject, found := claims[subjectClaimKey].(string) if !found { return identity, fmt.Errorf("missing \"%s\" claim", subjectClaimKey) } userNameKey := "name" if c.userNameKey != "" { userNameKey = c.userNameKey } name, found := claims[userNameKey].(string) if !found { return identity, fmt.Errorf("missing \"%s\" claim", userNameKey) } preferredUsername, found := claims["preferred_username"].(string) if (!found || c.overrideClaimMapping) && c.preferredUsernameKey != "" { preferredUsername, _ = claims[c.preferredUsernameKey].(string) } hasEmailScope := false for _, s := range c.oauth2Config.Scopes { if s == "email" { hasEmailScope = true break } } var email string emailKey := "email" email, found = claims[emailKey].(string) if (!found || c.overrideClaimMapping) && c.emailKey != "" { emailKey = c.emailKey email, found = claims[emailKey].(string) } if !found && hasEmailScope { return identity, fmt.Errorf("missing email claim, not found \"%s\" key", emailKey) } emailVerified, found := claims["email_verified"].(bool) if !found { if c.insecureSkipEmailVerified { emailVerified = true } else if hasEmailScope { return identity, errors.New("missing \"email_verified\" claim") } } var groups []string if c.insecureEnableGroups { groupsKey := "groups" vs, found := claims[groupsKey].([]interface{}) if (!found || c.overrideClaimMapping) && c.groupsKey != "" { groupsKey = c.groupsKey vs, found = claims[groupsKey].([]interface{}) } // Fallback when claims[groupsKey] is a string instead of an array of strings. if g, b := claims[groupsKey].(string); b { groups = []string{g} } if found { for _, v := range vs { if s, ok := v.(string); ok { if c.groupsFilter != nil && !c.groupsFilter.MatchString(s) { continue } groups = append(groups, s) } else if groupMap, ok := v.(map[string]interface{}); ok { if s, ok := groupMap["name"].(string); ok { if c.groupsFilter != nil && !c.groupsFilter.MatchString(s) { continue } groups = append(groups, s) } } else { return identity, fmt.Errorf("malformed \"%v\" claim", groupsKey) } } } // Validate that the user is part of allowedGroups if len(c.allowedGroups) > 0 { groupMatches := groups_pkg.Filter(groups, c.allowedGroups) if len(groupMatches) == 0 { // No group membership matches found, disallowing return identity, fmt.Errorf("user not a member of allowed groups") } groups = groupMatches } } // add prefix/suffix to groups if c.groupsPrefix != "" || c.groupsSuffix != "" { for i, group := range groups { groups[i] = c.groupsPrefix + group + c.groupsSuffix } } for _, config := range c.newGroupFromClaims { newGroupSegments := []string{ config.Prefix, } for _, claimName := range config.Claims { claimValue, ok := claims[claimName].(string) if !ok { // Non string claim value are ignored, concatenating them doesn't really make any sense continue } if config.ClearDelimiter { // Removing the delimiter string from the concatenated claim to ensure resulting claim structure // is in full control of Dex operator claimValue = strings.ReplaceAll(claimValue, config.Delimiter, "") } newGroupSegments = append(newGroupSegments, claimValue) } if len(newGroupSegments) > 1 { groups = append(groups, strings.Join(newGroupSegments, config.Delimiter)) } } cd := connectorData{ RefreshToken: []byte(token.RefreshToken), } connData, err := json.Marshal(&cd) if err != nil { return identity, fmt.Errorf("oidc: failed to encode connector data: %v", err) } identity = connector.Identity{ UserID: subject, Username: name, PreferredUsername: preferredUsername, Email: email, EmailVerified: emailVerified, Groups: groups, ConnectorData: connData, } if c.userIDKey != "" { userID, found := claims[c.userIDKey].(string) if !found { return identity, fmt.Errorf("oidc: not found %v claim", c.userIDKey) } identity.UserID = userID } return identity, nil } ================================================ FILE: connector/oidc/oidc_test.go ================================================ package oidc import ( "bytes" "context" "crypto/rand" "crypto/rsa" "encoding/base64" "encoding/binary" "encoding/json" "errors" "fmt" "log/slog" "net/http" "net/http/httptest" "reflect" "strings" "testing" "time" "github.com/go-jose/go-jose/v4" "github.com/stretchr/testify/require" "github.com/dexidp/dex/connector" ) func TestKnownBrokenAuthHeaderProvider(t *testing.T) { tests := []struct { issuerURL string expect bool }{ {"https://dev.oktapreview.com", true}, {"https://dev.okta.com", true}, {"https://okta.com", true}, {"https://dev.oktaaccounts.com", false}, {"https://accounts.google.com", false}, } for _, tc := range tests { got := knownBrokenAuthHeaderProvider(tc.issuerURL) if got != tc.expect { t.Errorf("knownBrokenAuthHeaderProvider(%q), want=%t, got=%t", tc.issuerURL, tc.expect, got) } } } func TestHandleCallback(t *testing.T) { t.Helper() tests := []struct { name string userIDKey string userNameKey string overrideClaimMapping bool preferredUsernameKey string emailKey string groupsKey string insecureSkipEmailVerified bool scopes []string expectUserID string expectUserName string expectGroups []string expectPreferredUsername string expectedEmailField string token map[string]interface{} groupsRegex string newGroupFromClaims []NewGroupFromClaims groupsPrefix string groupsSuffix string pkceChallenge string }{ { name: "simpleCase", userIDKey: "", // not configured userNameKey: "", // not configured expectUserID: "subvalue", expectUserName: "namevalue", expectGroups: []string{"group1", "group2"}, expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "groups": []string{"group1", "group2"}, "email": "emailvalue", "email_verified": true, }, }, { name: "customEmailClaim", userIDKey: "", // not configured userNameKey: "", // not configured emailKey: "mail", expectUserID: "subvalue", expectUserName: "namevalue", expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "mail": "emailvalue", "email_verified": true, }, }, { name: "overrideWithCustomEmailClaim", userIDKey: "", // not configured userNameKey: "", // not configured overrideClaimMapping: true, emailKey: "custommail", expectUserID: "subvalue", expectUserName: "namevalue", expectedEmailField: "customemailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "email": "emailvalue", "custommail": "customemailvalue", "email_verified": true, }, }, { name: "email_verified not in claims, configured to be skipped", insecureSkipEmailVerified: true, expectUserID: "subvalue", expectUserName: "namevalue", expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "email": "emailvalue", }, }, { name: "withUserIDKey", userIDKey: "name", expectUserID: "namevalue", expectUserName: "namevalue", expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "email": "emailvalue", "email_verified": true, }, }, { name: "withUserNameKey", userNameKey: "user_name", expectUserID: "subvalue", expectUserName: "username", expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "user_name": "username", "email": "emailvalue", "email_verified": true, }, }, { name: "withPreferredUsernameKey", preferredUsernameKey: "username_key", expectUserID: "subvalue", expectUserName: "namevalue", expectPreferredUsername: "username_value", expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "username_key": "username_value", "email": "emailvalue", "email_verified": true, }, }, { name: "withoutPreferredUsernameKeyAndBackendReturns", expectUserID: "subvalue", expectUserName: "namevalue", expectPreferredUsername: "preferredusernamevalue", expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "preferred_username": "preferredusernamevalue", "email": "emailvalue", "email_verified": true, }, }, { name: "withoutPreferredUsernameKeyAndBackendNotReturn", expectUserID: "subvalue", expectUserName: "namevalue", expectPreferredUsername: "", expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "email": "emailvalue", "email_verified": true, }, }, { name: "emptyEmailScope", expectUserID: "subvalue", expectUserName: "namevalue", expectedEmailField: "", scopes: []string{"groups"}, insecureSkipEmailVerified: true, token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "user_name": "username", }, }, { name: "emptyEmailScopeButEmailProvided", expectUserID: "subvalue", expectUserName: "namevalue", expectedEmailField: "emailvalue", scopes: []string{"groups"}, insecureSkipEmailVerified: true, token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "user_name": "username", "email": "emailvalue", }, }, { name: "customGroupsKey", groupsKey: "cognito:groups", expectUserID: "subvalue", expectUserName: "namevalue", expectedEmailField: "emailvalue", expectGroups: []string{"group3", "group4"}, scopes: []string{"groups"}, insecureSkipEmailVerified: true, token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "user_name": "username", "email": "emailvalue", "cognito:groups": []string{"group3", "group4"}, }, }, { name: "customGroupsKeyButGroupsProvided", groupsKey: "cognito:groups", expectUserID: "subvalue", expectUserName: "namevalue", expectedEmailField: "emailvalue", expectGroups: []string{"group1", "group2"}, scopes: []string{"groups"}, insecureSkipEmailVerified: true, token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "user_name": "username", "email": "emailvalue", "groups": []string{"group1", "group2"}, "cognito:groups": []string{"group3", "group4"}, }, }, { name: "customGroupsKeyDespiteGroupsProvidedButOverride", overrideClaimMapping: true, groupsKey: "cognito:groups", expectUserID: "subvalue", expectUserName: "namevalue", expectedEmailField: "emailvalue", expectGroups: []string{"group3", "group4"}, scopes: []string{"groups"}, insecureSkipEmailVerified: true, token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "user_name": "username", "email": "emailvalue", "groups": []string{"group1", "group2"}, "cognito:groups": []string{"group3", "group4"}, }, }, { name: "singularGroupResponseAsString", userIDKey: "", // not configured userNameKey: "", // not configured expectUserID: "subvalue", expectUserName: "namevalue", expectGroups: []string{"group1"}, expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "groups": "group1", "email": "emailvalue", "email_verified": true, }, }, { name: "singularGroupResponseAsMap", userIDKey: "", // not configured userNameKey: "", // not configured expectUserID: "subvalue", expectUserName: "namevalue", expectGroups: []string{"group1"}, expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "groups": []map[string]string{{"name": "group1"}}, "email": "emailvalue", "email_verified": true, }, }, { name: "multipleGroupResponseAsMap", userIDKey: "", // not configured userNameKey: "", // not configured expectUserID: "subvalue", expectUserName: "namevalue", expectGroups: []string{"group1", "group2"}, expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "groups": []map[string]string{{"name": "group1"}, {"name": "group2"}}, "email": "emailvalue", "email_verified": true, }, }, { name: "newGroupFromClaims", userIDKey: "", // not configured userNameKey: "", // not configured expectUserID: "subvalue", expectUserName: "namevalue", expectGroups: []string{"group1", "gh::acme::pipeline-one", "clr_delim-acme-foobar", "keep_delim-acme-foo-bar", "bk-emailvalue"}, expectedEmailField: "emailvalue", newGroupFromClaims: []NewGroupFromClaims{ { // The basic functionality, should create "gh::acme::pipeline-one". Claims: []string{ "organization", "pipeline", }, Delimiter: "::", Prefix: "gh", }, { // Non existing claims, should not generate any any new group claim. Claims: []string{ "non-existing1", "non-existing2", }, Delimiter: "::", Prefix: "tfe", }, { // In this case the delimiter character("-") should be removed removed from "claim-with-delimiter" claim to ensure the resulting // claim structure is in full control of the Dex operator and not the person creating a new pipeline. // Should create "clr_delim-acme-foobar" and not "tfe-acme-foo-bar". Claims: []string{ "organization", "claim-with-delimiter", }, Delimiter: "-", ClearDelimiter: true, Prefix: "clr_delim", }, { // In this case the delimiter character("-") should be NOT removed from "claim-with-delimiter" claim. // Should create "keep_delim-acme-foo-bar". Claims: []string{ "organization", "claim-with-delimiter", }, Delimiter: "-", // ClearDelimiter: false, Prefix: "keep_delim", }, { // Ignore non string claims (like arrays), this should result in "bk-emailvalue". Claims: []string{ "non-string-claim", "non-string-claim2", "email", }, Delimiter: "-", Prefix: "bk", }, }, token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "groups": "group1", "organization": "acme", "pipeline": "pipeline-one", "email": "emailvalue", "email_verified": true, "claim-with-delimiter": "foo-bar", "non-string-claim": []string{ "element1", "element2", }, "non-string-claim2": 666, }, }, { name: "prefixGroupNames", userIDKey: "", // not configured userNameKey: "", // not configured expectUserID: "subvalue", expectUserName: "namevalue", expectGroups: []string{"prefix-group1", "prefix-group2", "prefix-groupA", "prefix-groupB"}, expectedEmailField: "emailvalue", groupsPrefix: "prefix-", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "groups": []string{"group1", "group2", "groupA", "groupB"}, "email": "emailvalue", "email_verified": true, }, }, { name: "suffixGroupNames", userIDKey: "", // not configured userNameKey: "", // not configured expectUserID: "subvalue", expectUserName: "namevalue", expectGroups: []string{"group1-suffix", "group2-suffix", "groupA-suffix", "groupB-suffix"}, expectedEmailField: "emailvalue", groupsSuffix: "-suffix", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "groups": []string{"group1", "group2", "groupA", "groupB"}, "email": "emailvalue", "email_verified": true, }, }, { name: "preAndSuffixGroupNames", userIDKey: "", // not configured userNameKey: "", // not configured expectUserID: "subvalue", expectUserName: "namevalue", expectGroups: []string{"prefix-group1-suffix", "prefix-group2-suffix", "prefix-groupA-suffix", "prefix-groupB-suffix"}, expectedEmailField: "emailvalue", groupsPrefix: "prefix-", groupsSuffix: "-suffix", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "groups": []string{"group1", "group2", "groupA", "groupB"}, "email": "emailvalue", "email_verified": true, }, }, { name: "filterGroupClaims", userIDKey: "", // not configured userNameKey: "", // not configured groupsRegex: `^.*\d$`, expectUserID: "subvalue", expectUserName: "namevalue", expectGroups: []string{"group1", "group2"}, expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "groups": []string{"group1", "group2", "groupA", "groupB"}, "email": "emailvalue", "email_verified": true, }, }, { name: "filterGroupClaimsMap", userIDKey: "", // not configured userNameKey: "", // not configured groupsRegex: `^.*\d$`, expectUserID: "subvalue", expectUserName: "namevalue", expectGroups: []string{"group1", "group2"}, expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "groups": []map[string]string{{"name": "group1"}, {"name": "group2"}, {"name": "groupA"}, {"name": "groupB"}}, "email": "emailvalue", "email_verified": true, }, }, { name: "S256PKCEChallenge", userIDKey: "", // not configured userNameKey: "", // not configured pkceChallenge: "S256", expectUserID: "subvalue", expectUserName: "namevalue", expectGroups: []string{"group1", "group2"}, expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "groups": []string{"group1", "group2"}, "email": "emailvalue", "email_verified": true, }, }, { name: "plainPKCEChallenge", userIDKey: "", // not configured userNameKey: "", // not configured pkceChallenge: "plain", expectUserID: "subvalue", expectUserName: "namevalue", expectGroups: []string{"group1", "group2"}, expectedEmailField: "emailvalue", token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", "groups": []string{"group1", "group2"}, "email": "emailvalue", "email_verified": true, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { idTokenDesired := true testServer, err := setupServer(tc.token, idTokenDesired) if err != nil { t.Fatal("failed to setup test server", err) } defer testServer.Close() var scopes []string if len(tc.scopes) > 0 { scopes = tc.scopes } else { scopes = []string{"email", "groups"} } serverURL := testServer.URL basicAuth := true config := Config{ Issuer: serverURL, ClientID: "clientID", ClientSecret: "clientSecret", Scopes: scopes, RedirectURI: fmt.Sprintf("%s/callback", serverURL), UserIDKey: tc.userIDKey, UserNameKey: tc.userNameKey, InsecureSkipEmailVerified: tc.insecureSkipEmailVerified, InsecureEnableGroups: true, BasicAuthUnsupported: &basicAuth, OverrideClaimMapping: tc.overrideClaimMapping, PKCEChallenge: tc.pkceChallenge, } config.ClaimMapping.PreferredUsernameKey = tc.preferredUsernameKey config.ClaimMapping.EmailKey = tc.emailKey config.ClaimMapping.GroupsKey = tc.groupsKey config.ClaimMutations.NewGroupFromClaims = tc.newGroupFromClaims config.ClaimMutations.FilterGroupClaims.GroupsFilter = tc.groupsRegex config.ClaimMutations.ModifyGroupNames.Prefix = tc.groupsPrefix config.ClaimMutations.ModifyGroupNames.Suffix = tc.groupsSuffix conn, err := newConnector(config) if err != nil { t.Fatal("failed to create new connector", err) } req, err := newRequestWithAuthCode(testServer.URL, "someCode") if err != nil { t.Fatal("failed to create request", err) } connectorDataStrTemplate := `{"codeChallenge":"abcdefgh123456qwertuiop89101112uvpwizABC234","codeChallengeMethod":"%s"}` connectorDataStr := fmt.Sprintf(connectorDataStrTemplate, config.PKCEChallenge) connectorData := []byte(connectorDataStr) identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, connectorData, req) if err != nil { t.Fatal("handle callback failed", err) } expectEquals(t, identity.UserID, tc.expectUserID) expectEquals(t, identity.Username, tc.expectUserName) expectEquals(t, identity.PreferredUsername, tc.expectPreferredUsername) expectEquals(t, identity.Email, tc.expectedEmailField) expectEquals(t, identity.EmailVerified, true) expectEquals(t, identity.Groups, tc.expectGroups) }) } } func TestRefresh(t *testing.T) { t.Helper() tests := []struct { name string expectUserID string expectUserName string idTokenDesired bool token map[string]interface{} }{ { name: "IDTokenOnRefresh", expectUserID: "subvalue", expectUserName: "namevalue", idTokenDesired: true, token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", }, }, { name: "NoIDTokenOnRefresh", expectUserID: "subvalue", expectUserName: "namevalue", idTokenDesired: false, token: map[string]interface{}{ "sub": "subvalue", "name": "namevalue", }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { testServer, err := setupServer(tc.token, tc.idTokenDesired) if err != nil { t.Fatal("failed to setup test server", err) } defer testServer.Close() scopes := []string{"openid", "offline_access"} serverURL := testServer.URL config := Config{ Issuer: serverURL, ClientID: "clientID", ClientSecret: "clientSecret", Scopes: scopes, RedirectURI: fmt.Sprintf("%s/callback", serverURL), GetUserInfo: true, } conn, err := newConnector(config) if err != nil { t.Fatal("failed to create new connector", err) } req, err := newRequestWithAuthCode(testServer.URL, "someCode") if err != nil { t.Fatal("failed to create request", err) } refreshTokenStr := "{\"RefreshToken\":\"asdf\"}" refreshToken := []byte(refreshTokenStr) identity := connector.Identity{ UserID: tc.expectUserID, Username: tc.expectUserName, ConnectorData: refreshToken, } refreshIdentity, err := conn.Refresh(req.Context(), connector.Scopes{OfflineAccess: true}, identity) if err != nil { t.Fatal("Refresh failed", err) } expectEquals(t, refreshIdentity.UserID, tc.expectUserID) expectEquals(t, refreshIdentity.Username, tc.expectUserName) }) } } func TestTokenIdentity(t *testing.T) { tokenTypeAccess := "urn:ietf:params:oauth:token-type:access_token" tokenTypeID := "urn:ietf:params:oauth:token-type:id_token" long2short := map[string]string{ tokenTypeAccess: "access_token", tokenTypeID: "id_token", } tests := []struct { name string subjectType string userInfo bool expectError bool }{ { name: "id_token", subjectType: tokenTypeID, }, { name: "access_token", subjectType: tokenTypeAccess, expectError: true, }, { name: "id_token with user info", subjectType: tokenTypeID, userInfo: true, }, { name: "access_token with user info", subjectType: tokenTypeAccess, userInfo: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() testServer, err := setupServer(map[string]any{ "sub": "subvalue", "name": "namevalue", }, true) if err != nil { t.Fatal("failed to setup test server", err) } conn, err := newConnector(Config{ Issuer: testServer.URL, Scopes: []string{"openid", "groups"}, GetUserInfo: tc.userInfo, }) if err != nil { t.Fatal("failed to create new connector", err) } res, err := http.Get(testServer.URL + "/token") if err != nil { t.Fatal("failed to get initial token", err) } defer res.Body.Close() var tokenResponse map[string]any err = json.NewDecoder(res.Body).Decode(&tokenResponse) if err != nil { t.Fatal("failed to decode initial token", err) } origToken := tokenResponse[long2short[tc.subjectType]].(string) identity, err := conn.TokenIdentity(ctx, tc.subjectType, origToken) if err != nil { if tc.expectError { return } t.Fatal("failed to get token identity", err) } // assert identity expectEquals(t, identity.UserID, "subvalue") expectEquals(t, identity.Username, "namevalue") }) } } func TestPromptType(t *testing.T) { pointer := func(s string) *string { return &s } tests := []struct { name string promptType *string res string }{ {name: "none", promptType: pointer("none"), res: "none"}, {name: "provided empty string", promptType: pointer(""), res: ""}, {name: "login", promptType: pointer("login"), res: "login"}, {name: "consent", promptType: pointer("consent"), res: "consent"}, {name: "default value", promptType: nil, res: "consent"}, } testServer, err := setupServer(nil, true) require.NoError(t, err) for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { conn, err := newConnector(Config{ Issuer: testServer.URL, Scopes: []string{"openid", "groups"}, PromptType: tc.promptType, }) require.NoError(t, err) require.Equal(t, tc.res, conn.promptType) }) } } func TestProviderOverride(t *testing.T) { testServer, err := setupServer(map[string]any{ "sub": "subvalue", "name": "namevalue", }, true) if err != nil { t.Fatal("failed to setup test server", err) } t.Run("No override", func(t *testing.T) { conn, err := newConnector(Config{ Issuer: testServer.URL, Scopes: []string{"openid", "groups"}, }) if err != nil { t.Fatal("failed to create new connector", err) } expAuth := fmt.Sprintf("%s/authorize", testServer.URL) if conn.provider.Endpoint().AuthURL != expAuth { t.Fatalf("unexpected auth URL: %s, expected: %s\n", conn.provider.Endpoint().AuthURL, expAuth) } expToken := fmt.Sprintf("%s/token", testServer.URL) if conn.provider.Endpoint().TokenURL != expToken { t.Fatalf("unexpected token URL: %s, expected: %s\n", conn.provider.Endpoint().TokenURL, expToken) } }) t.Run("Override", func(t *testing.T) { conn, err := newConnector(Config{ Issuer: testServer.URL, Scopes: []string{"openid", "groups"}, ProviderDiscoveryOverrides: ProviderDiscoveryOverrides{TokenURL: "/test1", AuthURL: "/test2"}, }) if err != nil { t.Fatal("failed to create new connector", err) } expAuth := "/test2" if conn.provider.Endpoint().AuthURL != expAuth { t.Fatalf("unexpected auth URL: %s, expected: %s\n", conn.provider.Endpoint().AuthURL, expAuth) } expToken := "/test1" if conn.provider.Endpoint().TokenURL != expToken { t.Fatalf("unexpected token URL: %s, expected: %s\n", conn.provider.Endpoint().TokenURL, expToken) } }) } func setupServer(tok map[string]interface{}, idTokenDesired bool) (*httptest.Server, error) { key, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { return nil, fmt.Errorf("failed to generate rsa key: %v", err) } jwk := jose.JSONWebKey{ Key: key, KeyID: "keyId", Algorithm: "RSA", } mux := http.NewServeMux() mux.HandleFunc("/keys", func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(&map[string]interface{}{ "keys": []map[string]interface{}{{ "alg": jwk.Algorithm, "kty": jwk.Algorithm, "kid": jwk.KeyID, "n": n(&key.PublicKey), "e": e(&key.PublicKey), }}, }) }) mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { url := fmt.Sprintf("http://%s", r.Host) tok["iss"] = url tok["exp"] = time.Now().Add(time.Hour).Unix() tok["aud"] = "clientID" token, err := newToken(&jwk, tok) if err != nil { w.WriteHeader(http.StatusInternalServerError) } w.Header().Add("Content-Type", "application/json") if idTokenDesired { json.NewEncoder(w).Encode(&map[string]string{ "access_token": token, "id_token": token, "token_type": "Bearer", }) } else { json.NewEncoder(w).Encode(&map[string]string{ "access_token": token, "token_type": "Bearer", }) } }) mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(tok) }) mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) { url := fmt.Sprintf("http://%s", r.Host) json.NewEncoder(w).Encode(&map[string]string{ "issuer": url, "token_endpoint": fmt.Sprintf("%s/token", url), "authorization_endpoint": fmt.Sprintf("%s/authorize", url), "userinfo_endpoint": fmt.Sprintf("%s/userinfo", url), "jwks_uri": fmt.Sprintf("%s/keys", url), }) }) return httptest.NewServer(mux), nil } func newToken(key *jose.JSONWebKey, claims map[string]interface{}) (string, error) { signingKey := jose.SigningKey{ Key: key, Algorithm: jose.RS256, } signer, err := jose.NewSigner(signingKey, &jose.SignerOptions{}) if err != nil { return "", fmt.Errorf("failed to create new signer: %v", err) } payload, err := json.Marshal(claims) if err != nil { return "", fmt.Errorf("failed to marshal claims: %v", err) } signature, err := signer.Sign(payload) if err != nil { return "", fmt.Errorf("failed to sign: %v", err) } return signature.CompactSerialize() } func newConnector(config Config) (*oidcConnector, error) { logger := slog.New(slog.DiscardHandler) conn, err := config.Open("id", logger) if err != nil { return nil, fmt.Errorf("unable to open: %v", err) } oidcConn, ok := conn.(*oidcConnector) if !ok { return nil, errors.New("failed to convert to oidcConnector") } return oidcConn, nil } func newRequestWithAuthCode(serverURL string, code string) (*http.Request, error) { req, err := http.NewRequest("GET", serverURL, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %v", err) } values := req.URL.Query() values.Add("code", code) req.URL.RawQuery = values.Encode() return req, nil } func n(pub *rsa.PublicKey) string { return encode(pub.N.Bytes()) } func e(pub *rsa.PublicKey) string { data := make([]byte, 8) binary.BigEndian.PutUint64(data, uint64(pub.E)) return encode(bytes.TrimLeft(data, "\x00")) } func encode(payload []byte) string { result := base64.URLEncoding.EncodeToString(payload) return strings.TrimRight(result, "=") } func expectEquals(t *testing.T, a interface{}, b interface{}) { if !reflect.DeepEqual(a, b) { t.Errorf("Expected %+v to equal %+v", a, b) } } ================================================ FILE: connector/openshift/openshift.go ================================================ package openshift import ( "context" "encoding/json" "fmt" "io" "log/slog" "net/http" "strings" "golang.org/x/oauth2" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/groups" "github.com/dexidp/dex/pkg/httpclient" "github.com/dexidp/dex/storage/kubernetes/k8sapi" ) const ( wellKnownURLPath = "/.well-known/oauth-authorization-server" usersURLPath = "/apis/user.openshift.io/v1/users/~" ) // Config holds configuration options for OpenShift login type Config struct { Issuer string `json:"issuer"` ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` RedirectURI string `json:"redirectURI"` Groups []string `json:"groups"` InsecureCA bool `json:"insecureCA"` RootCA string `json:"rootCA"` } var ( _ connector.CallbackConnector = (*openshiftConnector)(nil) _ connector.RefreshConnector = (*openshiftConnector)(nil) ) type openshiftConnector struct { apiURL string redirectURI string clientID string clientSecret string cancel context.CancelFunc logger *slog.Logger httpClient *http.Client oauth2Config *oauth2.Config insecureCA bool rootCA string groups []string } type user struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` Identities []string `json:"identities" protobuf:"bytes,3,rep,name=identities"` FullName string `json:"fullName,omitempty" protobuf:"bytes,2,opt,name=fullName"` Groups []string `json:"groups" protobuf:"bytes,4,rep,name=groups"` } // Open returns a connector which can be used to login users through an upstream // OpenShift OAuth2 provider. func (c *Config) Open(id string, logger *slog.Logger) (conn connector.Connector, err error) { var rootCAs []string if c.RootCA != "" { rootCAs = append(rootCAs, c.RootCA) } httpClient, err := httpclient.NewHTTPClient(rootCAs, c.InsecureCA) if err != nil { return nil, fmt.Errorf("failed to create HTTP client: %w", err) } return c.OpenWithHTTPClient(id, logger, httpClient) } // OpenWithHTTPClient returns a connector which can be used to login users through an upstream // OpenShift OAuth2 provider. It provides the ability to inject a http.Client. func (c *Config) OpenWithHTTPClient(id string, logger *slog.Logger, httpClient *http.Client, ) (conn connector.Connector, err error) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() wellKnownURL := strings.TrimSuffix(c.Issuer, "/") + wellKnownURLPath req, err := http.NewRequest(http.MethodGet, wellKnownURL, nil) if err != nil { return nil, fmt.Errorf("failed to create a request to OpenShift endpoint %w", err) } openshiftConnector := openshiftConnector{ apiURL: c.Issuer, cancel: cancel, clientID: c.ClientID, clientSecret: c.ClientSecret, insecureCA: c.InsecureCA, logger: logger.With(slog.Group("connector", "type", "openshift", "id", id)), redirectURI: c.RedirectURI, rootCA: c.RootCA, groups: c.Groups, httpClient: httpClient, } var metadata struct { Auth string `json:"authorization_endpoint"` Token string `json:"token_endpoint"` } resp, err := openshiftConnector.httpClient.Do(req.WithContext(ctx)) if err != nil { return nil, fmt.Errorf("failed to query OpenShift endpoint %w", err) } defer resp.Body.Close() if err := json.NewDecoder(resp.Body).Decode(&metadata); err != nil { return nil, fmt.Errorf("discovery through endpoint %s failed to decode body: %w", wellKnownURL, err) } openshiftConnector.oauth2Config = &oauth2.Config{ ClientID: c.ClientID, ClientSecret: c.ClientSecret, Endpoint: oauth2.Endpoint{ AuthURL: metadata.Auth, TokenURL: metadata.Token, }, Scopes: []string{"user:info"}, RedirectURL: c.RedirectURI, } return &openshiftConnector, nil } func (c *openshiftConnector) Close() error { c.cancel() return nil } // LoginURL returns the URL to redirect the user to login with. func (c *openshiftConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) { if c.redirectURI != callbackURL { return "", nil, fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI) } return c.oauth2Config.AuthCodeURL(state), nil, nil } type oauth2Error struct { error string errorDescription string } func (e *oauth2Error) Error() string { if e.errorDescription == "" { return e.error } return e.error + ": " + e.errorDescription } // HandleCallback parses the request and returns the user's identity func (c *openshiftConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request, ) (identity connector.Identity, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return identity, &oauth2Error{errType, q.Get("error_description")} } ctx := r.Context() if c.httpClient != nil { ctx = context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient) } token, err := c.oauth2Config.Exchange(ctx, q.Get("code")) if err != nil { return identity, fmt.Errorf("oidc: failed to get token: %v", err) } return c.identity(ctx, s, token) } func (c *openshiftConnector) Refresh(ctx context.Context, s connector.Scopes, oldID connector.Identity, ) (connector.Identity, error) { var token oauth2.Token err := json.Unmarshal(oldID.ConnectorData, &token) if err != nil { return connector.Identity{}, fmt.Errorf("parsing token: %w", err) } if c.httpClient != nil { ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient) } return c.identity(ctx, s, &token) } func (c *openshiftConnector) identity(ctx context.Context, s connector.Scopes, token *oauth2.Token, ) (identity connector.Identity, err error) { client := c.oauth2Config.Client(ctx, token) user, err := c.user(ctx, client) if err != nil { return identity, fmt.Errorf("openshift: get user: %v", err) } if len(c.groups) > 0 { validGroups := validateAllowedGroups(user.Groups, c.groups) if !validGroups { return identity, fmt.Errorf("openshift: user %q is not in any of the required groups", user.Name) } } identity = connector.Identity{ UserID: user.UID, Username: user.Name, PreferredUsername: user.Name, Email: user.Name, Groups: user.Groups, } if s.OfflineAccess { connData, err := json.Marshal(token) if err != nil { return identity, fmt.Errorf("marshal connector data: %v", err) } identity.ConnectorData = connData } return identity, nil } // user function returns the OpenShift user associated with the authenticated user func (c *openshiftConnector) user(ctx context.Context, client *http.Client) (u user, err error) { url := c.apiURL + usersURLPath req, err := http.NewRequest("GET", url, nil) if err != nil { return u, fmt.Errorf("new req: %v", err) } resp, err := client.Do(req.WithContext(ctx)) if err != nil { return u, fmt.Errorf("get URL %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { return u, fmt.Errorf("read body: %v", err) } return u, fmt.Errorf("%s: %s", resp.Status, body) } if err := json.NewDecoder(resp.Body).Decode(&u); err != nil { return u, fmt.Errorf("JSON decode: %v", err) } return u, err } func validateAllowedGroups(userGroups, allowedGroups []string) bool { matchingGroups := groups.Filter(userGroups, allowedGroups) return len(matchingGroups) != 0 } ================================================ FILE: connector/openshift/openshift_test.go ================================================ package openshift import ( "context" "encoding/json" "fmt" "log/slog" "net/http" "net/http/httptest" "net/url" "reflect" "testing" "time" "golang.org/x/oauth2" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/httpclient" "github.com/dexidp/dex/storage/kubernetes/k8sapi" ) func TestOpen(t *testing.T) { s := newTestServer(map[string]interface{}{}) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) _, err = http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) c := Config{ Issuer: s.URL, ClientID: "testClientId", ClientSecret: "testClientSecret", RedirectURI: "https://localhost/callback", InsecureCA: true, } logger := slog.New(slog.DiscardHandler) oconfig, err := c.Open("id", logger) oc, ok := oconfig.(*openshiftConnector) expectNil(t, err) expectEquals(t, ok, true) expectEquals(t, oc.apiURL, s.URL) expectEquals(t, oc.clientID, "testClientId") expectEquals(t, oc.clientSecret, "testClientSecret") expectEquals(t, oc.redirectURI, "https://localhost/callback") expectEquals(t, oc.oauth2Config.Endpoint.AuthURL, fmt.Sprintf("%s/oauth/authorize", s.URL)) expectEquals(t, oc.oauth2Config.Endpoint.TokenURL, fmt.Sprintf("%s/oauth/token", s.URL)) } func TestGetUser(t *testing.T) { s := newTestServer(map[string]interface{}{ "/apis/user.openshift.io/v1/users/~": user{ ObjectMeta: k8sapi.ObjectMeta{ Name: "jdoe", }, FullName: "John Doe", Groups: []string{"users"}, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) _, err = http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) h, err := httpclient.NewHTTPClient(nil, true) expectNil(t, err) oc := openshiftConnector{apiURL: s.URL, httpClient: h} u, err := oc.user(context.Background(), h) expectNil(t, err) expectEquals(t, u.Name, "jdoe") expectEquals(t, u.FullName, "John Doe") expectEquals(t, len(u.Groups), 1) } func TestVerifySingleGroupFn(t *testing.T) { allowedGroups := []string{"users"} groupMembership := []string{"users", "org1"} validGroupMembership := validateAllowedGroups(groupMembership, allowedGroups) expectEquals(t, validGroupMembership, true) } func TestVerifySingleGroupFailureFn(t *testing.T) { allowedGroups := []string{"admins"} groupMembership := []string{"users"} validGroupMembership := validateAllowedGroups(groupMembership, allowedGroups) expectEquals(t, validGroupMembership, false) } func TestVerifyMultipleGroupFn(t *testing.T) { allowedGroups := []string{"users", "admins"} groupMembership := []string{"users", "org1"} validGroupMembership := validateAllowedGroups(groupMembership, allowedGroups) expectEquals(t, validGroupMembership, true) } func TestVerifyGroup(t *testing.T) { s := newTestServer(map[string]interface{}{ "/apis/user.openshift.io/v1/users/~": user{ ObjectMeta: k8sapi.ObjectMeta{ Name: "jdoe", }, FullName: "John Doe", Groups: []string{"users"}, }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) _, err = http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) h, err := httpclient.NewHTTPClient(nil, true) expectNil(t, err) oc := openshiftConnector{apiURL: s.URL, httpClient: h} u, err := oc.user(context.Background(), h) expectNil(t, err) expectEquals(t, u.Name, "jdoe") expectEquals(t, u.FullName, "John Doe") expectEquals(t, len(u.Groups), 1) } func TestCallbackIdentity(t *testing.T) { s := newTestServer(map[string]interface{}{ "/apis/user.openshift.io/v1/users/~": user{ ObjectMeta: k8sapi.ObjectMeta{ Name: "jdoe", UID: "12345", }, FullName: "John Doe", Groups: []string{"users"}, }, "/oauth/token": map[string]interface{}{ "access_token": "oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC", "expires_in": "30", }, }) defer s.Close() hostURL, err := url.Parse(s.URL) expectNil(t, err) req, err := http.NewRequest("GET", hostURL.String(), nil) expectNil(t, err) h, err := httpclient.NewHTTPClient(nil, true) expectNil(t, err) oc := openshiftConnector{apiURL: s.URL, httpClient: h, oauth2Config: &oauth2.Config{ Endpoint: oauth2.Endpoint{ AuthURL: fmt.Sprintf("%s/oauth/authorize", s.URL), TokenURL: fmt.Sprintf("%s/oauth/token", s.URL), }, }} identity, err := oc.HandleCallback(connector.Scopes{Groups: true}, nil, req) expectNil(t, err) expectEquals(t, identity.UserID, "12345") expectEquals(t, identity.Username, "jdoe") expectEquals(t, identity.PreferredUsername, "jdoe") expectEquals(t, identity.Email, "jdoe") expectEquals(t, len(identity.Groups), 1) expectEquals(t, identity.Groups[0], "users") } func TestRefreshIdentity(t *testing.T) { s := newTestServer(map[string]interface{}{ usersURLPath: user{ ObjectMeta: k8sapi.ObjectMeta{ Name: "jdoe", UID: "12345", }, FullName: "John Doe", Groups: []string{"users"}, }, }) defer s.Close() h, err := httpclient.NewHTTPClient(nil, true) expectNil(t, err) oc := openshiftConnector{apiURL: s.URL, httpClient: h, oauth2Config: &oauth2.Config{ Endpoint: oauth2.Endpoint{ AuthURL: fmt.Sprintf("%s/oauth/authorize", s.URL), TokenURL: fmt.Sprintf("%s/oauth/token", s.URL), }, }} data, err := json.Marshal(oauth2.Token{AccessToken: "fFAGRNJru1FTz70BzhT3Zg"}) expectNil(t, err) oldID := connector.Identity{ConnectorData: data} identity, err := oc.Refresh(context.Background(), connector.Scopes{Groups: true}, oldID) expectNil(t, err) expectEquals(t, identity.UserID, "12345") expectEquals(t, identity.Username, "jdoe") expectEquals(t, identity.PreferredUsername, "jdoe") expectEquals(t, identity.Email, "jdoe") expectEquals(t, len(identity.Groups), 1) expectEquals(t, identity.Groups[0], "users") } func TestRefreshIdentityFailure(t *testing.T) { s := newTestServer(map[string]interface{}{ usersURLPath: user{ ObjectMeta: k8sapi.ObjectMeta{ Name: "jdoe", UID: "12345", }, FullName: "John Doe", Groups: []string{"users"}, }, }) defer s.Close() h, err := httpclient.NewHTTPClient(nil, true) expectNil(t, err) oc := openshiftConnector{apiURL: s.URL, httpClient: h, oauth2Config: &oauth2.Config{ Endpoint: oauth2.Endpoint{ AuthURL: fmt.Sprintf("%s/oauth/authorize", s.URL), TokenURL: fmt.Sprintf("%s/oauth/token", s.URL), }, }} data, err := json.Marshal(oauth2.Token{AccessToken: "oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC", Expiry: time.Now().Add(-time.Hour)}) expectNil(t, err) oldID := connector.Identity{ConnectorData: data} identity, err := oc.Refresh(context.Background(), connector.Scopes{Groups: true}, oldID) expectNotNil(t, err) expectEquals(t, connector.Identity{}, identity) } func newTestServer(responses map[string]interface{}) *httptest.Server { var s *httptest.Server s = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { responses["/.well-known/oauth-authorization-server"] = map[string]interface{}{ "issuer": s.URL, "authorization_endpoint": fmt.Sprintf("%s/oauth/authorize", s.URL), "token_endpoint": fmt.Sprintf("%s/oauth/token", s.URL), "scopes_supported": []string{"user:full", "user:info", "user:check-access", "user:list-scoped-projects", "user:list-projects"}, "response_types_supported": []string{"token", "code"}, "grant_types_supported": []string{"authorization_code", "implicit"}, "code_challenge_methods_supported": []string{"plain", "S256"}, } response := responses[r.RequestURI] w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(response) })) return s } func expectNil(t *testing.T, a interface{}) { if a != nil { t.Errorf("Expected %+v to equal nil", a) } } func expectEquals(t *testing.T, a interface{}, b interface{}) { if !reflect.DeepEqual(a, b) { t.Errorf("Expected %+v to equal %+v", a, b) } } func expectNotNil(t *testing.T, a interface{}) { if a == nil { t.Errorf("Expected %+v to not equal nil", a) } } ================================================ FILE: connector/saml/saml.go ================================================ // Package saml contains login methods for SAML. package saml import ( "bytes" "context" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "encoding/xml" "fmt" "log/slog" "os" "strings" "sync" "time" "github.com/beevik/etree" xrv "github.com/mattermost/xml-roundtrip-validator" "github.com/pkg/errors" dsig "github.com/russellhaering/goxmldsig" "github.com/russellhaering/goxmldsig/etreeutils" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/groups" ) const ( bindingRedirect = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" bindingPOST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" nameIDFormatEmailAddress = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" nameIDFormatUnspecified = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" nameIDFormatX509Subject = "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName" nameIDFormatWindowsDN = "urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName" nameIDFormatEncrypted = "urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted" nameIDFormatEntity = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity" nameIDFormatKerberos = "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos" nameIDFormatPersistent = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" nameIDformatTransient = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" // top level status codes statusCodeSuccess = "urn:oasis:names:tc:SAML:2.0:status:Success" // subject confirmation methods subjectConfirmationMethodBearer = "urn:oasis:names:tc:SAML:2.0:cm:bearer" // allowed clock drift for timestamp validation allowedClockDrift = time.Duration(30) * time.Second ) var ( nameIDFormats = []string{ nameIDFormatEmailAddress, nameIDFormatUnspecified, nameIDFormatX509Subject, nameIDFormatWindowsDN, nameIDFormatEncrypted, nameIDFormatEntity, nameIDFormatKerberos, nameIDFormatPersistent, nameIDformatTransient, } nameIDFormatLookup = make(map[string]string) lookupOnce sync.Once ) // Config represents configuration options for the SAML provider. type Config struct { // TODO(ericchiang): A bunch of these fields could be auto-filled if // we supported SAML metadata discovery. // // https://www.oasis-open.org/committees/download.php/35391/sstc-saml-metadata-errata-2.0-wd-04-diff.pdf EntityIssuer string `json:"entityIssuer"` SSOIssuer string `json:"ssoIssuer"` SSOURL string `json:"ssoURL"` // X509 CA file or raw data to verify XML signatures. CA string `json:"ca"` CAData []byte `json:"caData"` InsecureSkipSignatureValidation bool `json:"insecureSkipSignatureValidation"` // Assertion attribute names to lookup various claims with. UsernameAttr string `json:"usernameAttr"` EmailAttr string `json:"emailAttr"` GroupsAttr string `json:"groupsAttr"` // If GroupsDelim is supplied the connector assumes groups are returned as a // single string instead of multiple attribute values. This delimiter will be // used split the groups string. GroupsDelim string `json:"groupsDelim"` AllowedGroups []string `json:"allowedGroups"` FilterGroups bool `json:"filterGroups"` RedirectURI string `json:"redirectURI"` // Requested format of the NameID. The NameID value is is mapped to the ID Token // 'sub' claim. // // This can be an abbreviated form of the full URI with just the last component. For // example, if this value is set to "emailAddress" the format will resolve to: // // urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress // // If no value is specified, this value defaults to: // // urn:oasis:names:tc:SAML:2.0:nameid-format:persistent // NameIDPolicyFormat string `json:"nameIDPolicyFormat"` } type certStore struct { certs []*x509.Certificate } func (c certStore) Certificates() (roots []*x509.Certificate, err error) { return c.certs, nil } // Open validates the config and returns a connector. It does not actually // validate connectivity with the provider. func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { logger = logger.With(slog.Group("connector", "type", "saml", "id", id)) return c.openConnector(logger) } func (c *Config) openConnector(logger *slog.Logger) (*provider, error) { requiredFields := []struct { name, val string }{ {"ssoURL", c.SSOURL}, {"usernameAttr", c.UsernameAttr}, {"emailAttr", c.EmailAttr}, {"redirectURI", c.RedirectURI}, } var missing []string for _, f := range requiredFields { if f.val == "" { missing = append(missing, f.name) } } switch len(missing) { case 0: case 1: return nil, fmt.Errorf("missing required field %q", missing[0]) default: return nil, fmt.Errorf("missing required fields %q", missing) } p := &provider{ entityIssuer: c.EntityIssuer, ssoIssuer: c.SSOIssuer, ssoURL: c.SSOURL, now: time.Now, usernameAttr: c.UsernameAttr, emailAttr: c.EmailAttr, groupsAttr: c.GroupsAttr, groupsDelim: c.GroupsDelim, allowedGroups: c.AllowedGroups, filterGroups: c.FilterGroups, redirectURI: c.RedirectURI, logger: logger, nameIDPolicyFormat: c.NameIDPolicyFormat, } if p.nameIDPolicyFormat == "" { p.nameIDPolicyFormat = nameIDFormatPersistent } else { lookupOnce.Do(func() { suffix := func(s, sep string) string { if i := strings.LastIndex(s, sep); i > 0 { return s[i+1:] } return s } for _, format := range nameIDFormats { nameIDFormatLookup[suffix(format, ":")] = format nameIDFormatLookup[format] = format } }) if format, ok := nameIDFormatLookup[p.nameIDPolicyFormat]; ok { p.nameIDPolicyFormat = format } else { return nil, fmt.Errorf("invalid nameIDPolicyFormat: %q", p.nameIDPolicyFormat) } } if !c.InsecureSkipSignatureValidation { if (c.CA == "") == (c.CAData == nil) { return nil, errors.New("must provide either 'ca' or 'caData'") } var caData []byte if c.CA != "" { data, err := os.ReadFile(c.CA) if err != nil { return nil, fmt.Errorf("read ca file: %v", err) } caData = data } else { caData = c.CAData } var ( certs []*x509.Certificate block *pem.Block ) for { block, caData = pem.Decode(caData) if block == nil { caData = bytes.TrimSpace(caData) if len(caData) > 0 { // if there's some left, we've been given bad caData return nil, fmt.Errorf("parse cert: trailing data: %q", string(caData)) } break } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return nil, fmt.Errorf("parse cert: %v", err) } certs = append(certs, cert) } if len(certs) == 0 { return nil, errors.New("no certificates found in ca data") } p.validator = dsig.NewDefaultValidationContext(certStore{certs}) } return p, nil } var ( _ connector.SAMLConnector = (*provider)(nil) _ connector.RefreshConnector = (*provider)(nil) ) type provider struct { entityIssuer string ssoIssuer string ssoURL string now func() time.Time // If nil, don't do signature validation. validator *dsig.ValidationContext // Attribute mappings usernameAttr string emailAttr string groupsAttr string groupsDelim string allowedGroups []string filterGroups bool redirectURI string nameIDPolicyFormat string logger *slog.Logger } // cachedIdentity stores the identity from SAML assertion for refresh token support. // Since SAML has no native refresh mechanism, we cache the identity obtained during // the initial authentication and return it on subsequent refresh requests. type cachedIdentity struct { UserID string `json:"userId"` Username string `json:"username"` PreferredUsername string `json:"preferredUsername"` Email string `json:"email"` EmailVerified bool `json:"emailVerified"` Groups []string `json:"groups,omitempty"` } // marshalCachedIdentity serializes the identity into ConnectorData for refresh token support. func marshalCachedIdentity(ident connector.Identity) (connector.Identity, error) { ci := cachedIdentity{ UserID: ident.UserID, Username: ident.Username, PreferredUsername: ident.PreferredUsername, Email: ident.Email, EmailVerified: ident.EmailVerified, Groups: ident.Groups, } connectorData, err := json.Marshal(ci) if err != nil { return ident, fmt.Errorf("saml: failed to marshal cached identity: %v", err) } ident.ConnectorData = connectorData return ident, nil } func (p *provider) POSTData(s connector.Scopes, id string) (action, value string, err error) { r := &authnRequest{ ProtocolBinding: bindingPOST, ID: id, IssueInstant: xmlTime(p.now()), Destination: p.ssoURL, NameIDPolicy: &nameIDPolicy{ AllowCreate: true, Format: p.nameIDPolicyFormat, }, AssertionConsumerServiceURL: p.redirectURI, } if p.entityIssuer != "" { // Issuer for the request is optional. For example, okta always ignores // this value. r.Issuer = &issuer{Issuer: p.entityIssuer} } data, err := xml.MarshalIndent(r, "", " ") if err != nil { return "", "", fmt.Errorf("marshal authn request: %v", err) } // See: https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf // "3.5.4 Message Encoding" return p.ssoURL, base64.StdEncoding.EncodeToString(data), nil } // HandlePOST interprets a request from a SAML provider attempting to verify a // user's identity. // // The steps taken are: // // * Validate XML document does not contain malicious inputs. // * Verify signature on XML document (or verify sig on assertion elements). // * Verify various parts of the Assertion element. Conditions, audience, etc. // * Map the Assertion's attribute elements to user info. func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo string) (ident connector.Identity, err error) { rawResp, err := base64.StdEncoding.DecodeString(samlResponse) if err != nil { return ident, fmt.Errorf("decode response: %v", err) } byteReader := bytes.NewReader(rawResp) if xrvErr := xrv.Validate(byteReader); xrvErr != nil { return ident, errors.Wrap(xrvErr, "validating XML response") } // Root element is allowed to not be signed if the Assertion element is. rootElementSigned := true if p.validator != nil { rawResp, rootElementSigned, err = verifyResponseSig(p.validator, rawResp) if err != nil { return ident, fmt.Errorf("verify signature: %v", err) } } var resp response if err := xml.Unmarshal(rawResp, &resp); err != nil { return ident, fmt.Errorf("unmarshal response: %v", err) } // If the root element isn't signed, there's no reason to inspect these // elements. They're not verified. if rootElementSigned { if p.ssoIssuer != "" && resp.Issuer != nil && resp.Issuer.Issuer != p.ssoIssuer { return ident, fmt.Errorf("expected Issuer value %s, got %s", p.ssoIssuer, resp.Issuer.Issuer) } // Verify InResponseTo value matches the expected ID associated with // the RelayState. if resp.InResponseTo != inResponseTo { return ident, fmt.Errorf("expected InResponseTo value %s, got %s", inResponseTo, resp.InResponseTo) } // Destination is optional. if resp.Destination != "" && resp.Destination != p.redirectURI { return ident, fmt.Errorf("expected destination %q got %q", p.redirectURI, resp.Destination) } // Status is a required element. if resp.Status == nil { return ident, fmt.Errorf("response did not contain a Status element") } if err = p.validateStatus(resp.Status); err != nil { return ident, err } } assertion := resp.Assertion if assertion == nil { return ident, fmt.Errorf("response did not contain an assertion") } // Subject is usually optional, but we need it for the user ID, so complain // if it's not present. subject := assertion.Subject if subject == nil { return ident, fmt.Errorf("response did not contain a subject") } // Validate that the response is to the request we originally sent. if err = p.validateSubject(subject, inResponseTo); err != nil { return ident, err } // Conditions element is optional, but must be validated if present. if assertion.Conditions != nil { // Validate that dex is the intended audience of this response. if err = p.validateConditions(assertion.Conditions); err != nil { return ident, err } } switch { case subject.NameID != nil: if ident.UserID = subject.NameID.Value; ident.UserID == "" { return ident, fmt.Errorf("element NameID does not contain a value") } default: return ident, fmt.Errorf("subject does not contain an NameID element") } // After verifying the assertion, map data in the attribute statements to // various user info. attributes := assertion.AttributeStatement if attributes == nil { return ident, fmt.Errorf("response did not contain a AttributeStatement") } // Log the actual attributes we got back from the server. This helps debug // configuration errors on the server side, where the SAML server doesn't // send us the correct attributes. p.logger.Info("parsed and verified saml response attributes", "attributes", attributes) // Grab the email. if ident.Email, _ = attributes.get(p.emailAttr); ident.Email == "" { return ident, fmt.Errorf("no attribute with name %q: %s", p.emailAttr, attributes.names()) } // TODO(ericchiang): Does SAML have an email_verified equivalent? ident.EmailVerified = true // Grab the username. if ident.Username, _ = attributes.get(p.usernameAttr); ident.Username == "" { return ident, fmt.Errorf("no attribute with name %q: %s", p.usernameAttr, attributes.names()) } if len(p.allowedGroups) == 0 && (!s.Groups || p.groupsAttr == "") { // Groups not requested or not configured. We're done. return marshalCachedIdentity(ident) } if len(p.allowedGroups) > 0 && (!s.Groups || p.groupsAttr == "") { // allowedGroups set but no groups or groupsAttr. Disallowing. return ident, fmt.Errorf("user not a member of allowed groups") } // Grab the groups. if p.groupsDelim != "" { groupsStr, ok := attributes.get(p.groupsAttr) if !ok { return ident, fmt.Errorf("no attribute with name %q: %s", p.groupsAttr, attributes.names()) } // TODO(ericchiang): Do we need to further trim whitespace? ident.Groups = strings.Split(groupsStr, p.groupsDelim) } else { groups, ok := attributes.all(p.groupsAttr) if !ok { return ident, fmt.Errorf("no attribute with name %q: %s", p.groupsAttr, attributes.names()) } ident.Groups = groups } if len(p.allowedGroups) == 0 { // No allowed groups set, just return the ident return marshalCachedIdentity(ident) } // Look for membership in one of the allowed groups groupMatches := groups.Filter(ident.Groups, p.allowedGroups) if len(groupMatches) == 0 { // No group membership matches found, disallowing return ident, fmt.Errorf("user not a member of allowed groups") } if p.filterGroups { ident.Groups = groupMatches } // Otherwise, we're good return marshalCachedIdentity(ident) } // Refresh implements connector.RefreshConnector. // Since SAML has no native refresh mechanism, this method returns the cached // identity from the initial SAML assertion stored in ConnectorData. func (p *provider) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) { if len(ident.ConnectorData) == 0 { return ident, fmt.Errorf("saml: no connector data available for refresh") } var ci cachedIdentity if err := json.Unmarshal(ident.ConnectorData, &ci); err != nil { return ident, fmt.Errorf("saml: failed to unmarshal cached identity: %v", err) } ident.UserID = ci.UserID ident.Username = ci.Username ident.PreferredUsername = ci.PreferredUsername ident.Email = ci.Email ident.EmailVerified = ci.EmailVerified // Only populate groups if the client requested the groups scope. if s.Groups { ident.Groups = ci.Groups } else { ident.Groups = nil } return ident, nil } // validateStatus verifies that the response has a good status code or // formats a human readable error based on the bad status. func (p *provider) validateStatus(status *status) error { // StatusCode is mandatory in the Status type statusCode := status.StatusCode if statusCode == nil { return fmt.Errorf("response did not contain a StatusCode") } if statusCode.Value != statusCodeSuccess { parts := strings.Split(statusCode.Value, ":") lastPart := parts[len(parts)-1] errorMessage := fmt.Sprintf("status code of the Response was not Success, was %q", lastPart) statusMessage := status.StatusMessage if statusMessage != nil && statusMessage.Value != "" { errorMessage += " -> " + statusMessage.Value } return errors.New(errorMessage) } return nil } // validateSubject ensures the response is to the request we expect. // // This is described in the spec "Profiles for the OASIS Security // Assertion Markup Language" in section 3.3 Bearer. // see https://www.oasis-open.org/committees/download.php/35389/sstc-saml-profiles-errata-2.0-wd-06-diff.pdf // // Some of these fields are optional, but we're going to be strict here since // we have no other way of guaranteeing that this is actually the response to // the request we expect. func (p *provider) validateSubject(subject *subject, inResponseTo string) error { // Optional according to the spec, but again, we're going to be strict here. if len(subject.SubjectConfirmations) == 0 { return fmt.Errorf("subject contained no SubjectConfirmations") } errs := make([]error, 0, len(subject.SubjectConfirmations)) // One of these must match our assumptions, not all. for _, c := range subject.SubjectConfirmations { err := func() error { if c.Method != subjectConfirmationMethodBearer { return fmt.Errorf("unexpected subject confirmation method: %v", c.Method) } data := c.SubjectConfirmationData if data == nil { return fmt.Errorf("no SubjectConfirmationData field found in SubjectConfirmation") } if data.InResponseTo != inResponseTo { return fmt.Errorf("expected SubjectConfirmationData InResponseTo value %q, got %q", inResponseTo, data.InResponseTo) } notBefore := time.Time(data.NotBefore) notOnOrAfter := time.Time(data.NotOnOrAfter) now := p.now() if !notBefore.IsZero() && before(now, notBefore) { return fmt.Errorf("at %s got response that cannot be processed before %s", now, notBefore) } if !notOnOrAfter.IsZero() && after(now, notOnOrAfter) { return fmt.Errorf("at %s got response that cannot be processed because it expired at %s", now, notOnOrAfter) } if r := data.Recipient; r != "" && r != p.redirectURI { return fmt.Errorf("expected Recipient %q got %q", p.redirectURI, r) } return nil }() if err == nil { // Subject is valid. return nil } errs = append(errs, err) } if len(errs) == 1 { return fmt.Errorf("failed to validate subject confirmation: %v", errs[0]) } return fmt.Errorf("failed to validate subject confirmation: %v", errs) } // validateConditions ensures that dex is the intended audience // for the request, and not another service provider. // // See: https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf // "2.3.3 Element " func (p *provider) validateConditions(conditions *conditions) error { // Ensure the conditions haven't expired. now := p.now() notBefore := time.Time(conditions.NotBefore) if !notBefore.IsZero() && before(now, notBefore) { return fmt.Errorf("at %s got response that cannot be processed before %s", now, notBefore) } notOnOrAfter := time.Time(conditions.NotOnOrAfter) if !notOnOrAfter.IsZero() && after(now, notOnOrAfter) { return fmt.Errorf("at %s got response that cannot be processed because it expired at %s", now, notOnOrAfter) } // Sometimes, dex's issuer string can be different than the redirect URI, // but if dex's issuer isn't explicitly provided assume the redirect URI. expAud := p.entityIssuer if expAud == "" { expAud = p.redirectURI } // AudienceRestriction elements indicate the intended audience(s) of an // assertion. If dex isn't in these audiences, reject the assertion. // // Note that if there are multiple AudienceRestriction elements, each must // individually contain dex in their audience list. for _, r := range conditions.AudienceRestriction { values := make([]string, len(r.Audiences)) issuerInAudiences := false for i, aud := range r.Audiences { if aud.Value == expAud { issuerInAudiences = true break } values[i] = aud.Value } if !issuerInAudiences { return fmt.Errorf("required audience %s was not in Response audiences %s", expAud, values) } } return nil } // verifyResponseSig attempts to verify the signature of a SAML response or // the assertion. // // If the root element is properly signed, this method returns it. // // The SAML spec requires supporting responses where the root element is // unverified, but the sub elements are signed. In these cases, // this method returns rootVerified=false to indicate that the // elements should be trusted, but all other elements MUST be ignored. // // Note: we still don't support multiple tags. If there are // multiple present this code will only process the first. func verifyResponseSig(validator *dsig.ValidationContext, data []byte) (signed []byte, rootVerified bool, err error) { doc := etree.NewDocument() if err = doc.ReadFromBytes(data); err != nil { return nil, false, fmt.Errorf("parse document: %v", err) } response := doc.Root() if response == nil { return nil, false, fmt.Errorf("parse document: empty root") } transformedResponse, err := validator.Validate(response) if err == nil { // Root element is verified, return it. doc.SetRoot(transformedResponse) signed, err = doc.WriteToBytes() return signed, true, err } // Ensures xmlns are copied down to the assertion element when they are defined in the root // // TODO: Only select from child elements of the root. assertion, err := etreeutils.NSSelectOne(response, "urn:oasis:names:tc:SAML:2.0:assertion", "Assertion") if err != nil || assertion == nil { return nil, false, fmt.Errorf("response does not contain an Assertion element") } transformedAssertion, err := validator.Validate(assertion) if err != nil { return nil, false, fmt.Errorf("response does not contain a valid signature element: %v", err) } // Verified an assertion but not the response. Can't trust any child elements, // except the assertion. Remove them all. for _, el := range response.ChildElements() { response.RemoveChild(el) } // We still return the full element, even though it's unverified // because the element is not a valid XML document on its own. // It still requires the root element to define things like namespaces. response.AddChild(transformedAssertion) signed, err = doc.WriteToBytes() return signed, false, err } // before determines if a given time is before the current time, with an // allowed clock drift. func before(now, notBefore time.Time) bool { return now.Add(allowedClockDrift).Before(notBefore) } // after determines if a given time is after the current time, with an // allowed clock drift. func after(now, notOnOrAfter time.Time) bool { return now.After(notOnOrAfter.Add(allowedClockDrift)) } ================================================ FILE: connector/saml/saml_test.go ================================================ package saml import ( "context" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "errors" "log/slog" "os" "sort" "testing" "time" "github.com/kylelemons/godebug/pretty" dsig "github.com/russellhaering/goxmldsig" "github.com/dexidp/dex/connector" ) // responseTest maps a SAML 2.0 response object to a set of expected values. // // Tests are defined in the "testdata" directory and are self-signed using xmlsec1. // // To add a new test, define a new, unsigned SAML 2.0 response that exercises some // case, then sign it using the "testdata/gen.sh" script. // // cp testdata/good-resp.tmpl testdata/( testname ).tmpl // vim ( testname ).tmpl # Modify your template for your test case. // vim testdata/gen.sh # Add a xmlsec1 command to the generation script. // ./testdata/gen.sh # Sign your template. // // To install xmlsec1 on Fedora run: // // sudo dnf install xmlsec1 xmlsec1-openssl // // On mac: // // brew install Libxmlsec1 type responseTest struct { // CA file and XML file of the response. caFile string respFile string // Values that should be used to validate the signature. now string inResponseTo string redirectURI string entityIssuer string // Attribute customization. usernameAttr string emailAttr string groupsAttr string allowedGroups []string filterGroups bool // Expected outcome of the test. wantErr bool wantIdent connector.Identity } func TestGoodResponse(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/good-resp.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantIdent: connector.Identity{ UserID: "eric.chiang+okta@coreos.com", Username: "Eric", Email: "eric.chiang+okta@coreos.com", EmailVerified: true, }, } test.run(t) } func TestGroups(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/good-resp.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", groupsAttr: "groups", inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantIdent: connector.Identity{ UserID: "eric.chiang+okta@coreos.com", Username: "Eric", Email: "eric.chiang+okta@coreos.com", EmailVerified: true, Groups: []string{"Admins", "Everyone"}, }, } test.run(t) } func TestGroupsWhitelist(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/good-resp.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", groupsAttr: "groups", allowedGroups: []string{"Admins"}, inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantIdent: connector.Identity{ UserID: "eric.chiang+okta@coreos.com", Username: "Eric", Email: "eric.chiang+okta@coreos.com", EmailVerified: true, Groups: []string{"Admins", "Everyone"}, }, } test.run(t) } func TestGroupsWhitelistWithFiltering(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/good-resp.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", groupsAttr: "groups", allowedGroups: []string{"Admins"}, filterGroups: true, inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantIdent: connector.Identity{ UserID: "eric.chiang+okta@coreos.com", Username: "Eric", Email: "eric.chiang+okta@coreos.com", EmailVerified: true, Groups: []string{"Admins"}, // "Everyone" is filtered }, } test.run(t) } func TestGroupsWhitelistEmpty(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/good-resp.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", groupsAttr: "groups", allowedGroups: []string{}, inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantIdent: connector.Identity{ UserID: "eric.chiang+okta@coreos.com", Username: "Eric", Email: "eric.chiang+okta@coreos.com", EmailVerified: true, Groups: []string{"Admins", "Everyone"}, }, } test.run(t) } func TestGroupsWhitelistDisallowed(t *testing.T) { test := responseTest{ wantErr: true, caFile: "testdata/ca.crt", respFile: "testdata/good-resp.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", groupsAttr: "groups", allowedGroups: []string{"Nope"}, inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantIdent: connector.Identity{ UserID: "eric.chiang+okta@coreos.com", Username: "Eric", Email: "eric.chiang+okta@coreos.com", EmailVerified: true, Groups: []string{"Admins", "Everyone"}, }, } test.run(t) } func TestGroupsWhitelistDisallowedNoGroupsOnIdent(t *testing.T) { test := responseTest{ wantErr: true, caFile: "testdata/ca.crt", respFile: "testdata/good-resp.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", groupsAttr: "groups", allowedGroups: []string{"Nope"}, inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantIdent: connector.Identity{ UserID: "eric.chiang+okta@coreos.com", Username: "Eric", Email: "eric.chiang+okta@coreos.com", EmailVerified: true, Groups: []string{}, }, } test.run(t) } // TestOkta tests against an actual response from Okta. func TestOkta(t *testing.T) { test := responseTest{ caFile: "testdata/okta-ca.pem", respFile: "testdata/okta-resp.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantIdent: connector.Identity{ UserID: "eric.chiang+okta@coreos.com", Username: "Eric", Email: "eric.chiang+okta@coreos.com", EmailVerified: true, }, } test.run(t) } func TestBadStatus(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/bad-status.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantErr: true, } test.run(t) } func TestInvalidCA(t *testing.T) { test := responseTest{ caFile: "testdata/bad-ca.crt", // Not the CA that signed this response. respFile: "testdata/good-resp.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantErr: true, } test.run(t) } func TestUnsignedResponse(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/good-resp.tmpl", // Use the unsigned template, not the signed document. now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantErr: true, } test.run(t) } func TestExpiredAssertion(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/assertion-signed.xml", now: "2020-04-04T04:34:59.330Z", // Assertion has expired. usernameAttr: "Name", emailAttr: "email", inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantErr: true, } test.run(t) } // TestAssertionSignedNotResponse ensures the connector validates SAML 2.0 // responses where the assertion is signed but the root element, the // response, isn't. func TestAssertionSignedNotResponse(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/assertion-signed.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantIdent: connector.Identity{ UserID: "eric.chiang+okta@coreos.com", Username: "Eric", Email: "eric.chiang+okta@coreos.com", EmailVerified: true, }, } test.run(t) } func TestInvalidSubjectInResponseTo(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/assertion-signed.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", inResponseTo: "invalid-id", // Bad InResponseTo value. redirectURI: "http://127.0.0.1:5556/dex/callback", wantErr: true, } test.run(t) } func TestInvalidSubjectRecipient(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/assertion-signed.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://bad.com/dex/callback", // Doesn't match Recipient value. wantErr: true, } test.run(t) } func TestInvalidAssertionAudience(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/assertion-signed.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", // EntityIssuer overrides RedirectURI when determining the expected // audience. In this case, ensure the audience is invalid. entityIssuer: "http://localhost:5556/dex/callback", wantErr: true, } test.run(t) } // TestTwoAssertionFirstSigned tries to catch an edge case where an attacker // provides a second assertion that's not signed. func TestTwoAssertionFirstSigned(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/two-assertions-first-signed.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantIdent: connector.Identity{ UserID: "eric.chiang+okta@coreos.com", Username: "Eric", Email: "eric.chiang+okta@coreos.com", EmailVerified: true, }, } test.run(t) } func TestTamperedResponseNameID(t *testing.T) { test := responseTest{ caFile: "testdata/ca.crt", respFile: "testdata/tampered-resp.xml", now: "2017-04-04T04:34:59.330Z", usernameAttr: "Name", emailAttr: "email", inResponseTo: "6zmm5mguyebwvajyf2sdwwcw6m", redirectURI: "http://127.0.0.1:5556/dex/callback", wantErr: true, } test.run(t) } func loadCert(ca string) (*x509.Certificate, error) { data, err := os.ReadFile(ca) if err != nil { return nil, err } block, _ := pem.Decode(data) if block == nil { return nil, errors.New("ca file didn't contain any PEM data") } return x509.ParseCertificate(block.Bytes) } func (r responseTest) run(t *testing.T) { c := Config{ CA: r.caFile, UsernameAttr: r.usernameAttr, EmailAttr: r.emailAttr, GroupsAttr: r.groupsAttr, RedirectURI: r.redirectURI, EntityIssuer: r.entityIssuer, AllowedGroups: r.allowedGroups, FilterGroups: r.filterGroups, // Never logging in, don't need this. SSOURL: "http://foo.bar/", } now, err := time.Parse(timeFormat, r.now) if err != nil { t.Fatalf("parse test time: %v", err) } conn, err := c.openConnector(slog.New(slog.DiscardHandler)) if err != nil { t.Fatal(err) } conn.now = func() time.Time { return now } resp, err := os.ReadFile(r.respFile) if err != nil { t.Fatal(err) } samlResp := base64.StdEncoding.EncodeToString(resp) scopes := connector.Scopes{ OfflineAccess: false, Groups: true, } ident, err := conn.HandlePOST(scopes, samlResp, r.inResponseTo) if err != nil { if !r.wantErr { t.Fatalf("handle response: %v", err) } return } if r.wantErr { t.Fatalf("wanted error") } sort.Strings(ident.Groups) sort.Strings(r.wantIdent.Groups) // Verify ConnectorData contains valid cached identity, then clear it // for the main identity comparison (ConnectorData is an implementation // detail of refresh token support). if len(ident.ConnectorData) > 0 { var ci cachedIdentity if err := json.Unmarshal(ident.ConnectorData, &ci); err != nil { t.Fatalf("failed to unmarshal ConnectorData: %v", err) } if ci.UserID != ident.UserID { t.Errorf("cached identity UserID mismatch: got %q, want %q", ci.UserID, ident.UserID) } if ci.Email != ident.Email { t.Errorf("cached identity Email mismatch: got %q, want %q", ci.Email, ident.Email) } } ident.ConnectorData = nil if diff := pretty.Compare(ident, r.wantIdent); diff != "" { t.Error(diff) } } func TestConfigCAData(t *testing.T) { logger := slog.New(slog.DiscardHandler) validPEM, err := os.ReadFile("testdata/ca.crt") if err != nil { t.Fatal(err) } valid2ndPEM, err := os.ReadFile("testdata/okta-ca.pem") if err != nil { t.Fatal(err) } // copy helper, avoid messing with the byte slice among different cases c := func(bs []byte) []byte { return append([]byte(nil), bs...) } tests := []struct { name string caData []byte wantErr bool }{ { name: "one valid PEM entry", caData: c(validPEM), }, { name: "one valid PEM entry with trailing newline", caData: append(c(validPEM), []byte("\n")...), }, { name: "one valid PEM entry with trailing spaces", caData: append(c(validPEM), []byte(" ")...), }, { name: "one valid PEM entry with two trailing newlines", caData: append(c(validPEM), []byte("\n\n")...), }, { name: "two valid PEM entries", caData: append(c(validPEM), c(valid2ndPEM)...), }, { name: "two valid PEM entries with newline in between", caData: append(append(c(validPEM), []byte("\n")...), c(valid2ndPEM)...), }, { name: "two valid PEM entries with trailing newline", caData: append(c(valid2ndPEM), append(c(validPEM), []byte("\n")...)...), }, { name: "empty", caData: []byte{}, wantErr: true, }, { name: "one valid PEM entry with trailing data", caData: append(c(validPEM), []byte("yaddayadda")...), wantErr: true, }, { name: "one valid PEM entry with bad data before", caData: append([]byte("yaddayadda"), c(validPEM)...), wantErr: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { c := Config{ CAData: tc.caData, UsernameAttr: "user", EmailAttr: "email", RedirectURI: "http://127.0.0.1:5556/dex/callback", SSOURL: "http://foo.bar/", } _, err := (&c).Open("samltest", logger) if tc.wantErr { if err == nil { t.Error("expected error, got nil") } } else if err != nil { t.Errorf("expected no error, got %v", err) } }) } } // Deprecated: Use testing framework established above. func runVerify(t *testing.T, ca string, resp string, shouldSucceed bool) { cert, err := loadCert(ca) if err != nil { t.Fatal(err) } s := certStore{[]*x509.Certificate{cert}} validator := dsig.NewDefaultValidationContext(s) data, err := os.ReadFile(resp) if err != nil { t.Fatal(err) } if _, _, err := verifyResponseSig(validator, data); err != nil { if shouldSucceed { t.Fatal(err) } } else { if !shouldSucceed { t.Fatalf("expected an invalid signature but verification has been successful") } } } func TestVerify(t *testing.T) { runVerify(t, "testdata/okta-ca.pem", "testdata/okta-resp.xml", true) } func TestVerifyUnsignedMessageAndSignedAssertionWithRootXmlNs(t *testing.T) { runVerify(t, "testdata/oam-ca.pem", "testdata/oam-resp.xml", true) } func TestVerifySignedMessageAndUnsignedAssertion(t *testing.T) { runVerify(t, "testdata/idp-cert.pem", "testdata/idp-resp-signed-message.xml", true) } func TestVerifyUnsignedMessageAndSignedAssertion(t *testing.T) { runVerify(t, "testdata/idp-cert.pem", "testdata/idp-resp-signed-assertion.xml", true) } func TestVerifySignedMessageAndSignedAssertion(t *testing.T) { runVerify(t, "testdata/idp-cert.pem", "testdata/idp-resp-signed-message-and-assertion.xml", true) } func TestVerifyUnsignedMessageAndUnsignedAssertion(t *testing.T) { runVerify(t, "testdata/idp-cert.pem", "testdata/idp-resp.xml", false) } func TestSAMLRefresh(t *testing.T) { // Create a provider using the same pattern as existing tests. c := Config{ CA: "testdata/ca.crt", UsernameAttr: "Name", EmailAttr: "email", GroupsAttr: "groups", RedirectURI: "http://127.0.0.1:5556/dex/callback", SSOURL: "http://foo.bar/", } conn, err := c.openConnector(slog.New(slog.DiscardHandler)) if err != nil { t.Fatal(err) } t.Run("SuccessfulRefresh", func(t *testing.T) { ci := cachedIdentity{ UserID: "test-user-id", Username: "testuser", PreferredUsername: "testuser", Email: "test@example.com", EmailVerified: true, Groups: []string{"group1", "group2"}, } connectorData, err := json.Marshal(ci) if err != nil { t.Fatal(err) } ident := connector.Identity{ UserID: "old-id", Username: "old-name", ConnectorData: connectorData, } refreshed, err := conn.Refresh(context.Background(), connector.Scopes{Groups: true}, ident) if err != nil { t.Fatalf("Refresh failed: %v", err) } if refreshed.UserID != "test-user-id" { t.Errorf("expected UserID %q, got %q", "test-user-id", refreshed.UserID) } if refreshed.Username != "testuser" { t.Errorf("expected Username %q, got %q", "testuser", refreshed.Username) } if refreshed.PreferredUsername != "testuser" { t.Errorf("expected PreferredUsername %q, got %q", "testuser", refreshed.PreferredUsername) } if refreshed.Email != "test@example.com" { t.Errorf("expected Email %q, got %q", "test@example.com", refreshed.Email) } if !refreshed.EmailVerified { t.Error("expected EmailVerified to be true") } if len(refreshed.Groups) != 2 || refreshed.Groups[0] != "group1" || refreshed.Groups[1] != "group2" { t.Errorf("expected groups [group1, group2], got %v", refreshed.Groups) } // ConnectorData should be preserved through refresh if len(refreshed.ConnectorData) == 0 { t.Error("expected ConnectorData to be preserved") } }) t.Run("RefreshPreservesConnectorData", func(t *testing.T) { ci := cachedIdentity{ UserID: "user-123", Username: "alice", Email: "alice@example.com", EmailVerified: true, } connectorData, err := json.Marshal(ci) if err != nil { t.Fatal(err) } ident := connector.Identity{ UserID: "old-id", ConnectorData: connectorData, } refreshed, err := conn.Refresh(context.Background(), connector.Scopes{}, ident) if err != nil { t.Fatalf("Refresh failed: %v", err) } // Verify the refreshed identity can be refreshed again (round-trip) var roundTrip cachedIdentity if err := json.Unmarshal(refreshed.ConnectorData, &roundTrip); err != nil { t.Fatalf("failed to unmarshal ConnectorData after refresh: %v", err) } if roundTrip.UserID != "user-123" { t.Errorf("round-trip UserID mismatch: got %q, want %q", roundTrip.UserID, "user-123") } }) t.Run("EmptyConnectorData", func(t *testing.T) { ident := connector.Identity{ UserID: "test-id", ConnectorData: nil, } _, err := conn.Refresh(context.Background(), connector.Scopes{}, ident) if err == nil { t.Error("expected error for empty ConnectorData") } }) t.Run("InvalidJSON", func(t *testing.T) { ident := connector.Identity{ UserID: "test-id", ConnectorData: []byte("not-json"), } _, err := conn.Refresh(context.Background(), connector.Scopes{}, ident) if err == nil { t.Error("expected error for invalid JSON") } }) t.Run("HandlePOSTThenRefresh", func(t *testing.T) { // Full integration: HandlePOST → get ConnectorData → Refresh → verify identity now, err := time.Parse(timeFormat, "2017-04-04T04:34:59.330Z") if err != nil { t.Fatal(err) } conn.now = func() time.Time { return now } resp, err := os.ReadFile("testdata/good-resp.xml") if err != nil { t.Fatal(err) } samlResp := base64.StdEncoding.EncodeToString(resp) scopes := connector.Scopes{ OfflineAccess: true, Groups: true, } ident, err := conn.HandlePOST(scopes, samlResp, "6zmm5mguyebwvajyf2sdwwcw6m") if err != nil { t.Fatalf("HandlePOST failed: %v", err) } if len(ident.ConnectorData) == 0 { t.Fatal("expected ConnectorData to be set after HandlePOST") } // Now refresh using the ConnectorData from HandlePOST refreshed, err := conn.Refresh(context.Background(), scopes, ident) if err != nil { t.Fatalf("Refresh failed: %v", err) } if refreshed.UserID != ident.UserID { t.Errorf("UserID mismatch: got %q, want %q", refreshed.UserID, ident.UserID) } if refreshed.Username != ident.Username { t.Errorf("Username mismatch: got %q, want %q", refreshed.Username, ident.Username) } if refreshed.Email != ident.Email { t.Errorf("Email mismatch: got %q, want %q", refreshed.Email, ident.Email) } if refreshed.EmailVerified != ident.EmailVerified { t.Errorf("EmailVerified mismatch: got %v, want %v", refreshed.EmailVerified, ident.EmailVerified) } sort.Strings(refreshed.Groups) sort.Strings(ident.Groups) if len(refreshed.Groups) != len(ident.Groups) { t.Errorf("Groups length mismatch: got %d, want %d", len(refreshed.Groups), len(ident.Groups)) } for i := range ident.Groups { if i < len(refreshed.Groups) && refreshed.Groups[i] != ident.Groups[i] { t.Errorf("Groups[%d] mismatch: got %q, want %q", i, refreshed.Groups[i], ident.Groups[i]) } } }) t.Run("HandlePOSTThenDoubleRefresh", func(t *testing.T) { // Verify that refresh tokens can be chained: HandlePOST → Refresh → Refresh now, err := time.Parse(timeFormat, "2017-04-04T04:34:59.330Z") if err != nil { t.Fatal(err) } conn.now = func() time.Time { return now } resp, err := os.ReadFile("testdata/good-resp.xml") if err != nil { t.Fatal(err) } samlResp := base64.StdEncoding.EncodeToString(resp) scopes := connector.Scopes{OfflineAccess: true, Groups: true} ident, err := conn.HandlePOST(scopes, samlResp, "6zmm5mguyebwvajyf2sdwwcw6m") if err != nil { t.Fatalf("HandlePOST failed: %v", err) } // First refresh refreshed1, err := conn.Refresh(context.Background(), scopes, ident) if err != nil { t.Fatalf("first Refresh failed: %v", err) } if len(refreshed1.ConnectorData) == 0 { t.Fatal("expected ConnectorData after first refresh") } // Second refresh using output of first refresh refreshed2, err := conn.Refresh(context.Background(), scopes, refreshed1) if err != nil { t.Fatalf("second Refresh failed: %v", err) } // All fields should match original if refreshed2.UserID != ident.UserID { t.Errorf("UserID mismatch after double refresh: got %q, want %q", refreshed2.UserID, ident.UserID) } if refreshed2.Email != ident.Email { t.Errorf("Email mismatch after double refresh: got %q, want %q", refreshed2.Email, ident.Email) } if refreshed2.Username != ident.Username { t.Errorf("Username mismatch after double refresh: got %q, want %q", refreshed2.Username, ident.Username) } }) t.Run("HandlePOSTWithAssertionSignedThenRefresh", func(t *testing.T) { // Test with assertion-signed.xml (signature on assertion, not response) now, err := time.Parse(timeFormat, "2017-04-04T04:34:59.330Z") if err != nil { t.Fatal(err) } conn.now = func() time.Time { return now } resp, err := os.ReadFile("testdata/assertion-signed.xml") if err != nil { t.Fatal(err) } samlResp := base64.StdEncoding.EncodeToString(resp) scopes := connector.Scopes{OfflineAccess: true, Groups: true} ident, err := conn.HandlePOST(scopes, samlResp, "6zmm5mguyebwvajyf2sdwwcw6m") if err != nil { t.Fatalf("HandlePOST with assertion-signed failed: %v", err) } if len(ident.ConnectorData) == 0 { t.Fatal("expected ConnectorData after HandlePOST with assertion-signed") } refreshed, err := conn.Refresh(context.Background(), scopes, ident) if err != nil { t.Fatalf("Refresh after assertion-signed HandlePOST failed: %v", err) } if refreshed.Email != ident.Email { t.Errorf("Email mismatch: got %q, want %q", refreshed.Email, ident.Email) } if refreshed.Username != ident.Username { t.Errorf("Username mismatch: got %q, want %q", refreshed.Username, ident.Username) } }) t.Run("HandlePOSTRefreshWithoutGroupsScope", func(t *testing.T) { // Verify that groups are NOT returned when groups scope is not requested during refresh now, err := time.Parse(timeFormat, "2017-04-04T04:34:59.330Z") if err != nil { t.Fatal(err) } conn.now = func() time.Time { return now } resp, err := os.ReadFile("testdata/good-resp.xml") if err != nil { t.Fatal(err) } samlResp := base64.StdEncoding.EncodeToString(resp) // Initial auth WITH groups scopesWithGroups := connector.Scopes{OfflineAccess: true, Groups: true} ident, err := conn.HandlePOST(scopesWithGroups, samlResp, "6zmm5mguyebwvajyf2sdwwcw6m") if err != nil { t.Fatalf("HandlePOST failed: %v", err) } if len(ident.Groups) == 0 { t.Fatal("expected groups in initial identity") } // Refresh WITHOUT groups scope scopesNoGroups := connector.Scopes{OfflineAccess: true, Groups: false} refreshed, err := conn.Refresh(context.Background(), scopesNoGroups, ident) if err != nil { t.Fatalf("Refresh failed: %v", err) } if len(refreshed.Groups) != 0 { t.Errorf("expected no groups when groups scope not requested, got %v", refreshed.Groups) } // Refresh WITH groups scope — groups should be back refreshedWithGroups, err := conn.Refresh(context.Background(), scopesWithGroups, ident) if err != nil { t.Fatalf("Refresh with groups failed: %v", err) } if len(refreshedWithGroups.Groups) == 0 { t.Error("expected groups when groups scope is requested") } }) } ================================================ FILE: connector/saml/testdata/assertion-signed.tmpl ================================================ http://www.okta.com/exk91cb99lKkKSYoy0h7 http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+okta@coreos.com http://127.0.0.1:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport eric.chiang+okta@coreos.com Eric Everyone Admins ================================================ FILE: connector/saml/testdata/assertion-signed.xml ================================================ http://www.okta.com/exk91cb99lKkKSYoy0h7 kDdpmKMPp7zjUeMTuntXJFpT6cs= Q12w9zPo9Bqn3W2OT7lbFbhfIDQrp7SXd/+9ZWpY4mv3ApgaOcNX2VtpSE0EjorU 1NyrktVOkCYueJm/HVQF7gyF85KRlPBDLBZOwxbnnmrRFoCk+U2kjIt8k8eZ7NsD jLFGFgEveS359uvaHZR1Exbr0PBYwS7aXR3fpmjMjZ9T8f8Oe3Nt/9nWPgz/dFhb Aa+AniWuupfq2v6YMLZ+9GLiO0sOr8UVkW8AYOm2Bin30epikXT1Axi/VxWz/fjP nMUkgusFnhkgmIf/YncAv4S9GY6DcaV2iEj6cL70S7pcgaeRFi3iozigl+9a+lFo dt3Jy8Jq/N3OAyDP2DxLEw== MIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV BAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z NzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI 4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO ZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD AB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA faXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF a7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9 QacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud IwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP oR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS hSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De oX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd esSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j 1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q= http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+okta@coreos.com http://127.0.0.1:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport eric.chiang+okta@coreos.com Eric Everyone Admins ================================================ FILE: connector/saml/testdata/bad-ca.crt ================================================ -----BEGIN CERTIFICATE----- MIIDGTCCAgGgAwIBAgIJAINei+KBx541MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV BAoMA0JBRDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z NzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0JBRDETMBEGA1UEAwwKY29yZW9zLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKfSVQCQMJhwdeesQqZo YHktgGjKA//p176kWoqVEX8+cVgKc1IEAzN73L0KsBcNkaItbZycQxkku8NGUYvt inEK8V61vjK3IQBOV2+qKOeWvHPXd280I2JSGy768ELpZcaEmnA0CVbMaLdHLFQ/ tcRocDcMKDhNexlsJ/cDPwzSkky6FU6UnaSih+I432UeJs/hRvAs1YJ3+G6uSVQ7 xo2Fk2i+qd3IvRWOBagwEhEFcd/MfAu4+w+UYW4W1BC9zJoO2ETBgUVJzaiUGZ5z rX/KNAX/+kAQWQLKW1sVgRKOI2yfyIVaUzF6V2uxwHrbeV75Pr3ClVdvuUWRcXKH Wn8CAwEAAaNQME4wHQYDVR0OBBYEFLw+L3SLMIR2wfd7IQG1m3rlgGP7MB8GA1Ud IwQYMBaAFLw+L3SLMIR2wfd7IQG1m3rlgGP7MAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQELBQADggEBABH2mDQuf+JqjHtBCp9coi1WuX5xJuoSKyOAvlz7QJk0bb+W oONg+ETQ7jT7KheiFQorghdulZerv+L1dmLU5ut/Zbf1zoaWLslOgVPpOy6LP0aS uaa31jm7Qpig47+kTZEpPN6vUPpmAD70/uo3NgRNj8pztkWr08fEIbX/3ukHvFUE XPxYxgF5tFj8EY6cdflzlC/0TYmJiHt/viv/yQfrvMBRvsVHfjfzNRxbNwfEdKuf YIs5KeP2al7APsmF5d/UFGzoQGXaKEOpCRJ+Oj3spooLhhzWqV4gPZLVgU0DD6ja GPb2XgLA9mdEmMukHU0RMeb8R+r8RvytWKvuJQE= -----END CERTIFICATE----- ================================================ FILE: connector/saml/testdata/bad-ca.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCn0lUAkDCYcHXn rEKmaGB5LYBoygP/6de+pFqKlRF/PnFYCnNSBAMze9y9CrAXDZGiLW2cnEMZJLvD RlGL7YpxCvFetb4ytyEATldvqijnlrxz13dvNCNiUhsu+vBC6WXGhJpwNAlWzGi3 RyxUP7XEaHA3DCg4TXsZbCf3Az8M0pJMuhVOlJ2koofiON9lHibP4UbwLNWCd/hu rklUO8aNhZNovqndyL0VjgWoMBIRBXHfzHwLuPsPlGFuFtQQvcyaDthEwYFFSc2o lBmec61/yjQF//pAEFkCyltbFYESjiNsn8iFWlMxeldrscB623le+T69wpVXb7lF kXFyh1p/AgMBAAECggEAHh/PSk6XqoVlZLSzMhPCXX4hcq3wkdtz8rCl4AJqJaEb z2Xw1WQK/w7YzMZCXaD951KoPlh+YuEJI0BYGvoEw83nDc0p2wisT9XANDcjKI8S POkMc1W0lE2Qu5onzpr+vefHoSR2GLKQiXWpK2ZURnFI01jHT3P5CNM1SU2336ES XT1GXlxTz59BS04bySS3s8PQZTtmFvQAjtjOWkg+nzTYgEO7xU5968Px+jdlO+xf ZKtjlRKRNRmVlylUvCmIT6kIzIdyjuBqw4yx0bM1Av9QwpPuDkRWBeiT8o5xeY+L yOSxUCg24CGgAl6mGVXWh+BC9Z3h8dXg9eMervT9gQKBgQDS6QffOBr+j19FiQwH t+5h2klOR/mu8TX6oW8Dc6o5YsnLt0uQqa2jLq8kYRkOBVg68mMFvZXmhPoKv4hk rOfDKjVfPz9ShrvuY301BAp/hdi/hNP+MAt683UlcnpwLOaASZMr+MjdnVVzfF6X iNlV2RE3pAxSFkVlMPJJTmPPEQKBgQDLsxbYFj4JBKhMehdcsqjUtDuzLBPqisVa cWKACxPAXIjztsEXF4F7sQk7q68uzLU80uPBVAJjf6lWwK9tcUdcBFHCXdqj8ZXB 77W4gZSkqHY0T6DJF+NZRF+z4cOg3qtDjTvqhetl5yyiY9IPyiSckNAz40pWD/0X U/0nObuwjwKBgQCpWpUHmHWUkmtd2n3edMLlr/HM+d5zqxw89APAMdAt5DVFbxku QBE9Ru87tvv3VjNSoe8BXQpQ39Yna0SKEozHGc1hfdfK3IVrFlgjieskGsXAg1f2 c33EbFlUiGfoSyWLPYj/dfVUflFvOh56b1iUpog8tW1vPJLcfkEOu/NJAQKBgQCu LAp7Z8FRarcQ9VAmhekQPq/RSv4YjOGkrNChVVdlInpDkV9XBFVF0yFm8SzQYl8R i+0McG2+b/j2YbleZf6zMkpKXH/HsJjxg6qpAbt8c0LnBbMgXxmZSXpfT8o7MknU b93scOfPcTRcAegqchiN+tDbnRwBrJgmqz0JnjbbBwKBgCXOWMgS8Azvvw+lUqua yLdcAilAaqchgVBkI5ATZQodopEP+Gvevvzh6G8uQJp9fGrL/fR/tNg2viwCF/X8 ROATx1z98/ItA3kyYhiNt/A6EqOb8SVOMq/eeMpvk+RB6gA9YdMf5H9XClW4vxLy jaw+YQ6hOyMTmFfUI2/FumFE -----END PRIVATE KEY----- ================================================ FILE: connector/saml/testdata/bad-status.tmpl ================================================ http://www.okta.com/exk91cb99lKkKSYoy0h7 http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+okta@coreos.com http://127.0.0.1:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport eric.chiang+okta@coreos.com Eric Everyone Admins ================================================ FILE: connector/saml/testdata/bad-status.xml ================================================ 6a1wCEwD2CQ3tYWkRrVs/upMJ10= J/yFKDQIAKkfoJyK/5Apylg6KFaK7Kwr/gn1stAUJqAab7I61gx/fVw4TCCGH5Nv 6yXdw1f/QN6CBoNXA9sus2tG5KQ7od/lrPs+kmyaDVTp87Q6mqvQBD/Ekt2N/SQ6 jvlK70BGjJibVGoZf20EZdojpZEgvDdNa2YsVws0ZEuY4/XMsmddJnY76UDZvR8X NVryuMuK8rYWXMl8tLaZo5k/LgxxeDDWWwcicBQcWIOl/tuGbfssixgBHFWY4ye3 HD8ChdxfS7sxHxAbipZ03BoutVriGQBeO3f+nI4TEIZov7V41YUqSyfcRGb3+WT4 dm7kMaNn6GUyv3/TXuGIQg== MIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV BAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z NzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI 4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO ZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD AB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA faXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF a7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9 QacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud IwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP oR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS hSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De oX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd esSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j 1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q= http://www.okta.com/exk91cb99lKkKSYoy0h7 http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+okta@coreos.com http://127.0.0.1:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport eric.chiang+okta@coreos.com Eric Everyone Admins ================================================ FILE: connector/saml/testdata/ca.crt ================================================ -----BEGIN CERTIFICATE----- MIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV BAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z NzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI 4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO ZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD AB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA faXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF a7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9 QacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud IwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP oR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS hSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De oX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd esSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j 1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q= -----END CERTIFICATE----- ================================================ FILE: connector/saml/testdata/ca.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCh93Slm0agiGQ9 pt2hyOJXwU/rv+AxAnXu+BoKvVroHU7nFUMw2VE2iO8M59eyTPYXT5l72kkRvQWg dR97jmQrP35nYQREUYT77hFXwGWhX0H0e75+1r1M0+88DCywfPhB8y93zkhjNkJw jjHlAwAduz29H7gVuNzV2kw/a8HxQh76NVD/kwSSD51euRzHjBiK7Ri7ezGUz7j5 ty52AH2ly39r1ksy+ZwIJyb1IHHmV5srnIgX16bfHNYyLI+Mt7QaEU0amsbmltRK O1jChWu7Oir0wShKzyrswKCHFQeIq+o5tbYnAIAe9kknCrTySXRD3pk10TTc5IsO 1W6WPUGnAgMBAAECggEBAJUJUDPHIwE7IAo/Drf9UpFvl2wWPmS6n+yKLeRuA0WN Gnq27QH5Jqro7BdTCv7NpLEklNYLsar55UCWJacbCn9lSJo2Aqge3yC3GwxFRP9t 2RHwAAVU8hHM/tmhVkn8ZLDC5o32qlNorVBG+BCEZ0n0bsYlds2+Mq8x1XGSZX7q Yyjt02q2VCEDPVUGqR2ONEE98Bv++mUfXzZjGohBPre67PuvEppEwo/uf4ILuyYB lusSYcmPD2IBE/7FcRPefb2uDP9c6Tjo8PHXpK5hDBCtLz8lXheYMuTp9GqJmcOk kw8af2jNkMUEpFho0RqnrJNQ3A7M5PXQiWxwYSL/2RkCgYEAz4Kg0qm4Oe5jl+4r R986nElujo9g9Ad2sy9yT6fqceWoMKf68wh6bMx7HWXRqASS7bJ8wmS9EwgaWLBO mjqZax99F1UiFk3+5lNEaYB1OlFaWRsZSapoX6b2JyfEbODqSgKVIaYVTYBHLip3 3ab/EKNUrNbW9bLSXgbt3aT2kU0CgYEAx9BiErhZqkgmip+4omCWuTr6Lj/awftB CVTfwLBYkh7SZcRAWc2bx7a1Bs1rvlczhGYsOlrZXnQRSM4fuUxUb+N5TCzZI9V/ Prc2r2Lps3AB5CDPLZoyv0efBjSLqAA1tYKTvAHREi9Wfe5PNdfKiwrz6KmIwV3c +s2YSWcU5MMCgYEArmzvIiTnZkqsDJl2aAOMELLo64w5wuZDMHtBaxOKThLtPXj1 yDPoNGvtUNi1UrYFiyftFrn29HhrLQGGEL4RF6pwS5yT+ou1J4X2i3gfEdYwS5Yr u3AyK7T8VA1pXtvwFCX3lUE1xt989aFdAEPPQv0HwAEWz5Bwo/jPGPABEkECgYBy vDWUikbygHuhHhXnJ49kzXjbFc+Hk77EnPferWQug4RM62QILQhGpaNNRKeZpHjw jbrXx1MJ6ZwDMlkFDc9ucDA2jYoiCXYHjSzZiPKpFqf/VtegV+rL61RlO8b1sSkm ENTEIEbtKkGADldtk3u6W4+zCaZ9YmiBm4zWmVpmAQKBgHvNRcbITib+sbQE+cJM 4TtrAHFTLWtGCd+n6rKrE8gjt7ypbBOMlOau60LZ2Pbt3DrqOLtDoOalvZhYraOb rkoPbDAVaXAmUJ8tw2M07PPLpJuruLSFw16VrBaDiyubO8H/hvnuF4kKpRvt8ty0 DBogMo9McFczyisRKebmANLw -----END PRIVATE KEY----- ================================================ FILE: connector/saml/testdata/gen.sh ================================================ #!/bin/bash -ex # Always run from the testdata directory cd "$(dirname "$0")" # Uncomment these commands to regenerate the CA files. # # openssl req \ # -nodes \ # -newkey rsa:2048 \ # -keyout ca.key \ # -new -x509 -days 7300 \ # -extensions v3_ca \ # -out ca.crt \ # -subj "/O=DEX/CN=coreos.com" # # openssl req \ # -nodes \ # -newkey rsa:2048 \ # -keyout bad-ca.key \ # -new -x509 -days 7300 \ # -extensions v3_ca \ # -out bad-ca.crt \ # -subj "/O=BAD/CN=coreos.com" # Sign these files using xmlsec1. # # Templates MUST have a element already embedded in them so # xmlsec1 can know where to embed the signature. # # See: https://sgros.blogspot.com/2013/01/signing-xml-document-using-xmlsec1.html xmlsec1 --sign --privkey-pem ca.key,ca.crt --output good-resp.xml good-resp.tmpl xmlsec1 --sign --privkey-pem ca.key,ca.crt --output bad-status.xml bad-status.tmpl # Sign a specific sub element, not just the root. # # Values match up to the element in the documents. xmlsec1 --sign --privkey-pem ca.key,ca.crt \ --id-attr:ID Assertion \ --output assertion-signed.xml assertion-signed.tmpl xmlsec1 --sign --privkey-pem ca.key,ca.crt \ --id-attr:ID Assertion \ --output two-assertions-first-signed.xml \ two-assertions-first-signed.tmpl ================================================ FILE: connector/saml/testdata/good-resp.tmpl ================================================ http://www.okta.com/exk91cb99lKkKSYoy0h7 http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+okta@coreos.com http://127.0.0.1:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport eric.chiang+okta@coreos.com Eric Everyone Admins ================================================ FILE: connector/saml/testdata/good-resp.xml ================================================ ew38E1LGMwYT+0gUZNq0RacD3GM= TQ84pCaZAyEDBGkNafTMfwPUWujFvmdoXzyYMXZURIlKhA8Pv1bIZfzQ5MgbQr1W z2Ye99/hss24Y4ueNT9nS+53LvDekhNctFGYfgdMjrbxs8Awo3KnbvveDib5zGvk fWd/0/QLvlbFd/3670QGb5JQE1nD9mlAqPonyQgoufk63gEM84+tU71cAM7XKiy6 09MC0y4s967qRAiLAtfgKbvi+46HkF/g+WsS74Wa8cu/A863URt56W0cogRjHWpQ B+q8/FyVeJRE0NlrOjhnsgTU2QJtvkxYYvqIpRDbMv53NLKeAFvRhOcyJxhFXtSj LF/oPMjbmHji4ylFiAlQWw== MIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV BAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z NzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI 4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO ZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD AB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA faXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF a7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9 QacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud IwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP oR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS hSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De oX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd esSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j 1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q= http://www.okta.com/exk91cb99lKkKSYoy0h7 http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+okta@coreos.com http://127.0.0.1:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport eric.chiang+okta@coreos.com Eric Everyone Admins ================================================ FILE: connector/saml/testdata/idp-cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNV BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNV BAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcN AQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3 WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMD SURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEb MBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3 vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsby CRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1u uVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sB TlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4Xn XsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQW BBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsH TXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3Ju aWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMT CmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb3 9nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW 6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1 wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUK FpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+a jSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor /NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0De Jc3men4= -----END CERTIFICATE----- ================================================ FILE: connector/saml/testdata/idp-resp-signed-assertion.xml ================================================ http://www.okta.com/exk91cb99lKkKSYoy0h7 http://www.okta.com/exk91cb99lKkKSYoy0h7 HFNooGfpAONF7T96W3bFsXkH51k=dI0QBihhNT5rtRYE9iB0lEKXkE7Yr4+QueOItRH2RcKwAXJ6DA/m3D/S7qwXk00Hn8ZpHu48ZO+HJpyweEEh2UuUWJCCTwwggagKybbSoRx3UTnSuNAFTdoDWTGt89z8j4+gRMC0sepYwppF3u87vJKRVBh8HjFfrHmWsZKwNtfoeXOOFCeatwxcI1sKCoBs2fTn78683ThoAJe3pygipSHY5WPt4dfT/yAY5Ars+OPY/N02M80OfIygZXdJwND0tVPJIF3M9DaehSkvCBHs7QA7DARsRXcuXdsYY7R8wHzqDVJZ4OvcsprONamm5AgUIpql1CjT94rFwWOFyxF2tg== MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4= eric.chiang+okta@coreos.com http://localhost:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport ================================================ FILE: connector/saml/testdata/idp-resp-signed-assertion0.xml ================================================ http://www.okta.com/exk91cb99lKkKSYoy0h7 http://www.okta.com/exk91cb99lKkKSYoy0h7 HFNooGfpAONF7T96W3bFsXkH51k=dI0QBihhNT5rtRYE9iB0lEKXkE7Yr4+QueOItRH2RcKwAXJ6DA/m3D/S7qwXk00Hn8ZpHu48ZO+HJpyweEEh2UuUWJCCTwwggagKybbSoRx3UTnSuNAFTdoDWTGt89z8j4+gRMC0sepYwppF3u87vJKRVBh8HjFfrHmWsZKwNtfoeXOOFCeatwxcI1sKCoBs2fTn78683ThoAJe3pygipSHY5WPt4dfT/yAY5Ars+OPY/N02M80OfIygZXdJwND0tVPJIF3M9DaehSkvCBHs7QA7DARsRXcuXdsYY7R8wHzqDVJZ4OvcsprONamm5AgUIpql1CjT94rFwWOFyxF2tg== MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4= eric.chiang+okta@coreos.com http://localhost:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+attacker@coreos.com http://localhost:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport attacker eric.chiang+attacker@coreos.com ================================================ FILE: connector/saml/testdata/idp-resp-signed-message-and-assertion.xml ================================================ P2k0nQ19ZcowlcaOz6do6Tyu8WI=ytdy1qRPdMeIGnlkkaeLdblzTPtIFc0EJNm8WktsNU1Mn6G/6AmNaXEUik2BkTpk8zKabHdSf6+le8hwRiyfNWPTF84lzVdMjQ/+I8pnX/srpG534zoSAsP6ZFQvHp46AHPx31KP75H/ymqx2DNppqxh8JjUeMKQkPUEqduWUZ4kFjcsrz9H3MNVsHfxntnswibiknU/wAthtBuY2I6yOIF55RprUgYb5j2TqDd3IArF6LkxWRvHvhaw66MdhY1iiit7AFOcuHJVyPe8Attra94jwM+O1Ch+HQgoI43nX91d/jkP0vyWzWD8Xkcwb+KuRPsQflxjV22UU0+JbwrBYA== MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4= http://www.okta.com/exk91cb99lKkKSYoy0h7 http://www.okta.com/exk91cb99lKkKSYoy0h7 4jNCSI3tTnbpozZ8qT4FZe+EWV8=xtTnl92ArZyxskD3b34cIjo5LIpeE+3RjW+jgtXMXhUIZp3uGJ2RC6n1CbJ6IWuo4KmezpnVUnSWNz/fgOTZCN/1VlqsfLDpoTf790GrP+q6rKyw8CW7nd0uVS5FRYe05HTO6C5RqnaE9PmZ/YYbiWtLIDx0+kqvu/jFr+D144G/mukaVG4ydnDQ/tl21N6hWIOpi1tWaNPv50OEEgY//9VPql9Us3YuhfrxNggVugauArwY9RL4nVFVjALP1wpkZn1JzpgNMFgvXfY3MxnI1OnWg6ypJESugIKroKqj5RyqMIaLICsUOBwIKk8R4zAATrB+D+kuFV9Ec837duW/Eg== MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4= eric.chiang+okta@coreos.com http://localhost:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport ================================================ FILE: connector/saml/testdata/idp-resp-signed-message.xml ================================================ Oy9CTB/hzFWyr6QF99EZ4ymIEfY=COKw1DUpDzNVulVNFlcrPwaalCwr8BfTye92GN5snTmx3IDKLudr1PT+WPt5i2N4xaoFcq/X1p/yEVmWtC1O+YYXNNIouFwps9Gyw/iDEMs1TtVKfikbloKWkDdYgfqgcon+mOq/lHagLVAcgz5QRBHVTIrFWcFYbnemsj1hy8q7ToIeoyHX9f5TAZBfZEEbdZcsD581xKPafNGowgfWxkgEwLBzFsJVYg/QfeoNnORTsKlsQBuswiXrsWatZNOOjWpdF9qqYn/3f9axqx2CJPD2HB38Vl0g4dTFnpmMAe45ndJq0IpXr9YJNCDJjUIvR7srdV1AW7qe2Mp6LxBBNw== MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4= http://www.okta.com/exk91cb99lKkKSYoy0h7 http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+okta@coreos.com http://localhost:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport ================================================ FILE: connector/saml/testdata/idp-resp.xml ================================================ http://www.okta.com/exk91cb99lKkKSYoy0h7 http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+okta@coreos.com http://localhost:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport admin eric.chiang+okta@coreos.com ================================================ FILE: connector/saml/testdata/oam-ca.pem ================================================ -----BEGIN CERTIFICATE----- MIIB/jCCAWegAwIBAgIBCjANBgkqhkiG9w0BAQQFADAkMSIwIAYDVQQDExlkZWFv YW0tZGV2MDIuanBsLm5hc2EuZ292MB4XDTE2MDYzMDA0NTQxNloXDTI2MDYyODA0 NTQxNlowJDEiMCAGA1UEAxMZZGVhb2FtLWRldjAyLmpwbC5uYXNhLmdvdjCBnzAN BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAht1N4lGdwUbl7YRyHwSCrnep6/e2I3+V eue0pSA/DGn8OuR/udM8UCja5utqlqJdq200ox4b4Mpz0Jg9kMckALtKe+1DgeES EIx9FpeuBdHlitYQNSbEr30HIG2nmeTOy4Vi5unBO54um3tNazcUTMA0/LJ6KQL8 LeZSlB/IxwUCAwEAAaNAMD4wDAYDVR0TAQH/BAIwADAPBgNVHQ8BAf8EBQMDB9gA MB0GA1UdDgQWBBRYo1YjfrNonauLzj6/AsueWFGSszANBgkqhkiG9w0BAQQFAAOB gQACq7GHK/Zsg0+qC0WWa2ZjmOXE6Dqk/xuooG49QT7ihABs7k9U27Fw3xKF6MkC 7pca1FwT82eZK1N3XKKpZe7Flu1fMKt2o/XSiBkDjWwUcChVnwGsUBe8hJFwFqg7 olNJn1kaVBJUqZIiXF9kS0d+1H55rStOd0CNXAzp9utr2A== -----END CERTIFICATE----- ================================================ FILE: connector/saml/testdata/oam-resp.xml ================================================ https://deaoam-dev02.jpl.nasa.gov:14101/oam/fedhttps://deaoam-dev02.jpl.nasa.gov:14101/oam/fedz1HD/59hv6UOd5+jeG+ihaFWLgI=I99oG5kiOfIgbXYa21z/TOmzftTkFnXe9ObhBNSKit9kAhT93apYROqqXv4Ax96P144Ld7ERX1hgJsytK8LC2874Pk7QrSNm4zvW3x0D4GR4lM06CvJK/EhIur3TrCUJDPigvyP7TJitheCyBejwt0x0lqNP/OzR3tMbAIMRoho=pkieuJSAuthurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport ================================================ FILE: connector/saml/testdata/okta-ca.pem ================================================ -----BEGIN CERTIFICATE----- MIIDpDCCAoygAwIBAgIGAVjgvNroMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05NjkyNDQxHDAaBgkqhkiG9w0BCQEW DWluZm9Ab2t0YS5jb20wHhcNMTYxMjA4MjMxOTIzWhcNMjYxMjA4MjMyMDIzWjCBkjELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTY5MjQ0MRwwGgYJ KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 4dW2YlcXjZwnTmLnV7IOBq8hhrdbqlNwdjCHRyx1BizOk3RbVP56grgPdyWScTCPpJ6vZZ8rtrY0 m1rwr+cxifNuQGKTlE33g2hReo/N9f3LFUMITlnnNH80Yium3SYuEqGeHLYerelXOnEKx6x+X5qD eg2DRW6I9/v/mfN2KAQEDqF9aSNlNFWZWmb52kukMv3tLWw0puaevicIZ/nZrW+D3CLDVVfWHeVt 46EF2bkLdgbIJOU3GzLoolgBOCkydX9x6xTw6knwQaqYsRGflacw6571IzWEwjmd17uJXkarnhM1 51pqwIoksTzycbjinIg6B1rNpGFDN7Ah+9EnVQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQDQOtlj 7Yk1j4GV/135zLOlsaonq8zfsu05VfY5XydNPFEkyIVcegLa8RiqALyFf+FjY/wVyhXtARw7NBUo u/Jh63cVya/VEoP1SVa0XQLf/ial+XuwdBBL1yc+7o2XHOfluDaXw/v2FWYpZtICf3a39giGaNWp eCT4g2TDWM4Jf/X7/mRbLX9tQO7XRural1CXx8VIMcmbKNbUtQiO8yEtVQ+FJKOsl7KOSzkgqNiL rJy+Y0D9biLZVKp07KWAY2FPCEtCkBrvo8BhvWbxWMA8CVQNAiTylM27Pc6kbc64pNr7C1Jx1wuE mVy9Fgb4PA2N3hPeD7mBmGGp7CfDbGcy -----END CERTIFICATE----- ================================================ FILE: connector/saml/testdata/okta-resp.xml ================================================ http://www.okta.com/exk91cb99lKkKSYoy0h7/jttoTHVc1zHB8tgDVQtiNLAhKAiL24V5Np/SNViPag=y1v4Q0No1l82Y32SHL8weC4mXPWRmhy0SdWPd4Dw1gMWW2aGdnMt11VGom4z1/42HxKB0EBje/UPJeagcpEgJ2JQTdb1SvZrQaxwcWexH0Qyw2Tim9kv66dgr0Uo6PRmkYw/ewcimgdNZUOieffUG1Wc+YWtllmE4nz6NR3aLT9WScbC5smbXk2HwWP9xSg3loKMYTUSH32sRePw76QdGbVExUYyFjnB+oY9mIzBHvCTjZw6JGqEaMBmzReCgLZEBdeo0BuitTWCJY5bayJEpasJCQE4iurcKsz6wMheBbEAcQBqiExSUU8dJgQPhD9y5nMBEGC4cvdq4xbvWBKfVQ==MIIDpDCCAoygAwIBAgIGAVjgvNroMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05NjkyNDQxHDAaBgkqhkiG9w0BCQEW DWluZm9Ab2t0YS5jb20wHhcNMTYxMjA4MjMxOTIzWhcNMjYxMjA4MjMyMDIzWjCBkjELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTY5MjQ0MRwwGgYJ KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 4dW2YlcXjZwnTmLnV7IOBq8hhrdbqlNwdjCHRyx1BizOk3RbVP56grgPdyWScTCPpJ6vZZ8rtrY0 m1rwr+cxifNuQGKTlE33g2hReo/N9f3LFUMITlnnNH80Yium3SYuEqGeHLYerelXOnEKx6x+X5qD eg2DRW6I9/v/mfN2KAQEDqF9aSNlNFWZWmb52kukMv3tLWw0puaevicIZ/nZrW+D3CLDVVfWHeVt 46EF2bkLdgbIJOU3GzLoolgBOCkydX9x6xTw6knwQaqYsRGflacw6571IzWEwjmd17uJXkarnhM1 51pqwIoksTzycbjinIg6B1rNpGFDN7Ah+9EnVQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQDQOtlj 7Yk1j4GV/135zLOlsaonq8zfsu05VfY5XydNPFEkyIVcegLa8RiqALyFf+FjY/wVyhXtARw7NBUo u/Jh63cVya/VEoP1SVa0XQLf/ial+XuwdBBL1yc+7o2XHOfluDaXw/v2FWYpZtICf3a39giGaNWp eCT4g2TDWM4Jf/X7/mRbLX9tQO7XRural1CXx8VIMcmbKNbUtQiO8yEtVQ+FJKOsl7KOSzkgqNiL rJy+Y0D9biLZVKp07KWAY2FPCEtCkBrvo8BhvWbxWMA8CVQNAiTylM27Pc6kbc64pNr7C1Jx1wuE mVy9Fgb4PA2N3hPeD7mBmGGp7CfDbGcyhttp://www.okta.com/exk91cb99lKkKSYoy0h7W9osU1+7JHYa6E3Y3tvOCBN/jAGVTl39Wngwkq+4jKI=OWa1WvK9+/HOgfm37RwpzUYryJl3ryzdqRUEMlV3ZdLcCz7ZnQ26PCoZD7KxdxUjRci81Ev3OsT8pMCj+sDbFbvBhdRrVvS0sJEgqIFftgD3CYH6OaNGmahSum5Cz+p7xNow/XNQcxjEI2luAtMs+l9JlH8eNPvVVE8RSnXdq9G+y6Iu6RKBVESN9Lgwc6+L2XpqzalZv4d3D+c5dyFCjtNXLOLCJtu3F5w+dYM3KuYJ913S7fbXB2Tv7yRioc9Ssuehft5YacPY9ACsi0PZYAs9Gh4wlzu2yI+7cBfA6hn+h3T/uLu3DMWtHelKtkyOvJS2kFYJlFDio+h7uUbi9A==MIIDpDCCAoygAwIBAgIGAVjgvNroMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05NjkyNDQxHDAaBgkqhkiG9w0BCQEW DWluZm9Ab2t0YS5jb20wHhcNMTYxMjA4MjMxOTIzWhcNMjYxMjA4MjMyMDIzWjCBkjELMAkGA1UE BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTY5MjQ0MRwwGgYJ KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 4dW2YlcXjZwnTmLnV7IOBq8hhrdbqlNwdjCHRyx1BizOk3RbVP56grgPdyWScTCPpJ6vZZ8rtrY0 m1rwr+cxifNuQGKTlE33g2hReo/N9f3LFUMITlnnNH80Yium3SYuEqGeHLYerelXOnEKx6x+X5qD eg2DRW6I9/v/mfN2KAQEDqF9aSNlNFWZWmb52kukMv3tLWw0puaevicIZ/nZrW+D3CLDVVfWHeVt 46EF2bkLdgbIJOU3GzLoolgBOCkydX9x6xTw6knwQaqYsRGflacw6571IzWEwjmd17uJXkarnhM1 51pqwIoksTzycbjinIg6B1rNpGFDN7Ah+9EnVQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQDQOtlj 7Yk1j4GV/135zLOlsaonq8zfsu05VfY5XydNPFEkyIVcegLa8RiqALyFf+FjY/wVyhXtARw7NBUo u/Jh63cVya/VEoP1SVa0XQLf/ial+XuwdBBL1yc+7o2XHOfluDaXw/v2FWYpZtICf3a39giGaNWp eCT4g2TDWM4Jf/X7/mRbLX9tQO7XRural1CXx8VIMcmbKNbUtQiO8yEtVQ+FJKOsl7KOSzkgqNiL rJy+Y0D9biLZVKp07KWAY2FPCEtCkBrvo8BhvWbxWMA8CVQNAiTylM27Pc6kbc64pNr7C1Jx1wuE mVy9Fgb4PA2N3hPeD7mBmGGp7CfDbGcyeric.chiang+okta@coreos.comhttp://127.0.0.1:5556/dex/callbackurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransporteric.chiang+okta@coreos.comEricEveryoneAdmins ================================================ FILE: connector/saml/testdata/tampered-resp.xml ================================================ ew38E1LGMwYT+0gUZNq0RacD3GM= TQ84pCaZAyEDBGkNafTMfwPUWujFvmdoXzyYMXZURIlKhA8Pv1bIZfzQ5MgbQr1W z2Ye99/hss24Y4ueNT9nS+53LvDekhNctFGYfgdMjrbxs8Awo3KnbvveDib5zGvk fWd/0/QLvlbFd/3670QGb5JQE1nD9mlAqPonyQgoufk63gEM84+tU71cAM7XKiy6 09MC0y4s967qRAiLAtfgKbvi+46HkF/g+WsS74Wa8cu/A863URt56W0cogRjHWpQ B+q8/FyVeJRE0NlrOjhnsgTU2QJtvkxYYvqIpRDbMv53NLKeAFvRhOcyJxhFXtSj LF/oPMjbmHji4ylFiAlQWw== MIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV BAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z NzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI 4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO ZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD AB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA faXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF a7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9 QacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud IwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP oR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS hSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De oX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd esSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j 1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q= http://www.okta.com/exk91cb99lKkKSYoy0h7 http://www.okta.com/exk91cb99lKkKSYoy0h7 noteric.chiang+okta@coreos.com http://127.0.0.1:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport eric.chiang+okta@coreos.com Eric Everyone Admins ================================================ FILE: connector/saml/testdata/two-assertions-first-signed.tmpl ================================================ http://www.okta.com/exk91cb99lKkKSYoy0h7 http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+okta@coreos.com http://127.0.0.1:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport eric.chiang+okta@coreos.com Eric Everyone Admins http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+okta@coreos.com http://127.0.0.1:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport eric.chiang+okta@coreos.com I AM AN ATTACKER TRYING TO GET YOU TO USE THIS ASSERTION!!! Everyone Admins ================================================ FILE: connector/saml/testdata/two-assertions-first-signed.xml ================================================ http://www.okta.com/exk91cb99lKkKSYoy0h7 kDdpmKMPp7zjUeMTuntXJFpT6cs= Q12w9zPo9Bqn3W2OT7lbFbhfIDQrp7SXd/+9ZWpY4mv3ApgaOcNX2VtpSE0EjorU 1NyrktVOkCYueJm/HVQF7gyF85KRlPBDLBZOwxbnnmrRFoCk+U2kjIt8k8eZ7NsD jLFGFgEveS359uvaHZR1Exbr0PBYwS7aXR3fpmjMjZ9T8f8Oe3Nt/9nWPgz/dFhb Aa+AniWuupfq2v6YMLZ+9GLiO0sOr8UVkW8AYOm2Bin30epikXT1Axi/VxWz/fjP nMUkgusFnhkgmIf/YncAv4S9GY6DcaV2iEj6cL70S7pcgaeRFi3iozigl+9a+lFo dt3Jy8Jq/N3OAyDP2DxLEw== MIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV BAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z NzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI 4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO ZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD AB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA faXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF a7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9 QacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud IwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP oR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS hSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De oX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd esSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j 1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q= http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+okta@coreos.com http://127.0.0.1:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport eric.chiang+okta@coreos.com Eric Everyone Admins http://www.okta.com/exk91cb99lKkKSYoy0h7 eric.chiang+okta@coreos.com http://127.0.0.1:5556/dex/callback urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport eric.chiang+okta@coreos.com I AM AN ATTACKER TRYING TO GET YOU TO USE THIS ASSERTION!!! Everyone Admins ================================================ FILE: connector/saml/types.go ================================================ package saml import ( "bytes" "encoding/xml" "fmt" "time" ) const timeFormat = "2006-01-02T15:04:05Z" type xmlTime time.Time func (t xmlTime) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { return xml.Attr{ Name: name, Value: time.Time(t).UTC().Format(timeFormat), }, nil } func (t *xmlTime) UnmarshalXMLAttr(attr xml.Attr) error { got, err := time.Parse(timeFormat, attr.Value) if err != nil { return err } *t = xmlTime(got) return nil } type samlVersion struct{} func (s samlVersion) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { return xml.Attr{ Name: name, Value: "2.0", }, nil } func (s *samlVersion) UnmarshalXMLAttr(attr xml.Attr) error { if attr.Value != "2.0" { return fmt.Errorf(`saml version expected "2.0" got %q`, attr.Value) } return nil } type authnRequest struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol AuthnRequest"` ID string `xml:"ID,attr"` Version samlVersion `xml:"Version,attr"` ProviderName string `xml:"ProviderName,attr,omitempty"` IssueInstant xmlTime `xml:"IssueInstant,attr,omitempty"` Consent bool `xml:"Consent,attr,omitempty"` Destination string `xml:"Destination,attr,omitempty"` ForceAuthn bool `xml:"ForceAuthn,attr,omitempty"` IsPassive bool `xml:"IsPassive,attr,omitempty"` ProtocolBinding string `xml:"ProtocolBinding,attr,omitempty"` AssertionConsumerServiceURL string `xml:"AssertionConsumerServiceURL,attr,omitempty"` Subject *subject `xml:"Subject,omitempty"` Issuer *issuer `xml:"Issuer,omitempty"` NameIDPolicy *nameIDPolicy `xml:"NameIDPolicy,omitempty"` // TODO(ericchiang): Make this configurable and determine appropriate default values. RequestAuthnContext *requestAuthnContext `xml:"RequestAuthnContext,omitempty"` } type subject struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Subject"` NameID *nameID `xml:"NameID,omitempty"` SubjectConfirmations []subjectConfirmation `xml:"SubjectConfirmation"` // TODO(ericchiang): Do we need to deal with baseID? } type nameID struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion NameID"` Format string `xml:"Format,omitempty"` Value string `xml:",chardata"` } type subjectConfirmationData struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion SubjectConfirmationData"` NotBefore xmlTime `xml:"NotBefore,attr,omitempty"` NotOnOrAfter xmlTime `xml:"NotOnOrAfter,attr,omitempty"` Recipient string `xml:"Recipient,attr,omitempty"` InResponseTo string `xml:"InResponseTo,attr,omitempty"` } type subjectConfirmation struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion SubjectConfirmation"` Method string `xml:"Method,attr,omitempty"` SubjectConfirmationData *subjectConfirmationData `xml:"SubjectConfirmationData,omitempty"` } type audience struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Audience"` Value string `xml:",chardata"` } type audienceRestriction struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion AudienceRestriction"` Audiences []audience `xml:"Audience"` } type conditions struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Conditions"` NotBefore xmlTime `xml:"NotBefore,attr,omitempty"` NotOnOrAfter xmlTime `xml:"NotOnOrAfter,attr,omitempty"` AudienceRestriction []audienceRestriction `xml:"AudienceRestriction,omitempty"` } type statusCode struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol StatusCode"` Value string `xml:"Value,attr,omitempty"` } type statusMessage struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol StatusMessage"` Value string `xml:",chardata"` } type status struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol Status"` StatusCode *statusCode `xml:"StatusCode"` StatusMessage *statusMessage `xml:"StatusMessage,omitempty"` } type issuer struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Issuer"` Issuer string `xml:",chardata"` } type nameIDPolicy struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol NameIDPolicy"` AllowCreate bool `xml:"AllowCreate,attr,omitempty"` Format string `xml:"Format,attr,omitempty"` } type requestAuthnContext struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol RequestAuthnContext"` AuthnContextClassRefs []authnContextClassRef } type authnContextClassRef struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol AuthnContextClassRef"` Value string `xml:",chardata"` } type response struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:protocol Response"` ID string `xml:"ID,attr"` InResponseTo string `xml:"InResponseTo,attr"` Version samlVersion `xml:"Version,attr"` Destination string `xml:"Destination,attr,omitempty"` Issuer *issuer `xml:"Issuer,omitempty"` Status *status `xml:"Status"` // TODO(ericchiang): How do deal with multiple assertions? Assertion *assertion `xml:"Assertion,omitempty"` } type assertion struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Assertion"` Version samlVersion `xml:"Version,attr"` ID string `xml:"ID,attr"` IssueInstance xmlTime `xml:"IssueInstance,attr"` Issuer issuer `xml:"Issuer"` Subject *subject `xml:"Subject,omitempty"` Conditions *conditions `xml:"Conditions"` AttributeStatement *attributeStatement `xml:"AttributeStatement,omitempty"` } type attributeStatement struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion AttributeStatement"` Attributes []attribute `xml:"Attribute"` } func (a *attributeStatement) get(name string) (s string, ok bool) { for _, attr := range a.Attributes { if attr.Name == name { ok = true if len(attr.AttributeValues) > 0 { return attr.AttributeValues[0].Value, true } } } return } func (a *attributeStatement) all(name string) (s []string, ok bool) { for _, attr := range a.Attributes { if attr.Name == name { ok = true for _, val := range attr.AttributeValues { s = append(s, val.Value) } } } return } // names list the names of all attributes in the attribute statement. func (a *attributeStatement) names() []string { s := make([]string, len(a.Attributes)) for i, attr := range a.Attributes { s[i] = attr.Name } return s } // String is a formatter for logging an attribute statement's sub statements. func (a *attributeStatement) String() string { buff := new(bytes.Buffer) for i, attr := range a.Attributes { if i != 0 { buff.WriteString(", ") } buff.WriteString(attr.String()) } return buff.String() } type attribute struct { XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:assertion Attribute"` Name string `xml:"Name,attr"` NameFormat string `xml:"NameFormat,attr,omitempty"` FriendlyName string `xml:"FriendlyName,attr,omitempty"` AttributeValues []attributeValue `xml:"AttributeValue,omitempty"` } type attributeValue struct { XMLName xml.Name `xml:"AttributeValue"` Value string `xml:",chardata"` } func (a attribute) String() string { if len(a.AttributeValues) == 1 { // "email" = "jane.doe@coreos.com" return fmt.Sprintf("%q = %q", a.Name, a.AttributeValues[0].Value) } values := make([]string, len(a.AttributeValues)) for i, av := range a.AttributeValues { values[i] = av.Value } // "groups" = ["engineering", "docs"] return fmt.Sprintf("%q = %q", a.Name, values) } ================================================ FILE: docker-compose.override.yaml.dist ================================================ version: "3.8" services: mysql: ports: - "127.0.0.1:3306:3306" mysql8: ports: - "127.0.0.1:3307:3306" postgres: ports: - "127.0.0.1:5432:5432" etcd: ports: - "127.0.0.1:2379:2379" ldap: ports: - "127.0.0.1:389:389" - "127.0.0.1:636:636" ================================================ FILE: docker-compose.test.yaml ================================================ version: "3.8" services: ldap: image: osixia/openldap:1.4.0 # Copying is required because the entrypoint modifies the *.ldif files. # For verbose output, use: command: ["--copy-service", "--loglevel", "debug"] command: ["--copy-service"] environment: LDAP_BASE_DN: "dc=example,dc=org" LDAP_TLS: "true" LDAP_TLS_VERIFY_CLIENT: try ports: - 3890:389 - 6360:636 volumes: - ./connector/ldap/testdata/certs:/container/service/slapd/assets/certs - ./connector/ldap/testdata/schema.ldif:/container/service/slapd/assets/config/bootstrap/ldif/99-schema.ldif ================================================ FILE: docker-compose.yaml ================================================ # This docker-compose file provides quick setups for testing different storage backend options. version: "3.8" services: mysql: # For using percona-xtradb you need to make strict mode permissive with: # docker-compose exec mysql mysql -uroot -proot -e "SET GLOBAL pxc_strict_mode=PERMISSIVE;" # See: https://www.percona.com/doc/percona-xtradb-cluster/5.7/features/pxc-strict-mode.html # image: percona/percona-xtradb-cluster:5.7 # image: mariadb:10.5 # image: mysql:5.6 # image: mysql:8.0 image: mysql:5.7 environment: MYSQL_DATABASE: dex MYSQL_USER: mysql MYSQL_PASSWORD: mysql MYSQL_ROOT_PASSWORD: root mysql8: image: mysql:8.0 command: --default-authentication-plugin=mysql_native_password environment: MYSQL_DATABASE: dex MYSQL_USER: mysql MYSQL_PASSWORD: mysql MYSQL_ROOT_PASSWORD: root postgres: image: postgres:10.15 environment: POSTGRES_DB: dex POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres etcd: image: gcr.io/etcd-development/etcd:v3.5.0 environment: ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379 ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379 # For testing the Kubernetes storage backend we suggest https://kind.sigs.k8s.io/: # kind create cluster ldap: image: osixia/openldap:1.4.0 # Copying is required because the entrypoint modifies the *.ldif files. # For verbose output, use: command: ["--copy-service", "--loglevel", "debug"] command: ["--copy-service"] environment: LDAP_BASE_DN: "dc=example,dc=org" LDAP_TLS: "true" LDAP_TLS_VERIFY_CLIENT: try volumes: - ./connector/ldap/testdata/certs:/container/service/slapd/assets/certs - ./connector/ldap/testdata/schema.ldif:/container/service/slapd/assets/config/bootstrap/ldif/99-schema.ldif vault: image: hashicorp/vault:1.21 environment: VAULT_DEV_ROOT_TOKEN_ID: root-token VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200" cap_add: - IPC_LOCK ports: - 8200:8200 openbao: image: quay.io/openbao/openbao:2.5 environment: BAO_DEV_ROOT_TOKEN_ID: root-token BAO_DEV_LISTEN_ADDRESS: "0.0.0.0:8200" cap_add: - IPC_LOCK ports: - 8210:8200 ================================================ FILE: docs/README.md ================================================ These documents have moved to the [dexidp/website repo](https://github.com/dexidp/website). ================================================ FILE: docs/enhancements/README.md ================================================ # Dex Enhancement Proposal ## Why do we need it? Dex Enhancement Proposal (DEP) is a design document providing information to the community, or describing a new feature for Dex. We intend DEPs to be the primary mechanisms for proposing major new features or significant changes to existing ones. 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. ## Process ### Before starting 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). 2. If a discussion does not exist, [open it](https://github.com/dexidp/dex/discussions/new?category=Ideas). 3. Ensure that writing enhancement proposal is necessary for you change by discussing it with a community. ### Writing an enhancement proposal 1. Fork the repo. 2. Copy the [`docs/enhancements/_title-YYYY-MM-DD-#issue.md`](docs/enhancements/_title-YYYY-MM-DD-#issue.md) template with the appropriate name. 3. Fill all sections according to hints in them. Provide as much information as you can. 4. Submit your PR and discuss it with the Dex team. ================================================ FILE: docs/enhancements/_title-YYYY-MM-DD-#issue.md ================================================ # Dex Enhancement Proposal (DEP) - - ## Table of Contents - [Summary](#summary) - [Motivation](#motivation) - [Goals/Pain](#goals) - [Non-Goals](#non-goals) - [Proposal](#proposal) - [User Experience](#user-experience) - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints) - [Risks and Mitigations](#risks-and-mitigations) - [Alternatives](#alternatives) - [Future Improvements](#future-improvements) ## Summary - Provide a one-paragraph description of the expected change here. ## Context - Link to any previous issues, RFCs, discussions, or briefs. - Link to any ongoing or future work relevant to this change. ## Motivation ### Goals/Pain - List work that is assumed to be done in the scope of this enhancement. - Mention problems solve by this enhancement. ### Non-goals - List work that is entirely out of the scope of this enhancement. Use this to define DEP borders to keep work focused. - All planned future enhancements should be listed in one of the following blocks - Future Improvements. ## Proposal ### User Experience - Explain your change as if you were describing it to end-users. - Explain the way users are supposed to use Dex with the proposed enhancement. ### Implementation Details/Notes/Constraints - Explain your change as if you were at a development team meeting (give more technical and implementation details). - When possible, demonstrate with pseudo-code, not text. - Be specific. Be opinionated. Avoid ambiguity. ### Risks and Mitigations - Mention all expected risks and migrations in detail here. - Do not forget to mention if the proposed enhancement is a breaking change. ### Alternatives - What other approaches have been considered, and why did you not choose them? - What happens if this enhancement will never be accepted and implemented? ## Future Improvements - List any future improvements. ================================================ FILE: docs/enhancements/auth-sessions-2026-02-18.md ================================================ # Dex Enhancement Proposal (DEP 4560) - 2026-02-18 - Auth Sessions ## Table of Contents - [Summary](#summary) - [Motivation](#motivation) - [Goals/Pain](#goalspain) - [Non-Goals](#non-goals) - [Proposal](#proposal) - [User Experience](#user-experience) - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints) - [Risks and Mitigations](#risks-and-mitigations) - [Alternatives](#alternatives) - [Future Improvements](#future-improvements) ## Summary This DEP introduces **auth sessions** - a persistent authentication state that enables Dex to track logged-in users across browser sessions. Currently, Dex relies entirely on refresh tokens for session management, which prevents proper implementation of OIDC conformance features like `prompt=none`, `prompt=login`, `id_token_hint`, SSO across clients, and proper logout. User Sessions will be stored server-side with a browser cookie reference, enabling these features while maintaining Dex's simplicity and compatibility with all storage backends (SQL, etcd, Kubernetes CRDs). ## Context - [OIDC Core 1.0 - Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) - `prompt` parameter specification - [OIDC Core 1.0 - ID Token Hint](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) - `id_token_hint` specification - [OIDC Session Management 1.0](https://openid.net/specs/openid-connect-session-1_0.html) - Session management specification - [OIDC RP-Initiated Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) - Logout specification - [OIDC Front-Channel Logout 1.0](https://openid.net/specs/openid-connect-frontchannel-1_0.html) - Front-channel logout - [Keycloak Sessions](https://www.keycloak.org/docs/latest/server_admin/#_sessions) - Reference implementation - [Ory Hydra Login & Consent Flow](https://www.ory.sh/docs/hydra/concepts/login) - Reference implementation Current limitations: - No support for `prompt=none` (silent authentication) - No support for `prompt=login` (force re-authentication) - No support for `max_age` parameter - No support for `id_token_hint` validation - No SSO between clients (each client requires separate login) - No proper logout (only refresh token revocation) - No consent persistence (user must approve every time if not skipped globally) - No 2FA enrollment storage - No "Remember Me" functionality ## Motivation ### Goals/Pain 1. **OIDC Conformance** - Enable proper `prompt=none`, `prompt=login`, `max_age`, and `id_token_hint` support 2. **SSO (Single Sign-On)** - Allow users to authenticate once and access multiple clients without re-login 3. **Remember Me** - Allow users to choose persistent vs session-based authentication 4. **Consent Persistence** - Store user consent decisions per client/scope combination within session 5. **Proper Logout** - Enable session termination with optional front-channel logout 6. **Foundation for 2FA** - Enable future TOTP/WebAuthn enrollment storage ### Non-Goals - **2FA Implementation** - This DEP only provides storage foundation; 2FA flow is a separate DEP - **Back-Channel Logout** - Server-to-server logout notifications are out of scope - **Session Clustering/Replication** - Storage backends handle this - **Admin Session Management UI** - API only, no admin UI - **Per-connector Session Policies** - Single global session policy initially - **Identity Refresh During Session** - Deferred to future DEP; initially identity is refreshed only at session termination (like Keycloak) - **Upstream Connector Logout** - Terminating sessions at upstream IDPs is deferred ## Proposal ### User Experience #### Configuration Sessions are controlled by a feature flag and configuration: ```yaml # Feature flag (environment variable) # DEX_SESSIONS_ENABLED=true # config.yaml sessions: # Session cookie name (default: "dex_session") # Other cookie settings (Secure, HttpOnly, SameSite=Lax) are not configurable # and are set to secure defaults automatically cookieName: "dex_session" # Session lifetime settings (matches refresh token expiry naming) absoluteLifetime: "24h" # Maximum session lifetime, default: 24h validIfNotUsedFor: "1h" # Session expires if not used, default: 1h # Default SSO sharing policy for clients without explicit ssoSharedWith config # Options: # "all" - clients without ssoSharedWith share sessions with all other clients (Keycloak-like) # "none" - clients without ssoSharedWith don't share sessions (default) ssoSharedWithDefault: "none" # Whether "Remember Me" checkbox is checked by default in login/approval forms # When true: checkbox is pre-checked, user can uncheck # When false: checkbox is unchecked, user must check to persist session (default) rememberMeCheckedByDefault: false ``` **ssoSharedWithDefault** controls the default SSO behavior: - `"none"` (default): Clients without explicit `ssoSharedWith` config don't participate in SSO - `"all"`: Clients without explicit `ssoSharedWith` config share sessions with all other clients (realm-wide SSO like Keycloak) Clients with explicit `ssoSharedWith` configuration always use their configured value. **Note**: The `ssoSharedWith` option is separate from the existing `trustedPeers` option. `trustedPeers` controls which clients can issue tokens on behalf of this client (existing behavior), while `ssoSharedWith` controls which clients can reuse this client's authentication session (new behavior). These can be configured independently based on different security requirements. **rememberMeCheckedByDefault** controls the initial checkbox state in templates. This value is passed to templates as `.RememberMeChecked` boolean. **SSO via ssoSharedWith**: SSO between clients is controlled by the new `ssoSharedWith` configuration on clients. The `ssoSharedWith` setting defines **which clients can USE this client's session**, not which clients this client can use. If client B is listed in client A's `ssoSharedWith`: 1. If user logged in via client A, client B can reuse that session This is intentionally separate from `trustedPeers` (which controls token issuance on behalf of another client). Organizations may want different policies for session sharing vs token delegation: - **ssoSharedWith**: "Can this client's login be reused by another client?" - **trustedPeers**: "Can another client issue tokens claiming to be this client?" **Wildcard Support**: `ssoSharedWith: ["*"]` enables SSO with all clients. This is similar to Keycloak's default behavior where all clients in a realm share sessions. **SSO Direction**: SSO sharing is **unidirectional**. Client A sharing with client B does NOT mean client B shares with client A. ```yaml staticClients: # Public app - allows any client to reuse its sessions - id: public-app name: Public App ssoSharedWith: ["*"] # trustedPeers can be configured separately for token delegation # ... # Admin app - only specific apps can reuse its sessions - id: admin-app name: Admin App ssoSharedWith: ["monitoring-app"] # Only monitoring can SSO from admin sessions # ... # Secret internal service - NO other clients can reuse its sessions - id: secret-service name: Secret Service ssoSharedWith: [] # Empty = no SSO allowed from this client's sessions # But this client CAN use sessions from other clients that share with it! # ... # Monitoring app - can SSO from admin-app (because admin-app shares with it) - id: monitoring-app name: Monitoring App ssoSharedWith: ["admin-app"] # Bidirectional sharing with admin-app # ... ``` **Example Scenarios:** | User logged in via | Accessing | SSO works? | Why | |-------------------|-----------|------------|-----| | public-app | admin-app | ✅ Yes | public-app has `ssoSharedWith: ["*"]` | | admin-app | public-app | ❌ No | admin-app only shares with monitoring-app | | admin-app | monitoring-app | ✅ Yes | admin-app shares with monitoring-app | | secret-service | any client | ❌ No | secret-service has `ssoSharedWith: []` | | public-app | secret-service | ✅ Yes | public-app has `ssoSharedWith: ["*"]` | **Key Insight**: A "secret" client that doesn't want others to SSO into it simply doesn't list them in `ssoSharedWith`. But it can still BENEFIT from SSO by being listed in OTHER clients' `ssoSharedWith`. **Comparison with Keycloak**: In Keycloak, SSO is realm-wide by default - all clients in a realm share sessions. Dex's approach is more granular: SSO is opt-in per client via `ssoSharedWith`. Use `["*"]` to achieve Keycloak-like behavior. **Comparison with trustedPeers**: The `trustedPeers` option continues to control cross-client token issuance (e.g., client B issuing tokens for client A). This is a separate security concern from session sharing. Organizations can configure these independently: - High SSO sharing, restricted token delegation - Restricted SSO sharing, high token delegation - Or any combination based on their security model **Cookie Security**: The session cookie is always set with secure defaults: - `HttpOnly: true` - Not accessible via JavaScript - `Secure: (issuerURL.Scheme == "https")` - Only sent over HTTPS; for `http` (commonly used on localhost in dev) this is disabled - `SameSite: Lax` - CSRF protection - `Path: <issuerURL.Path>` - Derived from issuer URL (e.g., `/dex` for `https://example.com/dex`) These settings are not configurable to prevent security misconfigurations. #### Authentication Flow with Sessions ``` ┌─────────┐ ┌─────────┐ ┌───────────┐ ┌───────────┐ │ Browser │ │ Dex │ │ Storage │ │ Connector │ └────┬────┘ └────┬────┘ └─────┬─────┘ └─────┬─────┘ │ │ │ │ │ GET /auth │ │ │ │ (no session) │ │ │ ├────────────────>│ │ │ │ │ │ │ │ │ Check session │ │ │ │ cookie │ │ │ ├─────────────────>│ │ │ │ (not found) │ │ │ │<─────────────────│ │ │ │ │ │ │ Redirect to │ │ │ │ connector │ │ │ │<────────────────│ │ │ │ │ │ │ │ ... connector auth flow ... │ │ │ │ │ │ │ Callback with │ │ │ │ identity │ │ │ ├────────────────>│ │ │ │ │ │ │ │ │ Create/update │ │ │ │ AuthSession │ │ │ │ (ALWAYS) │ │ │ ├─────────────────>│ │ │ │ │ │ │ Set-Cookie: │ │ │ │ - Session cookie (no MaxAge) │ │ │ if Remember Me unchecked │ │ │ - Persistent cookie (with MaxAge) │ │ │ if Remember Me checked │ │ │ + redirect to /approval │ │ │<────────────────│ │ │ │ │ │ │ ``` **Key Point**: AuthSession is always created on successful authentication. The "Remember Me" checkbox only controls whether the cookie is a session cookie (deleted on browser close) or a persistent cookie (survives browser restart). This is consistent with Keycloak's behavior. #### SSO Flow (Returning User) ``` ┌─────────┐ ┌─────────┐ ┌───────────┐ │ Browser │ │ Dex │ │ Storage │ └────┬────┘ └────┬────┘ └─────┬─────┘ │ │ │ │ GET /auth │ │ │ (with cookie) │ │ │ client_id=B │ │ ├────────────────>│ │ │ │ │ │ │ Get session │ │ ├─────────────────>│ │ │ (valid session) │ │ │<─────────────────│ │ │ │ │ │ Check SSO │ │ │ policy for │ │ │ client B │ │ │ │ │ │ Check consent │ │ │ for client B │ │ ├─────────────────>│ │ │ │ │ If consented: │ │ │ redirect with │ │ │ code │ │ │<────────────────│ │ │ │ │ │ If not: │ │ │ show approval │ │ │<────────────────│ │ │ │ │ ``` #### prompt=none Flow ``` ┌─────────┐ ┌─────────┐ ┌───────────┐ │ Browser │ │ Dex │ │ Storage │ └────┬────┘ └────┬────┘ └─────┬─────┘ │ │ │ │ GET /auth │ │ │ prompt=none │ │ ├────────────────>│ │ │ │ │ │ │ Get session │ │ ├─────────────────>│ │ │ │ │ If valid session + consent: │ │ redirect with code │ │<────────────────│ │ │ │ │ │ If no session or no consent: │ │ redirect with error=login_required│ │ or error=consent_required │ │<────────────────│ │ │ │ │ ``` #### Logout Flow ``` ┌─────────┐ ┌─────────┐ ┌───────────┐ │ Browser │ │ Dex │ │ Storage │ └────┬────┘ └────┬────┘ └─────┬─────┘ │ │ │ │ GET /logout │ │ │ id_token_hint= │ │ ├────────────────>│ │ │ │ │ │ │ Validate │ │ │ id_token_hint │ │ │ │ │ │ Get identity │ │ │ by session ID │ │ ├─────────────────>│ │ │ │ │ │ Deactivate │ │ │ (Active=false) │ │ ├─────────────────>│ │ │ │ │ │ Revoke refresh │ │ │ tokens │ │ ├─────────────────>│ │ │ │ │ Clear cookie + │ │ │ redirect or │ │ │ show logout │ │ │ confirmation │ │ │<────────────────│ │ │ │ │ ``` ### Implementation Details/Notes/Constraints #### Feature Flag ```go // pkg/featureflags/set.go var ( // ...existing flags... // SessionsEnabled enables user sessions feature SessionsEnabled = newFlag("sessions_enabled", false) ) ``` #### New Storage Entities Two entities are required to properly handle the case where a user might be logged into different clients as different identities in the same browser: ###### AuthSession ```go // storage/storage.go // AuthSession represents a browser's authentication state. // One per browser, referenced by session cookie. // Key: SessionID (random 32-byte string, stored in cookie) type AuthSession struct { // ID is the session identifier stored in cookie ID string // ClientStates maps clientID → authentication state for that client // Allows different users/identities per client in same browser // // Design note: This map-based approach is consistent with how OfflineSessions // stores refresh tokens per client (OfflineSessions.Refresh map). Given that // the number of OAuth clients in a typical deployment is bounded and relatively // small (tens to hundreds, not thousands), the serialized size of this map // will not exceed practical storage limits for any supported backend. ClientStates map[string]*ClientAuthState // CreatedAt is when this browser session started CreatedAt time.Time // LastActivity is when any client was last accessed LastActivity time.Time // IPAddress at session creation (for audit) IPAddress string // UserAgent at session creation (for audit) UserAgent string } // ClientAuthState represents authentication state for a specific client within an auth session. // Expiration follows OIDC conventions with both absolute and idle timeout: // - ExpiresAt enforces absolute lifetime (sessions.absoluteLifetime) // - LastActivity + sessions.validIfNotUsedFor enforces idle timeout // A client state is considered expired if EITHER condition is met. type ClientAuthState struct { // UserID + ConnectorID identify which UserIdentity is authenticated for this client UserID string ConnectorID string // Active indicates if authentication is active for this client Active bool // ExpiresAt is the absolute expiration time for this client session. // Set to time.Now() + absoluteLifetime at session creation. // Cannot be extended - hard upper bound on session duration. ExpiresAt time.Time // LastActivity is when this client session was last used (token issued, SSO check, etc.) // Used with validIfNotUsedFor to enforce idle timeout. // Updated on each request that touches this client state. LastActivity time.Time // LastTokenIssuedAt is when a token was last issued for this client. // Used for logout notifications and audit. LastTokenIssuedAt time.Time } ``` ###### UserIdentity ```go // storage/storage.go // UserIdentity represents a user's persistent identity data. // Stores data that persists across sessions: // - Consent decisions // - Future: 2FA enrollment // // Key: composite of UserID + ConnectorID (one per user per connector) type UserIdentity struct { // UserID is the subject identifier from the connector UserID string // ConnectorID is the connector that authenticated the user ConnectorID string // Claims holds the user's identity claims // Updated on: // 1. Each login (from connector callback) // 2. Each refresh token usage (from RefreshConnector.Refresh) // This ensures claims stay in sync with OfflineSessions and upstream IDP Claims Claims // Consents stores user consent per client: map[clientID][]scopes // Persists across sessions so user doesn't need to re-consent Consents map[string][]string // CreatedAt is when this identity was first created CreatedAt time.Time // LastLogin is when the user last authenticated (used for auth_time claim) LastLogin time.Time // BlockedUntil is set when user is blocked from logging in BlockedUntil time.Time // Future: 2FA fields // TOTPSecret string // WebAuthnCredentials []WebAuthnCredential } ``` **Two-Entity Design Rationale** | Entity | Purpose | Lifecycle | Key | |--------|---------|-----------|-----| | AuthSession | Browser binding, per-client auth state | Short-lived (session timeout) | SessionID (cookie) | | UserIdentity | User data, consents, 2FA | Long-lived (persists) | UserID + ConnectorID | **How It Works: Different Users in Different Clients** ``` Auth Session (cookie: dex_session=abc123) ├── ClientStates["client-A"]: │ └── UserID: "alice", ConnectorID: "google", Active: true ├── ClientStates["client-B"]: │ └── UserID: "bob", ConnectorID: "ldap", Active: true └── ClientStates["client-C"]: └── (empty - never authenticated) UserIdentity (alice + google): ├── Claims: {email: alice@example.com, ...} ├── Consents: {"client-A": ["openid", "email"]} └── LastLogin: 2024-01-01 UserIdentity (bob + ldap): ├── Claims: {email: bob@corp.com, ...} ├── Consents: {"client-B": ["openid", "groups"]} └── LastLogin: 2024-01-02 ``` **How SSO Works** When user accesses client-B with existing session: 1. Get `AuthSession` by cookie 2. Check `ClientStates["client-B"]`: - If exists and active → user already authenticated for this client 3. If not, check SSO: - Find any `ClientStates[X]` where client-X has `ssoSharedWith` containing "client-B" - If found → SSO! Copy auth state to `ClientStates["client-B"]` - If not found → require authentication **SSO Session Lookup Algorithm** ```go // findSSOSession searches for a valid SSO source session for the target client func (s *Server) findSSOSession(authSession *AuthSession, targetClientID string) (*ClientAuthState, *UserIdentity) { targetClient, err := s.storage.GetClient(ctx, targetClientID) if err != nil { return nil, nil } // Iterate through all active client states in this browser session for sourceClientID, state := range authSession.ClientStates { // Skip inactive or expired states if !state.Active || time.Now().After(state.ExpiresAt) { continue } // Get the source client configuration sourceClient, err := s.storage.GetClient(ctx, sourceClientID) if err != nil { continue } // Check if source client shares its session with the target client // SSO is allowed if: // 1. Source client has ssoSharedWith: ["*"] (shares with everyone) // 2. Source client has targetClientID in its ssoSharedWith list if !s.clientSharesSessionWith(sourceClient, targetClientID) { continue } // Found a valid SSO source! Get the user identity identity, err := s.storage.GetUserIdentity(ctx, state.UserID, state.ConnectorID) if err != nil { continue } // Check if user is not blocked if identity.BlockedUntil.After(time.Now()) { continue } return state, identity } return nil, nil } // clientSharesSessionWith checks if sourceClient shares its session with targetClientID func (s *Server) clientSharesSessionWith(sourceClient Client, targetClientID string) bool { for _, peer := range sourceClient.SSOSharedWith { if peer == "*" || peer == targetClientID { return true } } return false } ``` **SSO Lookup Flow Diagram** ``` User accesses client-B with existing session │ ▼ ┌─────────────────────────────────┐ │ Get AuthSession from cookie │ └─────────────────────────────────┘ │ ▼ ┌─────────────────────────────────┐ │ Check ClientStates["client-B"] │ │ exists and active? │ └─────────────────────────────────┘ │ │ Yes No │ │ ▼ ▼ ┌──────────────┐ ┌─────────────────────────────────┐ │ Use existing │ │ For each ClientStates[X]: │ │ session │ │ - Is state active? │ └──────────────┘ │ - Get client-X config │ │ - Does client-X share with B? │ │ (X.ssoSharedWith has B or *)│ └─────────────────────────────────┘ │ │ Found match No match │ │ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ SSO! Copy │ │ Require │ │ state to B │ │ authentication│ └──────────────┘ └──────────────┘ ``` **Example: SSO Flow** ``` 1. User logs into client-A as alice AuthSession.ClientStates["client-A"] = {UserID: "alice", Active: true} 2. User accesses client-B - client-A.ssoSharedWith includes "client-B" ✓ - SSO! Copy: ClientStates["client-B"] = {UserID: "alice", Active: true} - Issue tokens for alice to client-B ``` **Example: No SSO, Different User** ``` 1. User logged into client-A as alice AuthSession.ClientStates["client-A"] = {UserID: "alice", Active: true} 2. User accesses client-B (client-A does NOT share with client-B) - No SSO available - Redirect to connector for authentication 3. User logs in as bob (different account) AuthSession.ClientStates["client-B"] = {UserID: "bob", Active: true} Same browser, two different users, no conflict! ``` **Claims Synchronization with Refresh Tokens** When a refresh token is used: 1. `RefreshConnector.Refresh()` returns updated claims 2. Update `OfflineSessions.ConnectorData` (existing behavior) 3. **NEW**: Also update `UserIdentity.Claims`: ```go // In refresh token handler func (s *Server) handleRefreshToken(...) { // ...existing refresh logic... newIdentity, err := refreshConn.Refresh(ctx, scopes, oldIdentity) if err != nil { // Handle refresh failure } // Update OfflineSessions (existing) s.storage.UpdateOfflineSessions(...) // Update UserIdentity claims (NEW) if s.sessionsEnabled { s.storage.UpdateUserIdentity(ctx, newIdentity.UserID, connectorID, func(u UserIdentity) (UserIdentity, error) { u.Claims = storage.Claims{ UserID: newIdentity.UserID, Username: newIdentity.Username, Email: newIdentity.Email, Groups: newIdentity.Groups, // ... } return u, nil }) } } ``` This ensures `UserIdentity.Claims` stays synchronized with: - Connector's current user data - `OfflineSessions.ConnectorData` - Actual refresh token claims **Why UserIdentity instead of AuthSession?** The name `UserIdentity` is chosen because this entity stores more than just session state: 1. **Persistent data**: Consent decisions survive session expiration 2. **Future 2FA**: TOTP secrets and WebAuthn credentials will be stored here 3. **One per user/connector**: Unlike sessions which could be per-browser, this is per-identity **Session ID Regeneration** The `AuthSession.ID` is regenerated when: - User logs in from a new browser (new session created) - Security concern requires new session (e.g., after password change) Individual `ClientStates` can be invalidated without changing the auth session ID. **Multiple Users in Same Browser** With the two-entity design: - `AuthSession` tracks which user is authenticated for which client - Different clients can have different users (if no SSO trust) - Same user can be authenticated for multiple clients (SSO or separate logins) **SSO and Different Users** With SSO enabled between clients, the same user is used for all sharing clients: - User logs in to client-A as "alice@example.com" - User accesses client-B (client-A shares with client-B) → automatically authenticated as "alice@example.com" - SSO reuses the identity from the sharing client If user needs to login as different identity to a sharing client: - Use `prompt=login` to force re-authentication - This creates new ClientState for that client with potentially different user Without SSO, user can be different identities in different clients (see examples above). #### Storage Interface Extensions Two new entities require CRUD operations: ```go // storage/storage.go type Storage interface { // ...existing methods... // AuthSession management CreateAuthSession(ctx context.Context, s AuthSession) error GetAuthSession(ctx context.Context, sessionID string) (AuthSession, error) UpdateAuthSession(ctx context.Context, sessionID string, updater func(s AuthSession) (AuthSession, error)) error DeleteAuthSession(ctx context.Context, sessionID string) error // UserIdentity management CreateUserIdentity(ctx context.Context, u UserIdentity) error GetUserIdentity(ctx context.Context, userID, connectorID string) (UserIdentity, error) UpdateUserIdentity(ctx context.Context, userID, connectorID string, updater func(u UserIdentity) (UserIdentity, error)) error DeleteUserIdentity(ctx context.Context, userID, connectorID string) error // List for admin API ListUserIdentities(ctx context.Context) ([]UserIdentity, error) } ``` **Garbage Collection** ```go type GCResult struct { // ...existing fields... AuthSessions int64 // NEW: expired auth sessions cleaned up } ``` `AuthSession` objects are garbage collected when: - `LastActivity + validIfNotUsedFor` exceeded (inactivity) - All `ClientStates` have expired `UserIdentity` objects are NOT garbage collected (preserve consents, future 2FA). #### Session Expiration **AuthSession expiration:** - Entire session expires when `LastActivity + validIfNotUsedFor` is reached (idle timeout) - On expiration, `AuthSession` is deleted by GC - User must re-authenticate for all clients **ClientAuthState expiration (per-client within AuthSession):** Each client state enforces **both** absolute lifetime and idle timeout, consistent with standard OIDC session semantics: ```go func (s *Server) isClientStateValid(state *ClientAuthState) bool { now := time.Now() // 1. Check absolute lifetime - hard upper bound, cannot be extended if now.After(state.ExpiresAt) { return false } // 2. Check idle timeout - session unused for too long if now.After(state.LastActivity.Add(s.sessionsConfig.validIfNotUsedFor)) { return false } // 3. Check explicit deactivation (admin revoked) if !state.Active { return false } return true } ``` When a client state expires: - Other clients in same auth session remain active - User must re-authenticate only for the expired client - On successful re-authentication, a new `ClientAuthState` is created with fresh `ExpiresAt` **Admin can force re-authentication:** - Delete `AuthSession` → user must re-auth for all clients - Set `ClientStates[clientID].Active = false` → user must re-auth for that client only #### Deletion Risks **Deleting AuthSession:** - User must re-authenticate for all clients - No data loss (consents preserved in UserIdentity) - Safe operation for logout **Deleting UserIdentity:** | What's Lost | Impact | |-------------|--------| | Consent decisions | User must re-approve scopes for all clients | | Future: 2FA enrollment | User must re-enroll TOTP/WebAuthn | **When to delete UserIdentity:** - User explicitly requests account deletion (GDPR) - Admin cleanup of stale identities - User removed from upstream identity provider **When NOT to delete (delete AuthSession instead):** - Regular logout - delete AuthSession or set ClientState.Active = false - Session expiration - GC handles AuthSession cleanup - Security concern - delete AuthSession to force re-auth #### Session Cookie Format The session cookie contains only the session ID (not the session data): ``` Cookie: dex_session=<session_id>; Path=<issuer_path>; Secure; HttpOnly; SameSite=Lax ``` **Cookie Path**: Derived from the issuer URL path (`issuerURL.Path`). For example: - Issuer: `https://dex.example.com/` → `Path=/` - Issuer: `https://example.com/dex` → `Path=/dex` This is consistent with how Dex already handles routing - all endpoints are prefixed with the issuer path. **Session Creation vs Cookie Persistence (Keycloak-like behavior)** Unlike some implementations where "Remember Me" controls session creation, we follow Keycloak's approach: - **AuthSession is ALWAYS created** on successful authentication - **"Remember Me" controls cookie persistence**: - Unchecked: Session cookie (expires when browser closes) - Checked: Persistent cookie (expires at `absoluteLifetime`) This approach is better because: 1. SSO works within a browser session even without "Remember Me" 2. Consent decisions are preserved during the browser session 3. `prompt=none` works correctly within browser session 4. More intuitive: "Remember Me" = "remember me after I close the browser" ```go func (s *Server) setSessionCookie(w http.ResponseWriter, sessionID string, rememberMe bool) { cookie := &http.Cookie{ Name: s.sessionsConfig.CookieName, Value: sessionID, Path: s.issuerURL.Path, HttpOnly: true, Secure: s.issuerURL.Scheme == "https", SameSite: http.SameSiteLaxMode, } if rememberMe { // Persistent cookie - survives browser restart cookie.MaxAge = int(s.sessionsConfig.absoluteLifetime.Seconds()) } // else: Session cookie - no MaxAge, browser deletes on close http.SetCookie(w, cookie) } ``` Session ID generation: ```go func NewSessionID() string { return newSecureID(32) // 256-bit random value } ``` #### Client Configuration Extension A new client configuration field is introduced for SSO control: ```go // storage/storage.go type Client struct { // ...existing fields... // TrustedPeers are a list of peers which can issue tokens on this client's behalf. // This is used for cross-client token issuance (existing behavior). TrustedPeers []string `json:"trustedPeers" yaml:"trustedPeers"` // SSOSharedWith defines which other clients can reuse this client's authentication session. // When a user is authenticated for this client, clients listed here can skip authentication. // This is separate from TrustedPeers - organizations may want different policies for // session sharing vs token delegation. // Special value "*" means share with all clients (Keycloak-like realm-wide SSO). // nil means use ssoSharedWithDefault from sessions config. // Empty slice [] means explicitly share with no one. SSOSharedWith []string `json:"ssoSharedWith,omitempty" yaml:"ssoSharedWith,omitempty"` } ``` #### Connector Logout (Future) Logout URLs should be configured on connectors, not clients. A new connector interface will be added: ```go // connector/connector.go // LogoutConnector is an optional interface for connectors that support // terminating upstream sessions on logout. type LogoutConnector interface { // Logout terminates the user's session at the upstream identity provider. // Returns a URL to redirect the user to for upstream logout, or empty string // if no redirect is needed. Logout(ctx context.Context, connectorData []byte) (logoutURL string, err error) } ``` Connectors that implement this interface (e.g., OIDC with `end_session_endpoint`, SAML with SLO): - Are called during Dex logout flow - Can redirect user to upstream for complete logout - Implementation details are connector-specific This is tracked as a future improvement. #### Server Configuration Extension ```go // cmd/dex/config.go type Sessions struct { // CookieName is the session cookie name (default: "dex_session") CookieName string `json:"cookieName"` // AbsoluteLifetime is the maximum session lifetime (default: "24h") AbsoluteLifetime string `json:"absoluteLifetime"` // ValidIfNotUsedFor is the inactivity timeout (default: "1h") ValidIfNotUsedFor string `json:"validIfNotUsedFor"` // SSOSharedWithDefault is the default SSO sharing policy // "all" = share with all clients, "none" = share with no one (default: "none") SSOSharedWithDefault string `json:"ssoSharedWithDefault"` // RememberMeCheckedByDefault controls the initial checkbox state in templates // true = pre-checked, false = unchecked (default: false) RememberMeCheckedByDefault bool `json:"rememberMeCheckedByDefault"` } ``` **Using ssoSharedWithDefault in SSO logic:** ```go func (s *Server) clientSharesSessionWith(sourceClient Client, targetClientID string) bool { ssoSharedWith := sourceClient.SSOSharedWith // If client has no explicit ssoSharedWith, use default if ssoSharedWith == nil { switch s.sessionsConfig.SSOSharedWithDefault { case "all": return true // Share with everyone by default default: // "none" return false // Share with no one by default } } // Explicit configuration: empty slice means explicitly share with no one // This is different from nil (not configured) if len(ssoSharedWith) == 0 { return false } // Check explicit sharing list for _, peer := range ssoSharedWith { if peer == "*" || peer == targetClientID { return true } } return false } ``` **Three states for ssoSharedWith:** 1. `nil` (not configured) → use `ssoSharedWithDefault` 2. `[]` (empty slice) → explicitly share with no one 3. `["client-a", ...]` or `["*"]` → explicit sharing list #### Prompt Parameter Handling Dex will support the following `prompt` values per OIDC Core specification: - `none` - Silent authentication, no UI displayed - `login` - Force re-authentication - `consent` - Force consent screen - Empty (default) - Normal flow with session reuse The `select_account` value is not supported initially (would require account linking feature). ```go func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) { // ...existing parsing... prompt := r.Form.Get("prompt") maxAge := r.Form.Get("max_age") idTokenHint := r.Form.Get("id_token_hint") clientID := r.Form.Get("client_id") // Get auth session from cookie authSession, err := s.getAuthSessionFromCookie(r) // Get client auth state for this specific client var clientState *ClientAuthState var userIdentity *UserIdentity if authSession != nil { clientState = authSession.ClientStates[clientID] if clientState != nil && clientState.Active { userIdentity, _ = s.storage.GetUserIdentity(ctx, clientState.UserID, clientState.ConnectorID) } } // Handle max_age parameter (OIDC Core 3.1.2.1) if maxAge != "" && userIdentity != nil { maxAgeSeconds, err := strconv.Atoi(maxAge) if err == nil && maxAgeSeconds >= 0 { authAge := time.Since(userIdentity.LastLogin) if authAge > time.Duration(maxAgeSeconds)*time.Second { // Session is too old, force re-authentication clientState = nil userIdentity = nil } } } switch prompt { case "none": // Silent authentication - must have valid session and consent if clientState == nil || userIdentity == nil { s.authErr(w, r, redirectURI, "login_required", state) return } // Check consent in identity consentedScopes, hasConsent := userIdentity.Consents[clientID] if !hasConsent || !s.scopesCovered(consentedScopes, requestedScopes) { s.authErr(w, r, redirectURI, "consent_required", state) return } // Issue tokens without UI case "login": // Force re-authentication - ignore existing session for this client clientState = nil userIdentity = nil // Continue to connector login case "consent": // Force consent screen even if previously consented // Continue but don't check consent default: // "" - normal flow // Check for SSO from trusted clients if no direct session if clientState == nil && authSession != nil { clientState, userIdentity = s.findSSOSession(authSession, clientID) } } // Validate id_token_hint if provided if idTokenHint != "" { claims, err := s.validateIDTokenHint(idTokenHint) if err != nil { s.authErr(w, r, redirectURI, "invalid_request", state) return } if userIdentity != nil && userIdentity.UserID != claims.Subject { // Identity user doesn't match hint if prompt == "none" { s.authErr(w, r, redirectURI, "login_required", state) return } // Force re-login for different user clientState = nil userIdentity = nil } } // ...continue with flow... } // findSSOSession looks for a valid SSO session from a sharing client func (s *Server) findSSOSession(authSession *AuthSession, targetClientID string) (*ClientAuthState, *UserIdentity) { for sourceClientID, state := range authSession.ClientStates { if !state.Active { continue } sourceClient, _ := s.storage.GetClient(ctx, sourceClientID) if sourceClient == nil { continue } // Check if source client shares its session with target client if s.clientSharesSessionWith(sourceClient, targetClientID) { identity, _ := s.storage.GetUserIdentity(ctx, state.UserID, state.ConnectorID) if identity != nil { return state, identity } } } return nil, nil } ``` **max_age Parameter** The `max_age` parameter is supported per OIDC Core specification: - Specifies the maximum authentication age in seconds - If the identity's last authentication time (`LastLogin`) exceeds `max_age`, force re-authentication - When `max_age` is used, the `auth_time` claim MUST be included in the ID token #### New Endpoints ``` POST /logout GET /logout ``` Logout endpoint following the OpenID RP-Initiated Logout specification ([OpenID spec](https://openid.net/specs/openid-connect-rpinitiated-1_0.html)): ```go func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) { idTokenHint := r.FormValue("id_token_hint") postLogoutRedirectURI := r.FormValue("post_logout_redirect_uri") state := r.FormValue("state") clientID := r.FormValue("client_id") // Optional: logout from specific client // Get auth session from cookie authSession, _ := s.getAuthSessionFromCookie(r) // Validate id_token_hint if provided var hintUserID, hintConnectorID string if idTokenHint != "" { claims, err := s.validateIDTokenHint(idTokenHint) if err == nil { hintUserID = claims.Subject // Extract connector from token if possible } } if authSession != nil { if clientID != "" { // Logout from specific client only delete(authSession.ClientStates, clientID) s.storage.UpdateAuthSession(ctx, authSession.ID, ...) } else { // Logout from all clients - delete entire auth session s.storage.DeleteAuthSession(ctx, authSession.ID) } // Revoke refresh tokens for logged-out clients // ... } // Clear cookie and redirect s.clearSessionCookie(w) // Show logout confirmation or redirect if postLogoutRedirectURI != "" && s.isValidPostLogoutURI(postLogoutRedirectURI, idTokenHint) { u, _ := url.Parse(postLogoutRedirectURI) if state != "" { q := u.Query() q.Set("state", state) u.RawQuery = q.Encode() } http.Redirect(w, r, u.String(), http.StatusFound) return } // Show logout confirmation page s.templates.logout(w, r) } ``` **Future: Upstream Connector Logout** For CallbackConnectors (OIDC, OAuth, SAML), the upstream identity provider may also have an active session. Future work should include: - Implement `LogoutConnector` interface (see above) - OIDC connectors use `end_session_endpoint` from discovery - SAML connectors use Single Logout (SLO) - Redirect user to upstream after Dex logout This is tracked as a future improvement. #### Discovery Updates ```go func (s *Server) constructDiscovery(ctx context.Context) discovery { d := discovery{ // ...existing fields... } if s.sessionsEnabled { d.EndSessionEndpoint = s.absURL("/logout") } return d } ``` #### Login Template Updates When sessions are enabled, add "Remember Me" checkbox to authentication flow. **Template Data** The server passes these values to templates: ```go type templateData struct { // ...existing fields... // SessionsEnabled indicates if sessions feature is active SessionsEnabled bool // RememberMeChecked is the default checkbox state // Set from config: sessions.rememberMeCheckedByDefault RememberMeChecked bool } ``` **For PasswordConnector (login form exists in Dex):** ```html <!-- templates/password.html --> <form method="post"> <!-- existing fields --> {{ if .SessionsEnabled }} <div class="remember-me"> <input type="checkbox" id="remember_me" name="remember_me" value="true" {{ if .RememberMeChecked }}checked{{ end }}> <label for="remember_me">Remember me</label> </div> {{ end }} <button type="submit">Login</button> </form> ``` **For CallbackConnector (no login form in Dex):** For OAuth/OIDC/SAML connectors, the user is redirected to upstream IDP and there's no Dex login form. **Show on Approval Page** (recommended): Add "Remember Me" checkbox to the approval/consent page. User sees it after returning from upstream IDP, before granting consent. ```html <!-- templates/approval.html --> <form method="post"> <!-- existing scope approval fields --> {{ if .SessionsEnabled }} <div class="remember-me"> <input type="checkbox" id="remember_me" name="remember_me" value="true" {{ if .RememberMeChecked }}checked{{ end }}> <label for="remember_me">Remember me on this device</label> </div> {{ end }} <button type="submit" name="approval" value="approve">Grant Access</button> </form> ``` **When skipApprovalScreen is true**: If approval screen is skipped, the `rememberMeCheckedByDefault` config determines cookie persistence: - `false` (default): Session cookie (deleted on browser close) - `true`: Persistent cookie (survives browser restart) **Remember Me Behavior** (Keycloak-like): - **AuthSession is ALWAYS created** on successful authentication regardless of checkbox - **Checkbox controls cookie persistence only**: - **Unchecked**: Session cookie - expires when browser closes. SSO works within browser session. - **Checked**: Persistent cookie - survives browser restart until `absoluteLifetime` expires. #### Connector Type Considerations **CallbackConnector** (OIDC, OAuth, SAML, GitHub, etc.): - Session created after successful callback - Upstream tokens stored in refresh token's ConnectorData (not in session) - Identity refresh via RefreshConnector when refresh token is used **PasswordConnector** (LDAP, local passwords): - Session created after successful password verification - No upstream tokens - Identity refresh re-validates against password backend when refresh token is used Both types work the same way with sessions - the connector type only affects: 1. Initial authentication flow (redirect vs password form) 2. How identity refresh works (via refresh tokens, not sessions) #### Connector Configuration Changes Sessions reference a `ConnectorID`, but connector configuration may change after session creation (e.g., OIDC issuer URL changes, LDAP server replaced, connector removed entirely). **Behavior**: Dex does NOT automatically invalidate sessions when connector configuration changes. This is by design - Dex has no mechanism to detect configuration changes at runtime, and connectors are typically reconfigured during planned maintenance. **Administrator responsibility**: When connector configuration changes in a way that invalidates existing user identities (e.g., connector removed, upstream IdP replaced), administrators should: 1. Terminate affected sessions via gRPC admin API (future: `DexSessions.TerminateByConnector(connectorID)`) 2. Or wait for sessions to expire naturally 3. Or restart Dex with `DEX_SESSIONS_ENABLED=false` temporarily to force re-authentication If a session references a connector that no longer exists, the session will fail gracefully at the next use: `GetConnector()` will return an error, and the user will be redirected to authenticate again. ### Risks and Mitigations #### Security Risks | Risk | Mitigation | |------|------------| | Session hijacking | Secure cookie flags (HttpOnly, Secure, SameSite), short idle timeout | | Session fixation | Generate new session ID after authentication (see below) | | CSRF on logout | GET shows confirmation page, POST performs logout | | Cookie theft | Bind session to fingerprint (IP range, partial user agent) - optional | | Storage exposure | Session IDs are random 256-bit values, no sensitive data in cookie | **Session Fixation Protection** Session fixation attacks occur when an attacker sets a known session ID in a victim's browser before authentication, then hijacks the session after the victim logs in. References: - [OWASP Session Fixation](https://owasp.org/www-community/attacks/Session_fixation) - [OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html) **Mitigations implemented:** 1. **Regenerate session ID on authentication**: When a user successfully authenticates, ALWAYS generate a new `AuthSession.ID` even if a session already exists. Never reuse a pre-authentication session ID. ```go // This is not the real method signature, but the implementation example of a specific behavior. func (s *Server) onSuccessfulAuthentication(w http.ResponseWriter, userID, connectorID, clientID string, rememberMe bool) { // ALWAYS generate new session ID - prevents session fixation newSessionID := NewSessionID() // Create or update AuthSession with NEW ID authSession := &AuthSession{ ID: newSessionID, // Always new, never reuse ClientStates: make(map[string]*ClientAuthState), CreatedAt: time.Now(), // ... } // Set cookie with new session ID s.setSessionCookie(w, newSessionID, rememberMe) } ``` 2. **Don't accept session IDs from URL parameters**: Session IDs are ONLY accepted from cookies, never from query parameters or POST data. 3. **Strict cookie settings**: `HttpOnly`, `Secure`, `SameSite=Lax` prevent common session theft vectors. 4. **Session binding (optional future enhancement)**: Bind session to client characteristics (IP range, user agent) to detect stolen cookies. **Handling existing sessions during authentication:** When a user authenticates and an existing `AuthSession` is found: 1. Generate a completely new session ID 2. Copy relevant state from old session to new session (if any) 3. Delete the old `AuthSession` from storage 4. Set cookie with new session ID This ensures that even if an attacker set a session cookie before authentication, they cannot use it after the victim logs in. #### Operational Risks | Risk | Mitigation | |------|------------| | Storage growth | AuthSessions are GC'd on inactivity; UserIdentities are per-user like OfflineSessions; admin API allows cleanup | | Storage performance | Additional read per request to resolve session cookie. Impact depends on backend — see note below | | Migration complexity | Feature flag allows gradual rollout, no breaking changes | **Storage Performance Note** Enabling sessions introduces an additional storage read on each authorization request (to resolve the session cookie to an `AuthSession`). The actual performance impact depends on the storage backend: - **SQL (Postgres, MySQL, SQLite)**: Session lookup by primary key is a single indexed read — negligible overhead - **etcd**: Single key-value lookup — negligible overhead - **Kubernetes CRDs**: GET by resource name — slightly higher latency than SQL/etcd but still within acceptable bounds (may require [priority&fairness](https://kubernetes.io/docs/concepts/cluster-administration/flow-control/) tuning) - **Memory**: In-process map lookup — no overhead At this stage, we do not have production metrics to quantify the exact impact. The storage access pattern is identical to existing `OfflineSessions` lookups (single record by key), which are already proven in production. It is recommended to monitor storage latency after enabling sessions and adjusting `validIfNotUsedFor` if the GC frequency needs tuning. #### Breaking Changes **None** - Sessions are opt-in via feature flag and configuration. Existing deployments continue to work without changes. #### Rollback Plan Sessions are fully controlled by the `DEX_SESSIONS_ENABLED` feature flag. Rollback is straightforward: 1. **Disable feature flag**: Set `DEX_SESSIONS_ENABLED=false` (or remove it) 2. **Immediate effect**: Dex stops creating, reading, and validating sessions. All authorization requests proceed as before sessions were introduced — connector authentication on every request, no SSO, no session cookies 3. **Cookie cleanup**: Existing session cookies in browsers become inert — Dex ignores them when sessions are disabled. They expire naturally per their MaxAge or when the browser is closed 4. **Storage cleanup**: `AuthSession` and `UserIdentity` records remain in storage but are unused. They can be cleaned up manually or left to accumulate no further growth 5. **No downtime required**: Feature flag can be toggled without restart if environment variable reload is supported; otherwise, a rolling restart is sufficient **Key guarantee**: Disabling the feature flag returns Dex to its pre-sessions behavior with zero side effects. No existing functionality (refresh tokens, connector authentication, token issuance) depends on sessions. Additional tables in the database cost nothing when the feature flag is disabled: they remain unused schema objects and can be deleted later if desired. #### Migration Path 1. Deploy new Dex version - storage migrations create `AuthSession` and `UserIdentity` tables/resources automatically (no feature flag needed for schema) 2. Enable feature flag `DEX_SESSIONS_ENABLED=true` when ready to use sessions 3. Add `sessions:` configuration block 4. Sessions start being created for all new logins; "Remember Me" controls cookie persistence (session vs persistent cookie) 5. Existing refresh tokens continue to work **Note**: Storage schema changes (new tables/CRDs) are applied on startup regardless of feature flag. The feature flag only controls whether sessions are actually created and used. This simplifies deployment - you can deploy the new version, then enable sessions later without another deployment. ### Alternatives #### 1. Stateless Sessions (JWT in Cookie) **Approach**: Store session data directly in a signed/encrypted JWT cookie. **Pros**: - No server-side storage required - Scales horizontally without shared state **Cons**: - Cannot revoke sessions without blocklist - Cookie size limits (~4KB) - Cannot store consent history or client tracking for logout - No server-side session list for logout **Decision**: Rejected. Server-side sessions are required for proper logout and SSO. #### 2. Extend OfflineSessions **Approach**: Add session data to existing OfflineSessions entity. **Pros**: - Reuses existing storage - Simpler migration **Cons**: - OfflineSessions are per-connector, not per-browser - Different lifecycle (refresh token vs browser session) - Would complicate existing OfflineSessions logic **Decision**: Rejected. Clean separation is better for maintainability. #### 3. External Session Store (Redis) **Approach**: Use Redis for session storage instead of existing backends. **Pros**: - Built-in TTL support - Fast reads/writes - Proven session store **Cons**: - Adds infrastructure dependency - Against Dex's simplicity philosophy - Doesn't work with Kubernetes CRD backend **Decision**: Rejected. Must work with existing storage backends. #### 4. Do Nothing **Approach**: Keep using refresh tokens as implicit sessions. **Cons**: - Cannot implement OIDC conformance features - No proper SSO - No proper logout - Blocks future features (2FA, etc.) **Decision**: Rejected. These features are essential for enterprise adoption. ## Future Improvements 1. **Identity Refresh for Long-Lived Sessions** - Periodic refresh of user identity from connector during active session - Configurable refresh interval - Refresh on token request option - Handle connector revocation (terminate session) 2. **Upstream Connector Logout** - Redirect to upstream IDP logout endpoint after Dex logout - Support RP-Initiated Logout towards upstream OIDC providers - SAML Single Logout (SLO) support - Configurable per-connector logout URLs 3. **Session Introspection Endpoint** - Implement session check endpoint similar to [RFC 7662 Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662) - Could enable replacing OAuth2 Proxy in some deployments - Endpoint: `GET /session/introspect` or similar - Returns session validity and user claims - Useful for reverse proxies to validate session cookies directly 4. **Front-Channel Logout** - Implement [OIDC Front-Channel Logout 1.0](https://openid.net/specs/openid-connect-frontchannel-1_0.html) - Notify client applications when user logs out via iframes - Requires client `logoutURL` configuration 5. **2FA/MFA Support** - Store TOTP secrets in user profile - Add MFA enrollment flow - Step-up authentication for sensitive operations - WebAuthn/Passkey support 6. **Session Management API** - List active sessions via gRPC API - Revoke sessions via gRPC API - Session activity audit log 7. **Back-Channel Logout** - Implement [OIDC Back-Channel Logout](https://openid.net/specs/openid-connect-backchannel-1_0.html) - Server-to-server logout notifications 8. **Account Linking** - Link multiple connector identities to single user - Switch between linked identities 9. **Device/Session Fingerprinting** - Optional session binding to client characteristics - Anomaly detection for session theft 10. **Per-Connector Session Policies** - Different session lifetimes per connector - Different SSO policies per connector 11. **Session Impersonation for Admin** - Admin can impersonate user sessions for debugging - Audit logging for impersonation 12. **Consent Management UI** - User-facing page to view/revoke consents - GDPR compliance features ================================================ FILE: docs/enhancements/cel-expressions-2026-02-28.md ================================================ # Dex Enhancement Proposal (DEP) - 2026-02-28 - CEL (Common Expression Language) Integration ## Table of Contents - [Summary](#summary) - [Context](#context) - [Motivation](#motivation) - [Goals/Pain](#goalspain) - [Non-Goals](#non-goals) - [Proposal](#proposal) - [User Experience](#user-experience) - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints) - [Phase 1: pkg/cel - Core CEL Library](#phase-1-pkgcel---core-cel-library) - [Phase 2: Authentication Policies](#phase-2-authentication-policies) - [Phase 3: Token Policies](#phase-3-token-policies) - [Phase 4: OIDC Connector Claim Mapping](#phase-4-oidc-connector-claim-mapping) - [Policy Application Flow](#policy-application-flow) - [Risks and Mitigations](#risks-and-mitigations) - [Alternatives](#alternatives) - [Future Improvements](#future-improvements) ## Summary This DEP proposes integrating [CEL (Common Expression Language)][cel-spec] into Dex as a first-class expression engine for policy evaluation, claim mapping, and token customization. A new reusable `pkg/cel` package will provide a safe, sandboxed CEL environment with Kubernetes-grade compatibility guarantees, cost budgets, and a curated set of extension libraries. Subsequent phases will leverage this package to implement authentication policies, token policies, advanced claim mapping in connectors, and per-client/global access rules — replacing the need for ad-hoc configuration fields and external policy engines. [cel-spec]: https://github.com/google/cel-spec ## Context - [#1583 Add allowedGroups option for clients config][#1583] — a long-standing request for a configuration option to allow a client to specify a list of allowed groups. - [#1635 Connector Middleware][#1635] — long-standing request for a policy/middleware layer between connectors and the server for claim transformations and access control. - [#1052 Allow restricting connectors per client][#1052] — frequently requested feature to restrict which connectors are available to specific OAuth2 clients. - [#2178 Custom claims in ID tokens][#2178] — requests for including additional payload in issued tokens. - [#2812 Token Exchange DEP][dep-token-exchange] — mentions CEL/Rego as future improvement for policy-based assertions on exchanged tokens. - The OIDC connector already has a growing set of ad-hoc claim mutation options (`ClaimMapping`, `ClaimMutations.NewGroupFromClaims`, `FilterGroupClaims`, `ModifyGroupNames`) that would benefit from a unified expression language. - Previous community discussions explored OPA/Rego and JMESPath, but CEL offers a better fit (see [Alternatives](#alternatives)). [#1583]: https://github.com/dexidp/dex/pull/1583 [#1635]: https://github.com/dexidp/dex/issues/1635 [#1052]: https://github.com/dexidp/dex/issues/1052 [#2178]: https://github.com/dexidp/dex/issues/2178 [dep-token-exchange]: /docs/enhancements/token-exchange-2023-02-03-%232812.md ## Motivation ### Goals/Pain 1. **Complex query/filter capabilities** — Dex needs a way to express complex validations and mutations in multiple places (authentication flow, token issuance, claim mapping). Today each feature requires new Go code, new config fields, and a new release cycle. CEL allows operators to express these rules declaratively without code changes. 2. **Authentication policies** — Operators want to control _who_ can log in based on rich conditions: restrict specific connectors to specific clients, require group membership for certain clients, deny login based on email domain, enforce MFA claims, etc. Currently there is no unified mechanism; users rely on downstream applications or external proxies. 3. **Token policies** — Operators want to customize issued tokens: add extra claims to ID tokens, restrict scopes per client, modify `aud` claims, include upstream connector metadata, etc. Today this requires forking Dex or using a reverse proxy. 4. **Claim mapping in OIDC connector** — The OIDC connector has accumulated multiple ad-hoc config options for claim mapping and group mutations (`ClaimMapping`, `NewGroupFromClaims`, `FilterGroupClaims`, `ModifyGroupNames`). A single CEL expression field would replace all of these with a more powerful and composable approach. 5. **Per-client and global policies** — One of the most frequent requests is allowing different connectors for different clients and restricting group-based access per client. CEL policies at the global and per-client level address this cleanly. 6. **CNCF ecosystem alignment** — CEL has massive adoption across the CNCF ecosystem: | Project | CEL Usage | Evidence | |---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| | **Kubernetes** | ValidatingAdmissionPolicy, CRD validation rules (`x-kubernetes-validations`), AuthorizationPolicy, field selectors, CEL-based match conditions in webhooks | [KEP-3488][k8s-cel-kep], [CRD Validation Rules][k8s-crd-cel], [AuthorizationPolicy KEP-3221][k8s-authz-cel] | | **Kyverno** | CEL expressions in validation/mutation policies (v1.12+), preconditions | [Kyverno CEL docs][kyverno-cel] | | **OPA Gatekeeper** | Partially added support for CEL in constraint templates | [Gatekeeper CEL][gatekeeper-cel] | | **Istio** | AuthorizationPolicy conditions, request routing, telemetry | [Istio CEL docs][istio-cel] | | **Envoy / Envoy Gateway** | RBAC filter, ext_authz, rate limiting, route matching, access logging | [Envoy CEL docs][envoy-cel] | | **Tekton** | Pipeline when expressions, CEL custom tasks | [Tekton CEL Interceptor][tekton-cel] | | **Knative** | Trigger filters using CEL expressions | [Knative CEL filters][knative-cel] | | **Google Cloud** | IAM Conditions, Cloud Deploy, Security Command Center | [Google IAM CEL][gcp-cel] | | **Cert-Manager** | CertificateRequestPolicy approval using CEL | [cert-manager approver-policy CEL][cert-manager-cel] | | **Cilium** | Hubble CEL filter logic | [Cilium CEL docs][cilium-cel] | | **Crossplane** | Composition functions with CEL-based patch transforms | [Crossplane CEL transforms][crossplane-cel] | | **Kube-OVN** | Network policy extensions using CEL | [Kube-OVN CEL][kube-ovn-cel] | [k8s-cel-kep]: https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/3488-cel-admission-control [k8s-crd-cel]: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules [k8s-authz-cel]: https://github.com/kubernetes/enhancements/tree/master/keps/sig-auth/3221-structured-authorization-configuration [kyverno-cel]: https://kyverno.io/docs/writing-policies/cel/ [gatekeeper-cel]: https://open-policy-agent.github.io/gatekeeper/website/docs/validating-admission-policy/#policy-updates-to-add-vap-cel [istio-cel]: https://istio.io/latest/docs/reference/config/security/conditions/ [envoy-cel]: https://www.envoyproxy.io/docs/envoy/latest/xds/type/v3/cel.proto [tekton-cel]: https://tekton.dev/docs/triggers/cel_expressions/ [knative-cel]: https://github.com/knative/eventing/blob/main/docs/broker/filtering.md#add-cel-expression-filter [gcp-cel]: https://cloud.google.com/iam/docs/conditions-overview [cert-manager-cel]: https://cert-manager.io/docs/policy/approval/approver-policy/#validations [cilium-cel]: https://docs.cilium.io/en/stable/_api/v1/flow/README/#flowfilter-experimental [crossplane-cel]: https://github.com/crossplane-contrib/function-cel-filter [kube-ovn-cel]: https://kubeovn.github.io/docs/stable/en/advance/cel-expression/ By choosing CEL, Dex operators who already use Kubernetes or other CNCF tools can reuse their existing knowledge of the expression language. ### Non-Goals - **Full policy engine** — This DEP does not aim to replace dedicated external policy engines (OPA, Kyverno). CEL in Dex is scoped to identity and token operations. - **Breaking changes to existing configuration** — All existing config fields (`ClaimMapping`, `ClaimMutations`, etc.) will continue to work. CEL expressions are additive/opt-in. - **Authorization (beyond Dex scope)** — Dex is an identity provider; downstream authorization decisions remain the responsibility of relying parties. CEL policies in Dex are limited to authentication and token issuance concerns. - **Multi-phase CEL in a single DEP** — Only Phase 1 (`pkg/cel` package) is targeted for immediate implementation. Phases 2-4 are included here for design context and will have their own implementation PRs. - **Multi-step logic** — CEL in Dex is scoped to single-expression evaluation. Each expression is a standalone, stateless computation with no intermediate variables, chaining, or multi-step transformations. If a use case requires sequential logic or conditionally chained expressions, it belongs outside Dex (e.g. in an external policy engine or middleware). This boundary protects the design from scope creep that pushes CEL beyond what it's good at. ## Proposal ### User Experience #### Authentication Policy (Phase 2) Operators can define global and per-client authentication policies in the Dex config: ```yaml # Global authentication policy — each expression evaluates to bool. # If true — the request is denied. Evaluated in order; first match wins. authPolicy: - expression: "!identity.email.endsWith('@example.com')" message: "'Login restricted to example.com domain'" - expression: "!identity.email_verified" message: "'Email must be verified'" staticClients: - id: admin-app name: Admin Application secret: ... redirectURIs: [...] # Per-client policy — same structure as global authPolicy: - expression: "!(request.connector_id in ['okta', 'ldap'])" message: "'This application requires Okta or LDAP login'" - expression: "!('admin' in identity.groups)" message: "'Admin group membership required'" ``` #### Token Policy (Phase 3) Operators can add extra claims or mutate token contents: ```yaml tokenPolicy: # Global mutations applied to all ID tokens claims: # Add a custom claim based on group membership - key: "'role'" value: "identity.groups.exists(g, g == 'admin') ? 'admin' : 'user'" # Include connector ID as a claim - key: "'idp'" value: "request.connector_id" # Add department from upstream claims (only if present) - key: "'department'" value: "identity.extra['department']" condition: "'department' in identity.extra" staticClients: - id: internal-api name: Internal API secret: ... redirectURIs: [...] tokenPolicy: claims: - key: "'custom-claim.company.com/team'" value: "identity.extra['team'].orValue('engineering')" # Only add on-call claim for ops group members - key: "'on_call'" value: "true" condition: "identity.groups.exists(g, g == 'ops')" # Restrict scopes filter: expression: "request.scopes.all(s, s in ['openid', 'email', 'profile'])" message: "'Unsupported scope requested'" ``` #### OIDC Connector Claim Mapping (Phase 4) Replace ad-hoc claim mapping with CEL: ```yaml connectors: - type: oidc id: corporate-idp name: Corporate IdP config: issuer: https://idp.example.com clientID: dex-client clientSecret: ... # CEL-based claim mapping — replaces claimMapping and claimModifications claimMappingExpressions: username: "claims.preferred_username.orValue(claims.email)" email: "claims.email" groups: > claims.groups .filter(g, g.startsWith('dex:')) .map(g, g.trimPrefix('dex:')) emailVerified: "claims.email_verified.orValue(true)" # Extra claims to pass through to token policies extra: department: "claims.department.orValue('unknown')" cost_center: "claims.cost_center.orValue('')" ``` ### Implementation Details/Notes/Constraints ### Phase 1: `pkg/cel` — Core CEL Library This is the foundation that all subsequent phases build upon. The package provides a safe, reusable CEL environment with Kubernetes-grade guarantees. #### Package Structure ``` pkg/ cel/ cel.go # Core Environment, compilation, evaluation types.go # CEL type declarations (Identity, Request, etc.) cost.go # Cost estimation and budgeting doc.go # Package documentation library/ email.go # Email-related CEL functions groups.go # Group-related CEL functions ``` #### Dependencies ``` github.com/google/cel-go v0.27.0 ``` The `cel-go` library is the canonical Go implementation maintained by Google, used by Kubernetes and all major CNCF projects. It follows semantic versioning and provides strong backward compatibility guarantees. #### Core API Design **Public types:** ```go // CompilationResult holds a compiled CEL program ready for evaluation. type CompilationResult struct { Program cel.Program OutputType *cel.Type Expression string } // Compiler compiles CEL expressions against a specific environment. type Compiler struct { /* ... */ } // CompilerOption configures a Compiler. type CompilerOption func(*compilerConfig) ``` **Compilation pipeline:** Each `Compile*` call performs these steps sequentially: 1. Reject expressions exceeding `MaxExpressionLength` (10,240 chars). 2. Compile and type-check the expression via `cel-go`. 3. Validate output type matches the expected type (for typed variants). 4. Estimate cost using `defaultCostEstimator` with size hints — reject if estimated max cost exceeds the cost budget. 5. Create an optimized `cel.Program` with runtime cost limit. Presence tests (`has(field)`, `'key' in map`) have zero cost, matching Kubernetes CEL behavior. #### Variable Declarations Variables are declared via `VariableDeclaration{Name, Type}` and registered with `NewCompiler`. Helper constructors provide pre-defined variable sets: **`IdentityVariables()`** — the `identity` variable (from `connector.Identity`), typed as `cel.ObjectType`: | Field | CEL Type | Source | |-------|----------|--------| | `identity.user_id` | `string` | `connector.Identity.UserID` | | `identity.username` | `string` | `connector.Identity.Username` | | `identity.preferred_username` | `string` | `connector.Identity.PreferredUsername` | | `identity.email` | `string` | `connector.Identity.Email` | | `identity.email_verified` | `bool` | `connector.Identity.EmailVerified` | | `identity.groups` | `list(string)` | `connector.Identity.Groups` | **`RequestVariables()`** — the `request` variable (from `RequestContext`), typed as `cel.ObjectType`: | Field | CEL Type | |-------|----------| | `request.client_id` | `string` | | `request.connector_id` | `string` | | `request.scopes` | `list(string)` | | `request.redirect_uri` | `string` | **`ClaimsVariable()`** — the `claims` variable for raw upstream claims as `map(string, dyn)`. **Typing strategy:** `identity` and `request` use `cel.ObjectType` with explicitly declared fields. This gives compile-time type checking: a typo like `identity.emial` is rejected at config load time rather than silently evaluating to null in production — critical for an auth system where a misconfigured policy could lock users out. `claims` remains `map(string, dyn)` because its shape is genuinely unknown — it carries arbitrary upstream IdP data. #### Compatibility Guarantees Following the Kubernetes CEL compatibility model ([KEP-3488: CEL for Admission Control][kep-3488], [Kubernetes CEL Migration Guide][k8s-cel-compat]): 1. **Environment versioning** — The CEL environment is versioned. When new functions or variables are added, they are introduced under a new environment version. Existing expressions compiled against an older version continue to work. ```go // EnvironmentVersion represents the version of the CEL environment. // New variables, functions, or libraries are introduced in new versions. type EnvironmentVersion uint32 const ( // EnvironmentV1 is the initial CEL environment. EnvironmentV1 EnvironmentVersion = 1 ) // WithVersion sets the target environment version for the compiler. func WithVersion(v EnvironmentVersion) CompilerOption ``` This is directly modeled on `k8s.io/apiserver/pkg/cel/environment`. 2. **Library stability** — Custom functions in the `pkg/cel/library` subpackage follow these rules: - Functions MUST NOT be removed once released. - Function signatures MUST NOT change once released. - New functions MUST be added under a new `EnvironmentVersion`. - If a function needs to be replaced, the old one is deprecated but kept forever. 3. **Type stability** — CEL types (`Identity`, `Request`, `Claims`) follow the same rules: - Fields MUST NOT be removed. - Field types MUST NOT change. - New fields are added in a new `EnvironmentVersion`. 4. **Semantic versioning of `cel-go`** — The `cel-go` dependency follows semver. Dex pins to a minor version range and updates are tested for behavioral changes. This is exactly the approach Kubernetes takes: `k8s.io/apiextensions-apiserver` pins `cel-go` and gates new features behind environment versions. 5. **Feature gates** — New CEL-powered features are gated behind Dex feature flags (using the existing `pkg/featureflags` mechanism) during their alpha phase. [kep-3488]: https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/3488-cel-admission-control [k8s-cel-compat]: https://kubernetes.io/docs/reference/using-api/cel/ #### Cost Estimation and Budgets Like Kubernetes, Dex CEL expressions must be bounded to prevent denial-of-service. **Constants:** | Constant | Value | Description | |----------|-------|-------------| | `DefaultCostBudget` | `10_000_000` | Max cost units per evaluation (aligned with Kubernetes) | | `MaxExpressionLength` | `10_240` | Max expression string length in characters | | `DefaultStringMaxLength` | `256` | Estimated max string size for cost estimation | | `DefaultListMaxLength` | `100` | Estimated max list size for cost estimation | **How it works:** A `defaultCostEstimator` (implementing `checker.CostEstimator`) provides size hints for known variables (`identity`, `request`, `claims`) so the `cel-go` cost estimator doesn't assume unbounded sizes. It also provides call cost estimates for custom Dex functions (`dex.emailDomain`, `dex.emailLocalPart`, `dex.groupMatches`, `dex.groupFilter`). Expressions are validated at three levels: 1. **Length check** — reject expressions exceeding `MaxExpressionLength`. 2. **Compile-time cost estimation** — reject expressions whose estimated max cost exceeds the cost budget. 3. **Runtime cost limit** — abort evaluation if actual cost exceeds the budget. #### Extension Libraries The `pkg/cel` environment includes these cel-go standard extensions (same set as Kubernetes): | Library | Description | Examples | |---------|-------------|---------| | `ext.Strings()` | Extended string functions | `"hello".upperAscii()`, `"foo:bar".split(':')`, `s.trim()`, `s.replace('a','b')` | | `ext.Encoders()` | Base64 encoding/decoding | `base64.encode(bytes)`, `base64.decode(str)` | | `ext.Lists()` | Extended list functions | `list.slice(1, 3)`, `list.flatten()` | | `ext.Sets()` | Set operations on lists | `sets.contains(a, b)`, `sets.intersects(a, b)`, `sets.equivalent(a, b)` | | `ext.Math()` | Math functions | `math.greatest(a, b)`, `math.least(a, b)` | Plus custom Dex libraries in the `pkg/cel/library` subpackage, each implementing the `cel.Library` interface: **`library.Email`** — email-related helpers: | Function | Signature | Description | |----------|-----------|-------------| | `dex.emailDomain` | `(string) -> string` | Returns the domain portion of an email address. `dex.emailDomain("user@example.com") == "example.com"` | | `dex.emailLocalPart` | `(string) -> string` | Returns the local part of an email address. `dex.emailLocalPart("user@example.com") == "user"` | **`library.Groups`** — group-related helpers: | Function | Signature | Description | |----------|-----------|-------------| | `dex.groupMatches` | `(list(string), string) -> list(string)` | Returns groups matching a glob pattern. `dex.groupMatches(identity.groups, "team:*")` | | `dex.groupFilter` | `(list(string), list(string)) -> list(string)` | Returns only groups present in the allowed list. `dex.groupFilter(identity.groups, ["admin", "ops"])` | #### Example: Compile and Evaluate ```go // 1. Create a compiler with identity and request variables compiler, _ := cel.NewCompiler( append(cel.IdentityVariables(), cel.RequestVariables()...), ) // 2. Compile a policy expression (type-checked, cost-estimated) prog, _ := compiler.CompileBool( `identity.email.endsWith('@example.com') && 'admin' in identity.groups`, ) // 3. Evaluate against real data result, _ := cel.EvalBool(ctx, prog, map[string]any{ "identity": cel.IdentityFromConnector(connectorIdentity), "request": cel.RequestFromContext(cel.RequestContext{...}), }) // result == true ``` ### Phase 2: Authentication Policies **Config Model:** ```go // AuthPolicy is a list of deny expressions evaluated after a user // authenticates with a connector. Each expression evaluates to bool. // If true — the request is denied. Evaluated in order; first match wins. type AuthPolicy []PolicyExpression // PolicyExpression is a CEL expression with an optional human-readable message. type PolicyExpression struct { // Expression is a CEL expression that evaluates to bool. Expression string `json:"expression"` // Message is a CEL expression that evaluates to string (displayed to the user on deny). // If empty, a generic message is shown. Message string `json:"message,omitempty"` } ``` **Evaluation point:** After `connector.CallbackConnector.HandleCallback()` or `connector.PasswordConnector.Login()` returns an identity, and before the auth request is finalized. Implemented in `server/handlers.go` at `handleConnectorCallback`. **Available CEL variables:** `identity` (from connector), `request` (client_id, connector_id, scopes, redirect_uri). **Compilation:** All policy expressions are compiled once at config load time (in `cmd/dex/serve.go`) and stored in the `Server` struct. This ensures: - Syntax/type errors are caught at startup, not at runtime. - No compilation overhead per request. - Cost estimation can warn operators about expensive expressions at startup. **Evaluation flow:** ``` User authenticates via connector │ v connector.HandleCallback() returns Identity │ v Evaluate global authPolicy (in order) - For each expression: evaluate → bool - If true → deny with message, HTTP 403 │ v Evaluate per-client authPolicy (in order) - Same logic as global │ v Continue normal flow (approval screen or redirect) ``` ### Phase 3: Token Policies **Config Model:** ```go // TokenPolicy defines policies for token issuance. type TokenPolicy struct { // Claims adds or overrides claims in the issued ID token. Claims []ClaimExpression `json:"claims,omitempty"` // Filter validates the token request. If expression evaluates to false, // the request is denied. Filter *PolicyExpression `json:"filter,omitempty"` } type ClaimExpression struct { // Key is a CEL expression evaluating to string — the claim name. Key string `json:"key"` // Value is a CEL expression evaluating to dyn — the claim value. Value string `json:"value"` // Condition is an optional CEL expression evaluating to bool. // When set, the claim is only included in the token if the condition // evaluates to true. If omitted, the claim is always included. Condition string `json:"condition,omitempty"` } ``` **Evaluation point:** In `server/oauth2.go` during ID token construction, after standard claims are built but before JWT signing. **Available CEL variables:** `identity`, `request`, `existing_claims` (the standard claims already computed as `map(string, dyn)`). **Claim merge order:** 1. Standard Dex claims (sub, iss, aud, email, groups, etc.) 2. Global `tokenPolicy.claims` evaluated and merged 3. Per-client `tokenPolicy.claims` evaluated and merged (overrides global) **Reserved (forbidden) claim names:** Certain claim names are reserved and MUST NOT be set or overridden by CEL token policy expressions. Attempting to use a reserved claim key will result in a config validation error at startup. This prevents operators from accidentally breaking the OIDC/OAuth2 contract or undermining Dex's security guarantees. ```go // ReservedClaimNames is the set of claim names that CEL token policy // expressions are forbidden from setting. These are core OIDC/OAuth2 claims // managed exclusively by Dex. var ReservedClaimNames = map[string]struct{}{ "iss": {}, // Issuer — always set by Dex to its own issuer URL "sub": {}, // Subject — derived from connector identity, must not be spoofed "aud": {}, // Audience — determined by the OAuth2 client, not policy "exp": {}, // Expiration — controlled by Dex token TTL configuration "iat": {}, // Issued At — set by Dex at signing time "nbf": {}, // Not Before — set by Dex at signing time "jti": {}, // JWT ID — generated by Dex for token revocation/uniqueness "auth_time": {}, // Authentication Time — set by Dex from the auth session "nonce": {}, // Nonce — echoed from the client's authorization request "at_hash": {}, // Access Token Hash — computed by Dex from the access token "c_hash": {}, // Code Hash — computed by Dex from the authorization code } ``` The reserved list is enforced in two places: 1. **Config load time** — When compiling token policy `ClaimExpression` entries, Dex statically evaluates the `Key` expression (which must be a string literal or constant-foldable) and rejects it if the result is in `ReservedClaimNames`. 2. **Runtime (defense in depth)** — Before merging evaluated claims into the ID token, Dex checks each key against `ReservedClaimNames` and logs a warning + skips the claim if it matches. This guards against dynamic key expressions that couldn't be statically checked. ### Phase 4: OIDC Connector Claim Mapping **Config Model:** In `connector/oidc/oidc.go`: ```go type Config struct { // ... existing fields ... // ClaimMappingExpressions provides CEL-based claim mapping. // When set, these take precedence over ClaimMapping and ClaimMutations. ClaimMappingExpressions *ClaimMappingExpression `json:"claimMappingExpressions,omitempty"` } type ClaimMappingExpression struct { // Username is a CEL expression evaluating to string. // Available variable: 'claims' (map of upstream claims). Username string `json:"username,omitempty"` // Email is a CEL expression evaluating to string. Email string `json:"email,omitempty"` // Groups is a CEL expression evaluating to list(string). Groups string `json:"groups,omitempty"` // EmailVerified is a CEL expression evaluating to bool. EmailVerified string `json:"emailVerified,omitempty"` // Extra is a map of claim names to CEL expressions evaluating to dyn. // These are carried through to token policies. Extra map[string]string `json:"extra,omitempty"` } ``` **Available CEL variable:** `claims` — `map(string, dyn)` containing all raw upstream claims from the ID token and/or UserInfo endpoint. This replaces the need for `ClaimMapping`, `NewGroupFromClaims`, `FilterGroupClaims`, and `ModifyGroupNames` with a single, more powerful mechanism. **Backward compatibility:** When `claimMappingExpressions` is nil, the existing `ClaimMapping` and `ClaimMutations` logic is used unchanged. When `claimMappingExpressions` is set, a startup warning is logged if legacy mapping fields are also configured. ### Policy Application Flow The following diagram shows the order in which CEL policies are applied. Each step is optional — if not configured, it is skipped. ``` Connector Authentication │ │ upstream claims → connector.Identity │ v Authentication Policies │ │ Global authPolicy │ Per-client authPolicy │ v Token Issuance │ │ Global tokenPolicy.filter │ Per-client tokenPolicy.filter │ │ Global tokenPolicy.claims │ Per-client tokenPolicy.claims │ │ Sign JWT │ v Token Response ``` | Step | Policy | Scope | Action on match | |------|--------|-------|-----------------| | 2 | `authPolicy` (global) | Global | Expression → `true` = DENY login | | 3 | `authPolicy` (per-client) | Per-client | Expression → `true` = DENY login | | 4 | `tokenPolicy.filter` (global) | Global | Expression → `false` = DENY token | | 5 | `tokenPolicy.filter` (per-client) | Per-client | Expression → `false` = DENY token | | 6 | `tokenPolicy.claims` (global) | Global | Adds/overrides claims (with optional condition) | | 7 | `tokenPolicy.claims` (per-client) | Per-client | Adds/overrides claims (overrides global) | ### Risks and Mitigations | Risk | Mitigation | |------|------------| | **CEL expression complexity / DoS** | Cost budgets with configurable limits (default aligned with Kubernetes). Expressions are validated at config load time. Runtime evaluation is aborted if cost exceeds budget. | | **Learning curve for operators** | CEL has excellent documentation, playground ([cel.dev](https://cel.dev)), and massive CNCF adoption. Dex docs will include a dedicated CEL guide with examples. Most operators already know CEL from Kubernetes. | | **`cel-go` dependency size** | `cel-go` adds ~5MB to binary. This is acceptable for the functionality provided. Kubernetes, Istio, Envoy all accept this trade-off. | | **Breaking changes in `cel-go`** | Pin to semver minor range. Environment versioning ensures existing expressions continue to work across upgrades. | | **Security: CEL expression injection** | CEL expressions are defined by operators in the server config, not by end users. No CEL expression is ever constructed from user input at runtime. | | **Config migration** | Old config fields (`ClaimMapping`, `ClaimMutations`) continue to work. CEL expressions are opt-in. If both are specified, CEL takes precedence with a config-time warning. | | **Error messages exposing internals** | CEL deny `message` expressions are controlled by the operator. Default messages are generic. Evaluation errors are logged server-side, not exposed to end users. | | **Performance** | Expressions are compiled once at startup. Evaluation is sub-millisecond for typical identity operations. Cost budgets prevent pathological cases. Benchmarks will be included in `pkg/cel` tests. | ### Alternatives #### OPA/Rego OPA was previously considered ([#1635], token exchange DEP). While powerful, it has significant drawbacks for Dex: - **Separate daemon** — OPA typically runs as a sidecar or daemon; adds operational complexity. Even the embedded Go library (`github.com/open-policy-agent/opa/rego`) is significantly heavier than `cel-go`. - **Rego learning curve** — Rego is a Datalog-derived language unfamiliar to most developers. CEL syntax is closer to C/Java/Go and is immediately readable. - **Overkill** — Dex needs simple expression evaluation, not a full policy engine with data loading, bundles, and partial evaluation. - **No inline expressions** — Rego policies are typically separate files, not inline config expressions. This makes the config harder to understand and deploy. - **Smaller CNCF footprint for embedding** — While OPA is a graduated CNCF project, CEL has broader adoption as an _embedded_ language (Kubernetes, Istio, Envoy, Kyverno, etc.). #### JMESPath JMESPath was proposed for claim mapping. Drawbacks: - **Query-only** — JMESPath is a JSON query language. It cannot express boolean conditions, mutations, or string operations naturally. - **Limited type system** — No type checking at compile time. Errors are only caught at runtime. - **Small ecosystem** — Limited adoption compared to CEL. No CNCF projects use JMESPath for policy evaluation. - **No cost estimation** — No way to bound execution time. #### Hardcoded Go Logic The current approach: each feature requires new Go structs, config fields, and code. This is unsustainable: - `ClaimMapping`, `NewGroupFromClaims`, `FilterGroupClaims`, `ModifyGroupNames` are each separate features that could be one CEL expression. - Every new policy need requires a Dex code change and release. - Combinatorial explosion of config options. #### No Change Without CEL or an equivalent: - Operators continue to request per-client connector restrictions, custom claims, claim transformations, and access policies — issues remain open indefinitely. - Dex accumulates more ad-hoc config fields, increasing maintenance burden. - Complex use cases require external reverse proxies, forking Dex, or middleware. ## Future Improvements - **CEL in other connectors** — Extend CEL claim mapping beyond OIDC to LDAP (attribute mapping), SAML (assertion mapping), and other connectors with complex attribute mapping needs. - **Policy testing framework** — Unit test framework for operators to validate their CEL expressions against fixture data before deployment. - **Connector selection via CEL** — Replace the static connector-per-client mapping with a CEL expression that dynamically determines which connectors to show based on request attributes. ================================================ FILE: docs/enhancements/id-jag-2026-03-02#4600.md ================================================ # Dex Enhancement Proposal (DEP) 4600 - 2026-03-02 - Identity Assertion JWT Authorization Grant (ID-JAG) ## Table of Contents - [Dex Enhancement Proposal (DEP) 4600 - 2026-03-02 - Identity Assertion JWT Authorization Grant (ID-JAG)](#dex-enhancement-proposal-dep-4600---2026-03-02---identity-assertion-jwt-authorization-grant-id-jag) - [Table of Contents](#table-of-contents) - [Summary](#summary) - [Context](#context) - [Motivation](#motivation) - [Goals/Pain](#goalspain) - [Non-goals](#non-goals) - [Proposal](#proposal) - [User Experience](#user-experience) - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints) - [Observability](#observability) - [Risks and Mitigations](#risks-and-mitigations) - [Alternatives](#alternatives) - [Future Improvements](#future-improvements) ## Summary [draft-ietf-oauth-identity-assertion-authz-grant-02] specifies a mechanism for an application to use an identity assertion to obtain an access token for a third-party API by coordinating through a common enterprise identity provider using Token Exchange [RFC 8693] and JWT Profile for OAuth 2.0 Authorization Grants [RFC 7523]. This DEP proposes to extend Dex's existing Token Exchange implementation to support issuing Identity Assertion JWT Authorization Grants (ID-JAGs), enabling cross-domain access managed by the enterprise IdP. [draft-ietf-oauth-identity-assertion-authz-grant-02]: https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/ ## Context - [#2812 DEP for RFC 8693 OAuth 2 Token Exchange] established the Token Exchange foundation that ID-JAG builds upon. - [draft-ietf-oauth-identity-assertion-authz-grant-02] is the IETF Standards Track specification this DEP implements. - [draft-ietf-oauth-identity-chaining] is the broader identity chaining specification that ID-JAG profiles. The specification is authored by A. Parecki (Okta), K. McGuinness, and B. Campbell (Ping Identity). It is actively being developed within the IETF OAuth Working Group. Use cases: - LLM agents accessing enterprise APIs on behalf of users (Appendix A.3 of the spec) - Enterprise applications embedding content from third-party apps - Email/calendaring applications accessing cross-domain resources Real-world adoption: - [Okta Cross App Access] is GA, implementing ID-JAG for SaaS-to-SaaS and AI agent scenarios with a developer tutorial available. - [Okta AI Agent Token Exchange] (Early Access) uses ID-JAG for AI agents accessing enterprise APIs on behalf of authenticated users. - [Keycloak #43971] tracks ID-JAG support as a feature request. - The upcoming MCP (Model Context Protocol) specification references ID-JAG for AI agent authorization flows. [#2812 DEP for RFC 8693 OAuth 2 Token Exchange]: https://github.com/dexidp/dex/pull/2812 [draft-ietf-oauth-identity-chaining]: https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-chaining/ [Okta Cross App Access]: https://developer.okta.com/blog/2026/02/10/xaa-client [Okta AI Agent Token Exchange]: https://developer.okta.com/docs/guides/ai-agent-token-exchange/authserver/main/ [Keycloak #43971]: https://github.com/keycloak/keycloak/issues/43971 ## Motivation ### Goals/Pain In enterprise environments, applications are configured for SSO through a common IdP. When one application needs to access a user's data at another application, the current approach requires either: 1. A direct OAuth flow between apps (bypassing the IdP's visibility and policy) 2. Static API keys or service accounts (security risk) ID-JAG solves this by letting the IdP broker cross-domain access, maintaining visibility and policy control. **Specific goals:** - Issue ID-JAG tokens via Token Exchange (`requested_token_type=urn:ietf:params:oauth:token-type:id-jag`) - Support `audience` and `resource` parameters per the specification - Validate subject token audience against requesting client - Support configurable policy evaluation for token exchange requests ### Non-goals - Implementing the Resource Authorization Server role (JWT Bearer Grant / RFC 7523) is out of scope for this initial DEP. It may be addressed in a follow-up. - SAML assertion support as `subject_token_type` is deferred. - Step-up authentication flow is deferred. ## Proposal ### User Experience End-to-end flow: ```mermaid sequenceDiagram participant C as Client (Wiki App) participant Dex as Dex (IdP AS) participant RAS as Resource AS (Chat AS) participant RS as Resource Server (Chat API) C->>Dex: 1. OIDC Authentication (authorization_code flow) Dex-->>C: ID Token + optional Refresh Token C->>Dex: 2. Token Exchange (grant_type=token-exchange)<br/>subject_token=ID Token<br/>requested_token_type=id-jag<br/>audience=https://acme.chat.example/<br/>connector_id=google Note over Dex: Validate subject_token aud == client_id<br/>Evaluate policy (clientID → allowed audiences)<br/>Issue ID-JAG JWT (typ: oauth-id-jag+jwt) Dex-->>C: ID-JAG (access_token, token_type=N_A, expires_in=300) C->>RAS: 3. JWT Bearer Grant (RFC 7523)<br/>grant_type=jwt-bearer, assertion=ID-JAG Note over RAS: Validate ID-JAG signature (Dex JWKS)<br/>Validate aud == RAS issuer<br/>Issue access token RAS-->>C: Access Token C->>RS: 4. API Request with Access Token RS-->>C: Protected Resource ``` Clients can request ID-JAG tokens from Dex's `/token` endpoint by specifying `requested_token_type=urn:ietf:params:oauth:token-type:id-jag` in a Token Exchange request. ID-JAG support is enabled by adding `urn:ietf:params:oauth:token-type:id-jag` to `oauth2.tokenExchange.tokenTypes`. When not listed, requests with this `requested_token_type` are rejected, ensuring no change in behavior for existing deployments. The request parameters (extending existing Token Exchange): - `grant_type`: REQUIRED - `urn:ietf:params:oauth:grant-type:token-exchange` - `subject_token`: REQUIRED - the identity assertion (OpenID Connect ID Token) - `subject_token_type`: REQUIRED - `urn:ietf:params:oauth:token-type:id_token`. SAML 2.0 (`urn:ietf:params:oauth:token-type:saml2`) is deferred (see Non-goals). - `requested_token_type`: REQUIRED - `urn:ietf:params:oauth:token-type:id-jag` - `audience`: REQUIRED - the Issuer URL of the Resource Authorization Server. **Note**: The existing Token Exchange implementation uses a Dex-specific `connector_id` parameter (not part of RFC 8693) for connector selection. The `audience` parameter was not used in the current implementation despite DEP #2812 originally proposing it for connector identification. ID-JAG introduces `audience` with its standard RFC 8693 meaning (target Resource AS). This is purely additive and does not affect existing Token Exchange requests. - `connector_id`: REQUIRED (Dex extension) - the ID of the Dex connector to verify the subject token against. The connector validates the token (issuer, signature, etc.), so a mismatched token is rejected. This parameter already exists in the current Token Exchange implementation and is reused as-is. - `resource`: OPTIONAL - the Resource Identifier of the Resource Server - `scope`: OPTIONAL - the requested scopes at the Resource Server The response: - `access_token`: the ID-JAG JWT (named `access_token` for RFC 8693 compatibility) - `issued_token_type`: `urn:ietf:params:oauth:token-type:id-jag` - `token_type`: `N_A` (this is not an OAuth access token) - `expires_in`: lifetime in seconds (default: 300, configurable independently of ID token lifetime via `expiry.idJAGTokens`) - `scope`: OPTIONAL if the issued scope is identical to the requested scope; REQUIRED otherwise. Per Section 4.3.2 of the specification, policy evaluation at the IdP may result in different scopes being issued than were requested. Complete configuration example: ```yaml oauth2: grantTypes: - authorization_code - urn:ietf:params:oauth:grant-type:token-exchange tokenExchange: # List of token types enabled for exchange. Adding id-jag enables ID-JAG support. # Omitting it (default) disables ID-JAG without affecting other token exchange flows. # SAML2 (urn:ietf:params:oauth:token-type:saml2) may be added in a future release. tokenTypes: - urn:ietf:params:oauth:token-type:id_token - urn:ietf:params:oauth:token-type:id-jag expiry: idTokens: "24h" idJAGTokens: "5m" # default: 5m; independent of idTokens staticClients: - id: wiki-app name: "Wiki Application" secret: "wiki-secret" redirectURIs: - "https://wiki.example/callback" # Per-client ID-JAG policy. Clients without this section cannot obtain ID-JAG tokens # (default-deny). Only audiences and scopes listed here may be requested. idJAGPolicies: allowedAudiences: - "https://chat.example/" - "https://calendar.example/" allowedScopes: - "chat.read" - "calendar.read" - id: supermarket-app name: "Supermarket Application" secret: "supermarket-secret" redirectURIs: - "https://supermarket.example/callback" idJAGPolicies: allowedAudiences: - "https://grocery.store.1/" - "https://grocery.store.2/" allowedScopes: - "eat.bananas" - "eat.apples" ``` ### Implementation Details/Notes/Constraints - A new `id-jag` branch is added to the existing Token Exchange flow, issuing a signed JWT per Section 3 of the specification (header `typ: "oauth-id-jag+jwt"`, claims including `iss`, `sub`, `aud`, `client_id`, `jti`, `exp`, `iat`). - Per-client `idJAGPolicies` in `staticClients` control which audiences and scopes a given client may request in an ID-JAG. Clients without `idJAGPolicies` are denied by default. Dynamically registered clients are currently unsupported for ID-JAG policies; support via CEL expressions (building on the CEL infrastructure from #4601) is future work. - OIDC discovery is extended with `identity_chaining_requested_token_types_supported` per Section 7 of the specification. When ID-JAG is enabled, Dex includes `urn:ietf:params:oauth:token-type:id-jag` in this metadata property. - ID-JAG support is enabled by listing `urn:ietf:params:oauth:token-type:id-jag` in `oauth2.tokenExchange.tokenTypes`. When not listed (default), requests are rejected, ensuring no change in behavior for existing deployments. ### Observability - Every ID-JAG token exchange request (issued or rejected) emits a structured log entry with `client_id`, `connector_id`, `audience`, `resource` (if present), requested and granted `scope` (these may differ after policy evaluation), `sub`, `jti` (if issued), and the policy decision (`approved`/`denied` with reason like `audience_not_allowed` or `client_has_no_policy`). - The following Prometheus counters are exposed: - `dex_id_jag_requests_total` (labels: `result`) — issued vs rejected - `dex_id_jag_policy_rejections_total` (labels: `reason`) — breakdown by denial reason, useful for spotting misconfigurations or abuse - `dex_id_jag_scope_modifications_total` — cases where policy reduced the requested scopes ### Risks and Mitigations - **Lateral movement risk**: Same as existing Token Exchange. Mitigated by not issuing refresh tokens, short expiry (5 min recommended), and policy-based audience restrictions. - **Token confusion**: The `typ: "oauth-id-jag+jwt"` header and distinct `issued_token_type` prevent confusion with ID Tokens or access tokens. - **Replay attack risk**: Server-side `jti` tracking is deferred, so a stolen ID-JAG can be replayed within its 5-minute lifetime. Short `expires_in` is the only Dex-side mitigation; Resource Authorization Servers should implement `jti` caching independently. - **Public client misuse**: Per Section 8.1 of the specification, ID-JAG SHOULD only be used by confidential clients. Public clients should use the standard authorization code flow with interactive user consent at the Resource Authorization Server. Dex will enforce this by rejecting ID-JAG requests from public clients (clients without a secret). - **Breaking changes**: None. This is purely additive to the existing Token Exchange implementation. The `audience` parameter is newly introduced (not previously used in the implementation despite DEP #2812's original proposal), and `connector_id` already exists. ### Alternatives - **Wait for spec finalization**: The draft is Standards Track and stable enough to implement. Okta and Ping Identity (the spec authors) already ship implementations, and the spec has been adopted by the IETF OAuth WG. - **External policy engine (OPA/CEL)**: Config-based policies are sufficient for now. The CEL infrastructure (#4601) is merged; ID-JAG policy evaluation via CEL is future work. ## Future Improvements - Resource Authorization Server role (JWT Bearer Grant / RFC 7523) accepting ID-JAGs from external IdPs - SAML 2.0 assertion support as `subject_token_type` (`urn:ietf:params:oauth:token-type:saml2`) - CEL-based ID-JAG policy evaluation (building on #4601) enabling dynamic policies for DB-managed clients, including runtime policy changes without restart - Step-up authentication when authentication context is insufficient - `actor_token` support for delegation scenarios - Server-side `jti` tracking to prevent ID-JAG replay attacks ================================================ FILE: docs/enhancements/token-exchange-2023-02-03-#2812.md ================================================ # Dex Enhancement Proposal (DEP) 2812 - 2023-02-03 - Token Exchange ## Table of Contents - [Summary](#summary) - [Motivation](#motivation) - [Goals/Pain](#goals) - [Non-Goals](#non-goals) - [Proposal](#proposal) - [User Experience](#user-experience) - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints) - [Risks and Mitigations](#risks-and-mitigations) - [Alternatives](#alternatives) - [Future Improvements](#future-improvements) ## Summary [RFC 8693] specifies a new OAuth2 `grant_type` of `urn:ietf:params:oauth:grant-type:token-exchange`. Using this grant type, when clients start an authentication flow with Dex, in lieu of being redirected to their upstream IDP for authentication on demand, clients can present an independently obtained, valid token from their IDP to Dex. This is primarily useful in fully automated environments with job/machine identities, where there is no human in the loop to handle browser-based login flows. This DEP proposes to implement the new grant type for Dex. [RFC 8693]: https://www.rfc-editor.org/rfc/rfc8693.html ## Context - [#1668 Question: non-web based clients?] was closed with no real resolution - [#1484 Token exchange for external tokens] mentions that Keycloak has a similar capability - [#2657 Get OIDC token issued by Dex using a token issued by one of the connectors] is similar to the previous issue, but this time links to the new (January 2020) [RFC 8693]. I believe the context for all of these are similar: a downstream project using Dex as its only IDP wants to grant access to programmatic clients without issuing long lived API tokens. Examples of downstream issues: - [argoproj/argo-cd#11632 ArgoCD SSO login via Azure AD Auth using OIDC not work for cli sso login] Other related Dex issues: - [#2450 Non-OIDC JWT Connector] is a functionally similar request, but expanded to arbitrary JWTs - [#1225 GitHub Non-Web application flow support] also asks for an exchange, but for an opaque GitHub PAT More broadly, this fits into recent movements to issue machine identities: - [GCP Service Identity](https://cloud.google.com/run/docs/securing/service-identity) - [AWS Execution Role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html) - [GitHub Actions OIDC](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect) - [CircleCI OIDC](https://circleci.com/docs/openid-connect-tokens/) - [Kubernetes Service Accounts](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) - [SPIFFE](https://spiffe.io/) and granting access to resources based on trusting federated identities: - [GCP Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation) - [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html) [#1484 Token exchange for external tokens]: https://github.com/dexidp/dex/issues/1484 [#1668 Question: non-web based clients?]: https://github.com/dexidp/dex/issues/1668 [#2657 Get OIDC token issued by Dex using a token issued by one of the connectors]: https://github.com/dexidp/dex/issues/2657 [argoproj/argo-cd#11632 ArgoCD SSO login via Azure AD Auth using OIDC not work for cli sso login]: https://github.com/argoproj/argo-cd/issues/11632 [#2450 Non-OIDC JWT Connector]: https://github.com/dexidp/dex/issues/2450 [#1225 GitHub Non-Web application flow support]: https://github.com/dexidp/dex/issues/1225 An initial attempt is at [#2806](https://github.com/dexidp/dex/pull/2806) ## Motivation ### Goals/Pain The goal is to allow programmatic access to Dex-protected resources without the use of static/long-lived secret tokens (API keys, username/password) or web-based redirect flows. Such scenarios are common in CI/CD workflows, and in general automation of common tasks. ### Non-goals - Work will be scoped to just the OIDC connector - [RFC 8693 Section 2.1.1. Relationship between Resource, Audience, and Scope] details more complex authorization checks based on targeted resources. This is considered out of scope. [RFC 8693 Section 2.1.1. Relationship between Resource, Audience, and Scope]: https://www.rfc-editor.org/rfc/rfc8693.html#name-relationship-between-resour ## Proposal ### User Experience Clients can make `POST` requests with `application/x-www-form-urlencoded` parameters as specified by [RFC 8693] to Dex's `/token` endpoint. If successful, an access token will be returned, allowing direct authentication with Dex. No refresh tokens will be issued, perform a new exchange (possibly with refreshed upstream tokens) to obtain a new access token. The request parameters from [RFC 8693 Section 2.1](https://www.rfc-editor.org/rfc/rfc8693.html#name-request): - `grant_type`: REQUIRED - `urn:ietf:params:oauth:grant-type:token-exchange` - `resource`: OPTIONAL - the `audience` in the issued Dex token - `audience`: REQUIRED (RFC OPTIONAL) - the connector to verify the provided token against - `scope`: OPTIONAL - the `scope` in the issued Dex token - `requested_token_type`: OPTIONAL - one of `urn:ietf:params:oauth:token-type:access_token` or `urn:ietf:params:oauth:token-type:id_token`, defaulting to access token - `subject_token`: REQUIRED - the token issued by the upstream IDP - `subject_token_type`: REQUIRED - `urn:ietf:params:oauth:token-type:id_token` or `urn:ietf:params:oauth:token-type:access_token` if `getUserInfo` is `true`. - `actor_token`: OPTIONAL - unused - `actor_token_type`: OPTIONAL - unused The response parameters from [RFC 8693 Section 2.2](https://www.rfc-editor.org/rfc/rfc8693.html#name-response): - `access_token`: the issued token, the field is called `access_token` for legacy reasons - `issued_token_type`: the actual type of the issued token - `token_type`: the value `Bearer` - `expires_in`: validity lifetime in seconds - `scope`: the requested scope - `refresh_token`: unused The connector only needs to be configured with an issuer, no client ID / client secrets are necessary ```yaml connectors: - type: oidc id: my-platform name: My Platform config: issuer: https://oidc.my-platform.example/ ``` We expose a global and connector setting, `allowedGrantTypes: []string` defaulting to all implemented types. ### Implementation Details/Notes/Constraints - Connectors expose a new interface `TokenIdentity` that will verify the given token and return the associated identity. A Dex access/id token is then minted for the given identity. - `actor_token` and `actor_token_type` are "MUST ... if the actor token is present, also perform the appropriate validation procedures for its indicated token type". We will ignore these fields for the initial implementation. ### Risks and Mitigations With token exchanges (sometimes known as identity impersonation), is they allow for easier lateral movement if an attacker gains access to an upstream token. We limit the potential impact by not issuing refresh tokens, preventing persistent access. Combined with short token lifetimes, it should limit the period of time between authentication to upstream IDPs. Additionally, a new `allowedGrantTypes` would allow for disabling exchanges if the functionality isn't needed. ### Alternatives - Continue to use static keys - this is a secret management nightmare and quite painful when client storage of keys is [breached](https://circleci.com/blog/january-4-2023-security-alert/) ## Future Improvements - Other connectors may wish to implement the same capability under Oauth - The password connector could be switch to support this new endpoint, submitting passwords as access tokens, allowing for multiple password connectors to be configured - The `audience` field could be made optional if there is a single connector or the id token is inspected for issuer url - The `actor_token` and `actor_token_type` can be checked / validated if a suitable use case is determined. - A policy language like [cel] or [rego] as mentioned on [#1635 Connector Middleware] would allow for stronger assertions of the provided identity against requested resource access. [cel]: https://github.com/google/cel-go [rego]: https://www.openpolicyagent.org/docs/latest/policy-language/ [#1635 Connector Middleware]: https://github.com/dexidp/dex/issues/1635 ================================================ FILE: examples/.gitignore ================================================ *.db ================================================ FILE: examples/config-ad-kubelogin.yaml ================================================ # Active Directory and kubelogin Integration sample issuer: https://dex.example.com:32000/dex storage: type: sqlite3 config: file: examples/dex.db web: https: 0.0.0.0:32000 tlsCert: openid-ca.pem tlsKey: openid-key.pem connectors: - type: ldap name: OpenLDAP id: ldap config: host: localhost:636 # No TLS for this setup. insecureNoSSL: false insecureSkipVerify: true # This would normally be a read-only user. bindDN: cn=Administrator,cn=users,dc=example,dc=com bindPW: admin0! usernamePrompt: Email Address userSearch: baseDN: cn=Users,dc=example,dc=com filter: "(objectClass=person)" username: userPrincipalName # "DN" (case sensitive) is a special attribute name. It indicates that # this value should be taken from the entity's DN not an attribute on # the entity. idAttr: DN emailAttr: userPrincipalName nameAttr: cn groupSearch: baseDN: cn=Users,dc=example,dc=com filter: "(objectClass=group)" userMatchers: # A user is a member of a group when their DN matches # the value of a "member" attribute on the group entity. - userAttr: DN groupAttr: member # The group name should be the "cn" value. nameAttr: cn staticClients: - id: kubernetes redirectURIs: - 'http://localhost:8000' name: 'Kubernetes' secret: ZXhhbXBsZS1hcHAtc2VjcmV0 ================================================ FILE: examples/config-dev.yaml ================================================ # DEPRECATED: use config.yaml.dist and config.dev.yaml examples in the repository root. # TODO: keep this until all references are updated. # The base path of dex and the external name of the OpenID Connect service. # This is the canonical URL that all clients MUST use to refer to dex. If a # path is provided, dex's HTTP service will listen at a non-root URL. issuer: http://127.0.0.1:5556/dex # The storage configuration determines where dex stores its state. Supported # options include SQL flavors and Kubernetes third party resources. # # See the documentation (https://dexidp.io/docs/storage/) for further information. storage: type: sqlite3 config: file: examples/dex.db # type: mysql # config: # host: localhost # port: 3306 # database: dex # user: mysql # password: mysql # ssl: # mode: "false" # type: postgres # config: # host: localhost # port: 5432 # database: dex # user: postgres # password: postgres # ssl: # mode: disable # type: etcd # config: # endpoints: # - http://localhost:2379 # namespace: dex/ # type: kubernetes # config: # kubeConfigFile: $HOME/.kube/config # Configuration for the HTTP endpoints. web: http: 0.0.0.0:5556 # Uncomment for HTTPS options. # https: 127.0.0.1:5554 # tlsCert: /etc/dex/tls.crt # tlsKey: /etc/dex/tls.key # headers: # X-Frame-Options: "DENY" # X-Content-Type-Options: "nosniff" # X-XSS-Protection: "1; mode=block" # Content-Security-Policy: "default-src 'self'" # Strict-Transport-Security: "max-age=31536000; includeSubDomains" # clientRemoteIP: # header: X-Forwarded-For # trustedProxies: # - 10.0.0.0/8 # Configuration for dex appearance # frontend: # issuer: dex # logoURL: theme/logo.png # dir: web/ # Allowed values: light, dark # theme: light # Configuration for telemetry telemetry: http: 0.0.0.0:5558 # enableProfiling: true # Uncomment this block to enable the gRPC API. This values MUST be different # from the HTTP endpoints. # grpc: # addr: 127.0.0.1:5557 # tlsCert: examples/grpc-client/server.crt # tlsKey: examples/grpc-client/server.key # tlsClientCA: examples/grpc-client/ca.crt # Uncomment this block to enable configuration for the expiration time durations. # Is possible to specify units using only s, m and h suffixes. # expiry: # deviceRequests: "5m" # signingKeys: "6h" # idTokens: "24h" # refreshTokens: # reuseInterval: "3s" # validIfNotUsedFor: "2160h" # 90 days # absoluteLifetime: "3960h" # 165 days # Authentication sessions configuration. # Requires DEX_SESSIONS_ENABLED=true feature flag. # sessions: # cookieName: "dex_session" # absoluteLifetime: "24h" # validIfNotUsedFor: "1h" # rememberMeCheckedByDefault: false # Options for controlling the logger. # logger: # level: "debug" # format: "text" # can also be "json" # Default values shown below # oauth2: # # grantTypes determines the allowed set of authorization flows. # grantTypes: # - "authorization_code" # - "client_credentials" # - "refresh_token" # - "implicit" # - "password" # - "urn:ietf:params:oauth:grant-type:device_code" # - "urn:ietf:params:oauth:grant-type:token-exchange" # # responseTypes determines the allowed response contents of a successful authorization flow. # # use ["code", "token", "id_token"] to enable implicit flow for web-only clients. # responseTypes: [ "code" ] # also allowed are "token" and "id_token" # # By default, Dex will ask for approval to share data with application # # (approval for sharing data from connected IdP to Dex is separate process on IdP) # skipApprovalScreen: false # # If only one authentication method is enabled, the default behavior is to # # go directly to it. For connected IdPs, this redirects the browser away # # from application to upstream provider such as the Google login page # alwaysShowLoginScreen: false # # Uncomment the passwordConnector to use a specific connector for password grants # passwordConnector: local # # PKCE (Proof Key for Code Exchange) configuration # pkce: # # If true, PKCE is required for all authorization code flows (OAuth 2.1). # enforce: false # # Supported code challenge methods. Defaults to ["S256", "plain"]. # codeChallengeMethodsSupported: ["S256", "plain"] # Multi-factor authentication configuration. # Requires DEX_SESSIONS_ENABLED=true feature flag. # mfa: # authenticators: # - id: totp-1 # type: TOTP # config: # issuer: "dex-1" # # Optional: limit this authenticator to specific connector types (e.g., ldap, oidc, saml). # # If omitted or empty, applies to all connector types. # # It is recommended to use this option to prevent MFA from being used for connectors # # with their own MFA mechanisms, e.g., OIDC, Google, etc. (but technically, it is possible). # connectorTypes: # - mockCallback # # Default MFA chain applied to clients that don't specify their own mfaChain. # # If omitted or empty, no MFA is required by default. # defaultMFAChain: # - totp-1 # Instead of reading from an external storage, use this list of clients. # # If this option isn't chosen clients may be added through the gRPC API. staticClients: - id: example-app redirectURIs: - 'http://127.0.0.1:5555/callback' - '/dex/device/callback' name: 'Example App' secret: ZXhhbXBsZS1hcHAtc2VjcmV0 # Optional: restrict which connectors this client can use for authentication. # If omitted or empty, all connectors are allowed. # allowedConnectors: # - mock # Optional: ordered list of MFA authenticator IDs the user must complete during login. # References authenticator IDs from mfa.authenticators. # If omitted, mfa.defaultMFAChain is used. # mfaChain: # - totp-1 # Example using environment variables # Set DEX_CLIENT_ID and DEX_SECURE_CLIENT_SECRET before starting Dex # - idEnv: DEX_CLIENT_ID # secretEnv: DEX_CLIENT_SECRET # redirectURIs: # - 'http://127.0.0.1:5556/callback' # name: 'Secure Example App' # - id: example-device-client # redirectURIs: # - /device/callback # name: 'Static Client for Device Flow' # public: true connectors: - type: mockCallback id: mock name: Example # grantTypes restricts which grant types can use this connector. # If not specified, all grant types are allowed. # Supported values: # - "authorization_code" # - "implicit" # - "refresh_token" # - "password" # - "urn:ietf:params:oauth:grant-type:device_code" # - "urn:ietf:params:oauth:grant-type:token-exchange" # grantTypes: # - "authorization_code" # - "refresh_token" # - type: google # id: google # name: Google # config: # issuer: https://accounts.google.com # # Connector config values starting with a "$" will read from the environment. # clientID: $GOOGLE_CLIENT_ID # clientSecret: $GOOGLE_CLIENT_SECRET # redirectURI: http://127.0.0.1:5556/dex/callback # hostedDomains: # - $GOOGLE_HOSTED_DOMAIN # Let dex keep a list of passwords which can be used to login to dex. enablePasswordDB: true # A static list of passwords to login the end user. By identifying here, dex # won't look in its underlying storage for passwords. # # If this option isn't chosen users may be added through the gRPC API. staticPasswords: - email: "admin@example.com" # bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2) hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" username: "admin" name: "Admin User" emailVerified: true preferredUsername: "admin" groups: - "team-a" - "team-a/admins" userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" # Settings for signing JWT tokens. Available options: # - "local": use local keys (only RSA keys supported) # - "vault": use Vault Transit backend (RSA and EC keys supported) signer: type: local config: keysRotationPeriod: "6h" # signer # type: vault # config: # addr: http://127.0.0.1:8200 # token: root # keyName: dex-key ================================================ FILE: examples/example-app/handlers.go ================================================ package main import ( "fmt" "net/http" "net/url" "time" "github.com/coreos/go-oidc/v3/oidc" "golang.org/x/oauth2" ) func (a *app) handleIndex(w http.ResponseWriter, r *http.Request) { renderIndex(w, indexPageData{ ScopesSupported: a.scopesSupported, LogoURI: dexLogoDataURI, }) } func (a *app) handleLogin(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, fmt.Sprintf("failed to parse form: %v", err), http.StatusBadRequest) return } // Only use scopes that are checked in the form scopes := r.Form["extra_scopes"] crossClients := r.Form["cross_client"] // Build complete scope list with audience scopes scopes = buildScopes(scopes, crossClients) connectorID := "" if id := r.FormValue("connector_id"); id != "" { connectorID = id } authCodeURL := "" var authCodeOptions []oauth2.AuthCodeOption if a.pkce { authCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam("code_challenge", codeChallenge)) authCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam("code_challenge_method", "S256")) } // Check if offline_access scope is present to determine offline access mode hasOfflineAccess := false for _, scope := range scopes { if scope == "offline_access" { hasOfflineAccess = true break } } if hasOfflineAccess && !a.offlineAsScope { // Provider uses access_type=offline instead of offline_access scope authCodeOptions = append(authCodeOptions, oauth2.AccessTypeOffline) // Remove offline_access from scopes as it's not supported filteredScopes := make([]string, 0, len(scopes)) for _, scope := range scopes { if scope != "offline_access" { filteredScopes = append(filteredScopes, scope) } } scopes = filteredScopes } authCodeURL = a.oauth2Config(scopes).AuthCodeURL(exampleAppState, authCodeOptions...) // Parse the auth code URL and safely add connector_id parameter if provided u, err := url.Parse(authCodeURL) if err != nil { http.Error(w, "Failed to parse auth URL", http.StatusInternalServerError) return } if connectorID != "" { query := u.Query() query.Set("connector_id", connectorID) u.RawQuery = query.Encode() } http.Redirect(w, r, u.String(), http.StatusSeeOther) } func (a *app) handleCallback(w http.ResponseWriter, r *http.Request) { var ( err error token *oauth2.Token ) ctx := oidc.ClientContext(r.Context(), a.client) oauth2Config := a.oauth2Config(nil) switch r.Method { case http.MethodGet: // Authorization redirect callback from OAuth2 auth flow. if errMsg := r.FormValue("error"); errMsg != "" { http.Error(w, errMsg+": "+r.FormValue("error_description"), http.StatusBadRequest) return } code := r.FormValue("code") if code == "" { http.Error(w, fmt.Sprintf("no code in request: %q", r.Form), http.StatusBadRequest) return } if state := r.FormValue("state"); state != exampleAppState { http.Error(w, fmt.Sprintf("expected state %q got %q", exampleAppState, state), http.StatusBadRequest) return } var authCodeOptions []oauth2.AuthCodeOption if a.pkce { authCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam("code_verifier", codeVerifier)) } token, err = oauth2Config.Exchange(ctx, code, authCodeOptions...) case http.MethodPost: // Form request from frontend to refresh a token. refresh := r.FormValue("refresh_token") if refresh == "" { http.Error(w, fmt.Sprintf("no refresh_token in request: %q", r.Form), http.StatusBadRequest) return } t := &oauth2.Token{ RefreshToken: refresh, Expiry: time.Now().Add(-time.Hour), } token, err = oauth2Config.TokenSource(ctx, t).Token() default: http.Error(w, fmt.Sprintf("method not implemented: %s", r.Method), http.StatusBadRequest) return } if err != nil { http.Error(w, fmt.Sprintf("failed to get token: %v", err), http.StatusInternalServerError) return } parseAndRenderToken(w, r, a, token) } ================================================ FILE: examples/example-app/handlers_device.go ================================================ package main import ( "bytes" "encoding/json" "fmt" "net/http" "net/url" "strings" "golang.org/x/oauth2" ) func (a *app) handleDeviceLogin(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // Parse request body to get options var reqBody struct { Scopes []string `json:"scopes"` CrossClients []string `json:"cross_clients"` ConnectorID string `json:"connector_id"` } if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { http.Error(w, fmt.Sprintf("failed to parse request body: %v", err), http.StatusBadRequest) return } // Build complete scope list with audience scopes (same as handleLogin) scopes := buildScopes(reqBody.Scopes, reqBody.CrossClients) // Build scope string scopeStr := strings.Join(scopes, " ") // Get device authorization endpoint // Properly construct the device code endpoint URL authURL := a.provider.Endpoint().AuthURL deviceAuthURL := strings.TrimSuffix(authURL, "/auth") + "/device/code" // Request device code data := url.Values{} data.Set("client_id", a.clientID) data.Set("client_secret", a.clientSecret) data.Set("scope", scopeStr) // Add connector_id if specified if reqBody.ConnectorID != "" { data.Set("connector_id", reqBody.ConnectorID) } resp, err := a.client.PostForm(deviceAuthURL, data) if err != nil { http.Error(w, fmt.Sprintf("Failed to request device code: %v", err), http.StatusInternalServerError) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body := new(bytes.Buffer) body.ReadFrom(resp.Body) http.Error(w, fmt.Sprintf("Device code request failed: %s", body.String()), resp.StatusCode) return } var deviceResp struct { DeviceCode string `json:"device_code"` UserCode string `json:"user_code"` VerificationURI string `json:"verification_uri"` VerificationURIComplete string `json:"verification_uri_complete"` ExpiresIn int `json:"expires_in"` Interval int `json:"interval"` } if err := json.NewDecoder(resp.Body).Decode(&deviceResp); err != nil { http.Error(w, fmt.Sprintf("Failed to decode device response: %v", err), http.StatusInternalServerError) return } // Store device flow data with new session sessionID := generateSessionID() a.deviceFlowMutex.Lock() a.deviceFlowData.sessionID = sessionID a.deviceFlowData.deviceCode = deviceResp.DeviceCode a.deviceFlowData.userCode = deviceResp.UserCode a.deviceFlowData.verificationURI = deviceResp.VerificationURI a.deviceFlowData.pollInterval = deviceResp.Interval if a.deviceFlowData.pollInterval == 0 { a.deviceFlowData.pollInterval = 5 } a.deviceFlowData.token = nil a.deviceFlowMutex.Unlock() w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "status": "ok", "session_id": sessionID, }) } func (a *app) handleDevicePage(w http.ResponseWriter, r *http.Request) { a.deviceFlowMutex.Lock() data := devicePageData{ SessionID: a.deviceFlowData.sessionID, DeviceCode: a.deviceFlowData.deviceCode, UserCode: a.deviceFlowData.userCode, VerificationURI: a.deviceFlowData.verificationURI, PollInterval: a.deviceFlowData.pollInterval, LogoURI: dexLogoDataURI, } a.deviceFlowMutex.Unlock() if data.DeviceCode == "" { http.Error(w, "No device flow in progress", http.StatusBadRequest) return } renderDevice(w, data) } func (a *app) handleDevicePoll(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { DeviceCode string `json:"device_code"` SessionID string `json:"session_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request", http.StatusBadRequest) return } a.deviceFlowMutex.Lock() storedSessionID := a.deviceFlowData.sessionID storedDeviceCode := a.deviceFlowData.deviceCode existingToken := a.deviceFlowData.token a.deviceFlowMutex.Unlock() // Check if this session has been superseded by a new one if req.SessionID != storedSessionID { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusGone) json.NewEncoder(w).Encode(map[string]interface{}{ "error": "session_expired", "error_description": "This device flow session has been superseded by a new one", }) return } if req.DeviceCode != storedDeviceCode { http.Error(w, "Invalid device code", http.StatusBadRequest) return } // If we already have a token, return success if existingToken != nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "status": "complete", }) return } // Poll the token endpoint tokenURL := a.provider.Endpoint().TokenURL data := url.Values{} data.Set("grant_type", "urn:ietf:params:oauth:grant-type:device_code") data.Set("device_code", req.DeviceCode) data.Set("client_id", a.clientID) data.Set("client_secret", a.clientSecret) tokenResp, err := a.client.PostForm(tokenURL, data) if err != nil { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "status": "pending", }) return } defer tokenResp.Body.Close() if tokenResp.StatusCode == http.StatusOK { // Success! We got the token // Parse the full response including id_token var tokenData struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type"` RefreshToken string `json:"refresh_token"` ExpiresIn int `json:"expires_in"` IDToken string `json:"id_token"` } if err := json.NewDecoder(tokenResp.Body).Decode(&tokenData); err != nil { http.Error(w, "Failed to decode token", http.StatusInternalServerError) return } // Create oauth2.Token with all fields token := &oauth2.Token{ AccessToken: tokenData.AccessToken, TokenType: tokenData.TokenType, RefreshToken: tokenData.RefreshToken, } // Add id_token to Extra token = token.WithExtra(map[string]interface{}{ "id_token": tokenData.IDToken, }) // Store the token a.deviceFlowMutex.Lock() a.deviceFlowData.token = token a.deviceFlowMutex.Unlock() w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "status": "complete", }) return } // Check for errors var errorResp struct { Error string `json:"error"` ErrorDescription string `json:"error_description"` } if err := json.NewDecoder(tokenResp.Body).Decode(&errorResp); err == nil { if errorResp.Error == "authorization_pending" { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "status": "pending", }) return } // Other errors w.Header().Set("Content-Type", "application/json") w.WriteHeader(tokenResp.StatusCode) json.NewEncoder(w).Encode(map[string]interface{}{ "error": errorResp.Error, "error_description": errorResp.ErrorDescription, }) return } // Unknown response w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "status": "pending", }) } func (a *app) handleDeviceResult(w http.ResponseWriter, r *http.Request) { a.deviceFlowMutex.Lock() token := a.deviceFlowData.token a.deviceFlowMutex.Unlock() if token == nil { http.Error(w, "No token available", http.StatusBadRequest) return } parseAndRenderToken(w, r, a, token) } ================================================ FILE: examples/example-app/handlers_userinfo.go ================================================ package main import ( "encoding/json" "fmt" "io" "net/http" ) func (a *app) handleUserInfo(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // Parse form to get access token if err := r.ParseForm(); err != nil { http.Error(w, fmt.Sprintf("Failed to parse form: %v", err), http.StatusBadRequest) return } accessToken := r.FormValue("access_token") if accessToken == "" { http.Error(w, "access_token is required", http.StatusBadRequest) return } // Get UserInfo endpoint from provider userInfoEndpoint := a.provider.Endpoint().AuthURL if len(userInfoEndpoint) > 5 { // Replace /auth with /userinfo userInfoEndpoint = userInfoEndpoint[:len(userInfoEndpoint)-5] + "/userinfo" } // Create request to UserInfo endpoint req, err := http.NewRequestWithContext(r.Context(), "GET", userInfoEndpoint, nil) if err != nil { http.Error(w, fmt.Sprintf("Failed to create request: %v", err), http.StatusInternalServerError) return } // Add Authorization header with access token req.Header.Set("Authorization", "Bearer "+accessToken) // Make the request resp, err := a.client.Do(req) if err != nil { http.Error(w, fmt.Sprintf("Failed to fetch userinfo: %v", err), http.StatusInternalServerError) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) http.Error(w, fmt.Sprintf("UserInfo request failed: %s", string(body)), resp.StatusCode) return } // Parse and return the userinfo var userInfo map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil { http.Error(w, fmt.Sprintf("Failed to decode userinfo: %v", err), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(userInfo) } ================================================ FILE: examples/example-app/main.go ================================================ package main import ( "context" "errors" "fmt" "log" "net/http" "net/url" "os" "sync" "github.com/coreos/go-oidc/v3/oidc" "github.com/spf13/cobra" "golang.org/x/oauth2" ) const exampleAppState = "I wish to wash my irish wristwatch" var ( codeVerifier string codeChallenge string ) func init() { codeVerifier = oauth2.GenerateVerifier() codeChallenge = oauth2.S256ChallengeFromVerifier(codeVerifier) } type app struct { clientID string clientSecret string pkce bool redirectURI string verifier *oidc.IDTokenVerifier provider *oidc.Provider scopesSupported []string // Does the provider use "offline_access" scope to request a refresh token // or does it use "access_type=offline" (e.g. Google)? offlineAsScope bool client *http.Client // Device flow state // Only one session is possible at a time // Since it is an example, we don't bother locking', this is a simplicity tradeoff deviceFlowMutex sync.Mutex deviceFlowData struct { sessionID string // Unique ID for current flow session deviceCode string userCode string verificationURI string pollInterval int token *oauth2.Token } } func cmd() *cobra.Command { var ( a app issuerURL string listen string tlsCert string tlsKey string rootCAs string debug bool ) c := cobra.Command{ Use: "example-app", Short: "An example OpenID Connect client", Long: "", RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 0 { return errors.New("surplus arguments provided") } u, err := url.Parse(a.redirectURI) if err != nil { return fmt.Errorf("parse redirect-uri: %v", err) } listenURL, err := url.Parse(listen) if err != nil { return fmt.Errorf("parse listen address: %v", err) } if rootCAs != "" { client, err := httpClientForRootCAs(rootCAs) if err != nil { return err } a.client = client } if debug { if a.client == nil { a.client = &http.Client{ Transport: debugTransport{http.DefaultTransport}, } } else { a.client.Transport = debugTransport{a.client.Transport} } } if a.client == nil { a.client = http.DefaultClient } // TODO(ericchiang): Retry with backoff ctx := oidc.ClientContext(context.Background(), a.client) provider, err := oidc.NewProvider(ctx, issuerURL) if err != nil { return fmt.Errorf("failed to query provider %q: %v", issuerURL, err) } var s struct { // What scopes does a provider support? // // See: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata ScopesSupported []string `json:"scopes_supported"` } if err := provider.Claims(&s); err != nil { return fmt.Errorf("failed to parse provider scopes_supported: %v", err) } if len(s.ScopesSupported) == 0 { // scopes_supported is a "RECOMMENDED" discovery claim, not a required // one. If missing, assume that the provider follows the spec and has // an "offline_access" scope. a.offlineAsScope = true } else { // See if scopes_supported has the "offline_access" scope. a.offlineAsScope = func() bool { for _, scope := range s.ScopesSupported { if scope == oidc.ScopeOfflineAccess { return true } } return false }() } a.provider = provider a.verifier = provider.Verifier(&oidc.Config{ClientID: a.clientID}) a.scopesSupported = s.ScopesSupported http.Handle("/static/", http.StripPrefix("/static/", staticHandler)) http.HandleFunc("/", a.handleIndex) http.HandleFunc("/login", a.handleLogin) http.HandleFunc("/device/login", a.handleDeviceLogin) http.HandleFunc("/device", a.handleDevicePage) http.HandleFunc("/device/poll", a.handleDevicePoll) http.HandleFunc("/device/result", a.handleDeviceResult) http.HandleFunc("/userinfo", a.handleUserInfo) http.HandleFunc(u.Path, a.handleCallback) switch listenURL.Scheme { case "http": log.Printf("listening on %s", listen) return http.ListenAndServe(listenURL.Host, nil) case "https": log.Printf("listening on %s", listen) return http.ListenAndServeTLS(listenURL.Host, tlsCert, tlsKey, nil) default: return fmt.Errorf("listen address %q is not using http or https", listen) } }, } c.Flags().StringVar(&a.clientID, "client-id", "example-app", "OAuth2 client ID of this application.") c.Flags().StringVar(&a.clientSecret, "client-secret", "ZXhhbXBsZS1hcHAtc2VjcmV0", "OAuth2 client secret of this application.") c.Flags().BoolVar(&a.pkce, "pkce", true, "Use PKCE flow for the code exchange.") c.Flags().StringVar(&a.redirectURI, "redirect-uri", "http://127.0.0.1:5555/callback", "Callback URL for OAuth2 responses.") c.Flags().StringVar(&issuerURL, "issuer", "http://127.0.0.1:5556/dex", "URL of the OpenID Connect issuer.") c.Flags().StringVar(&listen, "listen", "http://127.0.0.1:5555", "HTTP(S) address to listen at.") c.Flags().StringVar(&tlsCert, "tls-cert", "", "X509 cert file to present when serving HTTPS.") c.Flags().StringVar(&tlsKey, "tls-key", "", "Private key for the HTTPS cert.") c.Flags().StringVar(&rootCAs, "issuer-root-ca", "", "Root certificate authorities for the issuer. Defaults to host certs.") c.Flags().BoolVar(&debug, "debug", false, "Print all request and responses from the OpenID Connect issuer.") return &c } func main() { if err := cmd().Execute(); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(2) } } ================================================ FILE: examples/example-app/static/app.js ================================================ (function() { const crossClientInput = document.getElementById("cross_client_input"); const crossClientList = document.getElementById("cross-client-list"); const addClientBtn = document.getElementById("add-cross-client"); const scopesList = document.getElementById("scopes-list"); const customScopeInput = document.getElementById("custom_scope_input"); const addCustomScopeBtn = document.getElementById("add-custom-scope"); // Default scopes that should be checked by default const defaultScopes = ["openid", "profile", "email", "offline_access"]; // Check default scopes on page load document.addEventListener("DOMContentLoaded", function() { const checkboxes = scopesList.querySelectorAll('input[type="checkbox"]'); checkboxes.forEach(cb => { if (defaultScopes.includes(cb.value)) { cb.checked = true; } }); }); function addCrossClient(value) { const trimmed = value.trim(); if (!trimmed) return; const chip = document.createElement("div"); chip.className = "chip"; const text = document.createElement("span"); text.textContent = trimmed; const hidden = document.createElement("input"); hidden.type = "hidden"; hidden.name = "cross_client"; hidden.value = trimmed; const remove = document.createElement("button"); remove.type = "button"; remove.textContent = "×"; remove.onclick = () => crossClientList.removeChild(chip); chip.append(text, hidden, remove); crossClientList.appendChild(chip); } function addCustomScope(scope) { const trimmed = scope.trim(); if (!trimmed || !scopesList) return; // Check if scope already exists const existingCheckboxes = scopesList.querySelectorAll('input[type="checkbox"]'); for (const cb of existingCheckboxes) { if (cb.value === trimmed) { cb.checked = true; return; } } // Add new scope checkbox const scopeItem = document.createElement("div"); scopeItem.className = "scope-item"; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.name = "extra_scopes"; checkbox.value = trimmed; checkbox.id = "scope_custom_" + trimmed; checkbox.checked = true; const label = document.createElement("label"); label.htmlFor = checkbox.id; label.textContent = trimmed; scopeItem.append(checkbox, label); scopesList.appendChild(scopeItem); } addClientBtn?.addEventListener("click", () => { addCrossClient(crossClientInput.value); crossClientInput.value = ""; crossClientInput.focus(); }); crossClientInput?.addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); addCrossClient(crossClientInput.value); crossClientInput.value = ""; } }); addCustomScopeBtn?.addEventListener("click", () => { addCustomScope(customScopeInput.value); customScopeInput.value = ""; customScopeInput.focus(); }); customScopeInput?.addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); addCustomScope(customScopeInput.value); customScopeInput.value = ""; } }); // Device Grant Login Handler const deviceGrantBtn = document.getElementById("device-grant-btn"); deviceGrantBtn?.addEventListener("click", async () => { deviceGrantBtn.disabled = true; deviceGrantBtn.textContent = "Loading..."; try { // Collect form data similar to regular login const form = document.getElementById("login-form"); const formData = new FormData(form); // Get selected scopes const scopes = formData.getAll("extra_scopes"); // Get cross-client values const crossClients = formData.getAll("cross_client"); // Get connector_id if specified const connectorId = formData.get("connector_id") || ""; // Initiate device flow with options const response = await fetch('/device/login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ scopes: scopes, cross_clients: crossClients, connector_id: connectorId }) }); if (response.ok) { // Redirect to device flow page window.location.href = '/device'; } else { const errorText = await response.text(); alert('Failed to start device flow: ' + errorText); } } catch (error) { alert('Error starting device flow: ' + error.message); } finally { deviceGrantBtn.disabled = false; deviceGrantBtn.textContent = "Device Code Flow"; } }); })(); ================================================ FILE: examples/example-app/static/device.js ================================================ (function() { const sessionID = document.getElementById("session-id")?.value; const deviceCode = document.getElementById("device-code")?.value; const pollInterval = parseInt(document.getElementById("poll-interval")?.value || "5", 10); const verificationURL = document.getElementById("verification-url")?.textContent; const userCode = document.getElementById("user-code")?.textContent; const statusText = document.getElementById("status-text"); const errorMessage = document.getElementById("error-message"); const openAuthBtn = document.getElementById("open-auth-btn"); let pollTimer = null; document.querySelectorAll(".copy-btn").forEach(btn => { btn.addEventListener("click", async function() { const targetId = this.getAttribute("data-copy"); const targetElement = document.getElementById(targetId); if (targetElement) { const textToCopy = targetElement.textContent; try { await navigator.clipboard.writeText(textToCopy); const originalText = this.textContent; this.textContent = "✓"; setTimeout(() => { this.textContent = originalText; }, 2000); } catch (err) { console.error('Failed to copy:', err); } } }); }); openAuthBtn?.addEventListener("click", () => { if (verificationURL && userCode) { const url = verificationURL + "?user_code=" + encodeURIComponent(userCode); window.open(url, "_blank", "width=600,height=800"); } }); async function pollForToken() { try { const response = await fetch('/device/poll', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ session_id: sessionID, device_code: deviceCode }) }); const data = await response.json(); if (response.ok && data.status === 'complete') { statusText.textContent = "Authentication successful! Redirecting..."; stopPolling(); window.location.href = '/device/result'; } else if (response.ok && data.status === 'pending') { statusText.textContent = "Waiting for authentication..."; } else { const errorText = data.error_description || data.error || 'Unknown error'; if (data.error === 'session_expired') { showError('This session has been superseded by a new device flow. Please start over.'); stopPolling(); } else if (data.error === 'expired_token' || data.error === 'access_denied') { showError(data.error === 'expired_token' ? 'The device code has expired. Please start over.' : 'Authentication was denied.'); stopPolling(); } } } catch (error) { console.error('Polling error:', error); } } function showError(message) { errorMessage.textContent = message; errorMessage.style.display = 'block'; // Hide the status indicator (contains spinner and status text) const statusIndicator = document.querySelector('.status-indicator'); if (statusIndicator) { statusIndicator.style.display = 'none'; } } function startPolling() { pollForToken(); pollTimer = setInterval(pollForToken, pollInterval * 1000); } function stopPolling() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } } if (deviceCode) { startPolling(); } window.addEventListener('beforeunload', stopPolling); })(); ================================================ FILE: examples/example-app/static/style.css ================================================ body { font-family: Arial, sans-serif; background-color: #f2f2f2; margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; flex-direction: column; padding: 20px; } /* Token page layout - no centering */ body.token-page { display: block; align-items: flex-start; justify-content: flex-start; padding: 20px; } .container { display: flex; flex-direction: column; align-items: center; width: 100%; max-width: 600px; } .logo { max-width: 100%; width: 200px; height: auto; margin-bottom: 30px; display: block; } form { background-color: #fff; padding: 30px; border-radius: 8px; width: 100%; } /* Shadow only for main login form */ .container form { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .primary-action { margin-bottom: 20px; } .advanced { margin-top: 20px; background: #f9fbfd; border: 1px solid #dbe7f3; border-radius: 6px; padding: 15px; } .advanced summary { cursor: pointer; font-weight: bold; color: #3F9FD8; user-select: none; text-align: center; } .advanced summary:hover { color: #357FAA; } .app-description { text-align: center; color: #666; font-size: 14px; margin-bottom: 25px; line-height: 1.5; } .app-description a { color: #3F9FD8; text-decoration: none; } .app-description a:hover { text-decoration: underline; } .field { margin-top: 15px; display: flex; flex-direction: column; gap: 8px; } .field label { font-weight: 600; color: #333; font-size: 14px; } .inline-input { display: flex; gap: 8px; } .inline-input input[type="text"] { flex: 1; } input[type="text"] { padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; outline: none; transition: border-color 0.2s; } input[type="text"]:focus { border-color: #3F9FD8; } .checkbox-field { flex-direction: row; align-items: center; gap: 8px; margin-top: 10px; } .checkbox-field label { margin: 0; font-weight: 500; } input[type="checkbox"] { width: 18px; height: 18px; cursor: pointer; } .scopes-list { display: flex; flex-direction: column; gap: 8px; max-height: 200px; overflow-y: auto; padding: 10px; background: white; border: 1px solid #ddd; border-radius: 4px; } .scope-item { display: flex; align-items: center; gap: 8px; } .scope-item input[type="checkbox"] { margin: 0; } .scope-item label { margin: 0; font-weight: 400; font-size: 13px; cursor: pointer; } input[type="submit"], button { padding: 12px 20px; background-color: #3F9FD8; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; transition: background-color 0.2s; } /* Full width submit button only on main login form */ form input[type="submit"] { width: 100%; font-size: 16px; } /* Token page forms should have auto-width buttons */ body.token-page input[type="submit"] { width: auto; font-size: 14px; } input[type="submit"]:hover, button:hover { background-color: #357FAA; } button { white-space: nowrap; } .chip-list { display: flex; flex-wrap: wrap; gap: 8px; min-height: 32px; margin-top: 8px; } .chip { display: inline-flex; align-items: center; gap: 6px; background: #e9f4fb; border: 1px solid #cde5f5; border-radius: 16px; padding: 6px 12px; font-size: 13px; } .chip button { border: none; background: transparent; cursor: pointer; font-weight: bold; color: #3F9FD8; padding: 0; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; border-radius: 50%; } .chip button:hover { background: #d0e8f5; } .hint { font-size: 12px; color: #666; margin-top: 4px; } .copy-btn { padding: 4px 10px; background-color: #3F9FD8; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px; transition: background-color 0.2s; margin-left: 8px; white-space: nowrap; } .copy-btn:hover { background-color: #357FAA; } /* Token page styles */ .back-button { display: inline-block; padding: 8px 16px; background-color: #EF4B5C; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; text-decoration: none; transition: background-color 0.3s ease, transform 0.2s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); position: fixed; right: 20px; bottom: 20px; } .back-button:hover { background-color: #C43B4B; } .token-block { background-color: #fff; padding: 10px 15px; border-radius: 8px; margin-bottom: 15px; word-wrap: break-word; display: flex; flex-direction: column; gap: 5px; position: relative; overflow: hidden; border: 1px solid #e0e0e0; } .token-block form { margin: 10px 0 0 0; padding: 0; background-color: transparent; box-shadow: none; border-radius: 0; } .token-title { font-weight: bold; display: flex; justify-content: space-between; align-items: center; } .token-title a { font-size: 0.9em; text-decoration: none; color: #3F9FD8; } .token-title a:hover { text-decoration: underline; } .token-code { overflow-wrap: break-word; word-break: break-all; white-space: normal; } pre { white-space: pre-wrap; background-color: #f9f9f9; padding: 8px; border-radius: 4px; border: 1px solid #ddd; margin: 0; font-family: 'Courier New', Courier, monospace; overflow-x: auto; font-size: 0.9em; position: relative; margin-top: 5px; } pre .key { color: #c00; } pre .string { color: #080; } pre .number { color: #00f; } /* Login Buttons Styles */ .login-buttons { display: flex; flex-direction: column; gap: 12px; margin-bottom: 20px; } .login-button { width: 100%; padding: 14px 24px; font-size: 16px; border-radius: 4px; cursor: pointer; transition: all 0.3s ease; font-weight: 600; border: 2px solid #3F9FD8; } .login-button.primary { background-color: #3F9FD8; color: #fff; } .login-button.primary:hover { background-color: #357FAA; border-color: #357FAA; } .login-button.secondary { background-color: #fff; color: #3F9FD8; } .login-button.secondary:hover { background-color: #f0f8ff; } /* Device Flow Page Styles */ .device-flow-container { background-color: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); width: 100%; } .device-instructions h2 { margin-top: 0; color: #333; text-align: center; } .instruction-text { text-align: center; color: #666; margin-bottom: 25px; } .verification-info { display: flex; flex-direction: column; gap: 20px; margin-bottom: 25px; } .info-item { display: flex; flex-direction: column; gap: 8px; } .info-item label { font-weight: 600; color: #333; font-size: 14px; } .code-display { display: flex; align-items: center; gap: 10px; background-color: #f5f5f5; padding: 12px; border-radius: 4px; border: 1px solid #ddd; } .code-display.large { padding: 20px; } .code-display code { flex: 1; font-family: 'Courier New', Courier, monospace; font-size: 14px; word-break: break-all; } .code-display code.user-code { font-size: 24px; font-weight: bold; letter-spacing: 2px; color: #3F9FD8; } .copy-btn { background: none; border: none; cursor: pointer; font-size: 18px; padding: 5px 10px; border-radius: 4px; transition: background-color 0.2s; } .copy-btn:hover { background-color: #e0e0e0; } .copy-btn:active { background-color: #d0d0d0; } .actions { text-align: center; } .primary-button { padding: 12px 32px; font-size: 16px; background-color: #3F9FD8; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-weight: 600; transition: background-color 0.3s; } .primary-button:hover { background-color: #357FAA; } .polling-status { margin-top: 30px; padding-top: 30px; border-top: 1px solid #eee; } .status-indicator { display: flex; align-items: center; justify-content: center; gap: 15px; color: #666; } .spinner { width: 20px; height: 20px; border: 3px solid #f3f3f3; border-top: 3px solid #3F9FD8; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .error-message { margin-top: 15px; padding: 12px; background-color: #fee; border: 1px solid #fcc; border-radius: 4px; color: #c00; text-align: center; } .device-data { display: none; } /* UserInfo Styles */ #userinfo-section { margin-top: 10px; } .fetch-userinfo-btn { padding: 10px 20px; background-color: #3F9FD8; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; transition: background-color 0.2s; } .fetch-userinfo-btn:hover { background-color: #357FAA; } .userinfo-loading { display: flex; align-items: center; gap: 10px; color: #666; margin-top: 10px; } .userinfo-loading .spinner { width: 16px; height: 16px; border: 2px solid #f3f3f3; border-top: 2px solid #3F9FD8; border-radius: 50%; animation: spin 1s linear infinite; } #userinfo-claims { margin-top: 15px; } #userinfo-error { margin-top: 10px; } ================================================ FILE: examples/example-app/static/token.js ================================================ // Simple JSON syntax highlighter document.addEventListener("DOMContentLoaded", function() { const claimsElement = document.getElementById("claims"); if (claimsElement) { try { const json = JSON.parse(claimsElement.textContent); claimsElement.innerHTML = syntaxHighlight(json); } catch (e) { console.error("Invalid JSON in claims:", e); } } }); function syntaxHighlight(json) { if (typeof json != 'string') { json = JSON.stringify(json, undefined, 2); } json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); return json.replace(/("(\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|\b\d+\b)/g, function (match) { let cls = 'number'; if (/^"/.test(match)) { if (/:$/.test(match)) { cls = 'key'; } else { cls = 'string'; } } else if (/true|false/.test(match)) { cls = 'boolean'; } else if (/null/.test(match)) { cls = 'null'; } return '<span class="' + cls + '">' + match + '</span>'; }); } function copyPublicKey() { const publicKeyElement = document.getElementById("public-key"); if (!publicKeyElement) return; const text = publicKeyElement.textContent; // Use modern clipboard API if available if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(() => { showCopyFeedback("Copied!"); }).catch(err => { console.error("Failed to copy:", err); fallbackCopy(text); }); } else { fallbackCopy(text); } } function fallbackCopy(text) { const textarea = document.createElement("textarea"); textarea.value = text; textarea.style.position = "fixed"; textarea.style.opacity = "0"; document.body.appendChild(textarea); textarea.select(); try { document.execCommand("copy"); showCopyFeedback("Copied!"); } catch (err) { console.error("Fallback copy failed:", err); showCopyFeedback("Failed to copy"); } document.body.removeChild(textarea); } function showCopyFeedback(message) { const btn = event.target; const originalText = btn.textContent; btn.textContent = message; btn.style.backgroundColor = "#28a745"; setTimeout(() => { btn.textContent = originalText; btn.style.backgroundColor = ""; }, 2000); } // UserInfo functionality document.addEventListener("DOMContentLoaded", function() { const form = document.getElementById("userinfo-form"); if (form) { form.addEventListener("submit", fetchUserInfo); } }); async function fetchUserInfo(event) { event.preventDefault(); const form = event.target; const loading = document.getElementById("userinfo-loading"); const error = document.getElementById("userinfo-error"); const claimsElement = document.getElementById("userinfo-claims"); const submitButton = form.querySelector('button[type="submit"]'); // Hide error and claims from previous attempts error.style.display = "none"; claimsElement.style.display = "none"; // Show loading, hide button submitButton.style.display = "none"; loading.style.display = "flex"; try { const formData = new FormData(form); // Convert FormData to URL-encoded string const urlEncodedData = new URLSearchParams(formData).toString(); const response = await fetch("/userinfo", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: urlEncodedData }); if (!response.ok) { const errorText = await response.text(); throw new Error(errorText || `HTTP ${response.status}`); } const userinfo = await response.json(); // Display the userinfo claims const code = claimsElement.querySelector("code"); const formattedJson = JSON.stringify(userinfo, null, 2); code.textContent = formattedJson; // Apply syntax highlighting try { code.innerHTML = syntaxHighlight(userinfo); } catch (e) { console.error("Failed to highlight JSON:", e); } claimsElement.style.display = "block"; } catch (err) { console.error("Failed to fetch userinfo:", err); error.textContent = "Failed to fetch UserInfo: " + err.message; error.style.display = "block"; submitButton.style.display = "inline-block"; } finally { loading.style.display = "none"; } } ================================================ FILE: examples/example-app/templates/device.html ================================================ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Device Login - Example App

Device Login

Please authenticate on your device:

{{.VerificationURI}}
{{.UserCode}}
Waiting for authentication...
================================================ FILE: examples/example-app/templates/index.html ================================================ Example App - Login
This is an example application for Dex OpenID Connect provider.
Learn more in the documentation.
Advanced options
Select OpenID Connect scopes to request. Standard scopes are pre-selected.
{{range .ScopesSupported}}
{{end}}
{{if eq (len .ScopesSupported) 0}}
No scopes from discovery - add custom scopes below.
{{end}}
Each client is sent as audience:server:client_id scope.
Specify a connector ID to bypass the connector selection screen.
================================================ FILE: examples/example-app/templates/token.html ================================================ Tokens {{ if .IDToken }}
ID Token: Decode on jwt.io
{{ .IDToken }}
{{ end }} {{ if .AccessToken }}
Access Token: Decode on jwt.io
{{ .AccessToken }}
{{ end }} {{ if .Claims }}
ID Token Claims:
{{ .Claims }}
{{ end }} {{ if .AccessToken }}
UserInfo:
{{ end }} {{ if .RefreshToken }}
Refresh Token:
{{ .RefreshToken }}
{{ end }} {{ if .PublicKeyPEM }}
Public Key (for JWT verification):
{{ .PublicKeyPEM }}
Copy this key and paste it into jwt.io's "Verify Signature" section to validate the token signature.
{{ end }} Back to Home ================================================ FILE: examples/example-app/templates.go ================================================ package main import ( "context" "crypto/rsa" "crypto/x509" "embed" "encoding/base64" "encoding/json" "encoding/pem" "html/template" "io/fs" "log" "math/big" "net/http" "net/url" "github.com/coreos/go-oidc/v3/oidc" ) //go:embed templates/*.html var templatesFS embed.FS //go:embed static/* var staticFS embed.FS const dexLogoDataURI = "/static/dex-glyph-color.svg" var ( indexTmpl *template.Template tokenTmpl *template.Template deviceTmpl *template.Template staticHandler http.Handler ) func init() { var err error indexTmpl, err = template.ParseFS(templatesFS, "templates/index.html") if err != nil { log.Fatalf("failed to parse index template: %v", err) } tokenTmpl, err = template.ParseFS(templatesFS, "templates/token.html") if err != nil { log.Fatalf("failed to parse token template: %v", err) } deviceTmpl, err = template.ParseFS(templatesFS, "templates/device.html") if err != nil { log.Fatalf("failed to parse device template: %v", err) } // Create handler for static files staticSubFS, err := fs.Sub(staticFS, "static") if err != nil { log.Fatalf("failed to create static sub filesystem: %v", err) } staticHandler = http.FileServer(http.FS(staticSubFS)) } func renderIndex(w http.ResponseWriter, data indexPageData) { renderTemplate(w, indexTmpl, data) } func renderDevice(w http.ResponseWriter, data devicePageData) { renderTemplate(w, deviceTmpl, data) } type indexPageData struct { ScopesSupported []string LogoURI string } type devicePageData struct { SessionID string DeviceCode string UserCode string VerificationURI string PollInterval int LogoURI string } type tokenTmplData struct { IDToken string IDTokenJWTLink string AccessToken string AccessTokenJWTLink string RefreshToken string RedirectURL string Claims string PublicKeyPEM string } func generateJWTIOLink(token string, provider *oidc.Provider, ctx context.Context) string { // JWT.io doesn't support automatic public key via URL parameter // The public key is displayed separately on the page for manual copy-paste return "https://jwt.io/#debugger-io?token=" + url.QueryEscape(token) } func getPublicKeyPEM(provider *oidc.Provider) string { if provider == nil { return "" } jwksURL := provider.Endpoint().AuthURL if len(jwksURL) > 5 { jwksURL = jwksURL[:len(jwksURL)-5] + "/keys" } else { return "" } resp, err := http.Get(jwksURL) if err != nil { return "" } defer resp.Body.Close() var jwks struct { Keys []json.RawMessage `json:"keys"` } if err := json.NewDecoder(resp.Body).Decode(&jwks); err != nil || len(jwks.Keys) == 0 { return "" } var key struct { N string `json:"n"` E string `json:"e"` Kty string `json:"kty"` } if err := json.Unmarshal(jwks.Keys[0], &key); err != nil || key.Kty != "RSA" { return "" } nBytes, err1 := base64.RawURLEncoding.DecodeString(key.N) eBytes, err2 := base64.RawURLEncoding.DecodeString(key.E) if err1 != nil || err2 != nil { return "" } var eInt int for _, b := range eBytes { eInt = eInt<<8 | int(b) } pubKey := &rsa.PublicKey{ N: new(big.Int).SetBytes(nBytes), E: eInt, } pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey) if err != nil { return "" } pubKeyPEM := pem.EncodeToMemory(&pem.Block{ Type: "PUBLIC KEY", Bytes: pubKeyBytes, }) return string(pubKeyPEM) } func renderToken(w http.ResponseWriter, ctx context.Context, provider *oidc.Provider, redirectURL, idToken, accessToken, refreshToken, claims string) { data := tokenTmplData{ IDToken: idToken, IDTokenJWTLink: generateJWTIOLink(idToken, provider, ctx), AccessToken: accessToken, AccessTokenJWTLink: generateJWTIOLink(accessToken, provider, ctx), RefreshToken: refreshToken, RedirectURL: redirectURL, Claims: claims, PublicKeyPEM: getPublicKeyPEM(provider), } renderTemplate(w, tokenTmpl, data) } func renderTemplate(w http.ResponseWriter, tmpl *template.Template, data interface{}) { err := tmpl.Execute(w, data) if err == nil { return } switch err := err.(type) { case *template.Error: log.Printf("Error rendering template %s: %s", tmpl.Name(), err) http.Error(w, "Internal server error", http.StatusInternalServerError) default: // An error with the underlying write, such as the connection being dropped. Ignore for now. } } ================================================ FILE: examples/example-app/utils.go ================================================ package main import ( "bytes" "crypto/rand" "crypto/tls" "crypto/x509" "encoding/hex" "encoding/json" "fmt" "log" "net" "net/http" "net/http/httputil" "os" "slices" "time" "github.com/coreos/go-oidc/v3/oidc" "golang.org/x/oauth2" ) // generateSessionID creates a random session identifier func generateSessionID() string { b := make([]byte, 16) if _, err := rand.Read(b); err != nil { // Fallback to timestamp if random fails return fmt.Sprintf("%d", time.Now().UnixNano()) } return hex.EncodeToString(b) } // buildScopes constructs a scope list from base scopes and cross-client IDs func buildScopes(baseScopes []string, crossClients []string) []string { scopes := make([]string, len(baseScopes)) copy(scopes, baseScopes) // Add audience scopes for cross-client authorization for _, client := range crossClients { if client != "" { scopes = append(scopes, "audience:server:client_id:"+client) } } return uniqueStrings(scopes) } func (a *app) oauth2Config(scopes []string) *oauth2.Config { return &oauth2.Config{ ClientID: a.clientID, ClientSecret: a.clientSecret, Endpoint: a.provider.Endpoint(), Scopes: scopes, RedirectURL: a.redirectURI, } } func uniqueStrings(values []string) []string { slices.Sort(values) values = slices.Compact(values) return values } // return an HTTP client which trusts the provided root CAs. func httpClientForRootCAs(rootCAs string) (*http.Client, error) { tlsConfig := tls.Config{RootCAs: x509.NewCertPool()} rootCABytes, err := os.ReadFile(rootCAs) if err != nil { return nil, fmt.Errorf("failed to read root-ca: %v", err) } if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCABytes) { return nil, fmt.Errorf("no certs found in root CA file %q", rootCAs) } return &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tlsConfig, Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).Dial, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, }, }, nil } type debugTransport struct { t http.RoundTripper } func (d debugTransport) RoundTrip(req *http.Request) (*http.Response, error) { reqDump, err := httputil.DumpRequest(req, true) if err != nil { return nil, err } log.Printf("%s", reqDump) resp, err := d.t.RoundTrip(req) if err != nil { return nil, err } respDump, err := httputil.DumpResponse(resp, true) if err != nil { resp.Body.Close() return nil, err } log.Printf("%s", respDump) return resp, nil } func encodeToken(idToken *oidc.IDToken) (string, error) { var claims json.RawMessage if err := idToken.Claims(&claims); err != nil { return "", fmt.Errorf("error decoding ID token claims: %v", err) } buff := new(bytes.Buffer) if err := json.Indent(buff, claims, "", " "); err != nil { return "", fmt.Errorf("error indenting ID token claims: %v", err) } return buff.String(), nil } func parseAndRenderToken(w http.ResponseWriter, r *http.Request, a *app, token *oauth2.Token) { rawIDToken, ok := token.Extra("id_token").(string) if !ok { http.Error(w, "no id_token in token response", http.StatusInternalServerError) return } idToken, err := a.verifier.Verify(r.Context(), rawIDToken) if err != nil { http.Error(w, fmt.Sprintf("failed to verify ID token: %v", err), http.StatusInternalServerError) return } accessToken, ok := token.Extra("access_token").(string) if !ok { accessToken = token.AccessToken if accessToken == "" { http.Error(w, "no access_token in token response", http.StatusInternalServerError) return } } buf, err := encodeToken(idToken) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } renderToken(w, r.Context(), a.provider, a.redirectURI, rawIDToken, accessToken, token.RefreshToken, buf) } ================================================ FILE: examples/go.mod ================================================ module github.com/dexidp/dex/examples go 1.25.0 require ( github.com/coreos/go-oidc/v3 v3.17.0 github.com/dexidp/dex/api/v2 v2.4.0 github.com/spf13/cobra v1.10.2 golang.org/x/oauth2 v0.36.0 google.golang.org/grpc v1.79.3 ) require ( github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/spf13/pflag v1.0.9 // indirect golang.org/x/net v0.48.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/protobuf v1.36.10 // indirect ) ================================================ FILE: examples/go.sum ================================================ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/dexidp/dex/api/v2 v2.4.0 h1:gNba7n6BKVp8X4Jp24cxYn5rIIGhM6kDOXcZoL6tr9A= github.com/dexidp/dex/api/v2 v2.4.0/go.mod h1:/p550ADvFFh7K95VmhUD+jgm15VdaNnab9td8DHOpyI= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= ================================================ FILE: examples/grpc-client/.gitignore ================================================ *.key *.crt *.csr index.* serial* ================================================ FILE: examples/grpc-client/README.md ================================================ # Running a Dex gRPC client 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/]. ## Generating Credentials 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`. ``` # Used to set certificate subject alt names. export SAN=IP.1:127.0.0.1 # Run the script ./examples/grpc-client/cert-gen ``` To verify that the server and client certificates were signed by the CA, run the following commands: ``` openssl verify -CAfile ca.crt server.crt openssl verify -CAfile ca.crt client.crt ``` ## Running the Dex server To expose the gRPC service, the gRPC option must be enabled via the dex config file as shown below. ```yaml # Enables the gRPC API. grpc: addr: 127.0.0.1:5557 tlsCert: server.crt tlsKey: server.key ``` Start an instance of the dex server with an in-memory data store: ``` ./bin/dex serve examples/grpc-client/config.yaml ``` ## Running the Dex client Finally run the Dex client providing the CA certificate, client certificate and client key as arguments. ``` ./bin/grpc-client -ca-crt=ca.crt -client-crt=client.crt -client-key=client.key ``` Running the gRPC client will cause the following API calls to be made to the server 1. CreatePassword 2. ListPasswords 3. VerifyPassword 4. DeletePassword 5. CreateClient 6. ListClients 7. DeleteClient ## Cleaning up Run the following command to destroy all the credentials files that were created by the `cert-gen` script: ``` ./examples/grpc-client/cert-destroy ``` [api-proto]: ../../api/api.proto [client]: client.go ================================================ FILE: examples/grpc-client/cert-destroy ================================================ #!/bin/bash rm -f ca.key ca.crt server.key server.csr server.crt client.key client.csr client.crt index.* serial* rm -rf certs crl newcerts ================================================ FILE: examples/grpc-client/cert-gen ================================================ #!/bin/bash if [ -z $SAN ] 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)." exit 1 fi echo "Creating CA, server cert/key, and client cert/key..." # Creating basic files/directories mkdir -p {certs,crl,newcerts} touch index.txt echo 1000 > serial # CA private key (unencrypted) openssl genrsa -out ca.key 4096 # Certificate Authority (self-signed certificate) 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" # Server private key (unencrypted) openssl genrsa -out server.key 2048 # Server certificate signing request (CSR) openssl req -config examples/grpc-client/openssl.conf -new -sha256 -key server.key -out server.csr -subj "/CN=fake-server" # Certificate Authority signs CSR to grant a certificate 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 # Client private key (unencrypted) openssl genrsa -out client.key 2048 # Signed client certificate signing request (CSR) openssl req -config examples/grpc-client/openssl.conf -new -sha256 -key client.key -out client.csr -subj "/CN=fake-client" # Certificate Authority signs CSR to grant a certificate 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 # Remove CSR's rm *.csr ================================================ FILE: examples/grpc-client/client.go ================================================ package main import ( "context" "crypto/tls" "crypto/x509" "flag" "fmt" "log" "os" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "github.com/dexidp/dex/api/v2" ) func newDexClient(hostAndPort, caPath, clientCrt, clientKey string) (api.DexClient, error) { cPool := x509.NewCertPool() caCert, err := os.ReadFile(caPath) if err != nil { return nil, fmt.Errorf("invalid CA crt file: %s", caPath) } if cPool.AppendCertsFromPEM(caCert) != true { return nil, fmt.Errorf("failed to parse CA crt") } clientCert, err := tls.LoadX509KeyPair(clientCrt, clientKey) if err != nil { return nil, fmt.Errorf("invalid client crt file: %s", caPath) } clientTLSConfig := &tls.Config{ RootCAs: cPool, Certificates: []tls.Certificate{clientCert}, } creds := credentials.NewTLS(clientTLSConfig) conn, err := grpc.Dial(hostAndPort, grpc.WithTransportCredentials(creds)) if err != nil { return nil, fmt.Errorf("dial: %v", err) } return api.NewDexClient(conn), nil } func createPassword(cli api.DexClient) error { p := api.Password{ Email: "test@example.com", // bcrypt hash of the value "test1" with cost 10 Hash: []byte("$2a$10$XVMN/Fid.Ks4CXgzo8fpR.iU1khOMsP5g9xQeXuBm1wXjRX8pjUtO"), Username: "test", UserId: "test", } createReq := &api.CreatePasswordReq{ Password: &p, } // Create password. if resp, err := cli.CreatePassword(context.TODO(), createReq); err != nil || resp.AlreadyExists { if resp != nil && resp.AlreadyExists { return fmt.Errorf("Password %s already exists", createReq.Password.Email) } return fmt.Errorf("failed to create password: %v", err) } log.Printf("Created password with email %s", createReq.Password.Email) // List all passwords. resp, err := cli.ListPasswords(context.TODO(), &api.ListPasswordReq{}) if err != nil { return fmt.Errorf("failed to list password: %v", err) } log.Print("Listing Passwords:\n") for _, pass := range resp.Passwords { log.Printf("%+v", pass) } // Verifying correct and incorrect passwords log.Print("Verifying Password:\n") verifyReq := &api.VerifyPasswordReq{ Email: "test@example.com", Password: "test1", } verifyResp, err := cli.VerifyPassword(context.TODO(), verifyReq) if err != nil { return fmt.Errorf("failed to run VerifyPassword for correct password: %v", err) } if !verifyResp.Verified { return fmt.Errorf("failed to verify correct password: %v", verifyResp) } log.Printf("properly verified correct password: %t\n", verifyResp.Verified) badVerifyReq := &api.VerifyPasswordReq{ Email: "test@example.com", Password: "wrong_password", } badVerifyResp, err := cli.VerifyPassword(context.TODO(), badVerifyReq) if err != nil { return fmt.Errorf("failed to run VerifyPassword for incorrect password: %v", err) } if badVerifyResp.Verified { return fmt.Errorf("verify returned true for incorrect password: %v", badVerifyResp) } log.Printf("properly failed to verify incorrect password: %t\n", badVerifyResp.Verified) log.Print("Listing Passwords:\n") for _, pass := range resp.Passwords { log.Printf("%+v", pass) } deleteReq := &api.DeletePasswordReq{ Email: p.Email, } // Delete password with email = test@example.com. if resp, err := cli.DeletePassword(context.TODO(), deleteReq); err != nil || resp.NotFound { if resp != nil && resp.NotFound { return fmt.Errorf("Password %s not found", deleteReq.Email) } return fmt.Errorf("failed to delete password: %v", err) } log.Printf("Deleted password with email %s", deleteReq.Email) return nil } func createAndListClients(cli api.DexClient) error { client := &api.Client{ Id: "example-client", Secret: "example-secret", RedirectUris: []string{"http://localhost:8080/callback"}, TrustedPeers: []string{}, Public: false, Name: "Example Client", LogoUrl: "http://example.com/logo.png", } createReq := &api.CreateClientReq{ Client: client, } if resp, err := cli.CreateClient(context.TODO(), createReq); err != nil || resp.AlreadyExists { if resp != nil && resp.AlreadyExists { log.Printf("Client %s already exists", createReq.Client.Id) } else { return fmt.Errorf("failed to create client: %v", err) } } else { log.Printf("Created client with ID %s", createReq.Client.Id) } listResp, err := cli.ListClients(context.TODO(), &api.ListClientReq{}) if err != nil { return fmt.Errorf("failed to list clients: %v", err) } log.Print("Listing Clients:\n") for _, client := range listResp.Clients { log.Printf("ID: %s, Name: %s, Public: %t, RedirectURIs: %v", client.Id, client.Name, client.Public, client.RedirectUris) } deleteReq := &api.DeleteClientReq{ Id: client.Id, } if resp, err := cli.DeleteClient(context.TODO(), deleteReq); err != nil || resp.NotFound { if resp != nil && resp.NotFound { return fmt.Errorf("Client %s not found", deleteReq.Id) } return fmt.Errorf("failed to delete client: %v", err) } log.Printf("Deleted client with ID %s", deleteReq.Id) return nil } func main() { caCrt := flag.String("ca-crt", "", "CA certificate") clientCrt := flag.String("client-crt", "", "Client certificate") clientKey := flag.String("client-key", "", "Client key") flag.Parse() if *clientCrt == "" || *caCrt == "" || *clientKey == "" { log.Fatal("Please provide CA & client certificates and client key. Usage: ./client --ca-crt= --client-crt= --client-key=") } client, err := newDexClient("127.0.0.1:5557", *caCrt, *clientCrt, *clientKey) if err != nil { log.Fatalf("failed creating dex client: %v ", err) } if err := createPassword(client); err != nil { log.Fatalf("testPassword failed: %v", err) } if err := createAndListClients(client); err != nil { log.Fatalf("testClients failed: %v", err) } } ================================================ FILE: examples/grpc-client/config.yaml ================================================ issuer: http://127.0.0.1:5556/dex storage: type: sqlite3 config: file: examples/dex.db # Configuration for the HTTP endpoints. web: http: 0.0.0.0:5556 grpc: addr: 127.0.0.1:5557 tlsCert: server.crt tlsKey: server.key tlsClientCA: ca.crt connectors: - type: mockCallback id: mock name: Example # Let dex keep a list of passwords which can be used to login to dex. enablePasswordDB: true ================================================ FILE: examples/grpc-client/openssl.conf ================================================ # OpenSSL configuration file. # Adapted from https://github.com/coreos/matchbox/blob/master/examples/etc/matchbox/openssl.conf # default environment variable values SAN = [ ca ] # `man ca` default_ca = CA_default [ CA_default ] # Directory and file locations. dir = . certs = $dir/certs crl_dir = $dir/crl new_certs_dir = $dir/newcerts database = $dir/index.txt serial = $dir/serial # certificate revocation lists. crlnumber = $dir/crlnumber crl = $dir/crl/intermediate-ca.crl crl_extensions = crl_ext default_crl_days = 30 default_md = sha256 name_opt = ca_default cert_opt = ca_default default_days = 375 preserve = no policy = policy_loose [ policy_loose ] # Allow the CA to sign a range of certificates. countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] # `man req` default_bits = 4096 distinguished_name = req_distinguished_name string_mask = utf8only default_md = sha256 [ req_distinguished_name ] countryName = Country Name (2 letter code) stateOrProvinceName = State or Province Name localityName = Locality Name 0.organizationName = Organization Name organizationalUnitName = Organizational Unit Name commonName = Common Name # Certificate extensions (`man x509v3_config`) [ v3_ca ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true, pathlen:0 keyUsage = critical, digitalSignature, cRLSign, keyCertSign [ usr_cert ] basicConstraints = CA:FALSE nsCertType = client nsComment = "OpenSSL Generated Client Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = clientAuth [ server_cert ] basicConstraints = CA:FALSE nsCertType = server nsComment = "OpenSSL Generated Server Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth subjectAltName = $ENV::SAN ================================================ FILE: examples/k8s/.gitignore ================================================ ssl/ ================================================ FILE: examples/k8s/dex.yaml ================================================ --- apiVersion: v1 kind: Namespace metadata: name: dex --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: dex name: dex namespace: dex spec: replicas: 3 selector: matchLabels: app: dex template: metadata: labels: app: dex spec: serviceAccountName: dex # This is created below containers: - image: ghcr.io/dexidp/dex:v2.32.0 name: dex command: ["/usr/local/bin/dex", "serve", "/etc/dex/cfg/config.yaml"] ports: - name: https containerPort: 5556 volumeMounts: - name: config mountPath: /etc/dex/cfg - name: tls mountPath: /etc/dex/tls env: - name: GITHUB_CLIENT_ID valueFrom: secretKeyRef: name: github-client key: client-id - name: GITHUB_CLIENT_SECRET valueFrom: secretKeyRef: name: github-client key: client-secret readinessProbe: httpGet: path: /healthz port: 5556 scheme: HTTPS volumes: - name: config configMap: name: dex items: - key: config.yaml path: config.yaml - name: tls secret: secretName: dex.example.com.tls --- kind: ConfigMap apiVersion: v1 metadata: name: dex namespace: dex data: config.yaml: | issuer: https://dex.example.com:32000 storage: type: kubernetes config: inCluster: true web: https: 0.0.0.0:5556 tlsCert: /etc/dex/tls/tls.crt tlsKey: /etc/dex/tls/tls.key connectors: - type: github id: github name: GitHub config: clientID: $GITHUB_CLIENT_ID clientSecret: $GITHUB_CLIENT_SECRET redirectURI: https://dex.example.com:32000/callback org: kubernetes oauth2: skipApprovalScreen: true staticClients: - id: example-app redirectURIs: - 'http://127.0.0.1:5555/callback' name: 'Example App' secret: ZXhhbXBsZS1hcHAtc2VjcmV0 enablePasswordDB: true staticPasswords: - email: "admin@example.com" # bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2) hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" username: "admin" name: "Admin User" emailVerified: true preferredUsername: "admin" groups: - "team-a" - "team-a/admins" userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" --- apiVersion: v1 kind: Service metadata: name: dex namespace: dex spec: type: NodePort ports: - name: dex port: 5556 protocol: TCP targetPort: 5556 nodePort: 32000 selector: app: dex --- apiVersion: v1 kind: ServiceAccount metadata: labels: app: dex name: dex namespace: dex --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: dex rules: - apiGroups: ["dex.coreos.com"] # API group created by dex resources: ["*"] verbs: ["*"] - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["create"] # To manage its own resources, dex must be able to create customresourcedefinitions --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: dex roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: dex subjects: - kind: ServiceAccount name: dex # Service account assigned to the dex pod, created above namespace: dex # The namespace dex is running in ================================================ FILE: examples/k8s/gencert.sh ================================================ #!/bin/bash mkdir -p ssl cat << EOF > ssl/req.cnf [req] req_extensions = v3_req distinguished_name = req_distinguished_name [req_distinguished_name] [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names [alt_names] DNS.1 = dex.example.com EOF openssl genrsa -out ssl/ca-key.pem 2048 openssl req -x509 -new -nodes -key ssl/ca-key.pem -days 10 -out ssl/ca.pem -subj "/CN=kube-ca" openssl genrsa -out ssl/key.pem 2048 openssl req -new -key ssl/key.pem -out ssl/csr.pem -subj "/CN=kube-ca" -config ssl/req.cnf 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 ================================================ FILE: examples/ldap/config-ldap.ldif ================================================ # Already included in default config of Docker image osixia/openldap:1.4.0. # # dn: dc=example,dc=org # objectClass: dcObject # objectClass: organization # o: Example Company # dc: example dn: ou=People,dc=example,dc=org objectClass: organizationalUnit ou: People dn: cn=jane,ou=People,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: jane mail: janedoe@example.com userpassword: foo dn: cn=john,ou=People,dc=example,dc=org objectClass: person objectClass: inetOrgPerson sn: doe cn: john mail: johndoe@example.com userpassword: bar # Group definitions. dn: ou=Groups,dc=example,dc=org objectClass: organizationalUnit ou: Groups dn: cn=admins,ou=Groups,dc=example,dc=org objectClass: groupOfNames cn: admins member: cn=john,ou=People,dc=example,dc=org member: cn=jane,ou=People,dc=example,dc=org dn: cn=developers,ou=Groups,dc=example,dc=org objectClass: groupOfNames cn: developers member: cn=jane,ou=People,dc=example,dc=org ================================================ FILE: examples/ldap/config-ldap.yaml ================================================ issuer: http://127.0.0.1:5556/dex storage: type: sqlite3 config: file: examples/dex.db web: http: 0.0.0.0:5556 connectors: - type: ldap name: OpenLDAP id: ldap config: # The following configurations seem to work with OpenLDAP: # # 1) Plain LDAP, without TLS: host: localhost:389 insecureNoSSL: true # # 2) LDAPS without certificate validation: #host: localhost:636 #insecureNoSSL: false #insecureSkipVerify: true # # 3) LDAPS with certificate validation: #host: YOUR-HOSTNAME:636 #insecureNoSSL: false #insecureSkipVerify: false #rootCAData: 'CERT' # ...where CERT="$( base64 -w 0 your-cert.crt )" # This would normally be a read-only user. bindDN: cn=admin,dc=example,dc=org bindPW: admin usernamePrompt: Email Address userSearch: baseDN: ou=People,dc=example,dc=org filter: "(objectClass=person)" username: mail # "DN" (case sensitive) is a special attribute name. It indicates that # this value should be taken from the entity's DN not an attribute on # the entity. idAttr: DN emailAttr: mail nameAttr: cn groupSearch: baseDN: ou=Groups,dc=example,dc=org filter: "(objectClass=groupOfNames)" userMatchers: # A user is a member of a group when their DN matches # the value of a "member" attribute on the group entity. - userAttr: DN groupAttr: member # The group name should be the "cn" value. nameAttr: cn staticClients: - id: example-app redirectURIs: - 'http://127.0.0.1:5555/callback' name: 'Example App' secret: ZXhhbXBsZS1hcHAtc2VjcmV0 ================================================ FILE: examples/ldap/docker-compose.yaml ================================================ version: "3" # For LDAPS with certificate validation: # How to extract the TLS certificate from the OpenLDAP container, and encode it for the Dex config (`rootCAData`): # $ docker-compose exec ldap cat /container/run/service/slapd/assets/certs/ca.crt | base64 -w 0 # But note this issue: https://github.com/osixia/docker-openldap/issues/506 services: ldap: image: osixia/openldap:1.4.0 # Copying is required because the entrypoint modifies the *.ldif files. # For verbose output, use: command: ["--copy-service", "--loglevel", "debug"] command: ["--copy-service"] environment: # Required if using LDAPS: # Since Dex doesn't use a client TLS certificate, downgrade from "demand" to "try". LDAP_TLS_VERIFY_CLIENT: try # The hostname is required if using LDAPS with certificate validation. # In Dex, use the same hostname (with port) for `connectors[].config.host`. #hostname: YOUR-HOSTNAME # # https://github.com/osixia/docker-openldap#seed-ldap-database-with-ldif # Option 1: Add custom seed file -> mount to /container/service/slapd/assets/config/bootstrap/ldif/custom/ # Option 2: Overwrite default seed file -> mount to /container/service/slapd/assets/config/bootstrap/ldif/ volumes: - ./config-ldap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/config-ldap.ldif ports: - 389:389 - 636:636 ================================================ FILE: examples/oidc-conformance/config.yaml.tmpl ================================================ # Dex configuration for OIDC Conformance Testing. # See https://dexidp.io/docs/development/oidc-certification/ # # This template is processed by run.sh which replaces ISSUER_URL and ALIAS # with actual values before starting Dex. issuer: ISSUER_URL/dex storage: type: sqlite3 config: file: examples/oidc-conformance/dex.db web: http: 0.0.0.0:5556 enablePasswordDB: true staticPasswords: - email: "admin@example.com" # bcrypt hash of the string "password" hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" username: "admin" staticClients: - id: first_client secret: 89d6205220381728e85c4cf5 redirectURIs: - https://www.certification.openid.net/test/a/ALIAS/callback name: First client - id: second_client secret: 51c612288018fd384b05d6ad redirectURIs: - https://www.certification.openid.net/test/a/ALIAS/callback name: Second client ================================================ FILE: examples/oidc-conformance/run.sh ================================================ #!/usr/bin/env bash # # OIDC Conformance Test Runner # # Starts Dex with a test configuration and exposes it via a public tunnel # for use with https://www.certification.openid.net/ # # Usage: # ./run.sh # uses cloudflared (default) # ./run.sh --tunnel ngrok # uses ngrok # ./run.sh --url https://my.url # uses a pre-existing public URL (no tunnel) # ./run.sh --alias my-dex # custom alias for the test plan (default: dex) # # Prerequisites: # - Dex binary in PATH or ../../bin/dex # - ngrok or cloudflared installed (unless --url is provided) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" DEX_PORT=5556 TUNNEL_TYPE="cloudflared" PUBLIC_URL="" ALIAS="dex" while [[ $# -gt 0 ]]; do case $1 in --tunnel) TUNNEL_TYPE="$2"; shift 2 ;; --url) PUBLIC_URL="$2"; shift 2 ;; --alias) ALIAS="$2"; shift 2 ;; -h|--help) sed -n '2,/^$/p' "$0" | sed 's/^# \?//' exit 0 ;; *) echo "Unknown option: $1"; exit 1 ;; esac done # Find dex binary. DEX_BIN="" for candidate in "dex" "$ROOT_DIR/bin/dex"; do if command -v "$candidate" &>/dev/null || [[ -x "$candidate" ]]; then DEX_BIN="$candidate" break fi done if [[ -z "$DEX_BIN" ]]; then echo "Error: dex binary not found. Run 'make build' first or install dex." exit 1 fi cleanup() { echo "" echo "Shutting down..." kill "${TUNNEL_PID:-}" "${DEX_PID:-}" 2>/dev/null || true rm -f "${CONFIG_FILE:-}" wait 2>/dev/null } trap cleanup EXIT # Start tunnel if no URL provided. TUNNEL_PID="" if [[ -z "$PUBLIC_URL" ]]; then case "$TUNNEL_TYPE" in ngrok) if ! command -v ngrok &>/dev/null; then echo "Error: ngrok not found. Install it from https://ngrok.com/ or use --url." exit 1 fi ngrok http "$DEX_PORT" --log=stdout --log-level=warn &>/dev/null & TUNNEL_PID=$! echo "Waiting for ngrok tunnel..." sleep 3 PUBLIC_URL=$(curl -s http://localhost:4040/api/tunnels | grep -o '"public_url":"https://[^"]*' | head -1 | cut -d'"' -f4) if [[ -z "$PUBLIC_URL" ]]; then echo "Error: failed to get ngrok public URL. Is ngrok running?" exit 1 fi ;; cloudflared) if ! command -v cloudflared &>/dev/null; then echo "Error: cloudflared not found. Install it from https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/" exit 1 fi CLOUDFLARED_LOG=$(mktemp) cloudflared tunnel --url "http://localhost:$DEX_PORT" --no-autoupdate 2>"$CLOUDFLARED_LOG" & TUNNEL_PID=$! echo "Waiting for cloudflared tunnel..." for _ in $(seq 1 30); do PUBLIC_URL=$(grep -o 'https://[^ ]*\.trycloudflare\.com' "$CLOUDFLARED_LOG" | head -1) && break sleep 1 done rm -f "$CLOUDFLARED_LOG" if [[ -z "$PUBLIC_URL" ]]; then echo "Error: failed to get cloudflared URL." exit 1 fi ;; *) echo "Error: unknown tunnel type '$TUNNEL_TYPE'. Use 'ngrok' or 'cloudflared'." exit 1 ;; esac fi PUBLIC_URL="${PUBLIC_URL%/}" echo "Public URL: $PUBLIC_URL" # Generate config from template. CONFIG_FILE=$(mktemp) sed -e "s|ISSUER_URL|$PUBLIC_URL|g" -e "s|ALIAS|$ALIAS|g" "$SCRIPT_DIR/config.yaml.tmpl" > "$CONFIG_FILE" echo "Starting Dex on port $DEX_PORT..." "$DEX_BIN" serve "$CONFIG_FILE" & DEX_PID=$! sleep 2 DISCOVERY_URL="$PUBLIC_URL/dex/.well-known/openid-configuration" echo "" echo "============================================================" echo " OIDC Conformance Test Setup Ready" echo "============================================================" echo "" echo " Discovery URL: $DISCOVERY_URL" echo " Alias: $ALIAS" echo "" echo " Client 1: id=first_client secret=89d6205220381728e85c4cf5" echo " Client 2: id=second_client secret=51c612288018fd384b05d6ad" echo "" echo " Steps:" echo " 1. Open https://www.certification.openid.net/" echo " 2. Log in with Google or GitLab" echo " 3. Create a new test plan:" echo " - Plan: OpenID Connect Core: Basic Certification Profile" echo " - Server metadata: discovery" echo " - Client registration: static_client" echo " - Alias: $ALIAS" echo " - Discovery URL: $DISCOVERY_URL" echo " - Enter both client credentials above" echo " 4. Run tests and follow instructions" echo "" echo " Press Ctrl+C to stop." echo "============================================================" wait "$DEX_PID" ================================================ FILE: flake.nix ================================================ { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; devenv.url = "github:cachix/devenv"; }; outputs = inputs@{ flake-parts, ... }: flake-parts.lib.mkFlake { inherit inputs; } { imports = [ inputs.devenv.flakeModule ]; systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" "aarch64-linux" ]; perSystem = { pkgs, ... }: rec { devenv.shells = { default = { languages = { go = { enable = true; package = pkgs.go_1_25; }; }; packages = with pkgs; [ gnumake # golangci-lint (golangci-lint.override (o: { buildGoModule = pkgs.buildGo125Module; })) gotestsum protobuf protoc-gen-go protoc-gen-go-grpc kind ]; # https://github.com/cachix/devenv/issues/528#issuecomment-1556108767 containers = pkgs.lib.mkForce { }; }; ci = devenv.shells.default; }; }; }; } ================================================ FILE: go.mod ================================================ module github.com/dexidp/dex go 1.25.0 require ( cloud.google.com/go/compute/metadata v0.9.0 entgo.io/ent v0.14.5 github.com/AppsFlyer/go-sundheit v0.6.0 github.com/Masterminds/semver v1.5.0 github.com/Masterminds/sprig/v3 v3.3.0 github.com/beevik/etree v1.6.0 github.com/coreos/go-oidc/v3 v3.17.0 github.com/dexidp/dex/api/v2 v2.4.0 github.com/fsnotify/fsnotify v1.9.0 github.com/ghodss/yaml v1.0.0 github.com/go-jose/go-jose/v4 v4.1.3 github.com/go-ldap/ldap/v3 v3.4.13 github.com/go-sql-driver/mysql v1.9.3 github.com/google/cel-go v0.27.0 github.com/google/uuid v1.6.0 github.com/gorilla/handlers v1.5.2 github.com/gorilla/mux v1.8.1 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/kylelemons/godebug v1.1.0 github.com/lib/pq v1.12.0 github.com/mattermost/xml-roundtrip-validator v0.1.0 github.com/mattn/go-sqlite3 v1.14.37 github.com/oklog/run v1.2.0 github.com/openbao/openbao/api/v2 v2.5.1 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.5.0 github.com/prometheus/client_golang v1.23.2 github.com/russellhaering/goxmldsig v1.6.0 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 go.etcd.io/etcd/client/pkg/v3 v3.6.8 go.etcd.io/etcd/client/v3 v3.6.8 golang.org/x/crypto v0.49.0 golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 golang.org/x/net v0.52.0 golang.org/x/oauth2 v0.36.0 google.golang.org/api v0.272.0 google.golang.org/grpc v1.79.3 google.golang.org/protobuf v1.36.11 ) require ( ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect cel.dev/expr v0.25.1 // indirect cloud.google.com/go/auth v0.18.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect dario.cat/mergo v1.0.1 // indirect filippo.io/edwards25519 v1.1.1 // indirect github.com/Azure/go-ntlmssp v0.1.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/inflect v0.19.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect github.com/googleapis/gax-go/v2 v2.18.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.7 // indirect github.com/hashicorp/hcl v1.0.1-vault-7 // indirect github.com/hashicorp/hcl/v2 v2.18.1 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/zclconf/go-cty v1.14.4 // indirect github.com/zclconf/go-cty-yaml v1.1.0 // indirect go.etcd.io/etcd/api/v3 v3.6.8 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.42.0 // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/dexidp/dex/api/v2 => ./api/v2 tool entgo.io/ent/cmd/ent ================================================ FILE: go.sum ================================================ ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 h1:E0wvcUXTkgyN4wy4LGtNzMNGMytJN8afmIWXJVMi4cc= ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w= cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= entgo.io/ent v0.14.5 h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4= entgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U= filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw= filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AppsFlyer/go-sundheit v0.6.0 h1:d2hBvCjBSb2lUsEWGfPigr4MCOt04sxB+Rppl0yUMSk= github.com/AppsFlyer/go-sundheit v0.6.0/go.mod h1:LDdBHD6tQBtmHsdW+i1GwdTt6Wqc0qazf5ZEJVTbTME= github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A= github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/beevik/etree v1.6.0 h1:u8Kwy8pp9D9XeITj2Z0XtA5qqZEmtJtuXZRQi+j03eE= github.com/beevik/etree v1.6.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ= github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= github.com/googleapis/gax-go/v2 v2.18.0 h1:jxP5Uuo3bxm3M6gGtV94P4lliVetoCB4Wk2x8QA86LI= github.com/googleapis/gax-go/v2 v2.18.0/go.mod h1:uSzZN4a356eRG985CzJ3WfbFSpqkLTjsnhWGJR6EwrE= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo= github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg= github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/openbao/openbao/api/v2 v2.5.1 h1:Br79D6L20SbAa5P7xqENxmvv8LyI4HoKosPy7klhn4o= github.com/openbao/openbao/api/v2 v2.5.1/go.mod h1:Dh5un77tqGgMbmlVEqjqN+8/dMyUohnkaQVg/wXW0Ig= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russellhaering/goxmldsig v1.6.0 h1:8fdWXEPh2k/NZNQBPFNoVfS3JmzS4ZprY/sAOpKQLks= github.com/russellhaering/goxmldsig v1.6.0/go.mod h1:TrnaquDcYxWXfJrOjeMBTX4mLBeYAqaHEyUeWPxZlBM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0= github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= go.etcd.io/etcd/api/v3 v3.6.8 h1:gqb1VN92TAI6G2FiBvWcqKtHiIjr4SU2GdXxTwyexbM= go.etcd.io/etcd/api/v3 v3.6.8/go.mod h1:qyQj1HZPUV3B5cbAL8scG62+fyz5dSxxu0w8pn28N6Q= go.etcd.io/etcd/client/pkg/v3 v3.6.8 h1:Qs/5C0LNFiqXxYf2GU8MVjYUEXJ6sZaYOz0zEqQgy50= go.etcd.io/etcd/client/pkg/v3 v3.6.8/go.mod h1:GsiTRUZE2318PggZkAo6sWb6l8JLVrnckTNfbG8PWtw= go.etcd.io/etcd/client/v3 v3.6.8 h1:B3G76t1UykqAOrbio7s/EPatixQDkQBevN8/mwiplrY= go.etcd.io/etcd/client/v3 v3.6.8/go.mod h1:MVG4BpSIuumPi+ELF7wYtySETmoTWBHVcDoHdVupwt8= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA= google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA= google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d h1:vsOm753cOAMkt76efriTCDKjpCbK18XGHMJHo0JUKhc= google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:0oz9d7g9QLSdv9/lgbIjowW1JoxMbxmBVNe8i6tORJI= google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d h1:EocjzKLywydp5uZ5tJ79iP6Q0UjDnyiHkGRWxuPBP8s= google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w= google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: pkg/cel/cel.go ================================================ package cel import ( "context" "fmt" "reflect" "github.com/google/cel-go/cel" "github.com/google/cel-go/checker" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/ext" "github.com/dexidp/dex/pkg/cel/library" ) // EnvironmentVersion represents the version of the CEL environment. // New variables, functions, or libraries are introduced in new versions. type EnvironmentVersion uint32 const ( // EnvironmentV1 is the initial CEL environment. EnvironmentV1 EnvironmentVersion = 1 ) // CompilationResult holds a compiled CEL program ready for evaluation. type CompilationResult struct { Program cel.Program OutputType *cel.Type Expression string ast *cel.Ast } // CompilerOption configures a Compiler. type CompilerOption func(*compilerConfig) type compilerConfig struct { costBudget uint64 version EnvironmentVersion } func defaultCompilerConfig() *compilerConfig { return &compilerConfig{ costBudget: DefaultCostBudget, version: EnvironmentV1, } } // WithCostBudget sets a custom cost budget for expression evaluation. func WithCostBudget(budget uint64) CompilerOption { return func(cfg *compilerConfig) { cfg.costBudget = budget } } // WithVersion sets the target environment version for the compiler. // Defaults to the latest version. Specifying an older version ensures // that only functions/types available at that version are used. func WithVersion(v EnvironmentVersion) CompilerOption { return func(cfg *compilerConfig) { cfg.version = v } } // Compiler compiles CEL expressions against a specific environment. type Compiler struct { env *cel.Env cfg *compilerConfig } // NewCompiler creates a new CEL compiler with the specified variable // declarations and options. // // All custom Dex libraries are automatically included. // The environment is configured with cost limits and safe defaults. func NewCompiler(variables []VariableDeclaration, opts ...CompilerOption) (*Compiler, error) { cfg := defaultCompilerConfig() for _, opt := range opts { opt(cfg) } envOpts := make([]cel.EnvOption, 0, 8+len(variables)) envOpts = append(envOpts, cel.DefaultUTCTimeZone(true), // Standard extension libraries (same set as Kubernetes) ext.Strings(), ext.Encoders(), ext.Lists(), ext.Sets(), ext.Math(), // Native Go types for typed variable access. // This gives compile-time field checking: identity.emial → error at config load. ext.NativeTypes( ext.ParseStructTags(true), reflect.TypeOf(IdentityVal{}), reflect.TypeOf(RequestVal{}), ), // Custom Dex libraries cel.Lib(&library.Email{}), cel.Lib(&library.Groups{}), // Presence tests like has(field) and 'key' in map are O(1) hash // lookups on map(string, dyn) variables, so they should not count // toward the cost budget. Without this, expressions with multiple // 'in' checks (e.g. "'admin' in identity.groups") would accumulate // inflated cost estimates. This matches Kubernetes CEL behavior // where presence tests are free for CRD validation rules. cel.CostEstimatorOptions( checker.PresenceTestHasCost(false), ), ) for _, v := range variables { envOpts = append(envOpts, cel.Variable(v.Name, v.Type)) } env, err := cel.NewEnv(envOpts...) if err != nil { return nil, fmt.Errorf("failed to create CEL environment: %w", err) } return &Compiler{env: env, cfg: cfg}, nil } // CompileBool compiles a CEL expression that must evaluate to bool. func (c *Compiler) CompileBool(expression string) (*CompilationResult, error) { return c.compile(expression, cel.BoolType) } // CompileString compiles a CEL expression that must evaluate to string. func (c *Compiler) CompileString(expression string) (*CompilationResult, error) { return c.compile(expression, cel.StringType) } // CompileStringList compiles a CEL expression that must evaluate to list(string). func (c *Compiler) CompileStringList(expression string) (*CompilationResult, error) { return c.compile(expression, cel.ListType(cel.StringType)) } // Compile compiles a CEL expression with any output type. func (c *Compiler) Compile(expression string) (*CompilationResult, error) { return c.compile(expression, nil) } func (c *Compiler) compile(expression string, expectedType *cel.Type) (*CompilationResult, error) { if len(expression) > MaxExpressionLength { return nil, fmt.Errorf("expression exceeds maximum length of %d characters", MaxExpressionLength) } ast, issues := c.env.Compile(expression) if issues != nil && issues.Err() != nil { return nil, fmt.Errorf("CEL compilation failed: %w", issues.Err()) } if expectedType != nil && !ast.OutputType().IsEquivalentType(expectedType) { return nil, fmt.Errorf( "expected expression output type %s, got %s", expectedType, ast.OutputType(), ) } // Estimate cost at compile time and reject expressions that are too expensive. costEst, err := c.env.EstimateCost(ast, &defaultCostEstimator{}) if err != nil { return nil, fmt.Errorf("CEL cost estimation failed: %w", err) } if costEst.Max > c.cfg.costBudget { return nil, fmt.Errorf( "CEL expression estimated cost %d exceeds budget %d", costEst.Max, c.cfg.costBudget, ) } prog, err := c.env.Program(ast, cel.EvalOptions(cel.OptOptimize), cel.CostLimit(c.cfg.costBudget), ) if err != nil { return nil, fmt.Errorf("CEL program creation failed: %w", err) } return &CompilationResult{ Program: prog, OutputType: ast.OutputType(), Expression: expression, ast: ast, }, nil } // Eval evaluates a compiled program against the given variables. func Eval(ctx context.Context, result *CompilationResult, variables map[string]any) (ref.Val, error) { out, _, err := result.Program.ContextEval(ctx, variables) if err != nil { return nil, fmt.Errorf("CEL evaluation failed: %w", err) } return out, nil } // EvalBool is a convenience function that evaluates and asserts bool output. func EvalBool(ctx context.Context, result *CompilationResult, variables map[string]any) (bool, error) { out, err := Eval(ctx, result, variables) if err != nil { return false, err } v, ok := out.Value().(bool) if !ok { return false, fmt.Errorf("expected bool result, got %T", out.Value()) } return v, nil } // EvalString is a convenience function that evaluates and asserts string output. func EvalString(ctx context.Context, result *CompilationResult, variables map[string]any) (string, error) { out, err := Eval(ctx, result, variables) if err != nil { return "", err } v, ok := out.Value().(string) if !ok { return "", fmt.Errorf("expected string result, got %T", out.Value()) } return v, nil } ================================================ FILE: pkg/cel/cel_test.go ================================================ package cel_test import ( "context" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/dexidp/dex/connector" dexcel "github.com/dexidp/dex/pkg/cel" ) func TestCompileBool(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) tests := map[string]struct { expr string wantErr bool }{ "true literal": { expr: "true", }, "comparison": { expr: "1 == 1", }, "string type mismatch": { expr: "'hello'", wantErr: true, }, "int type mismatch": { expr: "42", wantErr: true, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { result, err := compiler.CompileBool(tc.expr) if tc.wantErr { assert.Error(t, err) assert.Nil(t, result) } else { assert.NoError(t, err) assert.NotNil(t, result) } }) } } func TestCompileString(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) tests := map[string]struct { expr string wantErr bool }{ "string literal": { expr: "'hello'", }, "string concatenation": { expr: "'hello' + ' ' + 'world'", }, "bool type mismatch": { expr: "true", wantErr: true, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { result, err := compiler.CompileString(tc.expr) if tc.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.NotNil(t, result) } }) } } func TestCompileStringList(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) result, err := compiler.CompileStringList("['a', 'b', 'c']") assert.NoError(t, err) assert.NotNil(t, result) _, err = compiler.CompileStringList("'not a list'") assert.Error(t, err) } func TestCompile(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) // Compile accepts any type result, err := compiler.Compile("true") assert.NoError(t, err) assert.NotNil(t, result) result, err = compiler.Compile("'hello'") assert.NoError(t, err) assert.NotNil(t, result) result, err = compiler.Compile("42") assert.NoError(t, err) assert.NotNil(t, result) } func TestCompileErrors(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) tests := map[string]struct { expr string }{ "syntax error": { expr: "1 +", }, "undefined variable": { expr: "undefined_var", }, "undefined function": { expr: "undefinedFunc()", }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { _, err := compiler.Compile(tc.expr) assert.Error(t, err) }) } } func TestCompileRejectsUnknownFields(t *testing.T) { vars := dexcel.IdentityVariables() compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) // Typo in field name: should fail at compile time with ObjectType _, err = compiler.CompileBool("identity.emial == 'test@example.com'") assert.Error(t, err) assert.Contains(t, err.Error(), "compilation failed") // Type mismatch: comparing string field to int should fail at compile time _, err = compiler.CompileBool("identity.email == 123") assert.Error(t, err) assert.Contains(t, err.Error(), "compilation failed") // Valid field: should compile fine _, err = compiler.CompileBool("identity.email == 'test@example.com'") assert.NoError(t, err) } func TestMaxExpressionLength(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) longExpr := "'" + strings.Repeat("a", dexcel.MaxExpressionLength) + "'" _, err = compiler.Compile(longExpr) assert.Error(t, err) assert.Contains(t, err.Error(), "maximum length") } func TestEvalBool(t *testing.T) { vars := dexcel.IdentityVariables() compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) tests := map[string]struct { expr string identity dexcel.IdentityVal want bool }{ "email endsWith": { expr: "identity.email.endsWith('@example.com')", identity: dexcel.IdentityVal{Email: "user@example.com"}, want: true, }, "email endsWith false": { expr: "identity.email.endsWith('@example.com')", identity: dexcel.IdentityVal{Email: "user@other.com"}, want: false, }, "email_verified": { expr: "identity.email_verified == true", identity: dexcel.IdentityVal{EmailVerified: true}, want: true, }, "group membership": { expr: "identity.groups.exists(g, g == 'admin')", identity: dexcel.IdentityVal{Groups: []string{"admin", "dev"}}, want: true, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { prog, err := compiler.CompileBool(tc.expr) require.NoError(t, err) result, err := dexcel.EvalBool(context.Background(), prog, map[string]any{ "identity": tc.identity, }) require.NoError(t, err) assert.Equal(t, tc.want, result) }) } } func TestEvalString(t *testing.T) { vars := dexcel.IdentityVariables() compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) // With ObjectType, identity.email is typed as string, so CompileString works. prog, err := compiler.CompileString("identity.email") require.NoError(t, err) result, err := dexcel.EvalString(context.Background(), prog, map[string]any{ "identity": dexcel.IdentityVal{Email: "user@example.com"}, }) require.NoError(t, err) assert.Equal(t, "user@example.com", result) } func TestEvalWithIdentityAndRequest(t *testing.T) { vars := append(dexcel.IdentityVariables(), dexcel.RequestVariables()...) compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) prog, err := compiler.CompileBool( `identity.email.endsWith('@example.com') && 'admin' in identity.groups && request.connector_id == 'okta'`, ) require.NoError(t, err) identity := dexcel.IdentityFromConnector(connector.Identity{ UserID: "123", Username: "john", Email: "john@example.com", Groups: []string{"admin", "dev"}, }) request := dexcel.RequestFromContext(dexcel.RequestContext{ ClientID: "my-app", ConnectorID: "okta", Scopes: []string{"openid", "email"}, }) result, err := dexcel.EvalBool(context.Background(), prog, map[string]any{ "identity": identity, "request": request, }) require.NoError(t, err) assert.True(t, result) } func TestNewCompilerWithVariables(t *testing.T) { // Claims variable — remains map(string, dyn) compiler, err := dexcel.NewCompiler(dexcel.ClaimsVariable()) require.NoError(t, err) // claims.email returns dyn from map access, use Compile (not CompileString) prog, err := compiler.Compile("claims.email") require.NoError(t, err) result, err := dexcel.EvalString(context.Background(), prog, map[string]any{ "claims": map[string]any{ "email": "test@example.com", }, }) require.NoError(t, err) assert.Equal(t, "test@example.com", result) } ================================================ FILE: pkg/cel/cost.go ================================================ package cel import ( "fmt" "github.com/google/cel-go/checker" ) // DefaultCostBudget is the default cost budget for a single expression // evaluation. Aligned with Kubernetes defaults: enough for typical identity // operations but prevents runaway expressions. const DefaultCostBudget uint64 = 10_000_000 // MaxExpressionLength is the maximum length of a CEL expression string. const MaxExpressionLength = 10_240 // DefaultStringMaxLength is the estimated max length of string values // (emails, usernames, group names, etc.) used for compile-time cost estimation. const DefaultStringMaxLength = 256 // DefaultListMaxLength is the estimated max length of list values // (groups, scopes) used for compile-time cost estimation. const DefaultListMaxLength = 100 // CostEstimate holds the estimated cost range for a compiled expression. type CostEstimate struct { Min uint64 Max uint64 } // EstimateCost returns the estimated cost range for a compiled expression. // This is computed statically at compile time without evaluating the expression. func (c *Compiler) EstimateCost(result *CompilationResult) (CostEstimate, error) { costEst, err := c.env.EstimateCost(result.ast, &defaultCostEstimator{}) if err != nil { return CostEstimate{}, fmt.Errorf("CEL cost estimation failed: %w", err) } return CostEstimate{Min: costEst.Min, Max: costEst.Max}, nil } // defaultCostEstimator provides size hints for compile-time cost estimation. // Without these hints, the CEL cost estimator assumes unbounded sizes for // variables, leading to wildly overestimated max costs. type defaultCostEstimator struct{} func (defaultCostEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstimate { // Provide size hints for map(string, dyn) variables: identity, request, claims. // Without these, the estimator assumes lists/strings can be infinitely large. if element.Path() == nil { return nil } path := element.Path() if len(path) == 0 { return nil } root := path[0] switch root { case "identity", "request", "claims": // Nested field access (e.g. identity.email, identity.groups) if len(path) >= 2 { field := path[1] switch field { case "groups", "scopes": // list(string) fields return &checker.SizeEstimate{Min: 0, Max: DefaultListMaxLength} case "email_verified": // bool field — size is always 1 return &checker.SizeEstimate{Min: 1, Max: 1} default: // string fields (email, username, user_id, client_id, etc.) return &checker.SizeEstimate{Min: 0, Max: DefaultStringMaxLength} } } // The map itself: number of keys return &checker.SizeEstimate{Min: 0, Max: 20} } return nil } func (defaultCostEstimator) EstimateCallCost(function, overloadID string, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate { switch function { case "dex.emailDomain", "dex.emailLocalPart": // Simple string split — O(n) where n is string length, bounded. return &checker.CallEstimate{ CostEstimate: checker.CostEstimate{Min: 1, Max: 2}, } case "dex.groupMatches": // Iterates over groups list and matches each against a pattern. return &checker.CallEstimate{ CostEstimate: checker.CostEstimate{Min: 1, Max: DefaultListMaxLength}, } case "dex.groupFilter": // Builds a set from allowed list, then iterates groups. return &checker.CallEstimate{ CostEstimate: checker.CostEstimate{Min: 1, Max: 2 * DefaultListMaxLength}, } } return nil } ================================================ FILE: pkg/cel/cost_test.go ================================================ package cel_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dexcel "github.com/dexidp/dex/pkg/cel" ) func TestEstimateCost(t *testing.T) { vars := dexcel.IdentityVariables() compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) tests := map[string]struct { expr string }{ "simple bool": { expr: "true", }, "string comparison": { expr: "identity.email == 'test@example.com'", }, "group membership": { expr: "identity.groups.exists(g, g == 'admin')", }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { prog, err := compiler.Compile(tc.expr) require.NoError(t, err) est, err := compiler.EstimateCost(prog) require.NoError(t, err) assert.True(t, est.Max >= est.Min, "max cost should be >= min cost") assert.True(t, est.Max <= dexcel.DefaultCostBudget, "estimated max cost %d should be within default budget %d", est.Max, dexcel.DefaultCostBudget) }) } } func TestCompileTimeCostAcceptsSimpleExpressions(t *testing.T) { vars := append(dexcel.IdentityVariables(), dexcel.RequestVariables()...) compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) tests := map[string]string{ "literal": "true", "email endsWith": "identity.email.endsWith('@example.com')", "group check": "'admin' in identity.groups", "emailDomain": `dex.emailDomain(identity.email)`, "groupMatches": `dex.groupMatches(identity.groups, "team:*")`, "groupFilter": `dex.groupFilter(identity.groups, ["admin", "dev"])`, "combined policy": `identity.email.endsWith('@example.com') && 'admin' in identity.groups`, "complex policy": `identity.email.endsWith('@example.com') && identity.groups.exists(g, g == 'admin') && request.connector_id == 'okta' && request.scopes.exists(s, s == 'openid')`, "filter+map chain": `identity.groups .filter(g, g.startsWith('team:')) .map(g, g.replace('team:', '')) .size() > 0`, } for name, expr := range tests { t.Run(name, func(t *testing.T) { _, err := compiler.Compile(expr) assert.NoError(t, err, "expression should compile within default budget") }) } } func TestCompileTimeCostRejection(t *testing.T) { vars := append(dexcel.IdentityVariables(), dexcel.RequestVariables()...) tests := map[string]struct { budget uint64 expr string }{ "simple exists exceeds tiny budget": { budget: 1, expr: "identity.groups.exists(g, g == 'admin')", }, "endsWith exceeds tiny budget": { budget: 2, expr: "identity.email.endsWith('@example.com')", }, "nested comprehension over groups exceeds moderate budget": { // Two nested iterations over groups: O(n^2) where n=100 → ~280K budget: 10_000, expr: `identity.groups.exists(g1, identity.groups.exists(g2, g1 != g2 && g1.startsWith(g2) ) )`, }, "cross-variable comprehension exceeds moderate budget": { // filter groups then check each against scopes: O(n*m) → ~162K budget: 10_000, expr: `identity.groups .filter(g, g.startsWith('team:')) .exists(g, request.scopes.exists(s, s == g))`, }, "chained filter+map+filter+map exceeds small budget": { budget: 1000, expr: `identity.groups .filter(g, g.startsWith('team:')) .map(g, g.replace('team:', '')) .filter(g, g.size() > 3) .map(g, g.upperAscii()) .size() > 0`, }, "many independent exists exceeds small budget": { budget: 5000, expr: `identity.groups.exists(g, g.contains('a')) && identity.groups.exists(g, g.contains('b')) && identity.groups.exists(g, g.contains('c')) && identity.groups.exists(g, g.contains('d')) && identity.groups.exists(g, g.contains('e'))`, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { compiler, err := dexcel.NewCompiler(vars, dexcel.WithCostBudget(tc.budget)) require.NoError(t, err) _, err = compiler.Compile(tc.expr) assert.Error(t, err) assert.Contains(t, err.Error(), "estimated cost") assert.Contains(t, err.Error(), "exceeds budget") }) } } ================================================ FILE: pkg/cel/doc.go ================================================ // Package cel provides a safe, sandboxed CEL (Common Expression Language) // environment for policy evaluation, claim mapping, and token customization // in Dex. It includes cost budgets, Kubernetes-grade compatibility guarantees, // and a curated set of extension libraries. package cel ================================================ FILE: pkg/cel/library/doc.go ================================================ // Package library provides custom CEL function libraries for Dex. // Each library implements the cel.Library interface and can be registered // in a CEL environment. package library ================================================ FILE: pkg/cel/library/email.go ================================================ package library import ( "strings" "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" ) // Email provides email-related CEL functions. // // Functions (V1): // // dex.emailDomain(email: string) -> string // Returns the domain portion of an email address. // Example: dex.emailDomain("user@example.com") == "example.com" // // dex.emailLocalPart(email: string) -> string // Returns the local part of an email address. // Example: dex.emailLocalPart("user@example.com") == "user" type Email struct{} func (Email) CompileOptions() []cel.EnvOption { return []cel.EnvOption{ cel.Function("dex.emailDomain", cel.Overload("dex_email_domain_string", []*cel.Type{cel.StringType}, cel.StringType, cel.UnaryBinding(emailDomainImpl), ), ), cel.Function("dex.emailLocalPart", cel.Overload("dex_email_local_part_string", []*cel.Type{cel.StringType}, cel.StringType, cel.UnaryBinding(emailLocalPartImpl), ), ), } } func (Email) ProgramOptions() []cel.ProgramOption { return nil } func emailDomainImpl(arg ref.Val) ref.Val { email, ok := arg.Value().(string) if !ok { return types.NewErr("dex.emailDomain: expected string argument") } _, domain, found := strings.Cut(email, "@") if !found { return types.String("") } return types.String(domain) } func emailLocalPartImpl(arg ref.Val) ref.Val { email, ok := arg.Value().(string) if !ok { return types.NewErr("dex.emailLocalPart: expected string argument") } localPart, _, found := strings.Cut(email, "@") if !found { return types.String(email) } return types.String(localPart) } ================================================ FILE: pkg/cel/library/email_test.go ================================================ package library_test import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dexcel "github.com/dexidp/dex/pkg/cel" ) func TestEmailDomain(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) tests := map[string]struct { expr string want string }{ "standard email": { expr: `dex.emailDomain("user@example.com")`, want: "example.com", }, "subdomain": { expr: `dex.emailDomain("admin@sub.domain.org")`, want: "sub.domain.org", }, "no at sign": { expr: `dex.emailDomain("nodomain")`, want: "", }, "empty string": { expr: `dex.emailDomain("")`, want: "", }, "multiple at signs": { expr: `dex.emailDomain("user@name@example.com")`, want: "name@example.com", }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { prog, err := compiler.CompileString(tc.expr) require.NoError(t, err) result, err := dexcel.EvalString(context.Background(), prog, map[string]any{}) require.NoError(t, err) assert.Equal(t, tc.want, result) }) } } func TestEmailLocalPart(t *testing.T) { compiler, err := dexcel.NewCompiler(nil) require.NoError(t, err) tests := map[string]struct { expr string want string }{ "standard email": { expr: `dex.emailLocalPart("user@example.com")`, want: "user", }, "no at sign": { expr: `dex.emailLocalPart("justuser")`, want: "justuser", }, "empty string": { expr: `dex.emailLocalPart("")`, want: "", }, "multiple at signs": { expr: `dex.emailLocalPart("user@name@example.com")`, want: "user", }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { prog, err := compiler.CompileString(tc.expr) require.NoError(t, err) result, err := dexcel.EvalString(context.Background(), prog, map[string]any{}) require.NoError(t, err) assert.Equal(t, tc.want, result) }) } } func TestEmailDomainWithIdentityVariable(t *testing.T) { vars := dexcel.IdentityVariables() compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) prog, err := compiler.CompileString(`dex.emailDomain(identity.email)`) require.NoError(t, err) result, err := dexcel.EvalString(context.Background(), prog, map[string]any{ "identity": dexcel.IdentityVal{Email: "admin@corp.example.com"}, }) require.NoError(t, err) assert.Equal(t, "corp.example.com", result) } ================================================ FILE: pkg/cel/library/groups.go ================================================ package library import ( "path" "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/traits" ) // Groups provides group-related CEL functions. // // Functions (V1): // // dex.groupMatches(groups: list(string), pattern: string) -> list(string) // Returns groups matching a glob pattern. // Example: dex.groupMatches(["team:dev", "team:ops", "admin"], "team:*") // // dex.groupFilter(groups: list(string), allowed: list(string)) -> list(string) // Returns only groups present in the allowed list. // Example: dex.groupFilter(["admin", "dev", "ops"], ["admin", "ops"]) type Groups struct{} func (Groups) CompileOptions() []cel.EnvOption { return []cel.EnvOption{ cel.Function("dex.groupMatches", cel.Overload("dex_group_matches_list_string", []*cel.Type{cel.ListType(cel.StringType), cel.StringType}, cel.ListType(cel.StringType), cel.BinaryBinding(groupMatchesImpl), ), ), cel.Function("dex.groupFilter", cel.Overload("dex_group_filter_list_list", []*cel.Type{cel.ListType(cel.StringType), cel.ListType(cel.StringType)}, cel.ListType(cel.StringType), cel.BinaryBinding(groupFilterImpl), ), ), } } func (Groups) ProgramOptions() []cel.ProgramOption { return nil } func groupMatchesImpl(lhs, rhs ref.Val) ref.Val { groupList, ok := lhs.(traits.Lister) if !ok { return types.NewErr("dex.groupMatches: expected list(string) as first argument") } pattern, ok := rhs.Value().(string) if !ok { return types.NewErr("dex.groupMatches: expected string pattern as second argument") } iter := groupList.Iterator() var matched []ref.Val for iter.HasNext() == types.True { item := iter.Next() group, ok := item.Value().(string) if !ok { continue } ok, err := path.Match(pattern, group) if err != nil { return types.NewErr("dex.groupMatches: invalid pattern %q: %v", pattern, err) } if ok { matched = append(matched, types.String(group)) } } return types.NewRefValList(types.DefaultTypeAdapter, matched) } func groupFilterImpl(lhs, rhs ref.Val) ref.Val { groupList, ok := lhs.(traits.Lister) if !ok { return types.NewErr("dex.groupFilter: expected list(string) as first argument") } allowedList, ok := rhs.(traits.Lister) if !ok { return types.NewErr("dex.groupFilter: expected list(string) as second argument") } allowed := make(map[string]struct{}) iter := allowedList.Iterator() for iter.HasNext() == types.True { item := iter.Next() s, ok := item.Value().(string) if !ok { continue } allowed[s] = struct{}{} } var filtered []ref.Val iter = groupList.Iterator() for iter.HasNext() == types.True { item := iter.Next() group, ok := item.Value().(string) if !ok { continue } if _, exists := allowed[group]; exists { filtered = append(filtered, types.String(group)) } } return types.NewRefValList(types.DefaultTypeAdapter, filtered) } ================================================ FILE: pkg/cel/library/groups_test.go ================================================ package library_test import ( "context" "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" dexcel "github.com/dexidp/dex/pkg/cel" ) func TestGroupMatches(t *testing.T) { vars := dexcel.IdentityVariables() compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) tests := map[string]struct { expr string groups []string want []string }{ "wildcard pattern": { expr: `dex.groupMatches(identity.groups, "team:*")`, groups: []string{"team:dev", "team:ops", "admin"}, want: []string{"team:dev", "team:ops"}, }, "exact match": { expr: `dex.groupMatches(identity.groups, "admin")`, groups: []string{"team:dev", "admin", "user"}, want: []string{"admin"}, }, "no matches": { expr: `dex.groupMatches(identity.groups, "nonexistent")`, groups: []string{"team:dev", "admin"}, want: []string{}, }, "question mark pattern": { expr: `dex.groupMatches(identity.groups, "team?")`, groups: []string{"teamA", "teamB", "teams-long"}, want: []string{"teamA", "teamB"}, }, "match all": { expr: `dex.groupMatches(identity.groups, "*")`, groups: []string{"a", "b", "c"}, want: []string{"a", "b", "c"}, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { prog, err := compiler.CompileStringList(tc.expr) require.NoError(t, err) out, err := dexcel.Eval(context.Background(), prog, map[string]any{ "identity": dexcel.IdentityVal{Groups: tc.groups}, }) require.NoError(t, err) nativeVal, err := out.ConvertToNative(reflect.TypeOf([]string{})) require.NoError(t, err) got, ok := nativeVal.([]string) require.True(t, ok, "expected []string, got %T", nativeVal) assert.Equal(t, tc.want, got) }) } } func TestGroupMatchesInvalidPattern(t *testing.T) { vars := dexcel.IdentityVariables() compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) prog, err := compiler.CompileStringList(`dex.groupMatches(identity.groups, "[invalid")`) require.NoError(t, err) _, err = dexcel.Eval(context.Background(), prog, map[string]any{ "identity": dexcel.IdentityVal{Groups: []string{"admin"}}, }) require.Error(t, err) assert.Contains(t, err.Error(), "invalid pattern") } func TestGroupFilter(t *testing.T) { vars := dexcel.IdentityVariables() compiler, err := dexcel.NewCompiler(vars) require.NoError(t, err) tests := map[string]struct { expr string groups []string want []string }{ "filter to allowed": { expr: `dex.groupFilter(identity.groups, ["admin", "ops"])`, groups: []string{"admin", "dev", "ops"}, want: []string{"admin", "ops"}, }, "no overlap": { expr: `dex.groupFilter(identity.groups, ["marketing"])`, groups: []string{"admin", "dev"}, want: []string{}, }, "all allowed": { expr: `dex.groupFilter(identity.groups, ["a", "b", "c"])`, groups: []string{"a", "b", "c"}, want: []string{"a", "b", "c"}, }, "empty allowed list": { expr: `dex.groupFilter(identity.groups, [])`, groups: []string{"admin", "dev"}, want: []string{}, }, "preserves order": { expr: `dex.groupFilter(identity.groups, ["z", "a"])`, groups: []string{"a", "b", "z"}, want: []string{"a", "z"}, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { prog, err := compiler.CompileStringList(tc.expr) require.NoError(t, err) out, err := dexcel.Eval(context.Background(), prog, map[string]any{ "identity": dexcel.IdentityVal{Groups: tc.groups}, }) require.NoError(t, err) nativeVal, err := out.ConvertToNative(reflect.TypeOf([]string{})) require.NoError(t, err) got, ok := nativeVal.([]string) require.True(t, ok, "expected []string, got %T", nativeVal) assert.Equal(t, tc.want, got) }) } } ================================================ FILE: pkg/cel/types.go ================================================ package cel import ( "github.com/google/cel-go/cel" "github.com/dexidp/dex/connector" ) // VariableDeclaration declares a named variable and its CEL type // that will be available in expressions. type VariableDeclaration struct { Name string Type *cel.Type } // IdentityVal is the CEL native type for the identity variable. // Fields are typed so that the CEL compiler rejects unknown field access // (e.g. identity.emial) at config load time rather than at evaluation time. type IdentityVal struct { UserID string `cel:"user_id"` Username string `cel:"username"` PreferredUsername string `cel:"preferred_username"` Email string `cel:"email"` EmailVerified bool `cel:"email_verified"` Groups []string `cel:"groups"` } // RequestVal is the CEL native type for the request variable. type RequestVal struct { ClientID string `cel:"client_id"` ConnectorID string `cel:"connector_id"` Scopes []string `cel:"scopes"` RedirectURI string `cel:"redirect_uri"` } // identityTypeName is the CEL type name for IdentityVal. // Derived by ext.NativeTypes as simplePkgAlias(pkgPath) + "." + structName. const identityTypeName = "cel.IdentityVal" // requestTypeName is the CEL type name for RequestVal. const requestTypeName = "cel.RequestVal" // IdentityVariables provides the 'identity' variable with typed fields. // // identity.user_id — string // identity.username — string // identity.preferred_username — string // identity.email — string // identity.email_verified — bool // identity.groups — list(string) func IdentityVariables() []VariableDeclaration { return []VariableDeclaration{ {Name: "identity", Type: cel.ObjectType(identityTypeName)}, } } // RequestVariables provides the 'request' variable with typed fields. // // request.client_id — string // request.connector_id — string // request.scopes — list(string) // request.redirect_uri — string func RequestVariables() []VariableDeclaration { return []VariableDeclaration{ {Name: "request", Type: cel.ObjectType(requestTypeName)}, } } // ClaimsVariable provides a 'claims' map for raw upstream claims. // Claims remain map(string, dyn) because their shape is genuinely // unknown — they carry arbitrary upstream IdP data. // // claims — map(string, dyn) func ClaimsVariable() []VariableDeclaration { return []VariableDeclaration{ {Name: "claims", Type: cel.MapType(cel.StringType, cel.DynType)}, } } // IdentityFromConnector converts a connector.Identity to a CEL-compatible IdentityVal. func IdentityFromConnector(id connector.Identity) IdentityVal { return IdentityVal{ UserID: id.UserID, Username: id.Username, PreferredUsername: id.PreferredUsername, Email: id.Email, EmailVerified: id.EmailVerified, Groups: id.Groups, } } // RequestContext represents the authentication/token request context // available as the 'request' variable in CEL expressions. type RequestContext struct { ClientID string ConnectorID string Scopes []string RedirectURI string } // RequestFromContext converts a RequestContext to a CEL-compatible RequestVal. func RequestFromContext(rc RequestContext) RequestVal { return RequestVal{ ClientID: rc.ClientID, ConnectorID: rc.ConnectorID, Scopes: rc.Scopes, RedirectURI: rc.RedirectURI, } } ================================================ FILE: pkg/featureflags/doc.go ================================================ // Package featureflags provides a mechanism for toggling experimental or // optional Dex features via environment variables (DEX_). package featureflags ================================================ FILE: pkg/featureflags/flag.go ================================================ package featureflags import ( "os" "strconv" "strings" ) type flag struct { Name string Default bool } func (f *flag) env() string { return "DEX_" + strings.ToUpper(f.Name) } func (f *flag) Enabled() bool { raw := os.Getenv(f.env()) if raw == "" { return f.Default } res, err := strconv.ParseBool(raw) if err != nil { return f.Default } return res } func newFlag(s string, d bool) *flag { return &flag{Name: s, Default: d} } ================================================ FILE: pkg/featureflags/set.go ================================================ package featureflags var ( // EntEnabled enables experimental ent-based engine for the database storages. // https://entgo.io/ EntEnabled = newFlag("ent_enabled", false) // ExpandEnv can enable or disable env expansion in the config which can be useful in environments where, e.g., // $ sign is a part of the password for LDAP user. ExpandEnv = newFlag("expand_env", true) // APIConnectorsCRUD allows CRUD operations on connectors through the gRPC API APIConnectorsCRUD = newFlag("api_connectors_crud", false) // ContinueOnConnectorFailure allows the server to start even if some connectors fail to initialize. ContinueOnConnectorFailure = newFlag("continue_on_connector_failure", true) // ConfigDisallowUnknownFields enables to forbid unknown fields in the config while unmarshaling. ConfigDisallowUnknownFields = newFlag("config_disallow_unknown_fields", false) // ClientCredentialGrantEnabledByDefault enables the client_credentials grant type by default // without requiring explicit configuration in oauth2.grantTypes. ClientCredentialGrantEnabledByDefault = newFlag("client_credential_grant_enabled_by_default", false) // SessionsEnabled enables experimental auth sessions support. SessionsEnabled = newFlag("sessions_enabled", false) ) ================================================ FILE: pkg/groups/doc.go ================================================ // Package groups contains helper functions related to groups. package groups ================================================ FILE: pkg/groups/groups.go ================================================ package groups // Filter filters out any groups of given that are not in required. Thus it may // happen that the resulting slice is empty. func Filter(given, required []string) []string { groups := []string{} groupFilter := make(map[string]struct{}) for _, group := range required { groupFilter[group] = struct{}{} } for _, group := range given { if _, ok := groupFilter[group]; ok { groups = append(groups, group) } } return groups } ================================================ FILE: pkg/groups/groups_test.go ================================================ package groups_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/dexidp/dex/pkg/groups" ) func TestFilter(t *testing.T) { cases := map[string]struct { given, required, expected []string }{ "nothing given": {given: []string{}, required: []string{"ops"}, expected: []string{}}, "exactly one match": {given: []string{"foo"}, required: []string{"foo"}, expected: []string{"foo"}}, "no group of the required ones": {given: []string{"foo", "bar"}, required: []string{"baz"}, expected: []string{}}, "subset matching": {given: []string{"foo", "bar", "baz"}, required: []string{"bar", "baz"}, expected: []string{"bar", "baz"}}, } for name, tc := range cases { t.Run(name, func(t *testing.T) { actual := groups.Filter(tc.given, tc.required) assert.ElementsMatch(t, tc.expected, actual) }) } } ================================================ FILE: pkg/httpclient/doc.go ================================================ // Package httpclient provides a configurable HTTP client constructor with // support for custom CA certificates, root CAs, and TLS settings. package httpclient ================================================ FILE: pkg/httpclient/httpclient.go ================================================ package httpclient import ( "crypto/tls" "crypto/x509" "encoding/base64" "fmt" "net" "net/http" "os" "time" ) func extractCAs(input []string) [][]byte { result := make([][]byte, 0, len(input)) for _, ca := range input { if ca == "" { continue } pemData, err := os.ReadFile(ca) if err != nil { pemData, err = base64.StdEncoding.DecodeString(ca) if err != nil { pemData = []byte(ca) } } result = append(result, pemData) } return result } func NewHTTPClient(rootCAs []string, insecureSkipVerify bool) (*http.Client, error) { pool, err := x509.SystemCertPool() if err != nil { return nil, err } tlsConfig := tls.Config{RootCAs: pool, InsecureSkipVerify: insecureSkipVerify} for index, rootCABytes := range extractCAs(rootCAs) { if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCABytes) { return nil, fmt.Errorf("rootCAs.%d is not in PEM format, certificate must be "+ "a PEM encoded string, a base64 encoded bytes that contain PEM encoded string, "+ "or a path to a PEM encoded certificate", index) } } return &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tlsConfig, Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, DualStack: true, }).DialContext, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, }, }, nil } ================================================ FILE: pkg/httpclient/httpclient_test.go ================================================ package httpclient_test import ( "crypto/tls" "encoding/base64" "fmt" "io" "net/http" "net/http/httptest" "os" "testing" "github.com/stretchr/testify/assert" "github.com/dexidp/dex/pkg/httpclient" ) func TestRootCAs(t *testing.T) { ts, err := NewLocalHTTPSTestServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, client") })) assert.Nil(t, err) defer ts.Close() runTest := func(name string, certs []string) { t.Run(name, func(t *testing.T) { rootCAs := certs testClient, err := httpclient.NewHTTPClient(rootCAs, false) assert.Nil(t, err) res, err := testClient.Get(ts.URL) assert.Nil(t, err) greeting, err := io.ReadAll(res.Body) res.Body.Close() assert.Nil(t, err) assert.Equal(t, "Hello, client", string(greeting)) }) } runTest("From file", []string{"testdata/rootCA.pem"}) content, err := os.ReadFile("testdata/rootCA.pem") assert.NoError(t, err) runTest("From string", []string{string(content)}) contentStr := base64.StdEncoding.EncodeToString(content) runTest("From bytes", []string{contentStr}) } func TestInsecureSkipVerify(t *testing.T) { ts, err := NewLocalHTTPSTestServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, client") })) assert.Nil(t, err) defer ts.Close() insecureSkipVerify := true testClient, err := httpclient.NewHTTPClient(nil, insecureSkipVerify) assert.Nil(t, err) res, err := testClient.Get(ts.URL) assert.Nil(t, err) greeting, err := io.ReadAll(res.Body) res.Body.Close() assert.Nil(t, err) assert.Equal(t, "Hello, client", string(greeting)) } func NewLocalHTTPSTestServer(handler http.Handler) (*httptest.Server, error) { ts := httptest.NewUnstartedServer(handler) cert, err := tls.LoadX509KeyPair("testdata/server.crt", "testdata/server.key") if err != nil { return nil, err } ts.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} ts.StartTLS() return ts, nil } ================================================ FILE: pkg/httpclient/readme.md ================================================ # Regenerate testdata ### server.csr.cnf ``` [req] default_bits = 2048 prompt = no default_md = sha256 distinguished_name = dn [dn] C=US ST=RandomState L=RandomCity O=RandomOrganization OU=RandomOrganizationUnit emailAddress=hello@example.com CN = localhost ``` and ### v3.ext ``` authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment subjectAltName = @alt_names [alt_names] DNS.1 = localhost IP.1 = 127.0.0.1 ``` ### Then enter the following commands: `openssl genrsa -out rootCA.key 2048` `openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.pem -config server.csr.cnf` `openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key -config server.csr.cnf` `openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 3650 -sha256 -extfile v3.ext` ================================================ FILE: pkg/httpclient/testdata/rootCA.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA4dB5aQCjCmMsW71u9F0WNm1TYjXQBZ4p7oNT+BQwCc/MZ2xc 5NexS2O86nbRkw5jwyfAAMSMKRr9s2FluVTHqiln78rg+XUgmrmNT3ZroLmW6QL6 Ca8dbMPky+tQclZsvMd3HAeCyyrs4pf7wM1AyUJD7H0xAlVD1fsohkg7jhBFUfV+ q2VMMdnsaV5vFrW/2vPBWz1SNPW/Xm+Ilny7xg9njQLcPMNtVtF+7EPB6sxD6qrj BC+Kj5zQ3bZOfdrh7yy63dbh/Kh+3NScgO+k+x92HlAjRIvj5y4KrbGZl7CmOth5 y7fPywApVbDfZRWJChI1PVflOyDdnC+vhMLbHQIDAQABAoIBAEmjrrQrXP/6L3EL aa+O27uME3Enk1sBpTL+6Ncx3iiU91eS4whNvqeTMvxTGy0VuDrgL6EQd5TAFJP2 4zF5EFPRhO+R/aPcKnHKqOaM+7RCUZBTRC78SGA70dUeO/HNdVBqy9D8Mg8HRJDw d0z8om//iB8LBHx6SdDyQtjnnWRKFTzQRurBBoyLe2vPMFtINKtNUkahjc8HE4GO aIv1LICJUzf4ZnkntKd5cFHZ42R2Tmfj0Y9G9DyJbuSA3+0u5IhYB39Uy6jFxLi8 I5PoIVhgYZ0aivsVBIviShwQ9kgv6807YBxt22eSNovBDrSp+cAnIF9+p0b3MnkU aCHSiBECgYEA84lssi6AqfCEsSiQMSM9kMCXJ4KQI/l7pmrIA50+V5HSEby9lg2Y N6XJ4V4q46t8FcZBjmMvzn9fwiPMRw5e995cVNBQ31a1FX/1Hy6RNtEiLZRnkHI5 WznY9IxQ+c9JXJeFY1sO0BfO0TS3WvOf1rwqOb92q+cQaItnPQ+4Ya8CgYEA7V7e IqW3PpO4H+c5hH9egM0BjAxH71C9YpYzZpF9uiPIkuMnJ8nm9bB6RiuDaYCxvrfE A0h/SQewoYJKL4OfKGjrbG7U4zLMZHIWlf8Za55Zik5BNjvgBqFFrrSgLUGxdRTX N0+TlWlW1bvJblWpdjIbJbg/6kCU98TzK852fvMCgYAWYa/apElw1MjtGyQ9T9bN odWCbQ5gMAJ8Jd4h7uaW17DtrmHiE3fEzXjDPItGhzENMz49HsJ7ANvFFNMmSJzT vNzRcp+sFuTnh+34Iqh32DqC49usu8KnrqZQu0CJ5NICL26z1d+DolyAf47GThOH gZ2D1yPJ4p9wbDddtj8kwwKBgCFKB68mPG+rOcxHmjppvnAj0A66/i+izBySYf0F dHNxZ0SqVKhw2VIlgNBsc86M/OB5VyT6utccG/paklrdg6mgJTwcwwBl9GI12dMJ ZqBAIeCSnvSjKwTjAynALSKLrv5zgMdCArmWf1YUMuilXNG1rzb4AwawLfQdi9jd 6KJfAoGBALFl6ldywl3sGPk9K2xCDYYhb1TNQyheA5YvoZzZ6XCo1q0Lbwy/FamZ 0TSWkoEmGB/Hck3HgtZDRo3CTI1vYfbpAtgI7oD1NA1zMaLulNQxKjH3iVvyb+R7 ZcIT7EVPZgkUwr0bsp22yVDekh/CHoB6FZPCyoAb8WnfJfooTBzB -----END RSA PRIVATE KEY----- ================================================ FILE: pkg/httpclient/testdata/rootCA.pem ================================================ -----BEGIN CERTIFICATE----- MIID1jCCAr4CCQCG4JBeSi6cDjANBgkqhkiG9w0BAQsFADCBrDELMAkGA1UEBhMC VVMxFDASBgNVBAgMC1JhbmRvbVN0YXRlMRMwEQYDVQQHDApSYW5kb21DaXR5MRsw GQYDVQQKDBJSYW5kb21Pcmdhbml6YXRpb24xHzAdBgNVBAsMFlJhbmRvbU9yZ2Fu aXphdGlvblVuaXQxIDAeBgkqhkiG9w0BCQEWEWhlbGxvQGV4YW1wbGUuY29tMRIw EAYDVQQDDAlsb2NhbGhvc3QwHhcNMjIxMDA3MjIwNjQwWhcNMzIxMDA0MjIwNjQw WjCBrDELMAkGA1UEBhMCVVMxFDASBgNVBAgMC1JhbmRvbVN0YXRlMRMwEQYDVQQH DApSYW5kb21DaXR5MRswGQYDVQQKDBJSYW5kb21Pcmdhbml6YXRpb24xHzAdBgNV BAsMFlJhbmRvbU9yZ2FuaXphdGlvblVuaXQxIDAeBgkqhkiG9w0BCQEWEWhlbGxv QGV4YW1wbGUuY29tMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDh0HlpAKMKYyxbvW70XRY2bVNiNdAFninug1P4FDAJ z8xnbFzk17FLY7zqdtGTDmPDJ8AAxIwpGv2zYWW5VMeqKWfvyuD5dSCauY1Pdmug uZbpAvoJrx1sw+TL61ByVmy8x3ccB4LLKuzil/vAzUDJQkPsfTECVUPV+yiGSDuO EEVR9X6rZUwx2expXm8Wtb/a88FbPVI09b9eb4iWfLvGD2eNAtw8w21W0X7sQ8Hq zEPqquMEL4qPnNDdtk592uHvLLrd1uH8qH7c1JyA76T7H3YeUCNEi+PnLgqtsZmX sKY62HnLt8/LAClVsN9lFYkKEjU9V+U7IN2cL6+EwtsdAgMBAAEwDQYJKoZIhvcN AQELBQADggEBAN6g0qit/3R2X+KdR0LgRXF/h4qQFgcV6cxnhRAmLIDNJlxKSHqN IE5+bxzCbkblzGfr/jNPqW0s+yaN4CyMgKNYSzkLBPE4FF+19Uv+dyYfFms3mDJ7 0rGjS5bCscThWhpaSw20LcwQcr/+X+/fGzJ01dVFK1UOjBKg4d4dMwxklbIkZqIq siRW0GMy26mgVZ/BSjeh5kEjs6h6H3cJsGl7xYT+BI7wnxHwGeT9tkBgiyT5FwaS vtdZkBpQ9q8f7FwsEm3woLHdWuOnrtUtVpY/oc6WFGdROQdGzjSk0D3kHs9YhueC GSzZKrqX+TSIgpPrLYNHX4uxlo5TAwP/5GM= -----END CERTIFICATE----- ================================================ FILE: pkg/httpclient/testdata/rootCA.srl ================================================ C1B35F0051A641BB ================================================ FILE: pkg/httpclient/testdata/server.crt ================================================ -----BEGIN CERTIFICATE----- MIIE5TCCA82gAwIBAgIJAMGzXwBRpkG7MA0GCSqGSIb3DQEBCwUAMIGsMQswCQYD VQQGEwJVUzEUMBIGA1UECAwLUmFuZG9tU3RhdGUxEzARBgNVBAcMClJhbmRvbUNp dHkxGzAZBgNVBAoMElJhbmRvbU9yZ2FuaXphdGlvbjEfMB0GA1UECwwWUmFuZG9t T3JnYW5pemF0aW9uVW5pdDEgMB4GCSqGSIb3DQEJARYRaGVsbG9AZXhhbXBsZS5j b20xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMjEwMDcyMjA3MDhaFw0zMjEwMDQy MjA3MDhaMIGsMQswCQYDVQQGEwJVUzEUMBIGA1UECAwLUmFuZG9tU3RhdGUxEzAR BgNVBAcMClJhbmRvbUNpdHkxGzAZBgNVBAoMElJhbmRvbU9yZ2FuaXphdGlvbjEf MB0GA1UECwwWUmFuZG9tT3JnYW5pemF0aW9uVW5pdDEgMB4GCSqGSIb3DQEJARYR aGVsbG9AZXhhbXBsZS5jb20xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAMuKdpXP87Q7Kg3iafXzvBuVIyV1K5UmMYiN koztkC5XrCzHaQRS/CoIb7/nUqmtAxx7RL0jzhZ93zBN4HY/Zcnrd9tXoPPxi0mG ZZWfFU6nN8nOkMHWzEbHVBmhxpfGtwmLcajQ4HrK1TZwJUn6GqclHQRy/gjxkiw5 KPqzfVOVlA6ht4KdKstKazQkWZ5gdWT4d8yrEy/IT4oaW05xALBMQ7YGjkzWKsSF 6ygXI7xqF9rg9jCnUsPYg4f8ut3N0c00KjsfKOOj2dF/ZyjedQ5c0u4hHmxSo3Ka 0ZTmIrMfbVXgGjxRG2HZXLpPvQKoCf/fOX8Irdr+lahFVKASxN0CAwEAAaOCAQYw ggECMIHLBgNVHSMEgcMwgcChgbKkga8wgawxCzAJBgNVBAYTAlVTMRQwEgYDVQQI DAtSYW5kb21TdGF0ZTETMBEGA1UEBwwKUmFuZG9tQ2l0eTEbMBkGA1UECgwSUmFu ZG9tT3JnYW5pemF0aW9uMR8wHQYDVQQLDBZSYW5kb21Pcmdhbml6YXRpb25Vbml0 MSAwHgYJKoZIhvcNAQkBFhFoZWxsb0BleGFtcGxlLmNvbTESMBAGA1UEAwwJbG9j YWxob3N0ggkAhuCQXkounA4wCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwGgYDVR0R BBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCWmh5ebpkm v2B1yQgarSCSSkLZ5DZSAJjrPgW2IJqCW2q2D1HworbW1Yn5jqrM9FKGnJfjCyve zBB5AOlGp+0bsZGgMRMCavgv4QhTThXUoJqqHcfEu4wHndcgrqSadxmV5aisSR4u gXnjW43o3akby+h1K40RR3vVkpzPaoC3/bgk7WVpfpPiP32E24a01gETozRb/of/ ATN3JBe0xh+e63CrPX1sago5+u3UETIoOr0fW8M/gU9GApmJiFAXwHag6j54hLCG 23EtVDwmlarG8Pj+i0yru8s22QqzAJi5E0OwR4aB8tqicLKYBVfzyLCOielIBUrK OkuFKp+VjxQX -----END CERTIFICATE----- ================================================ FILE: pkg/httpclient/testdata/server.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIIC8jCCAdoCAQAwgawxCzAJBgNVBAYTAlVTMRQwEgYDVQQIDAtSYW5kb21TdGF0 ZTETMBEGA1UEBwwKUmFuZG9tQ2l0eTEbMBkGA1UECgwSUmFuZG9tT3JnYW5pemF0 aW9uMR8wHQYDVQQLDBZSYW5kb21Pcmdhbml6YXRpb25Vbml0MSAwHgYJKoZIhvcN AQkBFhFoZWxsb0BleGFtcGxlLmNvbTESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjAN BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy4p2lc/ztDsqDeJp9fO8G5UjJXUr lSYxiI2SjO2QLlesLMdpBFL8Kghvv+dSqa0DHHtEvSPOFn3fME3gdj9lyet321eg 8/GLSYZllZ8VTqc3yc6QwdbMRsdUGaHGl8a3CYtxqNDgesrVNnAlSfoapyUdBHL+ CPGSLDko+rN9U5WUDqG3gp0qy0prNCRZnmB1ZPh3zKsTL8hPihpbTnEAsExDtgaO TNYqxIXrKBcjvGoX2uD2MKdSw9iDh/y63c3RzTQqOx8o46PZ0X9nKN51DlzS7iEe bFKjcprRlOYisx9tVeAaPFEbYdlcuk+9AqgJ/985fwit2v6VqEVUoBLE3QIDAQAB oAAwDQYJKoZIhvcNAQELBQADggEBADjuujIFoDJllR6Xo/w7j5vfNOeHO5GSgxF2 XnuuDOI9Tomi7vURFZNbz3VAYiehpxRxYqLwFoQUwFtux2qRuGyg0P9fP1iQXPUE QUfFXmvB80uf2bG4lkbUwnmlZLFOEwhGZyPxpvsrxp2Ei2ppkUopCkzOMsSk3m0X MC50ZsTHOxfkA3r1WmS7oE2c0p0Fvyx+UJw0URAXFvDS1X0ONgww3FxqbBbm9W37 5N4FZzGAK6j1wzuynKKXrn20YDCANXYH55PZyupfCeSZT0H0AZifWL7rz/G9uqme RzbIYc/CNQQTympjinBegQdVeB3yjVNZIvpGOuPSKQqhwFtmDFo= -----END CERTIFICATE REQUEST----- ================================================ FILE: pkg/httpclient/testdata/server.csr.cnf ================================================ [req] default_bits = 2048 prompt = no default_md = sha256 distinguished_name = dn [dn] C=US ST=RandomState L=RandomCity O=RandomOrganization OU=RandomOrganizationUnit emailAddress=hello@example.com CN = localhost ================================================ FILE: pkg/httpclient/testdata/server.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDLinaVz/O0OyoN 4mn187wblSMldSuVJjGIjZKM7ZAuV6wsx2kEUvwqCG+/51KprQMce0S9I84Wfd8w TeB2P2XJ63fbV6Dz8YtJhmWVnxVOpzfJzpDB1sxGx1QZocaXxrcJi3Go0OB6ytU2 cCVJ+hqnJR0Ecv4I8ZIsOSj6s31TlZQOobeCnSrLSms0JFmeYHVk+HfMqxMvyE+K GltOcQCwTEO2Bo5M1irEhesoFyO8ahfa4PYwp1LD2IOH/LrdzdHNNCo7Hyjjo9nR f2co3nUOXNLuIR5sUqNymtGU5iKzH21V4Bo8URth2Vy6T70CqAn/3zl/CK3a/pWo RVSgEsTdAgMBAAECggEAU6cxu7q+54kVbKVsdThaTF/MFR4F7oPHAd9lpuQQSOuh iLngMHXGy6OyAgYZlEDWMYN8KdwoXFgZPaoUIaVGuWk8Vnq6XOgeHfbNk2PRhwT0 yc1K80/Lnx9XMj2p+EEkgxi7eu12BSGN5ZTLzo6rG50GQwjb3WMjd2d6rybL0GjC wg2arcBk3sSMYmvZOqlAsaQmtgwkJhvhVkVfEQSD3VKF7g0dh/h3LIPyM0Ff4M67 KpLMPPwzUJ/0Z4ewAP06mMKUA86R93M+dWs2eh1oBGnRkVQdhCJLXJpuGHZ6BTiB Ry0AeorHfnVXPbtpUeAq6m5/BBl6qX0ooB08BIFwAQKBgQDqJpTZS/ZzqL6Kcs14 MyFu+7DungSxQ5oK9ju7EFSosanSk4UEa/lw992kM6nsIMwgSVQgba5zKcVMeSmk AVbpznegQD1BYCwOGwbGvkJ8jbhPy+WLbbRjWT/E6AItZgUK+fyTIcNvSehcQqsT fhgWsK7ueZCmLQfVhK1AxtvY3QKBgQDeiKuo8plsH/7IxDn7KVHBOHKPC2ZPzg03 i7La6zomiRckwwPnhicRSYsjtfCCW6Ms+uzjTEItgFM+5PdrXheeku+z/sExRtZu emqPqDomixlXDRQ6RN3gnBSk4RU+ROB1u1uBLWXqRz8Gp2zJGRxhHfYt2zefBv4w /cIuPC3cAQKBgD2UsAkGJWb9tj8LOmama+CYaUwYWvuT3+uKHuNvxBQpxZQQICet jgjb53rL66Cib4z+PBXbQsoe7jjSlNUBVS5gkq2et31+IZgEG6AhYbMIQrUZ1uD4 lTybuF289vWhoynj3T2E37VhJq89CWky/HrbNOabKiPKLAlHv5kNs7wxAoGBANEJ XQbU7J2O6Iy7FyQBSlTQq3wHX1Iz4mJ9DcNrFzK/sEfOEMrZT7WDefpPm984KW3F P+S766ZGVuxLtMbcmh9RM23HLr8VJbSdtZ/AjO9L1r/Y/1lE+49TzmibLpNRq++r 0WbkuEl8J44ek6fLuMbZmDi3JeZycTCgDlnUGdgBAoGAYdliovtURZCm46t1uE3F idCLCXCccjkt1hcNGNjck/b0trHA7wOEqICIguoWDlEBTc0PDvHEq6PfKyqptGkj AgaZTMF/aZiGqlT7VRpBuzxM/uV5xzCg+i2ViaW/p3xq0z2PRljVZiEfe5aWcjiM ouTtnC3TgmcjhTgGmb48QQE= -----END PRIVATE KEY----- ================================================ FILE: pkg/httpclient/testdata/v3.ext ================================================ authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment subjectAltName = @alt_names [alt_names] DNS.1 = localhost IP.1 = 127.0.0.1 ================================================ FILE: scripts/git-version ================================================ #!/bin/sh -e # parse the current git commit hash COMMIT=`git rev-parse --short=8 HEAD` # check if the current commit has a matching tag (filter for v* tags, excluding api/) TAG=$(git describe --exact-match --abbrev=0 --tags --match="v[0-9]*" 2> /dev/null || true) # use the matching tag as the version, if available if [ -z "$TAG" ]; then # No exact tag on current commit, find the last version tag and bump minor version # Get all tags matching v[0-9]*, sort them, and take the last one LAST_TAG=$(git tag --list "v[0-9]*" --sort=-version:refname | head -1) if [ -z "$LAST_TAG" ]; then # No tags found, use v0.1.0 as fallback BASE_VERSION="v0.1.0" else # Parse the last tag and bump minor version # Remove 'v' prefix TAG_WITHOUT_V="${LAST_TAG#v}" # Split version into parts (major.minor.patch) MAJOR=$(echo "$TAG_WITHOUT_V" | cut -d. -f1) MINOR=$(echo "$TAG_WITHOUT_V" | cut -d. -f2) PATCH=$(echo "$TAG_WITHOUT_V" | cut -d. -f3) # Bump minor version MINOR=$((MINOR + 1)) # Construct base version with bumped minor BASE_VERSION="v${MAJOR}.${MINOR}.0" fi # Get commit timestamp in YYYYMMDDhhmmss format TIMESTAMP=$(git log -1 --format=%ci HEAD | sed 's/[-: ]//g' | cut -c1-14) # Construct pseudo-version VERSION="${BASE_VERSION}-${TIMESTAMP}-${COMMIT}" else VERSION=$TAG fi # check for changed files (not untracked files) if [ -n "$(git diff --shortstat 2> /dev/null | tail -n1)" ]; then VERSION="${VERSION}-dirty" fi echo $VERSION ================================================ FILE: scripts/manifests/.editorconfig ================================================ [{*.yml,*.yaml}] indent_size = 2 ================================================ FILE: scripts/manifests/crds/authcodes.yaml ================================================ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: authcodes.dex.coreos.com spec: group: dex.coreos.com names: kind: AuthCode listKind: AuthCodeList plural: authcodes singular: authcode scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object x-kubernetes-preserve-unknown-fields: true ================================================ FILE: scripts/manifests/crds/authrequests.yaml ================================================ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: authrequests.dex.coreos.com spec: group: dex.coreos.com names: kind: AuthRequest listKind: AuthRequestList plural: authrequests singular: authrequest scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object x-kubernetes-preserve-unknown-fields: true ================================================ FILE: scripts/manifests/crds/connectors.yaml ================================================ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: connectors.dex.coreos.com spec: group: dex.coreos.com names: kind: Connector listKind: ConnectorList plural: connectors singular: connector scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object x-kubernetes-preserve-unknown-fields: true ================================================ FILE: scripts/manifests/crds/devicerequests.yaml ================================================ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: devicerequests.dex.coreos.com spec: group: dex.coreos.com names: kind: DeviceRequest listKind: DeviceRequestList plural: devicerequests singular: devicerequest scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object x-kubernetes-preserve-unknown-fields: true ================================================ FILE: scripts/manifests/crds/devicetokens.yaml ================================================ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: devicetokens.dex.coreos.com spec: group: dex.coreos.com names: kind: DeviceToken listKind: DeviceTokenList plural: devicetokens singular: devicetoken scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object x-kubernetes-preserve-unknown-fields: true ================================================ FILE: scripts/manifests/crds/oauth2clients.yaml ================================================ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: oauth2clients.dex.coreos.com spec: group: dex.coreos.com names: kind: OAuth2Client listKind: OAuth2ClientList plural: oauth2clients singular: oauth2client scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object x-kubernetes-preserve-unknown-fields: true ================================================ FILE: scripts/manifests/crds/offlinesessionses.yaml ================================================ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: offlinesessionses.dex.coreos.com spec: group: dex.coreos.com names: kind: OfflineSessions listKind: OfflineSessionsList plural: offlinesessionses singular: offlinesessions scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object x-kubernetes-preserve-unknown-fields: true ================================================ FILE: scripts/manifests/crds/passwords.yaml ================================================ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: passwords.dex.coreos.com spec: group: dex.coreos.com names: kind: Password listKind: PasswordList plural: passwords singular: password scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object x-kubernetes-preserve-unknown-fields: true ================================================ FILE: scripts/manifests/crds/refreshtokens.yaml ================================================ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: refreshtokens.dex.coreos.com spec: group: dex.coreos.com names: kind: RefreshToken listKind: RefreshTokenList plural: refreshtokens singular: refreshtoken scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object x-kubernetes-preserve-unknown-fields: true ================================================ FILE: scripts/manifests/crds/signingkeies.yaml ================================================ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: signingkeies.dex.coreos.com spec: group: dex.coreos.com names: kind: SigningKey listKind: SigningKeyList plural: signingkeies singular: signingkey scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object x-kubernetes-preserve-unknown-fields: true ================================================ FILE: scripts/update-gomplate ================================================ #!/bin/sh -e # Script to check for a new gomplate version and update it in Dockerfile GOMPLATE_REPO="hairyhenderson/gomplate" DOCKERFILE="${1:-.}/Dockerfile" # Check if Dockerfile exists if [ ! -f "$DOCKERFILE" ]; then echo "Error: Dockerfile not found at $DOCKERFILE" exit 1 fi # Get the latest release version from GitHub echo "Checking for the latest gomplate version on GitHub..." LATEST_VERSION=$(curl -s "https://api.github.com/repos/${GOMPLATE_REPO}/releases/latest" | grep -o '"tag_name": "[^"]*"' | head -1 | cut -d'"' -f4) if [ -z "$LATEST_VERSION" ]; then echo "Error: Could not fetch the latest version from GitHub" exit 1 fi echo "Latest gomplate version: $LATEST_VERSION" # Get the current version from Dockerfile CURRENT_VERSION=$(grep 'ENV GOMPLATE_VERSION' "$DOCKERFILE" | sed 's/.*GOMPLATE_VERSION=//;s/[[:space:]]*$//') echo "Current gomplate version in Dockerfile: $CURRENT_VERSION" # Check if versions are different if [ "$LATEST_VERSION" = "$CURRENT_VERSION" ]; then echo "✓ Already on the latest version ($LATEST_VERSION)" exit 0 fi echo "✓ New version available: $LATEST_VERSION" echo "Updating Dockerfile..." # Update the Dockerfile - use a more specific pattern to avoid multiple replacements sed -i '' "s/ENV GOMPLATE_VERSION=.*/ENV GOMPLATE_VERSION=${LATEST_VERSION}/" "$DOCKERFILE" if grep -q "ENV GOMPLATE_VERSION=${LATEST_VERSION}" "$DOCKERFILE"; then echo "✓ Successfully updated Dockerfile to version $LATEST_VERSION" echo "" echo "Changes made:" echo " - GOMPLATE_VERSION: $CURRENT_VERSION → $LATEST_VERSION" else echo "Error: Failed to update Dockerfile" exit 1 fi ================================================ FILE: server/api.go ================================================ package server import ( "context" "encoding/json" "errors" "fmt" "log/slog" "strconv" "golang.org/x/crypto/bcrypt" "github.com/dexidp/dex/api/v2" "github.com/dexidp/dex/pkg/featureflags" "github.com/dexidp/dex/server/internal" "github.com/dexidp/dex/storage" ) // apiVersion increases every time a new call is added to the API. Clients should use this info // to determine if the server supports specific features. const apiVersion = 3 const ( // recCost is the recommended bcrypt cost, which balances hash strength and // efficiency. recCost = 12 // upBoundCost is a sane upper bound on bcrypt cost determined by benchmarking: // high enough to ensure secure encryption, low enough to not put unnecessary // load on a dex server. upBoundCost = 16 ) // NewAPI returns a server which implements the gRPC API interface. func NewAPI(s storage.Storage, logger *slog.Logger, version string, server *Server) api.DexServer { return dexAPI{ s: s, logger: logger.With("component", "api"), version: version, server: server, } } type dexAPI struct { api.UnimplementedDexServer s storage.Storage logger *slog.Logger version string server *Server } func (d dexAPI) GetClient(ctx context.Context, req *api.GetClientReq) (*api.GetClientResp, error) { c, err := d.s.GetClient(ctx, req.Id) if err != nil { return nil, err } return &api.GetClientResp{ Client: &api.Client{ Id: c.ID, Name: c.Name, Secret: c.Secret, RedirectUris: c.RedirectURIs, TrustedPeers: c.TrustedPeers, Public: c.Public, LogoUrl: c.LogoURL, AllowedConnectors: c.AllowedConnectors, }, }, nil } func (d dexAPI) CreateClient(ctx context.Context, req *api.CreateClientReq) (*api.CreateClientResp, error) { if req.Client == nil { return nil, errors.New("no client supplied") } if req.Client.Id == "" { req.Client.Id = storage.NewID() } if req.Client.Secret == "" && !req.Client.Public { req.Client.Secret = storage.NewID() + storage.NewID() } c := storage.Client{ ID: req.Client.Id, Secret: req.Client.Secret, RedirectURIs: req.Client.RedirectUris, TrustedPeers: req.Client.TrustedPeers, Public: req.Client.Public, Name: req.Client.Name, LogoURL: req.Client.LogoUrl, AllowedConnectors: req.Client.AllowedConnectors, } if err := d.s.CreateClient(ctx, c); err != nil { if err == storage.ErrAlreadyExists { return &api.CreateClientResp{AlreadyExists: true}, nil } d.logger.Error("failed to create client", "err", err) return nil, fmt.Errorf("create client: %v", err) } return &api.CreateClientResp{ Client: req.Client, }, nil } func (d dexAPI) UpdateClient(ctx context.Context, req *api.UpdateClientReq) (*api.UpdateClientResp, error) { if req.Id == "" { return nil, errors.New("update client: no client ID supplied") } err := d.s.UpdateClient(ctx, req.Id, func(old storage.Client) (storage.Client, error) { if req.RedirectUris != nil { old.RedirectURIs = req.RedirectUris } if req.TrustedPeers != nil { old.TrustedPeers = req.TrustedPeers } if req.Name != "" { old.Name = req.Name } if req.LogoUrl != "" { old.LogoURL = req.LogoUrl } if req.AllowedConnectors != nil { old.AllowedConnectors = req.AllowedConnectors } return old, nil }) if err != nil { if err == storage.ErrNotFound { return &api.UpdateClientResp{NotFound: true}, nil } d.logger.Error("failed to update the client", "err", err) return nil, fmt.Errorf("update client: %v", err) } return &api.UpdateClientResp{}, nil } func (d dexAPI) DeleteClient(ctx context.Context, req *api.DeleteClientReq) (*api.DeleteClientResp, error) { err := d.s.DeleteClient(ctx, req.Id) if err != nil { if err == storage.ErrNotFound { return &api.DeleteClientResp{NotFound: true}, nil } d.logger.Error("failed to delete client", "err", err) return nil, fmt.Errorf("delete client: %v", err) } return &api.DeleteClientResp{}, nil } func (d dexAPI) ListClients(ctx context.Context, req *api.ListClientReq) (*api.ListClientResp, error) { clientList, err := d.s.ListClients(ctx) if err != nil { d.logger.Error("failed to list clients", "err", err) return nil, fmt.Errorf("list clients: %v", err) } clients := make([]*api.ClientInfo, 0, len(clientList)) for _, client := range clientList { c := api.ClientInfo{ Id: client.ID, Name: client.Name, RedirectUris: client.RedirectURIs, TrustedPeers: client.TrustedPeers, Public: client.Public, LogoUrl: client.LogoURL, AllowedConnectors: client.AllowedConnectors, } clients = append(clients, &c) } return &api.ListClientResp{ Clients: clients, }, nil } // checkCost returns an error if the hash provided does not meet lower or upper // bound cost requirements. func checkCost(hash []byte) error { actual, err := bcrypt.Cost(hash) if err != nil { return fmt.Errorf("parsing bcrypt hash: %v", err) } if actual < bcrypt.DefaultCost { return fmt.Errorf("given hash cost = %d does not meet minimum cost requirement = %d", actual, bcrypt.DefaultCost) } if actual > upBoundCost { return fmt.Errorf("given hash cost = %d is above upper bound cost = %d, recommended cost = %d", actual, upBoundCost, recCost) } return nil } func (d dexAPI) CreatePassword(ctx context.Context, req *api.CreatePasswordReq) (*api.CreatePasswordResp, error) { if req.Password == nil { return nil, errors.New("no password supplied") } if req.Password.UserId == "" { return nil, errors.New("no user ID supplied") } if req.Password.Hash != nil { if err := checkCost(req.Password.Hash); err != nil { return nil, err } } else { return nil, errors.New("no hash of password supplied") } p := storage.Password{ Email: req.Password.Email, Hash: req.Password.Hash, Username: req.Password.Username, UserID: req.Password.UserId, } if err := d.s.CreatePassword(ctx, p); err != nil { if err == storage.ErrAlreadyExists { return &api.CreatePasswordResp{AlreadyExists: true}, nil } d.logger.Error("failed to create password", "err", err) return nil, fmt.Errorf("create password: %v", err) } return &api.CreatePasswordResp{}, nil } func (d dexAPI) UpdatePassword(ctx context.Context, req *api.UpdatePasswordReq) (*api.UpdatePasswordResp, error) { if req.Email == "" { return nil, errors.New("no email supplied") } if req.NewHash == nil && req.NewUsername == "" { return nil, errors.New("nothing to update") } if req.NewHash != nil { if err := checkCost(req.NewHash); err != nil { return nil, err } } updater := func(old storage.Password) (storage.Password, error) { if req.NewHash != nil { old.Hash = req.NewHash } if req.NewUsername != "" { old.Username = req.NewUsername } return old, nil } if err := d.s.UpdatePassword(ctx, req.Email, updater); err != nil { if err == storage.ErrNotFound { return &api.UpdatePasswordResp{NotFound: true}, nil } d.logger.Error("failed to update password", "err", err) return nil, fmt.Errorf("update password: %v", err) } return &api.UpdatePasswordResp{}, nil } func (d dexAPI) DeletePassword(ctx context.Context, req *api.DeletePasswordReq) (*api.DeletePasswordResp, error) { if req.Email == "" { return nil, errors.New("no email supplied") } err := d.s.DeletePassword(ctx, req.Email) if err != nil { if err == storage.ErrNotFound { return &api.DeletePasswordResp{NotFound: true}, nil } d.logger.Error("failed to delete password", "err", err) return nil, fmt.Errorf("delete password: %v", err) } return &api.DeletePasswordResp{}, nil } func (d dexAPI) GetVersion(ctx context.Context, req *api.VersionReq) (*api.VersionResp, error) { return &api.VersionResp{ Server: d.version, Api: apiVersion, }, nil } func (d dexAPI) GetDiscovery(ctx context.Context, req *api.DiscoveryReq) (*api.DiscoveryResp, error) { discoveryDoc := d.server.constructDiscovery(ctx) data, err := json.Marshal(discoveryDoc) if err != nil { return nil, fmt.Errorf("failed to marshal discovery data: %v", err) } resp := api.DiscoveryResp{} err = json.Unmarshal(data, &resp) if err != nil { return nil, fmt.Errorf("failed to unmarshal discovery data: %v", err) } return &resp, nil } func (d dexAPI) ListPasswords(ctx context.Context, req *api.ListPasswordReq) (*api.ListPasswordResp, error) { passwordList, err := d.s.ListPasswords(ctx) if err != nil { d.logger.Error("failed to list passwords", "err", err) return nil, fmt.Errorf("list passwords: %v", err) } passwords := make([]*api.Password, 0, len(passwordList)) for _, password := range passwordList { p := api.Password{ Email: password.Email, Username: password.Username, UserId: password.UserID, } passwords = append(passwords, &p) } return &api.ListPasswordResp{ Passwords: passwords, }, nil } func (d dexAPI) VerifyPassword(ctx context.Context, req *api.VerifyPasswordReq) (*api.VerifyPasswordResp, error) { if req.Email == "" { return nil, errors.New("no email supplied") } if req.Password == "" { return nil, errors.New("no password to verify supplied") } password, err := d.s.GetPassword(ctx, req.Email) if err != nil { if err == storage.ErrNotFound { return &api.VerifyPasswordResp{ NotFound: true, }, nil } d.logger.Error("there was an error retrieving the password", "err", err) return nil, fmt.Errorf("verify password: %v", err) } if err := bcrypt.CompareHashAndPassword(password.Hash, []byte(req.Password)); err != nil { d.logger.Info("password check failed", "err", err) return &api.VerifyPasswordResp{ Verified: false, }, nil } return &api.VerifyPasswordResp{ Verified: true, }, nil } func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api.ListRefreshResp, error) { id := new(internal.IDTokenSubject) if err := internal.Unmarshal(req.UserId, id); err != nil { d.logger.Error("failed to unmarshal ID Token subject", "err", err) return nil, err } offlineSessions, err := d.s.GetOfflineSessions(ctx, id.UserId, id.ConnId) if err != nil { if err == storage.ErrNotFound { // This means that this user-client pair does not have a refresh token yet. // An empty list should be returned instead of an error. return &api.ListRefreshResp{}, nil } d.logger.Error("failed to list refresh tokens here", "err", err) return nil, err } refreshTokenRefs := make([]*api.RefreshTokenRef, 0, len(offlineSessions.Refresh)) for _, session := range offlineSessions.Refresh { r := api.RefreshTokenRef{ Id: session.ID, ClientId: session.ClientID, CreatedAt: session.CreatedAt.Unix(), LastUsed: session.LastUsed.Unix(), } refreshTokenRefs = append(refreshTokenRefs, &r) } return &api.ListRefreshResp{ RefreshTokens: refreshTokenRefs, }, nil } func (d dexAPI) RevokeRefresh(ctx context.Context, req *api.RevokeRefreshReq) (*api.RevokeRefreshResp, error) { id := new(internal.IDTokenSubject) if err := internal.Unmarshal(req.UserId, id); err != nil { d.logger.Error("failed to unmarshal ID Token subject", "err", err) return nil, err } var ( refreshID string notFound bool ) updater := func(old storage.OfflineSessions) (storage.OfflineSessions, error) { refreshRef := old.Refresh[req.ClientId] if refreshRef == nil || refreshRef.ID == "" { d.logger.Error("refresh token issued to client not found for deletion", "client_id", req.ClientId, "user_id", id.UserId) notFound = true return old, storage.ErrNotFound } refreshID = refreshRef.ID // Remove entry from Refresh list of the OfflineSession object. delete(old.Refresh, req.ClientId) return old, nil } if err := d.s.UpdateOfflineSessions(ctx, id.UserId, id.ConnId, updater); err != nil { if err == storage.ErrNotFound { return &api.RevokeRefreshResp{NotFound: true}, nil } d.logger.Error("failed to update offline session object", "err", err) return nil, err } if notFound { return &api.RevokeRefreshResp{NotFound: true}, nil } // Delete the refresh token from the storage // // TODO(ericchiang): we don't have any good recourse if this call fails. // Consider garbage collection of refresh tokens with no associated ref. if err := d.s.DeleteRefresh(ctx, refreshID); err != nil { d.logger.Error("failed to delete refresh token", "err", err) return nil, err } return &api.RevokeRefreshResp{}, nil } func (d dexAPI) CreateConnector(ctx context.Context, req *api.CreateConnectorReq) (*api.CreateConnectorResp, error) { if !featureflags.APIConnectorsCRUD.Enabled() { return nil, fmt.Errorf("%s feature flag is not enabled", featureflags.APIConnectorsCRUD.Name) } if req.Connector.Id == "" { return nil, errors.New("no id supplied") } if req.Connector.Type == "" { return nil, errors.New("no type supplied") } if req.Connector.Name == "" { return nil, errors.New("no name supplied") } if len(req.Connector.Config) == 0 { return nil, errors.New("no config supplied") } if !json.Valid(req.Connector.Config) { return nil, errors.New("invalid config supplied") } for _, gt := range req.Connector.GrantTypes { if !ConnectorGrantTypes[gt] { return nil, fmt.Errorf("unknown grant type %q", gt) } } c := storage.Connector{ ID: req.Connector.Id, Name: req.Connector.Name, Type: req.Connector.Type, ResourceVersion: "1", Config: req.Connector.Config, GrantTypes: req.Connector.GrantTypes, } if err := d.s.CreateConnector(ctx, c); err != nil { if err == storage.ErrAlreadyExists { return &api.CreateConnectorResp{AlreadyExists: true}, nil } d.logger.Error("api: failed to create connector", "err", err) return nil, fmt.Errorf("create connector: %v", err) } // Make sure we don't reuse stale entries in the cache if d.server != nil { d.server.CloseConnector(req.Connector.Id) } return &api.CreateConnectorResp{}, nil } func (d dexAPI) UpdateConnector(ctx context.Context, req *api.UpdateConnectorReq) (*api.UpdateConnectorResp, error) { if !featureflags.APIConnectorsCRUD.Enabled() { return nil, fmt.Errorf("%s feature flag is not enabled", featureflags.APIConnectorsCRUD.Name) } if req.Id == "" { return nil, errors.New("no email supplied") } hasUpdate := len(req.NewConfig) != 0 || req.NewName != "" || req.NewType != "" || req.NewGrantTypes != nil if !hasUpdate { return nil, errors.New("nothing to update") } if len(req.NewConfig) != 0 && !json.Valid(req.NewConfig) { return nil, errors.New("invalid config supplied") } if req.NewGrantTypes != nil { for _, gt := range req.NewGrantTypes.GrantTypes { if !ConnectorGrantTypes[gt] { return nil, fmt.Errorf("unknown grant type %q", gt) } } } updater := func(old storage.Connector) (storage.Connector, error) { if req.NewType != "" { old.Type = req.NewType } if req.NewName != "" { old.Name = req.NewName } if len(req.NewConfig) != 0 { old.Config = req.NewConfig } if req.NewGrantTypes != nil { old.GrantTypes = req.NewGrantTypes.GrantTypes } if rev, err := strconv.Atoi(defaultTo(old.ResourceVersion, "0")); err == nil { old.ResourceVersion = strconv.Itoa(rev + 1) } return old, nil } if err := d.s.UpdateConnector(ctx, req.Id, updater); err != nil { if err == storage.ErrNotFound { return &api.UpdateConnectorResp{NotFound: true}, nil } d.logger.Error("api: failed to update connector", "err", err) return nil, fmt.Errorf("update connector: %v", err) } return &api.UpdateConnectorResp{}, nil } func (d dexAPI) DeleteConnector(ctx context.Context, req *api.DeleteConnectorReq) (*api.DeleteConnectorResp, error) { if !featureflags.APIConnectorsCRUD.Enabled() { return nil, fmt.Errorf("%s feature flag is not enabled", featureflags.APIConnectorsCRUD.Name) } if req.Id == "" { return nil, errors.New("no id supplied") } err := d.s.DeleteConnector(ctx, req.Id) if err != nil { if err == storage.ErrNotFound { return &api.DeleteConnectorResp{NotFound: true}, nil } d.logger.Error("api: failed to delete connector", "err", err) return nil, fmt.Errorf("delete connector: %v", err) } return &api.DeleteConnectorResp{}, nil } func (d dexAPI) ListConnectors(ctx context.Context, req *api.ListConnectorReq) (*api.ListConnectorResp, error) { if !featureflags.APIConnectorsCRUD.Enabled() { return nil, fmt.Errorf("%s feature flag is not enabled", featureflags.APIConnectorsCRUD.Name) } connectorList, err := d.s.ListConnectors(ctx) if err != nil { d.logger.Error("api: failed to list connectors", "err", err) return nil, fmt.Errorf("list connectors: %v", err) } connectors := make([]*api.Connector, 0, len(connectorList)) for _, connector := range connectorList { c := api.Connector{ Id: connector.ID, Name: connector.Name, Type: connector.Type, Config: connector.Config, GrantTypes: connector.GrantTypes, } connectors = append(connectors, &c) } return &api.ListConnectorResp{ Connectors: connectors, }, nil } func defaultTo[T comparable](v, def T) T { var zeroT T if v == zeroT { return def } return v } ================================================ FILE: server/api_cache_test.go ================================================ package server import ( "context" "encoding/json" "testing" "github.com/dexidp/dex/api/v2" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/connector/mock" "github.com/dexidp/dex/storage/memory" ) func TestConnectorCacheInvalidation(t *testing.T) { t.Setenv("DEX_API_CONNECTORS_CRUD", "true") logger := newLogger(t) s := memory.New(logger) serv := &Server{ storage: s, logger: logger, connectors: make(map[string]Connector), } apiServer := NewAPI(s, logger, "test", serv) ctx := context.Background() connID := "mock-conn" // 1. Create a connector via API config1 := mock.PasswordConfig{ Username: "user", Password: "first-password", } config1Bytes, _ := json.Marshal(config1) _, err := apiServer.CreateConnector(ctx, &api.CreateConnectorReq{ Connector: &api.Connector{ Id: connID, Type: "mockPassword", Name: "Mock", Config: config1Bytes, }, }) if err != nil { t.Fatalf("failed to create connector: %v", err) } // 2. Load it into server cache c1, err := serv.getConnector(ctx, connID) if err != nil { t.Fatalf("failed to get connector: %v", err) } pc1 := c1.Connector.(connector.PasswordConnector) _, valid, err := pc1.Login(ctx, connector.Scopes{}, "user", "first-password") if err != nil || !valid { t.Fatalf("failed to login with first password: %v", err) } // 3. Delete it via API _, err = apiServer.DeleteConnector(ctx, &api.DeleteConnectorReq{Id: connID}) if err != nil { t.Fatalf("failed to delete connector: %v", err) } // 4. Create it again with different password config2 := mock.PasswordConfig{ Username: "user", Password: "second-password", } config2Bytes, _ := json.Marshal(config2) _, err = apiServer.CreateConnector(ctx, &api.CreateConnectorReq{ Connector: &api.Connector{ Id: connID, Type: "mockPassword", Name: "Mock", Config: config2Bytes, }, }) if err != nil { t.Fatalf("failed to create connector: %v", err) } // 5. Load it again c2, err := serv.getConnector(ctx, connID) if err != nil { t.Fatalf("failed to get connector second time: %v", err) } pc2 := c2.Connector.(connector.PasswordConnector) // If the fix works, it should now use the second password. _, valid2, err := pc2.Login(ctx, connector.Scopes{}, "user", "second-password") if err != nil || !valid2 { t.Errorf("failed to login with second password, cache might still be stale") } _, valid1, _ := pc2.Login(ctx, connector.Scopes{}, "user", "first-password") if valid1 { t.Errorf("unexpectedly logged in with first password, cache is definitely stale") } // 6. Update it via API with a third password config3 := mock.PasswordConfig{ Username: "user", Password: "third-password", } config3Bytes, _ := json.Marshal(config3) _, err = apiServer.UpdateConnector(ctx, &api.UpdateConnectorReq{ Id: connID, NewConfig: config3Bytes, }) if err != nil { t.Fatalf("failed to update connector: %v", err) } // 7. Load it again c3, err := serv.getConnector(ctx, connID) if err != nil { t.Fatalf("failed to get connector third time: %v", err) } pc3 := c3.Connector.(connector.PasswordConnector) _, valid3, err := pc3.Login(ctx, connector.Scopes{}, "user", "third-password") if err != nil || !valid3 { t.Errorf("failed to login with third password, UpdateConnector might be missing cache invalidation") } } ================================================ FILE: server/api_test.go ================================================ package server import ( "log/slog" "net" "slices" "strings" "testing" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "github.com/dexidp/dex/api/v2" "github.com/dexidp/dex/server/internal" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/memory" ) // apiClient is a test gRPC client. When constructed, it runs a server in // the background to exercise the serialization and network configuration // instead of just this package's server implementation. type apiClient struct { // Embedded gRPC client to talk to the server. api.DexClient // Close releases resources associated with this client, including shutting // down the background server. Close func() } func newLogger(t *testing.T) *slog.Logger { return slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug})) } // newAPI constructs a gRCP client connected to a backing server. func newAPI(t *testing.T, s storage.Storage, logger *slog.Logger) *apiClient { l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } serv := grpc.NewServer() api.RegisterDexServer(serv, NewAPI(s, logger, "test", nil)) go serv.Serve(l) // NewClient will retry automatically if the serv.Serve() goroutine // hasn't started yet. conn, err := grpc.NewClient(l.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { t.Fatal(err) } return &apiClient{ DexClient: api.NewDexClient(conn), Close: func() { conn.Close() serv.Stop() l.Close() }, } } // Attempts to create, update and delete a test Password func TestPassword(t *testing.T) { logger := newLogger(t) s := memory.New(logger) client := newAPI(t, s, logger) defer client.Close() ctx := t.Context() email := "test@example.com" p := api.Password{ Email: email, // bcrypt hash of the value "test1" with cost 10 Hash: []byte("$2a$10$XVMN/Fid.Ks4CXgzo8fpR.iU1khOMsP5g9xQeXuBm1wXjRX8pjUtO"), Username: "test", UserId: "test123", } createReq := api.CreatePasswordReq{ Password: &p, } if resp, err := client.CreatePassword(ctx, &createReq); err != nil || resp.AlreadyExists { if resp.AlreadyExists { t.Fatalf("Unable to create password since %s already exists", createReq.Password.Email) } t.Fatalf("Unable to create password: %v", err) } // Attempt to create a password that already exists. if resp, _ := client.CreatePassword(ctx, &createReq); !resp.AlreadyExists { t.Fatalf("Created password %s twice", createReq.Password.Email) } // Attempt to verify valid password and email goodVerifyReq := &api.VerifyPasswordReq{ Email: email, Password: "test1", } goodVerifyResp, err := client.VerifyPassword(ctx, goodVerifyReq) if err != nil { t.Fatalf("Unable to run verify password we expected to be valid for correct email: %v", err) } if !goodVerifyResp.Verified { t.Fatalf("verify password failed for password expected to be valid for correct email. expected %t, found %t", true, goodVerifyResp.Verified) } if goodVerifyResp.NotFound { t.Fatalf("verify password failed to return not found response. expected %t, found %t", false, goodVerifyResp.NotFound) } // Check not found response for valid password with wrong email badEmailVerifyReq := &api.VerifyPasswordReq{ Email: "somewrongaddress@email.com", Password: "test1", } badEmailVerifyResp, err := client.VerifyPassword(ctx, badEmailVerifyReq) if err != nil { t.Fatalf("Unable to run verify password for incorrect email: %v", err) } if badEmailVerifyResp.Verified { t.Fatalf("verify password passed for password expected to be not found. expected %t, found %t", false, badEmailVerifyResp.Verified) } if !badEmailVerifyResp.NotFound { t.Fatalf("expected not found response for verify password with bad email. expected %t, found %t", true, badEmailVerifyResp.NotFound) } // Check that wrong password fails badPassVerifyReq := &api.VerifyPasswordReq{ Email: email, Password: "wrong_password", } badPassVerifyResp, err := client.VerifyPassword(ctx, badPassVerifyReq) if err != nil { t.Fatalf("Unable to run verify password for password we expected to be invalid: %v", err) } if badPassVerifyResp.Verified { t.Fatalf("verify password passed for password we expected to fail. expected %t, found %t", false, badPassVerifyResp.Verified) } if badPassVerifyResp.NotFound { t.Fatalf("did not expect expected not found response for verify password with bad email. expected %t, found %t", false, badPassVerifyResp.NotFound) } updateReq := api.UpdatePasswordReq{ Email: email, NewUsername: "test1", } if _, err := client.UpdatePassword(ctx, &updateReq); err != nil { t.Fatalf("Unable to update password: %v", err) } pass, err := s.GetPassword(ctx, updateReq.Email) if err != nil { t.Fatalf("Unable to retrieve password: %v", err) } if pass.Username != updateReq.NewUsername { t.Fatalf("UpdatePassword failed. Expected username %s retrieved %s", updateReq.NewUsername, pass.Username) } deleteReq := api.DeletePasswordReq{ Email: "test@example.com", } if _, err := client.DeletePassword(ctx, &deleteReq); err != nil { t.Fatalf("Unable to delete password: %v", err) } } // Ensures checkCost returns expected values func TestCheckCost(t *testing.T) { logger := newLogger(t) s := memory.New(logger) client := newAPI(t, s, logger) defer client.Close() tests := []struct { name string inputHash []byte wantErr bool }{ { name: "valid cost", // bcrypt hash of the value "test1" with cost 12 (default) inputHash: []byte("$2a$12$M2Ot95Qty1MuQdubh1acWOiYadJDzeVg3ve4n5b.dgcgPdjCseKx2"), }, { name: "invalid hash", inputHash: []byte(""), wantErr: true, }, { name: "cost below default", // bcrypt hash of the value "test1" with cost 4 inputHash: []byte("$2a$04$8bSTbuVCLpKzaqB3BmgI7edDigG5tIQKkjYUu/mEO9gQgIkw9m7eG"), wantErr: true, }, { name: "cost above recommendation", // bcrypt hash of the value "test1" with cost 17 inputHash: []byte("$2a$17$tWuZkTxtSmRyWZAGWVHQE.7npdl.TgP8adjzLJD.SyjpFznKBftPe"), wantErr: true, }, } for _, tc := range tests { if err := checkCost(tc.inputHash); err != nil { if !tc.wantErr { t.Errorf("%s: %s", tc.name, err) } continue } if tc.wantErr { t.Errorf("%s: expected err", tc.name) continue } } } // Attempts to list and revoke an existing refresh token. func TestRefreshToken(t *testing.T) { logger := newLogger(t) s := memory.New(logger) client := newAPI(t, s, logger) defer client.Close() ctx := t.Context() // Creating a storage with an existing refresh token and offline session for the user. id := storage.NewID() r := storage.RefreshToken{ ID: id, Token: "bar", Nonce: "foo", ClientID: "client_id", ConnectorID: "client_secret", Scopes: []string{"openid", "email", "profile"}, CreatedAt: time.Now().UTC().Round(time.Millisecond), LastUsed: time.Now().UTC().Round(time.Millisecond), Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, ConnectorData: []byte(`{"some":"data"}`), } if err := s.CreateRefresh(ctx, r); err != nil { t.Fatalf("create refresh token: %v", err) } tokenRef := storage.RefreshTokenRef{ ID: r.ID, ClientID: r.ClientID, CreatedAt: r.CreatedAt, LastUsed: r.LastUsed, } session := storage.OfflineSessions{ UserID: r.Claims.UserID, ConnID: r.ConnectorID, Refresh: make(map[string]*storage.RefreshTokenRef), } session.Refresh[tokenRef.ClientID] = &tokenRef if err := s.CreateOfflineSessions(ctx, session); err != nil { t.Fatalf("create offline session: %v", err) } subjectString, err := internal.Marshal(&internal.IDTokenSubject{ UserId: r.Claims.UserID, ConnId: r.ConnectorID, }) if err != nil { t.Errorf("failed to marshal offline session ID: %v", err) } // Testing the api. listReq := api.ListRefreshReq{ UserId: subjectString, } listResp, err := client.ListRefresh(ctx, &listReq) if err != nil { t.Fatalf("Unable to list refresh tokens for user: %v", err) } for _, tok := range listResp.RefreshTokens { if tok.CreatedAt != r.CreatedAt.Unix() { t.Errorf("Expected CreatedAt timestamp %v, got %v", r.CreatedAt.Unix(), tok.CreatedAt) } if tok.LastUsed != r.LastUsed.Unix() { t.Errorf("Expected LastUsed timestamp %v, got %v", r.LastUsed.Unix(), tok.LastUsed) } } revokeReq := api.RevokeRefreshReq{ UserId: subjectString, ClientId: r.ClientID, } resp, err := client.RevokeRefresh(ctx, &revokeReq) if err != nil { t.Fatalf("Unable to revoke refresh tokens for user: %v", err) } if resp.NotFound { t.Errorf("refresh token session wasn't found") } // Try to delete again. // // See https://github.com/dexidp/dex/issues/1055 resp, err = client.RevokeRefresh(ctx, &revokeReq) if err != nil { t.Fatalf("Unable to revoke refresh tokens for user: %v", err) } if !resp.NotFound { t.Errorf("refresh token session was found") } if resp, _ := client.ListRefresh(ctx, &listReq); len(resp.RefreshTokens) != 0 { t.Fatalf("Refresh token returned in spite of revoking it.") } } func TestUpdateClient(t *testing.T) { logger := newLogger(t) s := memory.New(logger) client := newAPI(t, s, logger) defer client.Close() ctx := t.Context() createClient := func(t *testing.T, clientId string) { resp, err := client.CreateClient(ctx, &api.CreateClientReq{ Client: &api.Client{ Id: clientId, Secret: "", RedirectUris: []string{}, TrustedPeers: nil, Public: true, Name: "", LogoUrl: "", }, }) if err != nil { t.Fatalf("unable to create the client: %v", err) } if resp == nil { t.Fatalf("create client returned no response") } if resp.AlreadyExists { t.Error("existing client was found") } if resp.Client == nil { t.Fatalf("no client created") } } deleteClient := func(t *testing.T, clientId string) { resp, err := client.DeleteClient(ctx, &api.DeleteClientReq{ Id: clientId, }) if err != nil { t.Fatalf("unable to delete the client: %v", err) } if resp == nil { t.Fatalf("delete client delete client returned no response") } } tests := map[string]struct { setup func(t *testing.T, clientId string) cleanup func(t *testing.T, clientId string) req *api.UpdateClientReq wantErr bool want *api.UpdateClientResp }{ "update client": { setup: createClient, cleanup: deleteClient, req: &api.UpdateClientReq{ Id: "test", RedirectUris: []string{"https://redirect"}, TrustedPeers: []string{"test"}, Name: "test", LogoUrl: "https://logout", }, wantErr: false, want: &api.UpdateClientResp{ NotFound: false, }, }, "update client without ID": { setup: createClient, cleanup: deleteClient, req: &api.UpdateClientReq{ Id: "", RedirectUris: nil, TrustedPeers: nil, Name: "test", LogoUrl: "test", }, wantErr: true, want: &api.UpdateClientResp{ NotFound: false, }, }, "update client which not exists ": { req: &api.UpdateClientReq{ Id: "test", RedirectUris: nil, TrustedPeers: nil, Name: "test", LogoUrl: "test", }, wantErr: true, want: &api.UpdateClientResp{ NotFound: false, }, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { if tc.setup != nil { tc.setup(t, tc.req.Id) } resp, err := client.UpdateClient(ctx, tc.req) if err != nil && !tc.wantErr { t.Fatalf("failed to update the client: %v", err) } if !tc.wantErr { if resp == nil { t.Fatalf("update client response not found") } if tc.want.NotFound != resp.NotFound { t.Errorf("expected in response NotFound: %t", tc.want.NotFound) } client, err := s.GetClient(ctx, tc.req.Id) if err != nil { t.Errorf("no client found in the storage: %v", err) } if tc.req.Id != client.ID { t.Errorf("expected stored client with ID: %s, found %s", tc.req.Id, client.ID) } if tc.req.Name != client.Name { t.Errorf("expected stored client with Name: %s, found %s", tc.req.Name, client.Name) } if tc.req.LogoUrl != client.LogoURL { t.Errorf("expected stored client with LogoURL: %s, found %s", tc.req.LogoUrl, client.LogoURL) } for _, redirectURI := range tc.req.RedirectUris { found := slices.Contains(client.RedirectURIs, redirectURI) if !found { t.Errorf("expected redirect URI: %s", redirectURI) } } for _, peer := range tc.req.TrustedPeers { found := slices.Contains(client.TrustedPeers, peer) if !found { t.Errorf("expected trusted peer: %s", peer) } } } if tc.cleanup != nil { tc.cleanup(t, tc.req.Id) } }) } } func TestCreateConnector(t *testing.T) { t.Setenv("DEX_API_CONNECTORS_CRUD", "true") logger := newLogger(t) s := memory.New(logger) client := newAPI(t, s, logger) defer client.Close() ctx := t.Context() connectorID := "connector123" connectorName := "TestConnector" connectorType := "TestType" connectorConfig := []byte(`{"key": "value"}`) createReq := api.CreateConnectorReq{ Connector: &api.Connector{ Id: connectorID, Name: connectorName, Type: connectorType, Config: connectorConfig, }, } // Test valid connector creation if resp, err := client.CreateConnector(ctx, &createReq); err != nil || resp.AlreadyExists { if err != nil { t.Fatalf("Unable to create connector: %v", err) } else if resp.AlreadyExists { t.Fatalf("Unable to create connector since %s already exists", connectorID) } t.Fatalf("Unable to create connector: %v", err) } // Test creating the same connector again (expecting failure) if resp, _ := client.CreateConnector(ctx, &createReq); !resp.AlreadyExists { t.Fatalf("Created connector %s twice", connectorID) } createReq.Connector.Config = []byte("invalid_json") // Test invalid JSON config if _, err := client.CreateConnector(ctx, &createReq); err == nil { t.Fatal("Expected an error for invalid JSON config, but none occurred") } else if !strings.Contains(err.Error(), "invalid config supplied") { t.Fatalf("Unexpected error: %v", err) } } func TestUpdateConnector(t *testing.T) { t.Setenv("DEX_API_CONNECTORS_CRUD", "true") logger := newLogger(t) s := memory.New(logger) client := newAPI(t, s, logger) defer client.Close() ctx := t.Context() connectorID := "connector123" newConnectorName := "UpdatedConnector" newConnectorType := "UpdatedType" newConnectorConfig := []byte(`{"updated_key": "updated_value"}`) // Create a connector for testing createReq := api.CreateConnectorReq{ Connector: &api.Connector{ Id: connectorID, Name: "TestConnector", Type: "TestType", Config: []byte(`{"key": "value"}`), }, } client.CreateConnector(ctx, &createReq) updateReq := api.UpdateConnectorReq{ Id: connectorID, NewName: newConnectorName, NewType: newConnectorType, NewConfig: newConnectorConfig, } // Test valid connector update if _, err := client.UpdateConnector(ctx, &updateReq); err != nil { t.Fatalf("Unable to update connector: %v", err) } resp, err := client.ListConnectors(ctx, &api.ListConnectorReq{}) if err != nil { t.Fatalf("Unexpected error: %v", err) } for _, connector := range resp.Connectors { if connector.Id == connectorID { if connector.Name != newConnectorName { t.Fatal("connector name should have been updated") } if string(connector.Config) != string(newConnectorConfig) { t.Fatal("connector config should have been updated") } if connector.Type != newConnectorType { t.Fatal("connector type should have been updated") } } } updateReq.NewConfig = []byte("invalid_json") // Test invalid JSON config in update request if _, err := client.UpdateConnector(ctx, &updateReq); err == nil { t.Fatal("Expected an error for invalid JSON config in update, but none occurred") } else if !strings.Contains(err.Error(), "invalid config supplied") { t.Fatalf("Unexpected error: %v", err) } } func TestUpdateConnectorGrantTypes(t *testing.T) { t.Setenv("DEX_API_CONNECTORS_CRUD", "true") logger := newLogger(t) s := memory.New(logger) client := newAPI(t, s, logger) defer client.Close() ctx := t.Context() connectorID := "connector-gt" // Create a connector without grant types createReq := api.CreateConnectorReq{ Connector: &api.Connector{ Id: connectorID, Name: "TestConnector", Type: "TestType", Config: []byte(`{"key": "value"}`), }, } _, err := client.CreateConnector(ctx, &createReq) if err != nil { t.Fatalf("failed to create connector: %v", err) } // Set grant types _, err = client.UpdateConnector(ctx, &api.UpdateConnectorReq{ Id: connectorID, NewGrantTypes: &api.GrantTypes{GrantTypes: []string{"authorization_code", "refresh_token"}}, }) if err != nil { t.Fatalf("failed to update connector grant types: %v", err) } resp, err := client.ListConnectors(ctx, &api.ListConnectorReq{}) if err != nil { t.Fatalf("failed to list connectors: %v", err) } for _, c := range resp.Connectors { if c.Id == connectorID { if !slices.Equal(c.GrantTypes, []string{"authorization_code", "refresh_token"}) { t.Fatalf("expected grant types [authorization_code refresh_token], got %v", c.GrantTypes) } } } // Clear grant types by passing empty GrantTypes message _, err = client.UpdateConnector(ctx, &api.UpdateConnectorReq{ Id: connectorID, NewGrantTypes: &api.GrantTypes{}, }) if err != nil { t.Fatalf("failed to clear connector grant types: %v", err) } resp, err = client.ListConnectors(ctx, &api.ListConnectorReq{}) if err != nil { t.Fatalf("failed to list connectors: %v", err) } for _, c := range resp.Connectors { if c.Id == connectorID { if len(c.GrantTypes) != 0 { t.Fatalf("expected empty grant types after clear, got %v", c.GrantTypes) } } } // Reject invalid grant type on update _, err = client.UpdateConnector(ctx, &api.UpdateConnectorReq{ Id: connectorID, NewGrantTypes: &api.GrantTypes{GrantTypes: []string{"bogus"}}, }) if err == nil { t.Fatal("expected error for invalid grant type, got nil") } if !strings.Contains(err.Error(), `unknown grant type "bogus"`) { t.Fatalf("unexpected error: %v", err) } // Reject invalid grant type on create _, err = client.CreateConnector(ctx, &api.CreateConnectorReq{ Connector: &api.Connector{ Id: "bad-gt", Name: "Bad", Type: "TestType", Config: []byte(`{}`), GrantTypes: []string{"invalid_type"}, }, }) if err == nil { t.Fatal("expected error for invalid grant type on create, got nil") } if !strings.Contains(err.Error(), `unknown grant type "invalid_type"`) { t.Fatalf("unexpected error: %v", err) } } func TestDeleteConnector(t *testing.T) { t.Setenv("DEX_API_CONNECTORS_CRUD", "true") logger := newLogger(t) s := memory.New(logger) client := newAPI(t, s, logger) defer client.Close() ctx := t.Context() connectorID := "connector123" // Create a connector for testing createReq := api.CreateConnectorReq{ Connector: &api.Connector{ Id: connectorID, Name: "TestConnector", Type: "TestType", Config: []byte(`{"key": "value"}`), }, } client.CreateConnector(ctx, &createReq) deleteReq := api.DeleteConnectorReq{ Id: connectorID, } // Test valid connector deletion if _, err := client.DeleteConnector(ctx, &deleteReq); err != nil { t.Fatalf("Unable to delete connector: %v", err) } // Test non existent connector deletion resp, err := client.DeleteConnector(ctx, &deleteReq) if err != nil { t.Fatalf("Unable to delete connector: %v", err) } if !resp.NotFound { t.Fatal("Should return not found") } } func TestListConnectors(t *testing.T) { t.Setenv("DEX_API_CONNECTORS_CRUD", "true") logger := newLogger(t) s := memory.New(logger) client := newAPI(t, s, logger) defer client.Close() ctx := t.Context() // Create connectors for testing createReq1 := api.CreateConnectorReq{ Connector: &api.Connector{ Id: "connector1", Name: "Connector1", Type: "Type1", Config: []byte(`{"key": "value1"}`), }, } client.CreateConnector(ctx, &createReq1) createReq2 := api.CreateConnectorReq{ Connector: &api.Connector{ Id: "connector2", Name: "Connector2", Type: "Type2", Config: []byte(`{"key": "value2"}`), }, } client.CreateConnector(ctx, &createReq2) listReq := api.ListConnectorReq{} // Test listing connectors if resp, err := client.ListConnectors(ctx, &listReq); err != nil { t.Fatalf("Unable to list connectors: %v", err) } else if len(resp.Connectors) != 2 { // Check the number of connectors in the response t.Fatalf("Expected 2 connectors, found %d", len(resp.Connectors)) } } func TestMissingConnectorsCRUDFeatureFlag(t *testing.T) { logger := newLogger(t) s := memory.New(logger) client := newAPI(t, s, logger) defer client.Close() ctx := t.Context() // Create connectors for testing createReq1 := api.CreateConnectorReq{ Connector: &api.Connector{ Id: "connector1", Name: "Connector1", Type: "Type1", Config: []byte(`{"key": "value1"}`), }, } client.CreateConnector(ctx, &createReq1) createReq2 := api.CreateConnectorReq{ Connector: &api.Connector{ Id: "connector2", Name: "Connector2", Type: "Type2", Config: []byte(`{"key": "value2"}`), }, } client.CreateConnector(ctx, &createReq2) listReq := api.ListConnectorReq{} if _, err := client.ListConnectors(ctx, &listReq); err == nil { t.Fatal("ListConnectors should have returned an error") } } func TestListClients(t *testing.T) { logger := newLogger(t) s := memory.New(logger) client := newAPI(t, s, logger) defer client.Close() ctx := t.Context() // List Clients listResp, err := client.ListClients(ctx, &api.ListClientReq{}) if err != nil { t.Fatalf("Unable to list clients: %v", err) } if len(listResp.Clients) != 0 { t.Fatalf("Expected 0 clients, got %d", len(listResp.Clients)) } client1 := &api.Client{ Id: "client1", Secret: "secret1", RedirectUris: []string{"http://localhost:8080/callback"}, TrustedPeers: []string{"peer1"}, Public: false, Name: "Test Client 1", LogoUrl: "http://example.com/logo1.png", } client2 := &api.Client{ Id: "client2", Secret: "secret2", RedirectUris: []string{"http://localhost:8081/callback"}, TrustedPeers: []string{"peer2"}, Public: true, Name: "Test Client 2", LogoUrl: "http://example.com/logo2.png", } _, err = client.CreateClient(ctx, &api.CreateClientReq{Client: client1}) if err != nil { t.Fatalf("Unable to create client1: %v", err) } _, err = client.CreateClient(ctx, &api.CreateClientReq{Client: client2}) if err != nil { t.Fatalf("Unable to create client2: %v", err) } listResp, err = client.ListClients(ctx, &api.ListClientReq{}) if err != nil { t.Fatalf("Unable to list clients: %v", err) } if len(listResp.Clients) != 2 { t.Fatalf("Expected 2 clients, got %d", len(listResp.Clients)) } clientMap := make(map[string]*api.ClientInfo) for _, c := range listResp.Clients { clientMap[c.Id] = c } if c1, exists := clientMap["client1"]; !exists { t.Fatal("client1 not found in list") } else { if c1.Name != "Test Client 1" { t.Errorf("Expected client1 name 'Test Client 1', got '%s'", c1.Name) } if len(c1.RedirectUris) != 1 || c1.RedirectUris[0] != "http://localhost:8080/callback" { t.Errorf("Expected client1 redirect URIs ['http://localhost:8080/callback'], got %v", c1.RedirectUris) } if c1.Public != false { t.Errorf("Expected client1 public false, got %v", c1.Public) } if c1.LogoUrl != "http://example.com/logo1.png" { t.Errorf("Expected client1 logo URL 'http://example.com/logo1.png', got '%s'", c1.LogoUrl) } } if c2, exists := clientMap["client2"]; !exists { t.Fatal("client2 not found in list") } else { if c2.Name != "Test Client 2" { t.Errorf("Expected client2 name 'Test Client 2', got '%s'", c2.Name) } if len(c2.RedirectUris) != 1 || c2.RedirectUris[0] != "http://localhost:8081/callback" { t.Errorf("Expected client2 redirect URIs ['http://localhost:8081/callback'], got %v", c2.RedirectUris) } if c2.Public != true { t.Errorf("Expected client2 public true, got %v", c2.Public) } if c2.LogoUrl != "http://example.com/logo2.png" { t.Errorf("Expected client2 logo URL 'http://example.com/logo2.png', got '%s'", c2.LogoUrl) } } } ================================================ FILE: server/deviceflowhandlers.go ================================================ package server import ( "encoding/json" "errors" "fmt" "net/http" "net/url" "path" "strconv" "strings" "time" "github.com/dexidp/dex/storage" ) type deviceCodeResponse struct { // The unique device code for device authentication DeviceCode string `json:"device_code"` // The code the user will exchange via a browser and log in UserCode string `json:"user_code"` // The url to verify the user code. VerificationURI string `json:"verification_uri"` // The verification uri with the user code appended for pre-filling form VerificationURIComplete string `json:"verification_uri_complete"` // The lifetime of the device code ExpireTime int `json:"expires_in"` // How often the device is allowed to poll to verify that the user login occurred PollInterval int `json:"interval"` } func (s *Server) getDeviceVerificationURI() string { return path.Join(s.issuerURL.Path, "/device/auth/verify_code") } func (s *Server) handleDeviceExchange(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: // Grab the parameter(s) from the query. // If "user_code" is set, pre-populate the user code text field. // If "invalid" is set, set the invalidAttempt boolean, which will display a message to the user that they // attempted to redeem an invalid or expired user code. userCode := r.URL.Query().Get("user_code") invalidAttempt, err := strconv.ParseBool(r.URL.Query().Get("invalid")) if err != nil { invalidAttempt = false } if err := s.templates.device(r, w, s.getDeviceVerificationURI(), userCode, invalidAttempt); err != nil { s.logger.ErrorContext(r.Context(), "server template error", "err", err) s.renderError(r, w, http.StatusNotFound, "Page not found") } default: s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist.") } } func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pollIntervalSeconds := 5 switch r.Method { case http.MethodPost: err := r.ParseForm() if err != nil { s.logger.ErrorContext(r.Context(), "could not parse Device Request body", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusNotFound) return } // Get the client id and scopes from the post clientID := r.Form.Get("client_id") clientSecret := r.Form.Get("client_secret") scopes := strings.Fields(r.Form.Get("scope")) codeChallenge := r.Form.Get("code_challenge") codeChallengeMethod := r.Form.Get("code_challenge_method") if codeChallengeMethod == "" { codeChallengeMethod = codeChallengeMethodPlain } if codeChallengeMethod != codeChallengeMethodS256 && codeChallengeMethod != codeChallengeMethodPlain { description := fmt.Sprintf("Unsupported PKCE challenge method (%q).", codeChallengeMethod) s.tokenErrHelper(w, errInvalidRequest, description, http.StatusBadRequest) return } if len(scopes) == 0 { // per RFC8628 section 3.1, https://datatracker.ietf.org/doc/html/rfc8628#section-3.1 // scope is optional but dex requires that it is always at least 'openid' so default it scopes = []string{"openid"} } s.logger.InfoContext(r.Context(), "received device request", "client_id", clientID, "scoped", scopes) // Make device code deviceCode := storage.NewDeviceCode() // make user code userCode := storage.NewUserCode() // Generate the expire time expireTime := time.Now().Add(s.deviceRequestsValidFor) // Store the Device Request deviceReq := storage.DeviceRequest{ UserCode: userCode, DeviceCode: deviceCode, ClientID: clientID, ClientSecret: clientSecret, Scopes: scopes, Expiry: expireTime, } if err := s.storage.CreateDeviceRequest(ctx, deviceReq); err != nil { s.logger.ErrorContext(r.Context(), "failed to store device request", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusInternalServerError) return } // Store the device token deviceToken := storage.DeviceToken{ DeviceCode: deviceCode, Status: deviceTokenPending, Expiry: expireTime, LastRequestTime: s.now(), PollIntervalSeconds: 0, PKCE: storage.PKCE{ CodeChallenge: codeChallenge, CodeChallengeMethod: codeChallengeMethod, }, } if err := s.storage.CreateDeviceToken(ctx, deviceToken); err != nil { s.logger.ErrorContext(r.Context(), "failed to store device token", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusInternalServerError) return } u, err := url.Parse(s.issuerURL.String()) if err != nil { s.logger.ErrorContext(r.Context(), "could not parse issuer URL", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusInternalServerError) return } u.Path = path.Join(u.Path, "device") vURI := u.String() q := u.Query() q.Set("user_code", userCode) u.RawQuery = q.Encode() vURIComplete := u.String() code := deviceCodeResponse{ DeviceCode: deviceCode, UserCode: userCode, VerificationURI: vURI, VerificationURIComplete: vURIComplete, ExpireTime: int(s.deviceRequestsValidFor.Seconds()), PollInterval: pollIntervalSeconds, } // Device Authorization Response can contain cache control header according to // https://tools.ietf.org/html/rfc8628#section-3.2 w.Header().Set("Cache-Control", "no-store") // Response type should be application/json according to // https://datatracker.ietf.org/doc/html/rfc6749#section-5.1 w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) enc.SetEscapeHTML(false) enc.SetIndent("", " ") enc.Encode(code) default: s.renderError(r, w, http.StatusBadRequest, "Invalid device code request type") s.tokenErrHelper(w, errInvalidRequest, "", http.StatusBadRequest) } } func (s *Server) handleDeviceTokenDeprecated(w http.ResponseWriter, r *http.Request) { s.logger.Warn(`the /device/token endpoint was called. It will be removed, use /token instead.`, "deprecated", true) w.Header().Set("Content-Type", "application/json") switch r.Method { case http.MethodPost: err := r.ParseForm() if err != nil { s.logger.Warn("could not parse Device Token Request body", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusBadRequest) return } grantType := r.PostFormValue("grant_type") if grantType != grantTypeDeviceCode { s.tokenErrHelper(w, errInvalidGrant, "", http.StatusBadRequest) return } s.handleDeviceToken(w, r) default: s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist.") } } func (s *Server) handleDeviceToken(w http.ResponseWriter, r *http.Request) { ctx := r.Context() deviceCode := r.Form.Get("device_code") if deviceCode == "" { s.tokenErrHelper(w, errInvalidRequest, "No device code received", http.StatusBadRequest) return } now := s.now() // Grab the device token, check validity deviceToken, err := s.storage.GetDeviceToken(ctx, deviceCode) if err != nil { if err != storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "failed to get device code", "err", err) } s.tokenErrHelper(w, errInvalidRequest, "Invalid Device code.", http.StatusBadRequest) return } else if now.After(deviceToken.Expiry) { s.tokenErrHelper(w, deviceTokenExpired, "", http.StatusBadRequest) return } // Rate Limiting check slowDown := false pollInterval := deviceToken.PollIntervalSeconds minRequestTime := deviceToken.LastRequestTime.Add(time.Second * time.Duration(pollInterval)) if now.Before(minRequestTime) { slowDown = true // Continually increase the poll interval until the user waits the proper time pollInterval += 5 } else { pollInterval = 5 } switch deviceToken.Status { case deviceTokenPending: updater := func(old storage.DeviceToken) (storage.DeviceToken, error) { old.PollIntervalSeconds = pollInterval old.LastRequestTime = now return old, nil } // Update device token last request time in storage if err := s.storage.UpdateDeviceToken(ctx, deviceCode, updater); err != nil { s.logger.ErrorContext(r.Context(), "failed to update device token", "err", err) s.renderError(r, w, http.StatusInternalServerError, "") return } if slowDown { s.tokenErrHelper(w, deviceTokenSlowDown, "", http.StatusBadRequest) } else { s.tokenErrHelper(w, deviceTokenPending, "", http.StatusBadRequest) } case deviceTokenComplete: codeChallengeFromStorage := deviceToken.PKCE.CodeChallenge providedCodeVerifier := r.Form.Get("code_verifier") switch { case providedCodeVerifier != "" && codeChallengeFromStorage != "": calculatedCodeChallenge, err := s.calculateCodeChallenge(providedCodeVerifier, deviceToken.PKCE.CodeChallengeMethod) if err != nil { s.logger.ErrorContext(r.Context(), "failed to calculate code challenge", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } if codeChallengeFromStorage != calculatedCodeChallenge { s.tokenErrHelper(w, errInvalidGrant, "Invalid code_verifier.", http.StatusBadRequest) return } case providedCodeVerifier != "": // Received no code_challenge on /auth, but a code_verifier on /token s.tokenErrHelper(w, errInvalidRequest, "No PKCE flow started. Cannot check code_verifier.", http.StatusBadRequest) return case codeChallengeFromStorage != "": // Received PKCE request on /auth, but no code_verifier on /token s.tokenErrHelper(w, errInvalidGrant, "Expecting parameter code_verifier in PKCE flow.", http.StatusBadRequest) return } w.Write([]byte(deviceToken.Token)) } } func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) { ctx := r.Context() switch r.Method { case http.MethodGet: userCode := r.FormValue("state") code := r.FormValue("code") if userCode == "" || code == "" { s.renderError(r, w, http.StatusBadRequest, "Request was missing parameters") return } // Authorization redirect callback from OAuth2 auth flow. if errMsg := r.FormValue("error"); errMsg != "" { // Log the error details but don't expose them to the user s.logger.ErrorContext(r.Context(), "OAuth2 authorization error", "error", errMsg, "error_description", r.FormValue("error_description")) s.renderError(r, w, http.StatusBadRequest, "Authorization failed. Please try again.") return } authCode, err := s.storage.GetAuthCode(ctx, code) if err != nil || s.now().After(authCode.Expiry) { errCode := http.StatusBadRequest if err != nil && err != storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "failed to get auth code", "err", err) errCode = http.StatusInternalServerError } s.renderError(r, w, errCode, "Invalid or expired auth code.") return } // Grab the device request from storage deviceReq, err := s.storage.GetDeviceRequest(ctx, userCode) if err != nil || s.now().After(deviceReq.Expiry) { errCode := http.StatusBadRequest if err != nil && err != storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "failed to get device code", "err", err) errCode = http.StatusInternalServerError } s.renderError(r, w, errCode, "Invalid or expired user code.") return } client, err := s.storage.GetClient(ctx, deviceReq.ClientID) if err != nil { if err != storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "failed to get client", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) } else { s.tokenErrHelper(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized) } return } if client.Secret != deviceReq.ClientSecret { s.tokenErrHelper(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized) return } resp, err := s.exchangeAuthCode(ctx, w, authCode, client) if err != nil { s.logger.ErrorContext(r.Context(), "could not exchange auth code for clien", "client_id", deviceReq.ClientID, "err", err) s.renderError(r, w, http.StatusInternalServerError, "Failed to exchange auth code.") return } // Grab the device token from storage old, err := s.storage.GetDeviceToken(ctx, deviceReq.DeviceCode) if err != nil || s.now().After(old.Expiry) { errCode := http.StatusBadRequest if err != nil && err != storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "failed to get device token", "err", err) errCode = http.StatusInternalServerError } s.renderError(r, w, errCode, "Invalid or expired device code.") return } updater := func(old storage.DeviceToken) (storage.DeviceToken, error) { if old.Status == deviceTokenComplete { return old, errors.New("device token already complete") } respStr, err := json.MarshalIndent(resp, "", " ") if err != nil { s.logger.ErrorContext(r.Context(), "failed to marshal device token response", "err", err) s.renderError(r, w, http.StatusInternalServerError, "") return old, err } old.Token = string(respStr) old.Status = deviceTokenComplete return old, nil } // Update refresh token in the storage, store the token and mark as complete if err := s.storage.UpdateDeviceToken(ctx, deviceReq.DeviceCode, updater); err != nil { s.logger.ErrorContext(r.Context(), "failed to update device token", "err", err) s.renderError(r, w, http.StatusBadRequest, "") return } if err := s.templates.deviceSuccess(r, w, client.Name); err != nil { s.logger.ErrorContext(r.Context(), "Server template error", "err", err) s.renderError(r, w, http.StatusNotFound, "Page not found") } default: s.logger.ErrorContext(r.Context(), "unsupported method in device callback", "method", r.Method) s.renderError(r, w, http.StatusBadRequest, ErrMsgMethodNotAllowed) return } } func (s *Server) verifyUserCode(w http.ResponseWriter, r *http.Request) { ctx := r.Context() switch r.Method { case http.MethodPost: err := r.ParseForm() if err != nil { s.logger.Warn("could not parse user code verification request body", "err", err) s.renderError(r, w, http.StatusBadRequest, "") return } userCode := r.Form.Get("user_code") if userCode == "" { s.renderError(r, w, http.StatusBadRequest, "No user code received") return } userCode = strings.ToUpper(userCode) // Find the user code in the available requests deviceRequest, err := s.storage.GetDeviceRequest(ctx, userCode) if err != nil || s.now().After(deviceRequest.Expiry) { if err != nil && err != storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "failed to get device request", "err", err) } if err := s.templates.device(r, w, s.getDeviceVerificationURI(), userCode, true); err != nil { s.logger.ErrorContext(r.Context(), "Server template error", "err", err) s.renderError(r, w, http.StatusNotFound, "Page not found") } return } // Redirect to Dex Auth Endpoint authURL := s.absURL("/auth") u, err := url.Parse(authURL) if err != nil { s.renderError(r, w, http.StatusInternalServerError, "Invalid auth URI.") return } q := u.Query() q.Set("client_id", deviceRequest.ClientID) q.Set("client_secret", deviceRequest.ClientSecret) q.Set("state", deviceRequest.UserCode) q.Set("response_type", "code") q.Set("redirect_uri", s.absPath(deviceCallbackURI)) q.Set("scope", strings.Join(deviceRequest.Scopes, " ")) u.RawQuery = q.Encode() http.Redirect(w, r, u.String(), http.StatusFound) default: s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist.") } } ================================================ FILE: server/deviceflowhandlers_test.go ================================================ package server import ( "bytes" "encoding/json" "io" "net/http" "net/http/httptest" "net/url" "path" "strings" "testing" "time" "github.com/dexidp/dex/storage" ) func TestDeviceVerificationURI(t *testing.T) { t0 := time.Now() now := func() time.Time { return t0 } // Setup a dex server. httpServer, s := newTestServer(t, func(c *Config) { c.Issuer += "/non-root-path" c.Now = now }) defer httpServer.Close() u, err := url.Parse(s.issuerURL.String()) if err != nil { t.Fatalf("Could not parse issuer URL %v", err) } u.Path = path.Join(u.Path, "/device/auth/verify_code") uri := s.getDeviceVerificationURI() if uri != u.Path { t.Errorf("Invalid verification URI. Expected %v got %v", u.Path, uri) } } func TestHandleDeviceCode(t *testing.T) { t0 := time.Now() now := func() time.Time { return t0 } tests := []struct { testName string clientID string codeChallengeMethod string requestType string scopes []string expectedResponseCode int expectedContentType string expectedServerResponse string }{ { testName: "New Code", clientID: "test", requestType: "POST", scopes: []string{"openid", "profile", "email"}, expectedResponseCode: http.StatusOK, expectedContentType: "application/json", }, { testName: "Invalid request Type (GET)", clientID: "test", requestType: "GET", scopes: []string{"openid", "profile", "email"}, expectedResponseCode: http.StatusBadRequest, expectedContentType: "application/json", }, { testName: "New Code with valid PKCE", clientID: "test", requestType: "POST", scopes: []string{"openid", "profile", "email"}, codeChallengeMethod: "S256", expectedResponseCode: http.StatusOK, expectedContentType: "application/json", }, { testName: "Invalid code challenge method", clientID: "test", requestType: "POST", codeChallengeMethod: "invalid", scopes: []string{"openid", "profile", "email"}, expectedResponseCode: http.StatusBadRequest, expectedContentType: "application/json", }, { testName: "New Code without scope", clientID: "test", requestType: "POST", scopes: []string{}, expectedResponseCode: http.StatusOK, expectedContentType: "application/json", }, } for _, tc := range tests { t.Run(tc.testName, func(t *testing.T) { // Setup a dex server. httpServer, s := newTestServer(t, func(c *Config) { c.Issuer += "/non-root-path" c.Now = now }) defer httpServer.Close() u, err := url.Parse(s.issuerURL.String()) if err != nil { t.Fatalf("Could not parse issuer URL %v", err) } u.Path = path.Join(u.Path, "device/code") data := url.Values{} data.Set("client_id", tc.clientID) data.Set("code_challenge_method", tc.codeChallengeMethod) for _, scope := range tc.scopes { data.Add("scope", scope) } req, _ := http.NewRequest(tc.requestType, u.String(), bytes.NewBufferString(data.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") rr := httptest.NewRecorder() s.ServeHTTP(rr, req) if rr.Code != tc.expectedResponseCode { t.Errorf("Unexpected Response Type. Expected %v got %v", tc.expectedResponseCode, rr.Code) } if rr.Header().Get("content-type") != tc.expectedContentType { t.Errorf("Unexpected Response Content Type. Expected %v got %v", tc.expectedContentType, rr.Header().Get("content-type")) } body, err := io.ReadAll(rr.Body) if err != nil { t.Errorf("Could read token response %v", err) } if tc.expectedResponseCode == http.StatusOK { var resp deviceCodeResponse if err := json.Unmarshal(body, &resp); err != nil { t.Errorf("Unexpected Device Code Response Format %v", string(body)) } } }) } } func TestDeviceCallback(t *testing.T) { t0 := time.Now() now := func() time.Time { return t0 } type formValues struct { state string code string error string } // Base "Control" test values baseFormValues := formValues{ state: "XXXX-XXXX", code: "somecode", } baseAuthCode := storage.AuthCode{ ID: "somecode", ClientID: "testclient", RedirectURI: deviceCallbackURI, Nonce: "", Scopes: []string{"openid", "profile", "email"}, ConnectorID: "mock", ConnectorData: nil, Claims: storage.Claims{}, Expiry: now().Add(5 * time.Minute), } baseDeviceRequest := storage.DeviceRequest{ UserCode: "XXXX-XXXX", DeviceCode: "devicecode", ClientID: "testclient", ClientSecret: "", Scopes: []string{"openid", "profile", "email"}, Expiry: now().Add(5 * time.Minute), } baseDeviceToken := storage.DeviceToken{ DeviceCode: "devicecode", Status: deviceTokenPending, Token: "", Expiry: now().Add(5 * time.Minute), LastRequestTime: time.Time{}, PollIntervalSeconds: 0, } tests := []struct { testName string expectedResponseCode int expectedServerResponse string values formValues testAuthCode storage.AuthCode testDeviceRequest storage.DeviceRequest testDeviceToken storage.DeviceToken }{ { testName: "Missing State", values: formValues{ state: "", code: "somecode", error: "", }, expectedResponseCode: http.StatusBadRequest, }, { testName: "Missing Code", values: formValues{ state: "XXXX-XXXX", code: "", error: "", }, expectedResponseCode: http.StatusBadRequest, }, { testName: "Error During Authorization", values: formValues{ state: "XXXX-XXXX", code: "somecode", error: "Error Condition", }, expectedResponseCode: http.StatusBadRequest, // Note: Error details should NOT be displayed to user anymore. // Instead, a safe generic message is shown. }, { testName: "Expired Auth Code", values: baseFormValues, testAuthCode: storage.AuthCode{ ID: "somecode", ClientID: "testclient", RedirectURI: deviceCallbackURI, Nonce: "", Scopes: []string{"openid", "profile", "email"}, ConnectorID: "pic", ConnectorData: nil, Claims: storage.Claims{}, Expiry: now().Add(-5 * time.Minute), }, expectedResponseCode: http.StatusBadRequest, }, { testName: "Invalid Auth Code", values: baseFormValues, testAuthCode: storage.AuthCode{ ID: "somecode", ClientID: "testclient", RedirectURI: deviceCallbackURI, Nonce: "", Scopes: []string{"openid", "profile", "email"}, ConnectorID: "pic", ConnectorData: nil, Claims: storage.Claims{}, Expiry: now().Add(5 * time.Minute), }, expectedResponseCode: http.StatusBadRequest, }, { testName: "Expired Device Request", values: baseFormValues, testAuthCode: baseAuthCode, testDeviceRequest: storage.DeviceRequest{ UserCode: "XXXX-XXXX", DeviceCode: "devicecode", ClientID: "testclient", Scopes: []string{"openid", "profile", "email"}, Expiry: now().Add(-5 * time.Minute), }, expectedResponseCode: http.StatusBadRequest, }, { testName: "Non-Existent User Code", values: baseFormValues, testAuthCode: baseAuthCode, testDeviceRequest: storage.DeviceRequest{ UserCode: "ZZZZ-ZZZZ", DeviceCode: "devicecode", Scopes: []string{"openid", "profile", "email"}, Expiry: now().Add(5 * time.Minute), }, expectedResponseCode: http.StatusBadRequest, }, { testName: "Bad Device Request Client", values: baseFormValues, testAuthCode: baseAuthCode, testDeviceRequest: storage.DeviceRequest{ UserCode: "XXXX-XXXX", DeviceCode: "devicecode", Scopes: []string{"openid", "profile", "email"}, Expiry: now().Add(5 * time.Minute), }, expectedResponseCode: http.StatusUnauthorized, }, { testName: "Bad Device Request Secret", values: baseFormValues, testAuthCode: baseAuthCode, testDeviceRequest: storage.DeviceRequest{ UserCode: "XXXX-XXXX", DeviceCode: "devicecode", ClientSecret: "foobar", Scopes: []string{"openid", "profile", "email"}, Expiry: now().Add(5 * time.Minute), }, expectedResponseCode: http.StatusUnauthorized, }, { testName: "Expired Device Token", values: baseFormValues, testAuthCode: baseAuthCode, testDeviceRequest: baseDeviceRequest, testDeviceToken: storage.DeviceToken{ DeviceCode: "devicecode", Status: deviceTokenPending, Token: "", Expiry: now().Add(-5 * time.Minute), LastRequestTime: time.Time{}, PollIntervalSeconds: 0, }, expectedResponseCode: http.StatusBadRequest, }, { testName: "Device Code Already Redeemed", values: baseFormValues, testAuthCode: baseAuthCode, testDeviceRequest: baseDeviceRequest, testDeviceToken: storage.DeviceToken{ DeviceCode: "devicecode", Status: deviceTokenComplete, Token: "", Expiry: now().Add(5 * time.Minute), LastRequestTime: time.Time{}, PollIntervalSeconds: 0, }, expectedResponseCode: http.StatusBadRequest, }, { testName: "Successful Exchange", values: baseFormValues, testAuthCode: baseAuthCode, testDeviceRequest: baseDeviceRequest, testDeviceToken: baseDeviceToken, expectedResponseCode: http.StatusOK, }, { testName: "Prevent cross-site scripting", values: formValues{ state: "XXXX-XXXX", code: "somecode", error: "", }, expectedResponseCode: http.StatusBadRequest, // Note: XSS data should NOT be displayed to user anymore. // Instead, a safe generic message is shown. }, } for _, tc := range tests { t.Run(tc.testName, func(t *testing.T) { ctx := t.Context() // Setup a dex server. httpServer, s := newTestServer(t, func(c *Config) { c.Issuer = c.Issuer + "/non-root-path" c.Now = now }) defer httpServer.Close() if err := s.storage.CreateAuthCode(ctx, tc.testAuthCode); err != nil { t.Fatalf("failed to create auth code: %v", err) } if err := s.storage.CreateDeviceRequest(ctx, tc.testDeviceRequest); err != nil { t.Fatalf("failed to create device request: %v", err) } if err := s.storage.CreateDeviceToken(ctx, tc.testDeviceToken); err != nil { t.Fatalf("failed to create device token: %v", err) } client := storage.Client{ ID: "testclient", Secret: "", RedirectURIs: []string{deviceCallbackURI}, } if err := s.storage.CreateClient(ctx, client); err != nil { t.Fatalf("failed to create client: %v", err) } u, err := url.Parse(s.issuerURL.String()) if err != nil { t.Fatalf("Could not parse issuer URL %v", err) } u.Path = path.Join(u.Path, "device/callback") q := u.Query() q.Set("state", tc.values.state) q.Set("code", tc.values.code) q.Set("error", tc.values.error) u.RawQuery = q.Encode() req, _ := http.NewRequest("GET", u.String(), nil) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") rr := httptest.NewRecorder() s.ServeHTTP(rr, req) if rr.Code != tc.expectedResponseCode { t.Errorf("%s: Unexpected Response Type. Expected %v got %v", tc.testName, tc.expectedResponseCode, rr.Code) } if len(tc.expectedServerResponse) > 0 { result, _ := io.ReadAll(rr.Body) if string(result) != tc.expectedServerResponse { t.Errorf("%s: Unexpected Response. Expected %q got %q", tc.testName, tc.expectedServerResponse, result) } } // Special check for error message safety tests if tc.testName == "Prevent cross-site scripting" || tc.testName == "Error During Authorization" { result, _ := io.ReadAll(rr.Body) responseBody := string(result) // Error details should NOT be present in the response (for security) if tc.testName == "Prevent cross-site scripting" { if strings.Contains(responseBody, " `, action, value, authReq.ID) default: s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist.") } default: s.renderError(r, w, http.StatusBadRequest, "Unsupported request method.") } } func (s *Server) handlePasswordLogin(w http.ResponseWriter, r *http.Request) { ctx := r.Context() authID := r.URL.Query().Get("state") if authID == "" { s.renderError(r, w, http.StatusBadRequest, "User session error.") return } backLink := r.URL.Query().Get("back") authReq, err := s.storage.GetAuthRequest(ctx, authID) if err != nil { if err == storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "invalid 'state' parameter provided", "err", err) s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist.") return } s.logger.ErrorContext(r.Context(), "failed to get auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Database error.") return } connID, err := url.PathUnescape(mux.Vars(r)["connector"]) if err != nil { s.logger.ErrorContext(r.Context(), "failed to parse connector", "err", err) s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist") return } else if connID != "" && connID != authReq.ConnectorID { s.logger.ErrorContext(r.Context(), "connector mismatch: password login triggered for different connector from authentication start", "start_connector_id", authReq.ConnectorID, "password_connector_id", connID) s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return } conn, err := s.getConnector(ctx, authReq.ConnectorID) if err != nil { s.logger.ErrorContext(r.Context(), "failed to get connector", "connector_id", authReq.ConnectorID, "err", err) s.renderError(r, w, http.StatusInternalServerError, "Connector failed to initialize.") return } pwConn, ok := conn.Connector.(connector.PasswordConnector) if !ok { s.logger.ErrorContext(r.Context(), "expected password connector in handlePasswordLogin()", "password_connector", pwConn) s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return } rememberMe := s.rememberMeDefault() switch r.Method { case http.MethodGet: if err := s.templates.password(r, w, r.URL.String(), "", usernamePrompt(pwConn), false, backLink, rememberMe); err != nil { s.logger.ErrorContext(r.Context(), "server template error", "err", err) } case http.MethodPost: username := r.FormValue("login") password := r.FormValue("password") scopes := parseScopes(authReq.Scopes) identity, ok, err := pwConn.Login(r.Context(), scopes, username, password) if err != nil { s.logger.ErrorContext(r.Context(), "failed to login user", "err", err) s.renderError(r, w, http.StatusInternalServerError, ErrMsgLoginError) return } if !ok { if err := s.templates.password(r, w, r.URL.String(), username, usernamePrompt(pwConn), true, backLink, rememberMe); err != nil { s.logger.ErrorContext(r.Context(), "server template error", "err", err) } s.logger.ErrorContext(r.Context(), "failed login attempt: Invalid credentials.", "user", username) return } redirectURL, canSkipApproval, err := s.finalizeLogin(r.Context(), identity, authReq, conn.Connector) if err != nil { s.logger.ErrorContext(r.Context(), "failed to finalize login", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Login error.") return } // Re-read auth request after finalizeLogin populated Claims. authReq, err = s.storage.GetAuthRequest(ctx, authReq.ID) if err != nil { s.logger.ErrorContext(r.Context(), "failed to get finalized auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Login error.") return } rememberMe := r.FormValue("remember_me") == "on" if err := s.createOrUpdateAuthSession(ctx, r, w, authReq, rememberMe); err != nil { s.logger.ErrorContext(ctx, "failed to create/update auth session", "err", err) } if canSkipApproval { // authReq was already re-read after finalizeLogin above. s.sendCodeResponse(w, r, authReq) return } http.Redirect(w, r, redirectURL, http.StatusSeeOther) default: s.renderError(r, w, http.StatusBadRequest, "Unsupported request method.") } } func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var authID string switch r.Method { case http.MethodGet: // OAuth2 callback if authID = r.URL.Query().Get("state"); authID == "" { s.renderError(r, w, http.StatusBadRequest, "User session error.") return } case http.MethodPost: // SAML POST binding if authID = r.PostFormValue("RelayState"); authID == "" { s.renderError(r, w, http.StatusBadRequest, "User session error.") return } default: s.renderError(r, w, http.StatusBadRequest, "Method not supported") return } authReq, err := s.storage.GetAuthRequest(ctx, authID) if err != nil { if err == storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "invalid 'state' parameter provided", "err", err) s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist.") return } s.logger.ErrorContext(r.Context(), "failed to get auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Database error.") return } connID, err := url.PathUnescape(mux.Vars(r)["connector"]) if err != nil { s.logger.ErrorContext(r.Context(), "failed to get connector", "connector_id", authReq.ConnectorID, "err", err) s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return } else if connID != "" && connID != authReq.ConnectorID { s.logger.ErrorContext(r.Context(), "connector mismatch: callback triggered for different connector than authentication start", "authentication_start_connector_id", authReq.ConnectorID, "connector_id", connID) s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return } conn, err := s.getConnector(ctx, authReq.ConnectorID) if err != nil { s.logger.ErrorContext(r.Context(), "failed to get connector", "connector_id", authReq.ConnectorID, "err", err) s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return } var identity connector.Identity switch conn := conn.Connector.(type) { case connector.CallbackConnector: if r.Method != http.MethodGet { s.logger.ErrorContext(r.Context(), "SAML request mapped to OAuth2 connector") s.renderError(r, w, http.StatusBadRequest, "Invalid request") return } identity, err = conn.HandleCallback(parseScopes(authReq.Scopes), authReq.ConnectorData, r) case connector.SAMLConnector: if r.Method != http.MethodPost { s.logger.ErrorContext(r.Context(), "OAuth2 request mapped to SAML connector") s.renderError(r, w, http.StatusBadRequest, "Invalid request") return } identity, err = conn.HandlePOST(parseScopes(authReq.Scopes), r.PostFormValue("SAMLResponse"), authReq.ID) default: s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return } if err != nil { s.logger.ErrorContext(r.Context(), "failed to authenticate", "err", err) var groupsErr *connector.UserNotInRequiredGroupsError if errors.As(err, &groupsErr) { s.renderError(r, w, http.StatusForbidden, ErrMsgNotInRequiredGroups) } else { s.renderError(r, w, http.StatusInternalServerError, ErrMsgAuthenticationFailed) } return } redirectURL, canSkipApproval, err := s.finalizeLogin(ctx, identity, authReq, conn.Connector) if err != nil { s.logger.ErrorContext(r.Context(), "failed to finalize login", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Login error.") return } // Re-read auth request after finalizeLogin populated Claims. authReq, err = s.storage.GetAuthRequest(ctx, authReq.ID) if err != nil { s.logger.ErrorContext(r.Context(), "failed to get finalized auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Login error.") return } // Connector callbacks don't render the remember_me checkbox, so we use the server default. // The password login handler reads r.FormValue("remember_me") from the submitted form instead. if err := s.createOrUpdateAuthSession(ctx, r, w, authReq, s.sessionConfig != nil && s.sessionConfig.RememberMeCheckedByDefault); err != nil { s.logger.ErrorContext(ctx, "failed to create/update auth session", "err", err) } if canSkipApproval { // authReq was already re-read after finalizeLogin above. s.sendCodeResponse(w, r, authReq) return } http.Redirect(w, r, redirectURL, http.StatusSeeOther) } // finalizeLogin associates the user's identity with the current AuthRequest, then returns // the approval page's path. func (s *Server) finalizeLogin(ctx context.Context, identity connector.Identity, authReq storage.AuthRequest, conn connector.Connector) (string, bool, error) { claims := storage.Claims{ UserID: identity.UserID, Username: identity.Username, PreferredUsername: identity.PreferredUsername, Email: identity.Email, EmailVerified: identity.EmailVerified, Groups: identity.Groups, } updater := func(a storage.AuthRequest) (storage.AuthRequest, error) { a.LoggedIn = true a.Claims = claims a.ConnectorData = identity.ConnectorData a.AuthTime = s.now() return a, nil } if err := s.storage.UpdateAuthRequest(ctx, authReq.ID, updater); err != nil { return "", false, fmt.Errorf("failed to update auth request: %v", err) } email := claims.Email if !claims.EmailVerified { email += " (unverified)" } s.logger.InfoContext(ctx, "login successful", "connector_id", authReq.ConnectorID, "user_id", claims.UserID, "username", claims.Username, "preferred_username", claims.PreferredUsername, "email", email, "groups", claims.Groups) offlineAccessRequested := false for _, scope := range authReq.Scopes { if scope == scopeOfflineAccess { offlineAccessRequested = true break } } _, canRefresh := conn.(connector.RefreshConnector) if offlineAccessRequested && canRefresh { // Try to retrieve an existing OfflineSession object for the corresponding user. session, err := s.storage.GetOfflineSessions(ctx, identity.UserID, authReq.ConnectorID) switch { case err != nil && err == storage.ErrNotFound: offlineSessions := storage.OfflineSessions{ UserID: identity.UserID, ConnID: authReq.ConnectorID, Refresh: make(map[string]*storage.RefreshTokenRef), ConnectorData: identity.ConnectorData, } // Create a new OfflineSession object for the user and add a reference object for // the newly received refreshtoken. if err := s.storage.CreateOfflineSessions(ctx, offlineSessions); err != nil { s.logger.ErrorContext(ctx, "failed to create offline session", "err", err) return "", false, err } case err == nil: // Update existing OfflineSession obj with new RefreshTokenRef. if err := s.storage.UpdateOfflineSessions(ctx, session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) { if len(identity.ConnectorData) > 0 { old.ConnectorData = identity.ConnectorData } return old, nil }); err != nil { s.logger.ErrorContext(ctx, "failed to update offline session", "err", err) return "", false, err } default: s.logger.ErrorContext(ctx, "failed to get offline session", "err", err) return "", false, err } } // Create or update UserIdentity to persist user claims across sessions. var userIdentity *storage.UserIdentity if featureflags.SessionsEnabled.Enabled() { now := s.now() ui, err := s.storage.GetUserIdentity(ctx, identity.UserID, authReq.ConnectorID) switch { case err != nil && errors.Is(err, storage.ErrNotFound): ui = storage.UserIdentity{ UserID: identity.UserID, ConnectorID: authReq.ConnectorID, Claims: claims, Consents: make(map[string][]string), CreatedAt: now, LastLogin: now, } if err := s.storage.CreateUserIdentity(ctx, ui); err != nil { s.logger.ErrorContext(ctx, "failed to create user identity", "err", err) return "", false, err } case err == nil: if err := s.storage.UpdateUserIdentity(ctx, identity.UserID, authReq.ConnectorID, func(old storage.UserIdentity) (storage.UserIdentity, error) { old.Claims = claims old.LastLogin = now return old, nil }); err != nil { s.logger.ErrorContext(ctx, "failed to update user identity", "err", err) return "", false, err } // Update the existing UserIdentity obj with new claims to use them later in the flow. ui.Claims = claims ui.LastLogin = now default: s.logger.ErrorContext(ctx, "failed to get user identity", "err", err) return "", false, err } userIdentity = &ui } // an HMAC is used here to ensure that the request ID is unpredictable, ensuring that an attacker who intercepted the original // flow would be unable to poll for the result at the /approval endpoint h := hmac.New(sha256.New, authReq.HMACKey) h.Write([]byte(authReq.ID)) mac := h.Sum(nil) hmacParam := base64.RawURLEncoding.EncodeToString(mac) // Check if the client requires MFA. mfaChain, err := s.mfaChainForClient(ctx, authReq.ClientID, authReq.ConnectorID) if err != nil { return "", false, fmt.Errorf("failed to get MFA chain for client: %v", err) } if len(mfaChain) > 0 { // Redirect to MFA verification starting with the first authenticator. // Each authenticator redirects to the next one in the chain upon success. // HMAC includes authenticatorID to prevent skipping steps by URL manipulation. h.Reset() h.Write([]byte(authReq.ID + "|" + mfaChain[0])) v := url.Values{} v.Set("req", authReq.ID) v.Set("hmac", base64.RawURLEncoding.EncodeToString(h.Sum(nil))) v.Set("authenticator", mfaChain[0]) returnURL := path.Join(s.issuerURL.Path, "/mfa/verify") + "?" + v.Encode() return returnURL, false, nil } // No MFA required — mark as validated. if err := s.storage.UpdateAuthRequest(ctx, authReq.ID, func(a storage.AuthRequest) (storage.AuthRequest, error) { a.MFAValidated = true return a, nil }); err != nil { return "", false, fmt.Errorf("failed to update auth request MFA status: %v", err) } // Skip approval if globally configured. if s.skipApproval && !authReq.ForceApprovalPrompt { return "", true, nil } // Skip approval if user already consented to the requested scopes for this client. if !authReq.ForceApprovalPrompt && userIdentity != nil { if scopesCoveredByConsent(userIdentity.Consents[authReq.ClientID], authReq.Scopes) { return "", true, nil } } returnURL := path.Join(s.issuerURL.Path, "/approval") + "?req=" + authReq.ID + "&hmac=" + hmacParam return returnURL, false, nil } func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) { ctx := r.Context() macEncoded := r.FormValue("hmac") if macEncoded == "" { s.renderError(r, w, http.StatusUnauthorized, "Unauthorized request") return } mac, err := base64.RawURLEncoding.DecodeString(macEncoded) if err != nil { s.renderError(r, w, http.StatusUnauthorized, "Unauthorized request") return } authReq, err := s.storage.GetAuthRequest(ctx, r.FormValue("req")) if err != nil { if err == storage.ErrNotFound { s.renderError(r, w, http.StatusBadRequest, "User session error.") return } s.logger.ErrorContext(r.Context(), "failed to get auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Database error.") return } if !authReq.LoggedIn { s.logger.ErrorContext(r.Context(), "auth request does not have an identity for approval") s.renderError(r, w, http.StatusInternalServerError, "Login process not yet finalized.") return } h := hmac.New(sha256.New, authReq.HMACKey) if !authReq.MFAValidated { // Check if MFA is actually required — if so, redirect to TOTP instead of blocking. // This handles the case where MFA was enabled after the auth flow started. mfaChain, err := s.mfaChainForClient(ctx, authReq.ClientID, authReq.ConnectorID) if err != nil { s.logger.ErrorContext(ctx, "failed to get MFA chain", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } if len(mfaChain) > 0 { h.Write([]byte(authReq.ID + "|" + mfaChain[0])) v := url.Values{} v.Set("req", authReq.ID) v.Set("hmac", base64.RawURLEncoding.EncodeToString(h.Sum(nil))) v.Set("authenticator", mfaChain[0]) h.Reset() totpURL := path.Join(s.issuerURL.Path, "/mfa/verify") + "?" + v.Encode() http.Redirect(w, r, totpURL, http.StatusSeeOther) return } // No MFA required but flag not set — allow through (backward compat). } // build expected hmac with secret key h.Write([]byte(authReq.ID)) expectedMAC := h.Sum(nil) // constant time comparison if !hmac.Equal(mac, expectedMAC) { s.renderError(r, w, http.StatusUnauthorized, "Unauthorized request") return } switch r.Method { case http.MethodGet: client, err := s.storage.GetClient(ctx, authReq.ClientID) if err != nil { s.logger.ErrorContext(r.Context(), "Failed to get client", "client_id", authReq.ClientID, "err", err) s.renderError(r, w, http.StatusInternalServerError, "Failed to retrieve client.") return } if err := s.templates.approval(r, w, authReq.ID, authReq.Claims.Username, client.Name, authReq.Scopes); err != nil { s.logger.ErrorContext(r.Context(), "server template error", "err", err) } case http.MethodPost: if r.FormValue("approval") != "approve" { s.renderError(r, w, http.StatusInternalServerError, "Approval rejected.") return } // Persist user-approved scopes as consent for this client. if featureflags.SessionsEnabled.Enabled() { if err := s.storage.UpdateUserIdentity(ctx, authReq.Claims.UserID, authReq.ConnectorID, func(old storage.UserIdentity) (storage.UserIdentity, error) { if old.Consents == nil { old.Consents = make(map[string][]string) } old.Consents[authReq.ClientID] = authReq.Scopes return old, nil }); err != nil { s.logger.ErrorContext(ctx, "failed to update user identity consents", "err", err) } } s.sendCodeResponse(w, r, authReq) } } func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authReq storage.AuthRequest) { s.updateSessionTokenIssuedAt(r, authReq.ClientID) ctx := r.Context() if s.now().After(authReq.Expiry) { s.renderError(r, w, http.StatusBadRequest, "User session has expired.") return } if err := s.storage.DeleteAuthRequest(ctx, authReq.ID); err != nil { if err != storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "Failed to delete authorization request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") } else { s.renderError(r, w, http.StatusBadRequest, "User session error.") } return } u, err := url.Parse(authReq.RedirectURI) if err != nil { s.renderError(r, w, http.StatusInternalServerError, "Invalid redirect URI.") return } var ( // Was the initial request using the implicit or hybrid flow instead of // the "normal" code flow? implicitOrHybrid = false // Only present in hybrid or code flow. code.ID == "" if this is not set. code storage.AuthCode // ID token returned immediately if the response_type includes "id_token". // Only valid for implicit and hybrid flows. idToken string idTokenExpiry time.Time // Access token accessToken string ) for _, responseType := range authReq.ResponseTypes { switch responseType { case responseTypeCode: code = storage.AuthCode{ ID: storage.NewID(), ClientID: authReq.ClientID, ConnectorID: authReq.ConnectorID, Nonce: authReq.Nonce, Scopes: authReq.Scopes, Claims: authReq.Claims, Expiry: s.now().Add(time.Minute * 30), RedirectURI: authReq.RedirectURI, ConnectorData: authReq.ConnectorData, PKCE: authReq.PKCE, AuthTime: authReq.AuthTime, } if err := s.storage.CreateAuthCode(ctx, code); err != nil { s.logger.ErrorContext(r.Context(), "Failed to create auth code", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } // Implicit and hybrid flows that try to use the OOB redirect URI are // rejected earlier. If we got here we're using the code flow. if authReq.RedirectURI == redirectURIOOB { if err := s.templates.oob(r, w, code.ID); err != nil { s.logger.ErrorContext(r.Context(), "server template error", "err", err) } return } case responseTypeToken: implicitOrHybrid = true var err error accessToken, _, err = s.newAccessToken(r.Context(), authReq.ClientID, authReq.Claims, authReq.Scopes, authReq.Nonce, authReq.ConnectorID, authReq.AuthTime) if err != nil { s.logger.ErrorContext(r.Context(), "failed to create new access token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } case responseTypeIDToken: implicitOrHybrid = true var err error idToken, idTokenExpiry, err = s.newIDToken(r.Context(), authReq.ClientID, authReq.Claims, authReq.Scopes, authReq.Nonce, accessToken, code.ID, authReq.ConnectorID, authReq.AuthTime) if err != nil { s.logger.ErrorContext(r.Context(), "failed to create ID token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } } } if implicitOrHybrid { v := url.Values{} if accessToken != "" { v.Set("access_token", accessToken) v.Set("token_type", "bearer") // The hybrid flow with "code token" or "code id_token token" doesn't return an // "expires_in" value. If "code" wasn't provided, indicating the implicit flow, // don't add it. // // https://openid.net/specs/openid-connect-core-1_0.html#HybridAuthResponse if code.ID == "" { v.Set("expires_in", strconv.Itoa(int(idTokenExpiry.Sub(s.now()).Seconds()))) } } v.Set("state", authReq.State) if idToken != "" { v.Set("id_token", idToken) } if code.ID != "" { v.Set("code", code.ID) } // Implicit and hybrid flows return their values as part of the fragment. // // HTTP/1.1 303 See Other // Location: https://client.example.org/cb# // access_token=SlAV32hkKG // &token_type=bearer // &id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso // &expires_in=3600 // &state=af0ifjsldkj // u.Fragment = v.Encode() } else { // The code flow add values to the URL query. // // HTTP/1.1 303 See Other // Location: https://client.example.org/cb? // code=SplxlOBeZQQYbYS6WxSbIA // &state=af0ifjsldkj // q := u.Query() q.Set("code", code.ID) q.Set("state", authReq.State) u.RawQuery = q.Encode() } http.Redirect(w, r, u.String(), http.StatusSeeOther) } // scopesCoveredByConsent checks whether the approved scopes cover all requested scopes. // The openid scope is excluded from the comparison as it is a technical scope // that does not require user consent. func scopesCoveredByConsent(approved, requested []string) bool { approvedSet := make(map[string]struct{}, len(approved)) for _, s := range approved { approvedSet[s] = struct{}{} } for _, scope := range requested { if scope == scopeOpenID { continue } if _, ok := approvedSet[scope]; !ok { return false } } return true } func (s *Server) withClientFromStorage(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request, storage.Client)) { ctx := r.Context() clientID, clientSecret, ok := r.BasicAuth() if ok { var err error if clientID, err = url.QueryUnescape(clientID); err != nil { s.tokenErrHelper(w, errInvalidRequest, "client_id improperly encoded", http.StatusBadRequest) return } if clientSecret, err = url.QueryUnescape(clientSecret); err != nil { s.tokenErrHelper(w, errInvalidRequest, "client_secret improperly encoded", http.StatusBadRequest) return } } else { clientID = r.PostFormValue("client_id") clientSecret = r.PostFormValue("client_secret") } client, err := s.storage.GetClient(ctx, clientID) if err != nil { if err != storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "failed to get client", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) } else { s.tokenErrHelper(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized) } return } if subtle.ConstantTimeCompare([]byte(client.Secret), []byte(clientSecret)) != 1 { if clientSecret == "" { s.logger.InfoContext(r.Context(), "missing client_secret on token request", "client_id", client.ID) } else { s.logger.InfoContext(r.Context(), "invalid client_secret on token request", "client_id", client.ID) } s.tokenErrHelper(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized) return } handler(w, r, client) } func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if r.Method != http.MethodPost { s.tokenErrHelper(w, errInvalidRequest, "method not allowed", http.StatusBadRequest) return } err := r.ParseForm() if err != nil { s.logger.ErrorContext(r.Context(), "could not parse request body", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusBadRequest) return } grantType := r.PostFormValue("grant_type") if !contains(s.supportedGrantTypes, grantType) { s.logger.ErrorContext(r.Context(), "unsupported grant type", "grant_type", grantType) s.tokenErrHelper(w, errUnsupportedGrantType, "", http.StatusBadRequest) return } switch grantType { case grantTypeDeviceCode: s.handleDeviceToken(w, r) case grantTypeAuthorizationCode: s.withClientFromStorage(w, r, s.handleAuthCode) case grantTypeRefreshToken: s.withClientFromStorage(w, r, s.handleRefreshToken) case grantTypePassword: s.withClientFromStorage(w, r, s.handlePasswordGrant) case grantTypeTokenExchange: s.withClientFromStorage(w, r, s.handleTokenExchange) case grantTypeClientCredentials: s.withClientFromStorage(w, r, s.handleClientCredentialsGrant) default: s.tokenErrHelper(w, errUnsupportedGrantType, "", http.StatusBadRequest) } } func (s *Server) calculateCodeChallenge(codeVerifier, codeChallengeMethod string) (string, error) { switch codeChallengeMethod { case codeChallengeMethodPlain: return codeVerifier, nil case codeChallengeMethodS256: shaSum := sha256.Sum256([]byte(codeVerifier)) return base64.RawURLEncoding.EncodeToString(shaSum[:]), nil default: return "", fmt.Errorf("unknown challenge method (%v)", codeChallengeMethod) } } // handle an access token request https://tools.ietf.org/html/rfc6749#section-4.1.3 func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client storage.Client) { ctx := r.Context() code := r.PostFormValue("code") redirectURI := r.PostFormValue("redirect_uri") if code == "" { s.tokenErrHelper(w, errInvalidRequest, `Required param: code.`, http.StatusBadRequest) return } authCode, err := s.storage.GetAuthCode(ctx, code) if err != nil || s.now().After(authCode.Expiry) || authCode.ClientID != client.ID { if err != storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "failed to get auth code", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) } else { s.tokenErrHelper(w, errInvalidGrant, "Invalid or expired code parameter.", http.StatusBadRequest) } return } // RFC 7636 (PKCE) codeChallengeFromStorage := authCode.PKCE.CodeChallenge providedCodeVerifier := r.PostFormValue("code_verifier") switch { case providedCodeVerifier != "" && codeChallengeFromStorage != "": calculatedCodeChallenge, err := s.calculateCodeChallenge(providedCodeVerifier, authCode.PKCE.CodeChallengeMethod) if err != nil { s.logger.ErrorContext(r.Context(), "failed to calculate code challenge", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } if codeChallengeFromStorage != calculatedCodeChallenge { s.tokenErrHelper(w, errInvalidGrant, "Invalid code_verifier.", http.StatusBadRequest) return } case providedCodeVerifier != "": // Received no code_challenge on /auth, but a code_verifier on /token s.tokenErrHelper(w, errInvalidRequest, "No PKCE flow started. Cannot check code_verifier.", http.StatusBadRequest) return case codeChallengeFromStorage != "": // Received PKCE request on /auth, but no code_verifier on /token s.tokenErrHelper(w, errInvalidGrant, "Expecting parameter code_verifier in PKCE flow.", http.StatusBadRequest) return } if authCode.RedirectURI != redirectURI { s.tokenErrHelper(w, errInvalidRequest, "redirect_uri did not match URI from initial request.", http.StatusBadRequest) return } tokenResponse, err := s.exchangeAuthCode(ctx, w, authCode, client) if err != nil { s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } s.writeAccessToken(w, tokenResponse) } func (s *Server) exchangeAuthCode(ctx context.Context, w http.ResponseWriter, authCode storage.AuthCode, client storage.Client) (*accessTokenResponse, error) { accessToken, _, err := s.newAccessToken(ctx, client.ID, authCode.Claims, authCode.Scopes, authCode.Nonce, authCode.ConnectorID, authCode.AuthTime) if err != nil { s.logger.ErrorContext(ctx, "failed to create new access token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return nil, err } idToken, expiry, err := s.newIDToken(ctx, client.ID, authCode.Claims, authCode.Scopes, authCode.Nonce, accessToken, authCode.ID, authCode.ConnectorID, authCode.AuthTime) if err != nil { s.logger.ErrorContext(ctx, "failed to create ID token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return nil, err } if err := s.storage.DeleteAuthCode(ctx, authCode.ID); err != nil { s.logger.ErrorContext(ctx, "failed to delete auth code", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return nil, err } reqRefresh := func() bool { // Determine whether to issue a refresh token. A refresh token is only // issued when all of the following are true: // 1. The connector implements RefreshConnector. // 2. The connector's grantTypes config allows refresh_token. // 3. The client requested the offline_access scope. // // When any condition is not met, the refresh token is silently omitted // rather than returning an error. This matches the OAuth2 spec: the // server is never required to issue a refresh token (RFC 6749 §1.5). // https://datatracker.ietf.org/doc/html/rfc6749#section-1.5 conn, err := s.getConnector(ctx, authCode.ConnectorID) if err != nil { s.logger.ErrorContext(ctx, "connector not found", "connector_id", authCode.ConnectorID, "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return false } _, ok := conn.Connector.(connector.RefreshConnector) if !ok { return false } if !GrantTypeAllowed(conn.GrantTypes, grantTypeRefreshToken) { return false } for _, scope := range authCode.Scopes { if scope == scopeOfflineAccess { return true } } return false }() var refreshToken string if reqRefresh { refresh := storage.RefreshToken{ ID: storage.NewID(), Token: storage.NewID(), ClientID: authCode.ClientID, ConnectorID: authCode.ConnectorID, Scopes: authCode.Scopes, Claims: authCode.Claims, Nonce: authCode.Nonce, ConnectorData: authCode.ConnectorData, CreatedAt: s.now(), LastUsed: s.now(), } token := &internal.RefreshToken{ RefreshId: refresh.ID, Token: refresh.Token, } if refreshToken, err = internal.Marshal(token); err != nil { s.logger.ErrorContext(ctx, "failed to marshal refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return nil, err } if err := s.storage.CreateRefresh(ctx, refresh); err != nil { s.logger.ErrorContext(ctx, "failed to create refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return nil, err } // deleteToken determines if we need to delete the newly created refresh token // due to a failure in updating/creating the OfflineSession object for the // corresponding user. var deleteToken bool defer func() { if deleteToken { // Delete newly created refresh token from storage. if err := s.storage.DeleteRefresh(ctx, refresh.ID); err != nil { s.logger.ErrorContext(ctx, "failed to delete refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } } }() tokenRef := storage.RefreshTokenRef{ ID: refresh.ID, ClientID: refresh.ClientID, CreatedAt: refresh.CreatedAt, LastUsed: refresh.LastUsed, } // Try to retrieve an existing OfflineSession object for the corresponding user. if session, err := s.storage.GetOfflineSessions(ctx, refresh.Claims.UserID, refresh.ConnectorID); err != nil { if err != storage.ErrNotFound { s.logger.ErrorContext(ctx, "failed to get offline session", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return nil, err } offlineSessions := storage.OfflineSessions{ UserID: refresh.Claims.UserID, ConnID: refresh.ConnectorID, Refresh: make(map[string]*storage.RefreshTokenRef), ConnectorData: refresh.ConnectorData, } offlineSessions.Refresh[tokenRef.ClientID] = &tokenRef // Create a new OfflineSession object for the user and add a reference object for // the newly received refreshtoken. if err := s.storage.CreateOfflineSessions(ctx, offlineSessions); err != nil { s.logger.ErrorContext(ctx, "failed to create offline session", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return nil, err } } else { if oldTokenRef, ok := session.Refresh[tokenRef.ClientID]; ok { // Delete old refresh token from storage. if err := s.storage.DeleteRefresh(ctx, oldTokenRef.ID); err != nil && err != storage.ErrNotFound { s.logger.ErrorContext(ctx, "failed to delete refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return nil, err } } // Update existing OfflineSession obj with new RefreshTokenRef. if err := s.storage.UpdateOfflineSessions(ctx, session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) { old.Refresh[tokenRef.ClientID] = &tokenRef if len(refresh.ConnectorData) > 0 { old.ConnectorData = refresh.ConnectorData } return old, nil }); err != nil { s.logger.ErrorContext(ctx, "failed to update offline session", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return nil, err } } } return s.toAccessTokenResponse(idToken, accessToken, refreshToken, expiry), nil } func (s *Server) handleUserInfo(w http.ResponseWriter, r *http.Request) { ctx := r.Context() const prefix = "Bearer " auth := r.Header.Get("authorization") if len(auth) < len(prefix) || !strings.EqualFold(prefix, auth[:len(prefix)]) { w.Header().Set("WWW-Authenticate", "Bearer") s.tokenErrHelper(w, errAccessDenied, "Invalid bearer token.", http.StatusUnauthorized) return } rawIDToken := auth[len(prefix):] verifier := oidc.NewVerifier(s.issuerURL.String(), &signerKeySet{s.signer}, &oidc.Config{SkipClientIDCheck: true}) idToken, err := verifier.Verify(ctx, rawIDToken) if err != nil { s.logger.ErrorContext(r.Context(), "failed to verify ID token", "err", err) s.tokenErrHelper(w, errAccessDenied, "Invalid bearer token.", http.StatusForbidden) return } var claims json.RawMessage if err := idToken.Claims(&claims); err != nil { s.logger.ErrorContext(r.Context(), "failed to decode ID token claims", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(claims) } func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, client storage.Client) { ctx := r.Context() // Parse the fields if err := r.ParseForm(); err != nil { s.tokenErrHelper(w, errInvalidRequest, "Couldn't parse data", http.StatusBadRequest) return } q := r.Form nonce := q.Get("nonce") // Some clients, like the old go-oidc, provide extra whitespace. Tolerate this. scopes := strings.Fields(q.Get("scope")) // Parse the scopes if they are passed var ( unrecognized []string invalidScopes []string ) hasOpenIDScope := false for _, scope := range scopes { switch scope { case scopeOpenID: hasOpenIDScope = true case scopeOfflineAccess, scopeEmail, scopeProfile, scopeGroups, scopeFederatedID: default: peerID, ok := parseCrossClientScope(scope) if !ok { unrecognized = append(unrecognized, scope) continue } isTrusted, err := s.validateCrossClientTrust(ctx, client.ID, peerID) if err != nil { s.logger.ErrorContext(r.Context(), "error validating cross client trust", "client_id", client.ID, "peer_id", peerID, "err", err) s.tokenErrHelper(w, errInvalidClient, "Error validating cross client trust.", http.StatusBadRequest) return } if !isTrusted { invalidScopes = append(invalidScopes, scope) } } } if !hasOpenIDScope { s.tokenErrHelper(w, errInvalidRequest, `Missing required scope(s) ["openid"].`, http.StatusBadRequest) return } if len(unrecognized) > 0 { s.tokenErrHelper(w, errInvalidRequest, fmt.Sprintf("Unrecognized scope(s) %q", unrecognized), http.StatusBadRequest) return } if len(invalidScopes) > 0 { s.tokenErrHelper(w, errInvalidRequest, fmt.Sprintf("Client can't request scope(s) %q", invalidScopes), http.StatusBadRequest) return } // Which connector connID := s.passwordConnector conn, err := s.getConnector(ctx, connID) if err != nil { s.tokenErrHelper(w, errInvalidRequest, "Requested connector does not exist.", http.StatusBadRequest) return } if !GrantTypeAllowed(conn.GrantTypes, grantTypePassword) { s.logger.ErrorContext(r.Context(), "connector does not allow password grant", "connector_id", connID) s.tokenErrHelper(w, errInvalidRequest, "Requested connector does not support password grant.", http.StatusBadRequest) return } passwordConnector, ok := conn.Connector.(connector.PasswordConnector) if !ok { s.tokenErrHelper(w, errInvalidRequest, "Requested password connector does not correct type.", http.StatusBadRequest) return } // Login username := q.Get("username") password := q.Get("password") identity, ok, err := passwordConnector.Login(ctx, parseScopes(scopes), username, password) if err != nil { s.logger.ErrorContext(r.Context(), "failed to login user", "err", err) s.tokenErrHelper(w, errInvalidRequest, "Could not login user", http.StatusBadRequest) return } if !ok { s.tokenErrHelper(w, errAccessDenied, "Invalid username or password", http.StatusUnauthorized) return } // Build the claims to send the id token claims := storage.Claims{ UserID: identity.UserID, Username: identity.Username, PreferredUsername: identity.PreferredUsername, Email: identity.Email, EmailVerified: identity.EmailVerified, Groups: identity.Groups, } accessToken, _, err := s.newAccessToken(ctx, client.ID, claims, scopes, nonce, connID, time.Time{}) if err != nil { s.logger.ErrorContext(r.Context(), "password grant failed to create new access token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } idToken, expiry, err := s.newIDToken(ctx, client.ID, claims, scopes, nonce, accessToken, "", connID, time.Time{}) if err != nil { s.logger.ErrorContext(r.Context(), "password grant failed to create new ID token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } reqRefresh := func() bool { // Same logic as in exchangeAuthCode: silently omit refresh token // when the connector doesn't support it or grantTypes forbids it. // See RFC 6749 §1.5 — refresh tokens are never mandatory. // https://datatracker.ietf.org/doc/html/rfc6749#section-1.5 if _, ok := conn.Connector.(connector.RefreshConnector); !ok { return false } if !GrantTypeAllowed(conn.GrantTypes, grantTypeRefreshToken) { return false } for _, scope := range scopes { if scope == scopeOfflineAccess { return true } } return false }() var refreshToken string if reqRefresh { refresh := storage.RefreshToken{ ID: storage.NewID(), Token: storage.NewID(), ClientID: client.ID, ConnectorID: connID, Scopes: scopes, Claims: claims, Nonce: nonce, // ConnectorData: authCode.ConnectorData, CreatedAt: s.now(), LastUsed: s.now(), } token := &internal.RefreshToken{ RefreshId: refresh.ID, Token: refresh.Token, } if refreshToken, err = internal.Marshal(token); err != nil { s.logger.ErrorContext(r.Context(), "failed to marshal refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } if err := s.storage.CreateRefresh(ctx, refresh); err != nil { s.logger.ErrorContext(r.Context(), "failed to create refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } // deleteToken determines if we need to delete the newly created refresh token // due to a failure in updating/creating the OfflineSession object for the // corresponding user. var deleteToken bool defer func() { if deleteToken { // Delete newly created refresh token from storage. if err := s.storage.DeleteRefresh(ctx, refresh.ID); err != nil { s.logger.ErrorContext(r.Context(), "failed to delete refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } } }() tokenRef := storage.RefreshTokenRef{ ID: refresh.ID, ClientID: refresh.ClientID, CreatedAt: refresh.CreatedAt, LastUsed: refresh.LastUsed, } // Try to retrieve an existing OfflineSession object for the corresponding user. if session, err := s.storage.GetOfflineSessions(ctx, refresh.Claims.UserID, refresh.ConnectorID); err != nil { if err != storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "failed to get offline session", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return } offlineSessions := storage.OfflineSessions{ UserID: refresh.Claims.UserID, ConnID: refresh.ConnectorID, Refresh: make(map[string]*storage.RefreshTokenRef), ConnectorData: identity.ConnectorData, } offlineSessions.Refresh[tokenRef.ClientID] = &tokenRef // Create a new OfflineSession object for the user and add a reference object for // the newly received refreshtoken. if err := s.storage.CreateOfflineSessions(ctx, offlineSessions); err != nil { s.logger.ErrorContext(r.Context(), "failed to create offline session", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return } } else { if oldTokenRef, ok := session.Refresh[tokenRef.ClientID]; ok { // Delete old refresh token from storage. if err := s.storage.DeleteRefresh(ctx, oldTokenRef.ID); err != nil { if err == storage.ErrNotFound { s.logger.Warn("database inconsistent, refresh token missing", "token_id", oldTokenRef.ID) } else { s.logger.ErrorContext(r.Context(), "failed to delete refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return } } } // Update existing OfflineSession obj with new RefreshTokenRef. if err := s.storage.UpdateOfflineSessions(ctx, session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) { old.Refresh[tokenRef.ClientID] = &tokenRef old.ConnectorData = identity.ConnectorData return old, nil }); err != nil { s.logger.ErrorContext(r.Context(), "failed to update offline session", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return } } } resp := s.toAccessTokenResponse(idToken, accessToken, refreshToken, expiry) s.writeAccessToken(w, resp) } func (s *Server) handleTokenExchange(w http.ResponseWriter, r *http.Request, client storage.Client) { ctx := r.Context() if err := r.ParseForm(); err != nil { s.logger.ErrorContext(r.Context(), "could not parse request body", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusBadRequest) return } q := r.Form scopes := strings.Fields(q.Get("scope")) // OPTIONAL, map to issued token scope requestedTokenType := q.Get("requested_token_type") // OPTIONAL, default to access token if requestedTokenType == "" { requestedTokenType = tokenTypeAccess } subjectToken := q.Get("subject_token") // REQUIRED subjectTokenType := q.Get("subject_token_type") // REQUIRED connID := q.Get("connector_id") // REQUIRED, not in RFC switch subjectTokenType { case tokenTypeID, tokenTypeAccess: // ok, continue default: s.tokenErrHelper(w, errRequestNotSupported, "Invalid subject_token_type.", http.StatusBadRequest) return } if subjectToken == "" { s.tokenErrHelper(w, errInvalidRequest, "Missing subject_token", http.StatusBadRequest) return } conn, err := s.getConnector(ctx, connID) if err != nil { s.logger.ErrorContext(r.Context(), "failed to get connector", "err", err) s.tokenErrHelper(w, errInvalidRequest, "Requested connector does not exist.", http.StatusBadRequest) return } if !GrantTypeAllowed(conn.GrantTypes, grantTypeTokenExchange) { s.logger.ErrorContext(r.Context(), "connector does not allow token exchange", "connector_id", connID) s.tokenErrHelper(w, errInvalidRequest, "Requested connector does not support token exchange.", http.StatusBadRequest) return } teConn, ok := conn.Connector.(connector.TokenIdentityConnector) if !ok { s.logger.ErrorContext(r.Context(), "connector doesn't implement token exchange", "connector_id", connID) s.tokenErrHelper(w, errInvalidRequest, "Requested connector does not exist.", http.StatusBadRequest) return } identity, err := teConn.TokenIdentity(ctx, subjectTokenType, subjectToken) if err != nil { s.logger.ErrorContext(r.Context(), "failed to verify subject token", "err", err) s.tokenErrHelper(w, errAccessDenied, "", http.StatusUnauthorized) return } claims := storage.Claims{ UserID: identity.UserID, Username: identity.Username, PreferredUsername: identity.PreferredUsername, Email: identity.Email, EmailVerified: identity.EmailVerified, Groups: identity.Groups, } resp := accessTokenResponse{ IssuedTokenType: requestedTokenType, TokenType: "bearer", } var expiry time.Time switch requestedTokenType { case tokenTypeID: resp.AccessToken, expiry, err = s.newIDToken(r.Context(), client.ID, claims, scopes, "", "", "", connID, time.Time{}) case tokenTypeAccess: resp.AccessToken, expiry, err = s.newAccessToken(r.Context(), client.ID, claims, scopes, "", connID, time.Time{}) default: s.tokenErrHelper(w, errRequestNotSupported, "Invalid requested_token_type.", http.StatusBadRequest) return } if err != nil { s.logger.ErrorContext(r.Context(), "token exchange failed to create new token", "requested_token_type", requestedTokenType, "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } resp.ExpiresIn = int(time.Until(expiry).Seconds()) // Token response must include cache headers https://tools.ietf.org/html/rfc6749#section-5.1 w.Header().Set("Cache-Control", "no-store") w.Header().Set("Pragma", "no-cache") w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) } func (s *Server) handleClientCredentialsGrant(w http.ResponseWriter, r *http.Request, client storage.Client) { ctx := r.Context() // client_credentials requires a confidential client. if client.Public { s.tokenErrHelper(w, errUnauthorizedClient, "Public clients cannot use client_credentials grant.", http.StatusBadRequest) return } // Parse scopes from request. if err := r.ParseForm(); err != nil { s.tokenErrHelper(w, errInvalidRequest, "Couldn't parse data", http.StatusBadRequest) return } scopes := strings.Fields(r.Form.Get("scope")) // Validate scopes. var ( unrecognized []string invalidScopes []string ) hasOpenIDScope := false for _, scope := range scopes { switch scope { case scopeOpenID: hasOpenIDScope = true case scopeEmail, scopeProfile, scopeGroups: // allowed case scopeOfflineAccess: s.tokenErrHelper(w, errInvalidScope, "client_credentials grant does not support offline_access scope.", http.StatusBadRequest) return case scopeFederatedID: s.tokenErrHelper(w, errInvalidScope, "client_credentials grant does not support federated:id scope.", http.StatusBadRequest) return default: peerID, ok := parseCrossClientScope(scope) if !ok { unrecognized = append(unrecognized, scope) continue } isTrusted, err := s.validateCrossClientTrust(ctx, client.ID, peerID) if err != nil { s.logger.ErrorContext(ctx, "error validating cross client trust", "client_id", client.ID, "peer_id", peerID, "err", err) s.tokenErrHelper(w, errInvalidClient, "Error validating cross client trust.", http.StatusBadRequest) return } if !isTrusted { invalidScopes = append(invalidScopes, scope) } } } if len(unrecognized) > 0 { s.tokenErrHelper(w, errInvalidScope, fmt.Sprintf("Unrecognized scope(s) %q", unrecognized), http.StatusBadRequest) return } if len(invalidScopes) > 0 { s.tokenErrHelper(w, errInvalidScope, fmt.Sprintf("Client can't request scope(s) %q", invalidScopes), http.StatusBadRequest) return } // Build claims from the client itself — no user involved. claims := storage.Claims{ UserID: client.ID, } // Only populate Username/PreferredUsername when the profile scope is requested. for _, scope := range scopes { if scope == scopeProfile { claims.Username = client.Name claims.PreferredUsername = client.Name break } } nonce := r.Form.Get("nonce") // Empty connector ID is unique for cluster credentials grant // Creating connectors with an empty ID with the config and API is prohibited connID := "" accessToken, expiry, err := s.newAccessToken(ctx, client.ID, claims, scopes, nonce, connID, time.Time{}) if err != nil { s.logger.ErrorContext(ctx, "client_credentials grant failed to create new access token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } var idToken string if hasOpenIDScope { idToken, expiry, err = s.newIDToken(ctx, client.ID, claims, scopes, nonce, accessToken, "", connID, time.Time{}) if err != nil { s.logger.ErrorContext(ctx, "client_credentials grant failed to create new ID token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } } resp := s.toAccessTokenResponse(idToken, accessToken, "", expiry) s.writeAccessToken(w, resp) } type accessTokenResponse struct { AccessToken string `json:"access_token"` IssuedTokenType string `json:"issued_token_type,omitempty"` TokenType string `json:"token_type"` ExpiresIn int `json:"expires_in,omitempty"` RefreshToken string `json:"refresh_token,omitempty"` IDToken string `json:"id_token,omitempty"` Scope string `json:"scope,omitempty"` } func (s *Server) toAccessTokenResponse(idToken, accessToken, refreshToken string, expiry time.Time) *accessTokenResponse { return &accessTokenResponse{ AccessToken: accessToken, TokenType: "bearer", ExpiresIn: int(expiry.Sub(s.now()).Seconds()), RefreshToken: refreshToken, IDToken: idToken, } } func (s *Server) writeAccessToken(w http.ResponseWriter, resp *accessTokenResponse) { data, err := json.Marshal(resp) if err != nil { // TODO(nabokihms): error with context s.logger.Error("failed to marshal access token response", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Length", strconv.Itoa(len(data))) // Token response must include cache headers https://tools.ietf.org/html/rfc6749#section-5.1 w.Header().Set("Cache-Control", "no-store") w.Header().Set("Pragma", "no-cache") w.Write(data) } func (s *Server) renderError(r *http.Request, w http.ResponseWriter, status int, description string) { if err := s.templates.err(r, w, status, description); err != nil { s.logger.ErrorContext(r.Context(), "server template error", "err", err) } } func (s *Server) tokenErrHelper(w http.ResponseWriter, typ string, description string, statusCode int) { if err := tokenErr(w, typ, description, statusCode); err != nil { // TODO(nabokihms): error with context s.logger.Error("token error response", "err", err) } } // Check for username prompt override from connector. Defaults to "Username". func usernamePrompt(conn connector.PasswordConnector) string { if attr := conn.Prompt(); attr != "" { return attr } return "Username" } ================================================ FILE: server/handlers_approval_test.go ================================================ package server import ( "context" "crypto/hmac" "crypto/sha256" "encoding/base64" "errors" "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" "github.com/stretchr/testify/require" "github.com/dexidp/dex/storage" ) type getAuthRequestErrorStorage struct { storage.Storage err error } func (s *getAuthRequestErrorStorage) GetAuthRequest(context.Context, string) (storage.AuthRequest, error) { return storage.AuthRequest{}, s.err } func TestHandleApprovalGetAuthRequestErrorGET(t *testing.T) { httpServer, server := newTestServer(t, func(c *Config) { c.Storage = &getAuthRequestErrorStorage{Storage: c.Storage, err: errors.New("storage unavailable")} }) defer httpServer.Close() rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/approval?req=any&hmac=AQ", nil) server.ServeHTTP(rr, req) require.Equal(t, http.StatusInternalServerError, rr.Code) require.Contains(t, rr.Body.String(), "Database error.") } func TestHandleApprovalGetAuthRequestNotFoundGET(t *testing.T) { httpServer, server := newTestServer(t, nil) defer httpServer.Close() rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/approval?req=does-not-exist&hmac=AQ", nil) server.ServeHTTP(rr, req) require.Equal(t, http.StatusBadRequest, rr.Code) require.Contains(t, rr.Body.String(), "User session error.") require.NotContains(t, rr.Body.String(), "Database error.") } func TestHandleApprovalGetAuthRequestNotFoundPOST(t *testing.T) { httpServer, server := newTestServer(t, nil) defer httpServer.Close() body := strings.NewReader("approval=approve&req=does-not-exist&hmac=AQ") rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, "/approval", body) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") server.ServeHTTP(rr, req) require.Equal(t, http.StatusBadRequest, rr.Code) require.Contains(t, rr.Body.String(), "User session error.") require.NotContains(t, rr.Body.String(), "Database error.") } func TestHandleApprovalDoubleSubmitPOST(t *testing.T) { ctx := t.Context() httpServer, server := newTestServer(t, nil) defer httpServer.Close() authReq := storage.AuthRequest{ ID: "approval-double-submit", ClientID: "test", ResponseTypes: []string{responseTypeCode}, RedirectURI: "https://client.example/callback", Expiry: time.Now().Add(time.Minute), LoggedIn: true, MFAValidated: true, HMACKey: []byte("approval-double-submit-key"), } require.NoError(t, server.storage.CreateAuthRequest(ctx, authReq)) h := hmac.New(sha256.New, authReq.HMACKey) h.Write([]byte(authReq.ID)) mac := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) form := url.Values{ "approval": {"approve"}, "req": {authReq.ID}, "hmac": {mac}, } firstRR := httptest.NewRecorder() firstReq := httptest.NewRequest(http.MethodPost, "/approval", strings.NewReader(form.Encode())) firstReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") server.ServeHTTP(firstRR, firstReq) require.Equal(t, http.StatusSeeOther, firstRR.Code) require.Contains(t, firstRR.Header().Get("Location"), "https://client.example/callback") secondRR := httptest.NewRecorder() secondReq := httptest.NewRequest(http.MethodPost, "/approval", strings.NewReader(form.Encode())) secondReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") server.ServeHTTP(secondRR, secondReq) require.Equal(t, http.StatusBadRequest, secondRR.Code) require.Contains(t, secondRR.Body.String(), "User session error.") require.NotContains(t, secondRR.Body.String(), "Database error.") } ================================================ FILE: server/handlers_test.go ================================================ package server import ( "bytes" "context" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "errors" "fmt" "net/http" "net/http/httptest" "net/url" "path" "strings" "testing" "time" gosundheit "github.com/AppsFlyer/go-sundheit" "github.com/AppsFlyer/go-sundheit/checks" "github.com/coreos/go-oidc/v3/oidc" "github.com/stretchr/testify/require" "golang.org/x/crypto/bcrypt" "golang.org/x/oauth2" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/server/internal" "github.com/dexidp/dex/storage" ) func boolPtr(v bool) *bool { return &v } func TestHandleHealth(t *testing.T) { httpServer, server := newTestServer(t, nil) defer httpServer.Close() rr := httptest.NewRecorder() server.ServeHTTP(rr, httptest.NewRequest("GET", "/healthz", nil)) if rr.Code != http.StatusOK { t.Errorf("expected 200 got %d", rr.Code) } } func TestHandleDiscovery(t *testing.T) { httpServer, server := newTestServer(t, nil) defer httpServer.Close() rr := httptest.NewRecorder() server.ServeHTTP(rr, httptest.NewRequest("GET", "/.well-known/openid-configuration", nil)) if rr.Code != http.StatusOK { t.Errorf("expected 200 got %d", rr.Code) } var res discovery err := json.NewDecoder(rr.Result().Body).Decode(&res) require.NoError(t, err) require.Equal(t, discovery{ Issuer: httpServer.URL, Auth: fmt.Sprintf("%s/auth", httpServer.URL), Token: fmt.Sprintf("%s/token", httpServer.URL), Keys: fmt.Sprintf("%s/keys", httpServer.URL), UserInfo: fmt.Sprintf("%s/userinfo", httpServer.URL), DeviceEndpoint: fmt.Sprintf("%s/device/code", httpServer.URL), Introspect: fmt.Sprintf("%s/token/introspect", httpServer.URL), GrantTypes: []string{ "authorization_code", "client_credentials", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code", "urn:ietf:params:oauth:grant-type:token-exchange", }, ResponseTypes: []string{ "code", }, Subjects: []string{ "public", }, IDTokenAlgs: []string{ "RS256", }, CodeChallengeAlgs: []string{ "S256", "plain", }, Scopes: []string{ "openid", "email", "groups", "profile", "offline_access", }, AuthMethods: []string{ "client_secret_basic", "client_secret_post", }, Claims: []string{ "iss", "sub", "aud", "iat", "exp", "email", "email_verified", "locale", "name", "preferred_username", "at_hash", }, }, res) } func TestHandleHealthFailure(t *testing.T) { httpServer, server := newTestServer(t, func(c *Config) { c.HealthChecker = gosundheit.New() c.HealthChecker.RegisterCheck( &checks.CustomCheck{ CheckName: "fail", CheckFunc: func(_ context.Context) (details interface{}, err error) { return nil, errors.New("error") }, }, gosundheit.InitiallyPassing(false), gosundheit.ExecutionPeriod(1*time.Second), ) }) defer httpServer.Close() rr := httptest.NewRecorder() server.ServeHTTP(rr, httptest.NewRequest("GET", "/healthz", nil)) if rr.Code != http.StatusInternalServerError { t.Errorf("expected 500 got %d", rr.Code) } } type emptyStorage struct { storage.Storage } func (*emptyStorage) GetAuthRequest(context.Context, string) (storage.AuthRequest, error) { return storage.AuthRequest{}, storage.ErrNotFound } func TestHandleInvalidOAuth2Callbacks(t *testing.T) { httpServer, server := newTestServer(t, func(c *Config) { c.Storage = &emptyStorage{c.Storage} }) defer httpServer.Close() tests := []struct { TargetURI string ExpectedCode int }{ {"/callback", http.StatusBadRequest}, {"/callback?code=&state=", http.StatusBadRequest}, {"/callback?code=AAAAAAA&state=BBBBBBB", http.StatusBadRequest}, } rr := httptest.NewRecorder() for i, r := range tests { server.ServeHTTP(rr, httptest.NewRequest("GET", r.TargetURI, nil)) if rr.Code != r.ExpectedCode { t.Fatalf("test %d expected %d, got %d", i, r.ExpectedCode, rr.Code) } } } func TestHandleInvalidSAMLCallbacks(t *testing.T) { httpServer, server := newTestServer(t, func(c *Config) { c.Storage = &emptyStorage{c.Storage} }) defer httpServer.Close() type requestForm struct { RelayState string } tests := []struct { RequestForm requestForm ExpectedCode int }{ {requestForm{}, http.StatusBadRequest}, {requestForm{RelayState: "AAAAAAA"}, http.StatusBadRequest}, } rr := httptest.NewRecorder() for i, r := range tests { jsonValue, err := json.Marshal(r.RequestForm) if err != nil { t.Fatal(err.Error()) } server.ServeHTTP(rr, httptest.NewRequest("POST", "/callback", bytes.NewBuffer(jsonValue))) if rr.Code != r.ExpectedCode { t.Fatalf("test %d expected %d, got %d", i, r.ExpectedCode, rr.Code) } } } // TestHandleAuthCode checks that it is forbidden to use same code twice func TestHandleAuthCode(t *testing.T) { tests := []struct { name string handleCode func(*testing.T, context.Context, *oauth2.Config, string) }{ { name: "Code Reuse should return invalid_grant", handleCode: func(t *testing.T, ctx context.Context, oauth2Config *oauth2.Config, code string) { _, err := oauth2Config.Exchange(ctx, code) require.NoError(t, err) _, err = oauth2Config.Exchange(ctx, code) require.Error(t, err) oauth2Err, ok := err.(*oauth2.RetrieveError) require.True(t, ok) var errResponse struct{ Error string } err = json.Unmarshal(oauth2Err.Body, &errResponse) require.NoError(t, err) // invalid_grant must be returned for invalid values // https://tools.ietf.org/html/rfc6749#section-5.2 require.Equal(t, errInvalidGrant, errResponse.Error) }, }, { name: "No Code should return invalid_request", handleCode: func(t *testing.T, ctx context.Context, oauth2Config *oauth2.Config, _ string) { _, err := oauth2Config.Exchange(ctx, "") require.Error(t, err) oauth2Err, ok := err.(*oauth2.RetrieveError) require.True(t, ok) var errResponse struct{ Error string } err = json.Unmarshal(oauth2Err.Body, &errResponse) require.NoError(t, err) require.Equal(t, errInvalidRequest, errResponse.Error) }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctx := t.Context() httpServer, s := newTestServer(t, func(c *Config) { c.Issuer += "/non-root-path" }) defer httpServer.Close() p, err := oidc.NewProvider(ctx, httpServer.URL) require.NoError(t, err) var oauth2Client oauth2Client oauth2Client.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/callback" { http.Redirect(w, r, oauth2Client.config.AuthCodeURL(""), http.StatusSeeOther) return } q := r.URL.Query() require.Equal(t, q.Get("error"), "", q.Get("error_description")) code := q.Get("code") tc.handleCode(t, ctx, oauth2Client.config, code) w.WriteHeader(http.StatusOK) })) defer oauth2Client.server.Close() redirectURL := oauth2Client.server.URL + "/callback" client := storage.Client{ ID: "testclient", Secret: "testclientsecret", RedirectURIs: []string{redirectURL}, } err = s.storage.CreateClient(ctx, client) require.NoError(t, err) oauth2Client.config = &oauth2.Config{ ClientID: client.ID, ClientSecret: client.Secret, Endpoint: p.Endpoint(), Scopes: []string{oidc.ScopeOpenID, "email", "offline_access"}, RedirectURL: redirectURL, } resp, err := http.Get(oauth2Client.server.URL + "/login") require.NoError(t, err) resp.Body.Close() }) } } func mockConnectorDataTestStorage(t *testing.T, s storage.Storage) { ctx := t.Context() c := storage.Client{ ID: "test", Secret: "barfoo", RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"}, Name: "dex client", LogoURL: "https://goo.gl/JIyzIC", } err := s.CreateClient(ctx, c) require.NoError(t, err) c1 := storage.Connector{ ID: "test", Type: "mockPassword", Name: "mockPassword", Config: []byte(`{ "username": "test", "password": "test" }`), } err = s.CreateConnector(ctx, c1) require.NoError(t, err) c2 := storage.Connector{ ID: "http://any.valid.url/", Type: "mock", Name: "mockURLID", } err = s.CreateConnector(ctx, c2) require.NoError(t, err) } func TestHandlePassword(t *testing.T) { ctx := t.Context() tests := []struct { name string scopes string offlineSessionCreated bool }{ { name: "Password login, request refresh token", scopes: "openid offline_access email", offlineSessionCreated: true, }, { name: "Password login", scopes: "openid email", offlineSessionCreated: false, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { // Setup a dex server. httpServer, s := newTestServer(t, func(c *Config) { c.PasswordConnector = "test" c.Now = time.Now }) defer httpServer.Close() mockConnectorDataTestStorage(t, s.storage) makeReq := func(username, password string) *httptest.ResponseRecorder { u, err := url.Parse(s.issuerURL.String()) require.NoError(t, err) u.Path = path.Join(u.Path, "/token") v := url.Values{} v.Add("scope", tc.scopes) v.Add("grant_type", "password") v.Add("username", username) v.Add("password", password) req, _ := http.NewRequest("POST", u.String(), bytes.NewBufferString(v.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.SetBasicAuth("test", "barfoo") rr := httptest.NewRecorder() s.ServeHTTP(rr, req) return rr } // Check unauthorized error { rr := makeReq("test", "invalid") require.Equal(t, 401, rr.Code) } // Check that we received expected refresh token { rr := makeReq("test", "test") require.Equal(t, 200, rr.Code) var ref struct { Token string `json:"refresh_token"` } err := json.Unmarshal(rr.Body.Bytes(), &ref) require.NoError(t, err) newSess, err := s.storage.GetOfflineSessions(ctx, "0-385-28089-0", "test") if tc.offlineSessionCreated { require.NoError(t, err) require.Equal(t, `{"test": "true"}`, string(newSess.ConnectorData)) } else { require.Error(t, storage.ErrNotFound, err) } } }) } } func TestHandlePassword_LocalPasswordDBClaims(t *testing.T) { ctx := t.Context() // Setup a dex server. httpServer, s := newTestServer(t, func(c *Config) { c.PasswordConnector = "local" }) defer httpServer.Close() // Client credentials for password grant. client := storage.Client{ ID: "test", Secret: "barfoo", RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"}, } require.NoError(t, s.storage.CreateClient(ctx, client)) // Enable local connector. localConn := storage.Connector{ ID: "local", Type: LocalConnector, Name: "Email", ResourceVersion: "1", } require.NoError(t, s.storage.CreateConnector(ctx, localConn)) _, err := s.OpenConnector(localConn) require.NoError(t, err) // Create a user in the password DB with groups and preferred_username. pw := "secret" hash, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost) require.NoError(t, err) require.NoError(t, s.storage.CreatePassword(ctx, storage.Password{ Email: "user@example.com", Username: "user-login", Name: "User Full Name", EmailVerified: boolPtr(false), PreferredUsername: "user-public", UserID: "user-id", Groups: []string{"team-a", "team-a/admins"}, Hash: hash, })) u, err := url.Parse(s.issuerURL.String()) require.NoError(t, err) u.Path = path.Join(u.Path, "/token") v := url.Values{} v.Add("scope", "openid profile email groups") v.Add("grant_type", "password") v.Add("username", "user@example.com") v.Add("password", pw) req, _ := http.NewRequest("POST", u.String(), bytes.NewBufferString(v.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.SetBasicAuth("test", "barfoo") rr := httptest.NewRecorder() s.ServeHTTP(rr, req) require.Equal(t, http.StatusOK, rr.Code) var tokenResponse struct { IDToken string `json:"id_token"` } require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &tokenResponse)) require.NotEmpty(t, tokenResponse.IDToken) p, err := oidc.NewProvider(ctx, httpServer.URL) require.NoError(t, err) idToken, err := p.Verifier(&oidc.Config{SkipClientIDCheck: true}).Verify(ctx, tokenResponse.IDToken) require.NoError(t, err) var claims struct { Name string `json:"name"` EmailVerified bool `json:"email_verified"` PreferredUsername string `json:"preferred_username"` Groups []string `json:"groups"` } require.NoError(t, idToken.Claims(&claims)) require.Equal(t, "User Full Name", claims.Name) require.False(t, claims.EmailVerified) require.Equal(t, "user-public", claims.PreferredUsername) require.Equal(t, []string{"team-a", "team-a/admins"}, claims.Groups) } func setSessionsEnabled(t *testing.T, enabled bool) { t.Helper() if enabled { t.Setenv("DEX_SESSIONS_ENABLED", "true") } else { t.Setenv("DEX_SESSIONS_ENABLED", "false") } } func TestFinalizeLoginCreatesUserIdentity(t *testing.T) { ctx := t.Context() setSessionsEnabled(t, true) connID := "mockPw" authReqID := "test-create-ui" expiry := time.Now().Add(100 * time.Second) httpServer, s := newTestServer(t, func(c *Config) { c.SkipApprovalScreen = true c.Now = time.Now }) defer httpServer.Close() sc := storage.Connector{ ID: connID, Type: "mockPassword", Name: "MockPassword", ResourceVersion: "1", Config: []byte(`{"username": "foo", "password": "password"}`), } require.NoError(t, s.storage.CreateConnector(ctx, sc)) _, err := s.OpenConnector(sc) require.NoError(t, err) authReq := storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: []string{responseTypeCode}, } require.NoError(t, s.storage.CreateAuthRequest(ctx, authReq)) rr := httptest.NewRecorder() reqPath := fmt.Sprintf("/auth/%s/login?state=%s&back=&login=foo&password=password", connID, authReqID) s.handlePasswordLogin(rr, httptest.NewRequest("POST", reqPath, nil)) require.Equal(t, 303, rr.Code) ui, err := s.storage.GetUserIdentity(ctx, "0-385-28089-0", connID) require.NoError(t, err, "UserIdentity should exist after login") require.Equal(t, "0-385-28089-0", ui.UserID) require.Equal(t, connID, ui.ConnectorID) require.Equal(t, "kilgore@kilgore.trout", ui.Claims.Email) require.NotZero(t, ui.CreatedAt, "CreatedAt should be set") require.NotZero(t, ui.LastLogin, "LastLogin should be set") } func TestFinalizeLoginUpdatesUserIdentity(t *testing.T) { ctx := t.Context() setSessionsEnabled(t, true) connID := "mockPw" authReqID := "test-update-ui" expiry := time.Now().Add(100 * time.Second) oldTime := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) httpServer, s := newTestServer(t, func(c *Config) { c.SkipApprovalScreen = true c.Now = time.Now }) defer httpServer.Close() sc := storage.Connector{ ID: connID, Type: "mockPassword", Name: "MockPassword", ResourceVersion: "1", Config: []byte(`{"username": "foo", "password": "password"}`), } require.NoError(t, s.storage.CreateConnector(ctx, sc)) _, err := s.OpenConnector(sc) require.NoError(t, err) // Pre-create UserIdentity with old data require.NoError(t, s.storage.CreateUserIdentity(ctx, storage.UserIdentity{ UserID: "0-385-28089-0", ConnectorID: connID, Claims: storage.Claims{ UserID: "0-385-28089-0", Username: "Old Name", Email: "old@example.com", }, Consents: map[string][]string{"existing-client": {"openid"}}, CreatedAt: oldTime, LastLogin: oldTime, })) authReq := storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: []string{responseTypeCode}, } require.NoError(t, s.storage.CreateAuthRequest(ctx, authReq)) rr := httptest.NewRecorder() reqPath := fmt.Sprintf("/auth/%s/login?state=%s&back=&login=foo&password=password", connID, authReqID) s.handlePasswordLogin(rr, httptest.NewRequest("POST", reqPath, nil)) require.Equal(t, 303, rr.Code) ui, err := s.storage.GetUserIdentity(ctx, "0-385-28089-0", connID) require.NoError(t, err, "UserIdentity should exist after login") require.Equal(t, "Kilgore Trout", ui.Claims.Username, "claims should be refreshed from the connector") require.Equal(t, "kilgore@kilgore.trout", ui.Claims.Email, "claims should be refreshed from the connector") require.True(t, ui.LastLogin.After(oldTime), "LastLogin should be updated") require.Equal(t, oldTime, ui.CreatedAt, "CreatedAt should not change on update") require.Equal(t, []string{"openid"}, ui.Consents["existing-client"], "existing consents should be preserved") } func TestFinalizeLoginSkipsUserIdentityWhenDisabled(t *testing.T) { ctx := t.Context() setSessionsEnabled(t, false) connID := "mockPw" authReqID := "test-no-ui" expiry := time.Now().Add(100 * time.Second) httpServer, s := newTestServer(t, func(c *Config) { c.SkipApprovalScreen = true c.Now = time.Now }) defer httpServer.Close() sc := storage.Connector{ ID: connID, Type: "mockPassword", Name: "MockPassword", ResourceVersion: "1", Config: []byte(`{"username": "foo", "password": "password"}`), } require.NoError(t, s.storage.CreateConnector(ctx, sc)) _, err := s.OpenConnector(sc) require.NoError(t, err) authReq := storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: []string{responseTypeCode}, } require.NoError(t, s.storage.CreateAuthRequest(ctx, authReq)) rr := httptest.NewRecorder() reqPath := fmt.Sprintf("/auth/%s/login?state=%s&back=&login=foo&password=password", connID, authReqID) s.handlePasswordLogin(rr, httptest.NewRequest("POST", reqPath, nil)) require.Equal(t, 303, rr.Code) _, err = s.storage.GetUserIdentity(ctx, "0-385-28089-0", connID) require.ErrorIs(t, err, storage.ErrNotFound, "UserIdentity should not be created when sessions disabled") } func TestSkipApprovalWithExistingConsent(t *testing.T) { ctx := t.Context() setSessionsEnabled(t, true) connID := "mock" authReqID := "test-consent-skip" expiry := time.Now().Add(100 * time.Second) tests := []struct { name string consents map[string][]string scopes []string clientID string forcePrompt bool wantPath string }{ { name: "Existing consent covers requested scopes", consents: map[string][]string{"test": {"email", "profile"}}, scopes: []string{"openid", "email", "profile"}, clientID: "test", wantPath: "/callback/cb", }, { name: "Existing consent missing a scope", consents: map[string][]string{"test": {"email"}}, scopes: []string{"openid", "email", "profile"}, clientID: "test", wantPath: "/approval", }, { name: "Force approval overrides consent", consents: map[string][]string{"test": {"email", "profile"}}, scopes: []string{"openid", "email", "profile"}, clientID: "test", forcePrompt: true, wantPath: "/approval", }, { name: "No consent for this client", consents: map[string][]string{"other-client": {"email"}}, scopes: []string{"openid", "email"}, clientID: "test", wantPath: "/approval", }, { name: "Only openid scope - skip with empty consent", consents: map[string][]string{"test": {}}, scopes: []string{"openid"}, clientID: "test", wantPath: "/callback/cb", }, { name: "offline_access requires consent", consents: map[string][]string{"test": {}}, scopes: []string{"openid", "offline_access"}, clientID: "test", wantPath: "/approval", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { httpServer, s := newTestServer(t, func(c *Config) { c.SkipApprovalScreen = false c.Now = time.Now }) defer httpServer.Close() // Pre-create UserIdentity with consents require.NoError(t, s.storage.CreateUserIdentity(ctx, storage.UserIdentity{ UserID: "0-385-28089-0", ConnectorID: connID, Claims: storage.Claims{ UserID: "0-385-28089-0", Username: "Kilgore Trout", Email: "kilgore@kilgore.trout", EmailVerified: true, }, Consents: tc.consents, CreatedAt: time.Now(), LastLogin: time.Now(), })) authReq := storage.AuthRequest{ ID: authReqID, ConnectorID: connID, ClientID: tc.clientID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: []string{responseTypeCode}, Scopes: tc.scopes, ForceApprovalPrompt: tc.forcePrompt, } require.NoError(t, s.storage.CreateAuthRequest(ctx, authReq)) rr := httptest.NewRecorder() reqPath := fmt.Sprintf("/callback/%s?state=%s", connID, authReqID) s.handleConnectorCallback(rr, httptest.NewRequest("GET", reqPath, nil)) require.Equal(t, 303, rr.Code) cb, err := url.Parse(rr.Result().Header.Get("Location")) require.NoError(t, err) require.Equal(t, tc.wantPath, cb.Path) }) } } func TestConsentPersistedOnApproval(t *testing.T) { ctx := t.Context() setSessionsEnabled(t, true) httpServer, s := newTestServer(t, nil) defer httpServer.Close() userID := "test-user" connectorID := "mock" clientID := "test" // Pre-create UserIdentity (would have been created during login) require.NoError(t, s.storage.CreateUserIdentity(ctx, storage.UserIdentity{ UserID: userID, ConnectorID: connectorID, Claims: storage.Claims{UserID: userID}, Consents: make(map[string][]string), CreatedAt: time.Now(), LastLogin: time.Now(), })) authReq := storage.AuthRequest{ ID: "approval-consent-test", ClientID: clientID, ConnectorID: connectorID, ResponseTypes: []string{responseTypeCode}, RedirectURI: "https://client.example/callback", Expiry: time.Now().Add(time.Minute), LoggedIn: true, Claims: storage.Claims{UserID: userID}, Scopes: []string{"openid", "email", "profile"}, HMACKey: []byte("consent-test-key"), } require.NoError(t, s.storage.CreateAuthRequest(ctx, authReq)) h := hmac.New(sha256.New, authReq.HMACKey) h.Write([]byte(authReq.ID)) mac := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) form := url.Values{ "approval": {"approve"}, "req": {authReq.ID}, "hmac": {mac}, } rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, "/approval", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") s.ServeHTTP(rr, req) require.Equal(t, http.StatusSeeOther, rr.Code, "approval should redirect") ui, err := s.storage.GetUserIdentity(ctx, userID, connectorID) require.NoError(t, err, "UserIdentity should exist") require.Equal(t, []string{"openid", "email", "profile"}, ui.Consents[clientID], "approved scopes should be persisted") } func TestScopesCoveredByConsent(t *testing.T) { tests := []struct { name string approved []string requested []string want bool }{ { name: "All scopes covered", approved: []string{"email", "profile"}, requested: []string{"openid", "email", "profile"}, want: true, }, { name: "Missing scope", approved: []string{"email"}, requested: []string{"openid", "email", "groups"}, want: false, }, { name: "Only openid scope skipped", approved: []string{}, requested: []string{"openid"}, want: true, }, { name: "offline_access requires consent", approved: []string{}, requested: []string{"openid", "offline_access"}, want: false, }, { name: "offline_access covered by consent", approved: []string{"offline_access"}, requested: []string{"openid", "offline_access"}, want: true, }, { name: "Nil approved", approved: nil, requested: []string{"email"}, want: false, }, { name: "Empty requested", approved: []string{"email"}, requested: []string{}, want: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got := scopesCoveredByConsent(tc.approved, tc.requested) require.Equal(t, tc.want, got) }) } } func TestHandlePasswordLoginWithSkipApproval(t *testing.T) { ctx := t.Context() connID := "mockPw" authReqID := "test" expiry := time.Now().Add(100 * time.Second) resTypes := []string{responseTypeCode} tests := []struct { name string skipApproval bool authReq storage.AuthRequest expectedRes string offlineSessionCreated bool }{ { name: "Force approval", skipApproval: false, authReq: storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: resTypes, ForceApprovalPrompt: true, }, expectedRes: "/approval", offlineSessionCreated: false, }, { name: "Skip approval by server config", skipApproval: true, authReq: storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: resTypes, ForceApprovalPrompt: true, }, expectedRes: "/approval", offlineSessionCreated: false, }, { name: "No skip", skipApproval: false, authReq: storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: resTypes, ForceApprovalPrompt: false, }, expectedRes: "/approval", offlineSessionCreated: false, }, { name: "Skip approval", skipApproval: true, authReq: storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: resTypes, ForceApprovalPrompt: false, }, expectedRes: "/auth/mockPw/cb", offlineSessionCreated: false, }, { name: "Force approval, request refresh token", skipApproval: false, authReq: storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: resTypes, ForceApprovalPrompt: true, Scopes: []string{"offline_access"}, }, expectedRes: "/approval", offlineSessionCreated: true, }, { name: "Skip approval, request refresh token", skipApproval: true, authReq: storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: resTypes, ForceApprovalPrompt: false, Scopes: []string{"offline_access"}, }, expectedRes: "/auth/mockPw/cb", offlineSessionCreated: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { httpServer, s := newTestServer(t, func(c *Config) { c.SkipApprovalScreen = tc.skipApproval c.Now = time.Now }) defer httpServer.Close() sc := storage.Connector{ ID: connID, Type: "mockPassword", Name: "MockPassword", ResourceVersion: "1", Config: []byte("{\"username\": \"foo\", \"password\": \"password\"}"), } if err := s.storage.CreateConnector(ctx, sc); err != nil { t.Fatalf("create connector: %v", err) } if _, err := s.OpenConnector(sc); err != nil { t.Fatalf("open connector: %v", err) } if err := s.storage.CreateAuthRequest(ctx, tc.authReq); err != nil { t.Fatalf("failed to create AuthRequest: %v", err) } rr := httptest.NewRecorder() path := fmt.Sprintf("/auth/%s/login?state=%s&back=&login=foo&password=password", connID, authReqID) s.handlePasswordLogin(rr, httptest.NewRequest("POST", path, nil)) require.Equal(t, 303, rr.Code) resp := rr.Result() defer resp.Body.Close() cb, _ := url.Parse(resp.Header.Get("Location")) require.Equal(t, tc.expectedRes, cb.Path) offlineSession, err := s.storage.GetOfflineSessions(ctx, "0-385-28089-0", connID) if tc.offlineSessionCreated { require.NoError(t, err) require.NotEmpty(t, offlineSession) } else { require.Error(t, storage.ErrNotFound, err) } }) } } func TestHandleClientCredentials(t *testing.T) { tests := []struct { name string clientID string clientSecret string scopes string wantCode int wantAccessTok bool wantIDToken bool wantUsername string }{ { name: "Basic grant, no scopes", clientID: "test", clientSecret: "barfoo", scopes: "", wantCode: 200, wantAccessTok: true, wantIDToken: false, }, { name: "With openid scope", clientID: "test", clientSecret: "barfoo", scopes: "openid", wantCode: 200, wantAccessTok: true, wantIDToken: true, }, { name: "With openid and profile scope includes username", clientID: "test", clientSecret: "barfoo", scopes: "openid profile", wantCode: 200, wantAccessTok: true, wantIDToken: true, wantUsername: "Test Client", }, { name: "With openid email profile groups", clientID: "test", clientSecret: "barfoo", scopes: "openid email profile groups", wantCode: 200, wantAccessTok: true, wantIDToken: true, wantUsername: "Test Client", }, { name: "Invalid client secret", clientID: "test", clientSecret: "wrong", scopes: "", wantCode: 401, }, { name: "Unknown client", clientID: "nonexistent", clientSecret: "secret", scopes: "", wantCode: 401, }, { name: "offline_access scope rejected", clientID: "test", clientSecret: "barfoo", scopes: "openid offline_access", wantCode: 400, }, { name: "Unrecognized scope", clientID: "test", clientSecret: "barfoo", scopes: "openid bogus", wantCode: 400, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctx := t.Context() httpServer, s := newTestServer(t, func(c *Config) { c.Now = time.Now }) defer httpServer.Close() // Create a confidential client for testing. err := s.storage.CreateClient(ctx, storage.Client{ ID: "test", Secret: "barfoo", RedirectURIs: []string{"https://example.com/callback"}, Name: "Test Client", }) require.NoError(t, err) u, err := url.Parse(s.issuerURL.String()) require.NoError(t, err) u.Path = path.Join(u.Path, "/token") v := url.Values{} v.Add("grant_type", "client_credentials") if tc.scopes != "" { v.Add("scope", tc.scopes) } req, _ := http.NewRequest("POST", u.String(), bytes.NewBufferString(v.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.SetBasicAuth(tc.clientID, tc.clientSecret) rr := httptest.NewRecorder() s.ServeHTTP(rr, req) require.Equal(t, tc.wantCode, rr.Code) if tc.wantCode == 200 { var resp struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type"` ExpiresIn int `json:"expires_in"` IDToken string `json:"id_token"` RefreshToken string `json:"refresh_token"` } err := json.Unmarshal(rr.Body.Bytes(), &resp) require.NoError(t, err) if tc.wantAccessTok { require.NotEmpty(t, resp.AccessToken) require.Equal(t, "bearer", resp.TokenType) require.Greater(t, resp.ExpiresIn, 0) } if tc.wantIDToken { require.NotEmpty(t, resp.IDToken) // Verify the ID token claims. provider, err := oidc.NewProvider(ctx, httpServer.URL) require.NoError(t, err) verifier := provider.Verifier(&oidc.Config{ClientID: tc.clientID}) idToken, err := verifier.Verify(ctx, resp.IDToken) require.NoError(t, err) // Decode the subject to verify the connector ID. var sub internal.IDTokenSubject require.NoError(t, internal.Unmarshal(idToken.Subject, &sub)) require.Equal(t, "", sub.ConnId) require.Equal(t, tc.clientID, sub.UserId) var claims struct { Name string `json:"name"` PreferredUsername string `json:"preferred_username"` } require.NoError(t, idToken.Claims(&claims)) if tc.wantUsername != "" { require.Equal(t, tc.wantUsername, claims.Name) require.Equal(t, tc.wantUsername, claims.PreferredUsername) } else { require.Empty(t, claims.Name) require.Empty(t, claims.PreferredUsername) } } else { require.Empty(t, resp.IDToken) } // client_credentials must never return a refresh token. require.Empty(t, resp.RefreshToken) } }) } } func TestHandleConnectorCallbackWithSkipApproval(t *testing.T) { ctx := t.Context() connID := "mock" authReqID := "test" expiry := time.Now().Add(100 * time.Second) resTypes := []string{responseTypeCode} tests := []struct { name string skipApproval bool authReq storage.AuthRequest expectedRes string offlineSessionCreated bool }{ { name: "Force approval", skipApproval: false, authReq: storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: resTypes, ForceApprovalPrompt: true, }, expectedRes: "/approval", offlineSessionCreated: false, }, { name: "Skip approval by server config", skipApproval: true, authReq: storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: resTypes, ForceApprovalPrompt: true, }, expectedRes: "/approval", offlineSessionCreated: false, }, { name: "Skip approval by auth request", skipApproval: false, authReq: storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: resTypes, ForceApprovalPrompt: false, }, expectedRes: "/approval", offlineSessionCreated: false, }, { name: "Skip approval", skipApproval: true, authReq: storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: resTypes, ForceApprovalPrompt: false, }, expectedRes: "/callback/cb", offlineSessionCreated: false, }, { name: "Force approval, request refresh token", skipApproval: false, authReq: storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: resTypes, ForceApprovalPrompt: true, Scopes: []string{"offline_access"}, }, expectedRes: "/approval", offlineSessionCreated: true, }, { name: "Skip approval, request refresh token", skipApproval: true, authReq: storage.AuthRequest{ ID: authReqID, ConnectorID: connID, RedirectURI: "cb", Expiry: expiry, ResponseTypes: resTypes, ForceApprovalPrompt: false, Scopes: []string{"offline_access"}, }, expectedRes: "/callback/cb", offlineSessionCreated: false, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { httpServer, s := newTestServer(t, func(c *Config) { c.SkipApprovalScreen = tc.skipApproval c.Now = time.Now }) defer httpServer.Close() if err := s.storage.CreateAuthRequest(ctx, tc.authReq); err != nil { t.Fatalf("failed to create AuthRequest: %v", err) } rr := httptest.NewRecorder() path := fmt.Sprintf("/callback/%s?state=%s", connID, authReqID) s.handleConnectorCallback(rr, httptest.NewRequest("GET", path, nil)) require.Equal(t, 303, rr.Code) resp := rr.Result() defer resp.Body.Close() cb, _ := url.Parse(resp.Header.Get("Location")) require.Equal(t, tc.expectedRes, cb.Path) offlineSession, err := s.storage.GetOfflineSessions(ctx, "0-385-28089-0", connID) if tc.offlineSessionCreated { require.NoError(t, err) require.NotEmpty(t, offlineSession) } else { require.Error(t, storage.ErrNotFound, err) } }) } } func TestHandleTokenExchange(t *testing.T) { tests := []struct { name string scope string requestedTokenType string subjectTokenType string subjectToken string expectedCode int expectedTokenType string }{ { "id-for-acccess", "openid", tokenTypeAccess, tokenTypeID, "foobar", http.StatusOK, tokenTypeAccess, }, { "id-for-id", "openid", tokenTypeID, tokenTypeID, "foobar", http.StatusOK, tokenTypeID, }, { "id-for-default", "openid", "", tokenTypeID, "foobar", http.StatusOK, tokenTypeAccess, }, { "access-for-access", "openid", tokenTypeAccess, tokenTypeAccess, "foobar", http.StatusOK, tokenTypeAccess, }, { "missing-subject_token_type", "openid", tokenTypeAccess, "", "foobar", http.StatusBadRequest, "", }, { "missing-subject_token", "openid", tokenTypeAccess, tokenTypeAccess, "", http.StatusBadRequest, "", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctx := t.Context() httpServer, s := newTestServer(t, func(c *Config) { c.Storage.CreateClient(ctx, storage.Client{ ID: "client_1", Secret: "secret_1", }) }) defer httpServer.Close() vals := make(url.Values) vals.Set("grant_type", grantTypeTokenExchange) setNonEmpty(vals, "connector_id", "mock") setNonEmpty(vals, "scope", tc.scope) setNonEmpty(vals, "requested_token_type", tc.requestedTokenType) setNonEmpty(vals, "subject_token_type", tc.subjectTokenType) setNonEmpty(vals, "subject_token", tc.subjectToken) setNonEmpty(vals, "client_id", "client_1") setNonEmpty(vals, "client_secret", "secret_1") rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, httpServer.URL+"/token", strings.NewReader(vals.Encode())) req.Header.Set("content-type", "application/x-www-form-urlencoded") s.handleToken(rr, req) require.Equal(t, tc.expectedCode, rr.Code, rr.Body.String()) require.Equal(t, "application/json", rr.Result().Header.Get("content-type")) if tc.expectedCode == http.StatusOK { var res accessTokenResponse err := json.NewDecoder(rr.Result().Body).Decode(&res) require.NoError(t, err) require.Equal(t, tc.expectedTokenType, res.IssuedTokenType) } }) } } func TestHandleTokenExchangeConnectorGrantTypeRestriction(t *testing.T) { ctx := t.Context() httpServer, s := newTestServer(t, func(c *Config) { c.Storage.CreateClient(ctx, storage.Client{ ID: "client_1", Secret: "secret_1", }) }) defer httpServer.Close() // Restrict mock connector to authorization_code only err := s.storage.UpdateConnector(ctx, "mock", func(c storage.Connector) (storage.Connector, error) { c.GrantTypes = []string{grantTypeAuthorizationCode} return c, nil }) require.NoError(t, err) // Clear cached connector to pick up new grant types s.mu.Lock() delete(s.connectors, "mock") s.mu.Unlock() vals := make(url.Values) vals.Set("grant_type", grantTypeTokenExchange) vals.Set("connector_id", "mock") vals.Set("scope", "openid") vals.Set("requested_token_type", tokenTypeAccess) vals.Set("subject_token_type", tokenTypeID) vals.Set("subject_token", "foobar") vals.Set("client_id", "client_1") vals.Set("client_secret", "secret_1") rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, httpServer.URL+"/token", strings.NewReader(vals.Encode())) req.Header.Set("content-type", "application/x-www-form-urlencoded") s.handleToken(rr, req) require.Equal(t, http.StatusBadRequest, rr.Code, rr.Body.String()) } func TestHandleAuthorizationConnectorGrantTypeFiltering(t *testing.T) { tests := []struct { name string // grantTypes per connector ID; nil means unrestricted connectorGrantTypes map[string][]string responseType string wantCode int // wantRedirectContains is checked when wantCode == 302 wantRedirectContains string // wantBodyContains is checked when wantCode != 302 wantBodyContains string }{ { name: "one connector filtered, redirect to remaining", connectorGrantTypes: map[string][]string{ "mock": {grantTypeDeviceCode}, "mock2": nil, }, responseType: "code", wantCode: http.StatusFound, wantRedirectContains: "/auth/mock2", }, { name: "all connectors filtered", connectorGrantTypes: map[string][]string{ "mock": {grantTypeDeviceCode}, "mock2": {grantTypeDeviceCode}, }, responseType: "code", wantCode: http.StatusBadRequest, wantBodyContains: "No connectors available", }, { name: "no restrictions, both available", connectorGrantTypes: map[string][]string{ "mock": nil, "mock2": nil, }, responseType: "code", wantCode: http.StatusOK, }, { name: "implicit flow filters auth_code-only connector", connectorGrantTypes: map[string][]string{ "mock": {grantTypeAuthorizationCode}, "mock2": nil, }, responseType: "token", wantCode: http.StatusFound, wantRedirectContains: "/auth/mock2", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctx := t.Context() httpServer, s := newTestServerMultipleConnectors(t, func(c *Config) { c.Storage.CreateClient(ctx, storage.Client{ ID: "test", RedirectURIs: []string{"http://example.com/callback"}, }) }) defer httpServer.Close() for id, gts := range tc.connectorGrantTypes { err := s.storage.UpdateConnector(ctx, id, func(c storage.Connector) (storage.Connector, error) { c.GrantTypes = gts return c, nil }) require.NoError(t, err) s.mu.Lock() delete(s.connectors, id) s.mu.Unlock() } rr := httptest.NewRecorder() reqURL := fmt.Sprintf("%s/auth?response_type=%s&client_id=test&redirect_uri=http://example.com/callback&scope=openid", httpServer.URL, tc.responseType) req := httptest.NewRequest(http.MethodGet, reqURL, nil) s.handleAuthorization(rr, req) require.Equal(t, tc.wantCode, rr.Code) if tc.wantRedirectContains != "" { require.Contains(t, rr.Header().Get("Location"), tc.wantRedirectContains) } if tc.wantBodyContains != "" { require.Contains(t, rr.Body.String(), tc.wantBodyContains) } }) } } func TestHandleConnectorLoginGrantTypeRejection(t *testing.T) { ctx := t.Context() httpServer, s := newTestServer(t, func(c *Config) { c.Storage.CreateClient(ctx, storage.Client{ ID: "test-client", Secret: "secret", RedirectURIs: []string{"http://example.com/callback"}, }) }) defer httpServer.Close() // Restrict mock connector to device_code only err := s.storage.UpdateConnector(ctx, "mock", func(c storage.Connector) (storage.Connector, error) { c.GrantTypes = []string{grantTypeDeviceCode} return c, nil }) require.NoError(t, err) s.mu.Lock() delete(s.connectors, "mock") s.mu.Unlock() // Try to use mock connector for auth code flow via the full server router rr := httptest.NewRecorder() reqURL := httpServer.URL + "/auth/mock?response_type=code&client_id=test-client&redirect_uri=http://example.com/callback&scope=openid" req := httptest.NewRequest(http.MethodGet, reqURL, nil) s.ServeHTTP(rr, req) require.Equal(t, http.StatusBadRequest, rr.Code) require.Contains(t, rr.Body.String(), "does not support this grant type") } func setNonEmpty(vals url.Values, key, value string) { if value != "" { vals.Set(key, value) } } // registerTestConnector creates a connector in storage and registers it in the server's connectors map. func registerTestConnector(t *testing.T, s *Server, connID string, c connector.Connector) { t.Helper() ctx := t.Context() storageConn := storage.Connector{ ID: connID, Type: "saml", Name: "Test SAML", ResourceVersion: "1", } if err := s.storage.CreateConnector(ctx, storageConn); err != nil { t.Fatalf("failed to create connector in storage: %v", err) } s.mu.Lock() s.connectors[connID] = Connector{ ResourceVersion: "1", Connector: c, } s.mu.Unlock() } func TestConnectorDataPersistence(t *testing.T) { // Test that ConnectorData is correctly stored in refresh token // and can be used for subsequent refresh operations. httpServer, server := newTestServer(t, func(c *Config) { c.RefreshTokenPolicy = &RefreshTokenPolicy{rotateRefreshTokens: true} }) defer httpServer.Close() ctx := t.Context() connID := "saml-conndata" // Create a mock SAML connector that also implements RefreshConnector mockConn := &mockSAMLRefreshConnector{ refreshIdentity: connector.Identity{ UserID: "refreshed-user", Username: "refreshed-name", Email: "refreshed@example.com", EmailVerified: true, Groups: []string{"refreshed-group"}, }, } registerTestConnector(t, server, connID, mockConn) // Create client client := storage.Client{ ID: "conndata-client", Secret: "conndata-secret", RedirectURIs: []string{"https://example.com/callback"}, Name: "ConnData Test Client", } require.NoError(t, server.storage.CreateClient(ctx, client)) // Create refresh token with ConnectorData (simulating what HandlePOST would store) connectorData := []byte(`{"userID":"user-123","username":"testuser","email":"test@example.com","emailVerified":true,"groups":["admin","dev"]}`) refreshToken := storage.RefreshToken{ ID: "conndata-refresh", Token: "conndata-token", CreatedAt: time.Now(), LastUsed: time.Now(), ClientID: client.ID, ConnectorID: connID, Scopes: []string{"openid", "email", "offline_access"}, Claims: storage.Claims{ UserID: "user-123", Username: "testuser", Email: "test@example.com", EmailVerified: true, Groups: []string{"admin", "dev"}, }, ConnectorData: connectorData, Nonce: "conndata-nonce", } require.NoError(t, server.storage.CreateRefresh(ctx, refreshToken)) offlineSession := storage.OfflineSessions{ UserID: "user-123", ConnID: connID, Refresh: map[string]*storage.RefreshTokenRef{client.ID: {ID: refreshToken.ID, ClientID: client.ID}}, ConnectorData: connectorData, } require.NoError(t, server.storage.CreateOfflineSessions(ctx, offlineSession)) // Verify ConnectorData is stored correctly storedToken, err := server.storage.GetRefresh(ctx, refreshToken.ID) require.NoError(t, err) require.Equal(t, connectorData, storedToken.ConnectorData, "ConnectorData should be persisted in refresh token storage") // Verify ConnectorData is stored in offline session storedSession, err := server.storage.GetOfflineSessions(ctx, "user-123", connID) require.NoError(t, err) require.Equal(t, connectorData, storedSession.ConnectorData, "ConnectorData should be persisted in offline session storage") } // mockSAMLRefreshConnector implements SAMLConnector + RefreshConnector for testing. type mockSAMLRefreshConnector struct { refreshIdentity connector.Identity } func (m *mockSAMLRefreshConnector) POSTData(s connector.Scopes, requestID string) (ssoURL, samlRequest string, err error) { return "", "", nil } func (m *mockSAMLRefreshConnector) HandlePOST(s connector.Scopes, samlResponse, inResponseTo string) (connector.Identity, error) { return connector.Identity{}, nil } func (m *mockSAMLRefreshConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) { return m.refreshIdentity, nil } func TestFilterConnectors(t *testing.T) { connectors := []storage.Connector{ {ID: "github", Type: "github", Name: "GitHub"}, {ID: "google", Type: "oidc", Name: "Google"}, {ID: "ldap", Type: "ldap", Name: "LDAP"}, } tests := []struct { name string allowedConnectors []string wantIDs []string }{ { name: "No filter - all connectors returned", allowedConnectors: nil, wantIDs: []string{"github", "google", "ldap"}, }, { name: "Empty filter - all connectors returned", allowedConnectors: []string{}, wantIDs: []string{"github", "google", "ldap"}, }, { name: "Filter to one connector", allowedConnectors: []string{"github"}, wantIDs: []string{"github"}, }, { name: "Filter to two connectors", allowedConnectors: []string{"github", "ldap"}, wantIDs: []string{"github", "ldap"}, }, { name: "Filter with non-existent connector ID", allowedConnectors: []string{"nonexistent"}, wantIDs: []string{}, }, { name: "Filter with mix of valid and invalid IDs", allowedConnectors: []string{"google", "nonexistent"}, wantIDs: []string{"google"}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { result := filterConnectors(connectors, tc.allowedConnectors) gotIDs := make([]string, len(result)) for i, c := range result { gotIDs[i] = c.ID } require.Equal(t, tc.wantIDs, gotIDs) }) } } func TestIsConnectorAllowed(t *testing.T) { tests := []struct { name string allowedConnectors []string connectorID string want bool }{ { name: "No restrictions - all allowed", allowedConnectors: nil, connectorID: "any", want: true, }, { name: "Empty list - all allowed", allowedConnectors: []string{}, connectorID: "any", want: true, }, { name: "Connector in allowed list", allowedConnectors: []string{"github", "google"}, connectorID: "github", want: true, }, { name: "Connector not in allowed list", allowedConnectors: []string{"github", "google"}, connectorID: "ldap", want: false, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got := isConnectorAllowed(tc.allowedConnectors, tc.connectorID) require.Equal(t, tc.want, got) }) } } func TestHandleAuthorizationWithAllowedConnectors(t *testing.T) { ctx := t.Context() httpServer, s := newTestServerMultipleConnectors(t, nil) defer httpServer.Close() // Create a client that only allows "mock" connector (not "mock2") client := storage.Client{ ID: "filtered-client", Secret: "secret", RedirectURIs: []string{"https://example.com/callback"}, Name: "Filtered Client", AllowedConnectors: []string{"mock"}, } require.NoError(t, s.storage.CreateClient(ctx, client)) // Request the auth page with this client - should only show "mock" connector rr := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/auth?client_id=%s&redirect_uri=%s&response_type=code&scope=openid", client.ID, url.QueryEscape("https://example.com/callback")), nil) s.ServeHTTP(rr, req) // With only one allowed connector and alwaysShowLogin=false (default), // the server should redirect directly to the connector require.Equal(t, http.StatusFound, rr.Code) location := rr.Header().Get("Location") require.Contains(t, location, "/auth/mock") require.NotContains(t, location, "mock2") } func TestHandleAuthorizationWithNoMatchingConnectors(t *testing.T) { ctx := t.Context() httpServer, s := newTestServerMultipleConnectors(t, nil) defer httpServer.Close() // Create a client that only allows a non-existent connector client := storage.Client{ ID: "no-connectors-client", Secret: "secret", RedirectURIs: []string{"https://example.com/callback"}, Name: "No Connectors Client", AllowedConnectors: []string{"nonexistent"}, } require.NoError(t, s.storage.CreateClient(ctx, client)) rr := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/auth?client_id=%s&redirect_uri=%s&response_type=code&scope=openid", client.ID, url.QueryEscape("https://example.com/callback")), nil) s.ServeHTTP(rr, req) // Should return an error, not an empty login page require.Equal(t, http.StatusBadRequest, rr.Code) } func TestHandleAuthorizationWithoutAllowedConnectors(t *testing.T) { ctx := t.Context() httpServer, s := newTestServerMultipleConnectors(t, nil) defer httpServer.Close() // Create a client with no connector restrictions client := storage.Client{ ID: "unfiltered-client", Secret: "secret", RedirectURIs: []string{"https://example.com/callback"}, Name: "Unfiltered Client", } require.NoError(t, s.storage.CreateClient(ctx, client)) // Request the auth page - should show all connectors (rendered as HTML) rr := httptest.NewRecorder() req := httptest.NewRequest("GET", fmt.Sprintf("/auth?client_id=%s&redirect_uri=%s&response_type=code&scope=openid", client.ID, url.QueryEscape("https://example.com/callback")), nil) s.ServeHTTP(rr, req) // With multiple connectors and no filter, the login page should be rendered (200 OK) require.Equal(t, http.StatusOK, rr.Code) } ================================================ FILE: server/internal/codec.go ================================================ package internal import ( "encoding/base64" "google.golang.org/protobuf/proto" ) // Marshal converts a protobuf message to a URL legal string. func Marshal(message proto.Message) (string, error) { data, err := proto.Marshal(message) if err != nil { return "", err } return base64.RawURLEncoding.EncodeToString(data), nil } // Unmarshal decodes a protobuf message. func Unmarshal(s string, message proto.Message) error { data, err := base64.RawURLEncoding.DecodeString(s) if err != nil { return err } return proto.Unmarshal(data, message) } ================================================ FILE: server/internal/types.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.5 // protoc v5.29.3 // source: server/internal/types.proto // Package internal holds protobuf types used by the server. package internal import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" unsafe "unsafe" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // RefreshToken is a message that holds refresh token data used by dex. type RefreshToken struct { state protoimpl.MessageState `protogen:"open.v1"` RefreshId string `protobuf:"bytes,1,opt,name=refresh_id,json=refreshId,proto3" json:"refresh_id,omitempty"` Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *RefreshToken) Reset() { *x = RefreshToken{} mi := &file_server_internal_types_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *RefreshToken) String() string { return protoimpl.X.MessageStringOf(x) } func (*RefreshToken) ProtoMessage() {} func (x *RefreshToken) ProtoReflect() protoreflect.Message { mi := &file_server_internal_types_proto_msgTypes[0] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use RefreshToken.ProtoReflect.Descriptor instead. func (*RefreshToken) Descriptor() ([]byte, []int) { return file_server_internal_types_proto_rawDescGZIP(), []int{0} } func (x *RefreshToken) GetRefreshId() string { if x != nil { return x.RefreshId } return "" } func (x *RefreshToken) GetToken() string { if x != nil { return x.Token } return "" } // IDTokenSubject represents both the userID and connID which is returned // as the "sub" claim in the ID Token. type IDTokenSubject struct { state protoimpl.MessageState `protogen:"open.v1"` UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` ConnId string `protobuf:"bytes,2,opt,name=conn_id,json=connId,proto3" json:"conn_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *IDTokenSubject) Reset() { *x = IDTokenSubject{} mi := &file_server_internal_types_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } func (x *IDTokenSubject) String() string { return protoimpl.X.MessageStringOf(x) } func (*IDTokenSubject) ProtoMessage() {} func (x *IDTokenSubject) ProtoReflect() protoreflect.Message { mi := &file_server_internal_types_proto_msgTypes[1] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use IDTokenSubject.ProtoReflect.Descriptor instead. func (*IDTokenSubject) Descriptor() ([]byte, []int) { return file_server_internal_types_proto_rawDescGZIP(), []int{1} } func (x *IDTokenSubject) GetUserId() string { if x != nil { return x.UserId } return "" } func (x *IDTokenSubject) GetConnId() string { if x != nil { return x.ConnId } return "" } var File_server_internal_types_proto protoreflect.FileDescriptor var file_server_internal_types_proto_rawDesc = string([]byte{ 0x0a, 0x1b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x43, 0x0a, 0x0c, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x42, 0x0a, 0x0e, 0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x6e, 0x49, 0x64, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x78, 0x69, 0x64, 0x70, 0x2f, 0x64, 0x65, 0x78, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( file_server_internal_types_proto_rawDescOnce sync.Once file_server_internal_types_proto_rawDescData []byte ) func file_server_internal_types_proto_rawDescGZIP() []byte { file_server_internal_types_proto_rawDescOnce.Do(func() { file_server_internal_types_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_server_internal_types_proto_rawDesc), len(file_server_internal_types_proto_rawDesc))) }) return file_server_internal_types_proto_rawDescData } var file_server_internal_types_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_server_internal_types_proto_goTypes = []any{ (*RefreshToken)(nil), // 0: internal.RefreshToken (*IDTokenSubject)(nil), // 1: internal.IDTokenSubject } var file_server_internal_types_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_server_internal_types_proto_init() } func file_server_internal_types_proto_init() { if File_server_internal_types_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_server_internal_types_proto_rawDesc), len(file_server_internal_types_proto_rawDesc)), NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_server_internal_types_proto_goTypes, DependencyIndexes: file_server_internal_types_proto_depIdxs, MessageInfos: file_server_internal_types_proto_msgTypes, }.Build() File_server_internal_types_proto = out.File file_server_internal_types_proto_goTypes = nil file_server_internal_types_proto_depIdxs = nil } ================================================ FILE: server/internal/types.proto ================================================ syntax = "proto3"; // Package internal holds protobuf types used by the server. package internal; option go_package = "github.com/dexidp/dex/server/internal"; // RefreshToken is a message that holds refresh token data used by dex. message RefreshToken { string refresh_id = 1; string token = 2; } // IDTokenSubject represents both the userID and connID which is returned // as the "sub" claim in the ID Token. message IDTokenSubject { string user_id = 1; string conn_id = 2; } ================================================ FILE: server/introspectionhandler.go ================================================ package server import ( "context" "encoding/json" "errors" "fmt" "net/http" "github.com/coreos/go-oidc/v3/oidc" "github.com/dexidp/dex/server/internal" ) // Introspection contains an access token's session data as specified by // [IETF RFC 7662](https://tools.ietf.org/html/rfc7662) type Introspection struct { // Boolean indicator of whether or not the presented token // is currently active. The specifics of a token's "active" state // will vary depending on the implementation of the authorization // server and the information it keeps about its tokens, but a "true" // value return for the "active" property will generally indicate // that a given token has been issued by this authorization server, // has not been revoked by the resource owner, and is within its // given time window of validity (e.g., after its issuance time and // before its expiration time). Active bool `json:"active"` // JSON string containing a space-separated list of // scopes associated with this token. Scope string `json:"scope,omitempty"` // Client identifier for the OAuth 2.0 client that // requested this token. ClientID string `json:"client_id"` // Subject of the token, as defined in JWT [RFC7519]. // Usually a machine-readable identifier of the resource owner who // authorized this token. Subject string `json:"sub"` // Integer timestamp, measured in the number of seconds // since January 1 1970 UTC, indicating when this token will expire. Expiry int64 `json:"exp"` // Integer timestamp, measured in the number of seconds // since January 1 1970 UTC, indicating when this token was // originally issued. IssuedAt int64 `json:"iat"` // Integer timestamp, measured in the number of seconds // since January 1 1970 UTC, indicating when this token is not to be // used before. NotBefore int64 `json:"nbf"` // Human-readable identifier for the resource owner who // authorized this token. Username string `json:"username,omitempty"` // Service-specific string identifier or list of string // identifiers representing the intended audience for this token, as // defined in JWT Audience audience `json:"aud"` // String representing the issuer of this token, as // defined in JWT Issuer string `json:"iss"` // String identifier for the token, as defined in JWT [RFC7519]. JwtTokenID string `json:"jti,omitempty"` // TokenType is the introspected token's type, typically `bearer`. TokenType string `json:"token_type"` // TokenUse is the introspected token's use, for example `access_token` or `refresh_token`. TokenUse string `json:"token_use"` // Extra is arbitrary data set from the token claims. Extra IntrospectionExtra `json:"ext,omitempty"` } type IntrospectionExtra struct { AuthorizingParty string `json:"azp,omitempty"` Email string `json:"email,omitempty"` EmailVerified *bool `json:"email_verified,omitempty"` Groups []string `json:"groups,omitempty"` Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` FederatedIDClaims *federatedIDClaims `json:"federated_claims,omitempty"` } type TokenTypeEnum int const ( AccessToken TokenTypeEnum = iota RefreshToken ) func (t TokenTypeEnum) String() string { switch t { case AccessToken: return "access_token" case RefreshToken: return "refresh_token" default: return fmt.Sprintf("TokenTypeEnum(%d)", t) } } type introspectionError struct { typ string code int desc string } func (e *introspectionError) Error() string { return fmt.Sprintf("introspection error: status %d, %q %s", e.code, e.typ, e.desc) } func (e *introspectionError) Is(tgt error) bool { target, ok := tgt.(*introspectionError) if !ok { return false } return e.typ == target.typ && e.code == target.code && e.desc == target.desc } func newIntrospectInactiveTokenError() *introspectionError { return &introspectionError{typ: errInactiveToken, desc: "", code: http.StatusUnauthorized} } func newIntrospectInternalServerError() *introspectionError { return &introspectionError{typ: errServerError, desc: "", code: http.StatusInternalServerError} } func newIntrospectBadRequestError(desc string) *introspectionError { return &introspectionError{typ: errInvalidRequest, desc: desc, code: http.StatusBadRequest} } func (s *Server) guessTokenType(ctx context.Context, token string) (TokenTypeEnum, error) { // We skip every checks, we only want to know if it's a valid JWT verifierConfig := oidc.Config{ SkipClientIDCheck: true, SkipExpiryCheck: true, SkipIssuerCheck: true, // We skip signature checks to avoid database calls; InsecureSkipSignatureCheck: true, } verifier := oidc.NewVerifier(s.issuerURL.String(), nil, &verifierConfig) if _, err := verifier.Verify(ctx, token); err != nil { // If it's not an access token, let's assume it's a refresh token; return RefreshToken, nil } // If it's a valid JWT, it's an access token. return AccessToken, nil } func (s *Server) getTokenFromRequest(r *http.Request) (string, TokenTypeEnum, error) { if r.Method != "POST" { return "", 0, newIntrospectBadRequestError(fmt.Sprintf("HTTP method is \"%s\", expected \"POST\".", r.Method)) } else if err := r.ParseForm(); err != nil { return "", 0, newIntrospectBadRequestError("Unable to parse HTTP body, make sure to send a properly formatted form request body.") } else if len(r.PostForm) == 0 { return "", 0, newIntrospectBadRequestError("The POST body can not be empty.") } else if !r.PostForm.Has("token") { return "", 0, newIntrospectBadRequestError("The POST body doesn't contain 'token' parameter.") } token := r.PostForm.Get("token") tokenType, err := s.guessTokenType(r.Context(), token) if err != nil { s.logger.ErrorContext(r.Context(), "failed to guess token type", "err", err) return "", 0, newIntrospectInternalServerError() } requestTokenType := r.PostForm.Get("token_type_hint") if requestTokenType != "" { if tokenType.String() != requestTokenType { s.logger.Warn("token type hint doesn't match token type", "request_token_type", requestTokenType, "token_type", tokenType) } } return token, tokenType, nil } func (s *Server) introspectRefreshToken(ctx context.Context, token string) (*Introspection, error) { rToken := new(internal.RefreshToken) if err := internal.Unmarshal(token, rToken); err != nil { // For backward compatibility, assume the refresh_token is a raw refresh token ID // if it fails to decode. // // Because refresh_token values that aren't unmarshable were generated by servers // that don't have a Token value, we'll still reject any attempts to claim a // refresh_token twice. rToken = &internal.RefreshToken{RefreshId: token, Token: ""} } rCtx, err := s.getRefreshTokenFromStorage(ctx, nil, rToken) if err != nil { if errors.Is(err, invalidErr) || errors.Is(err, expiredErr) { return nil, newIntrospectInactiveTokenError() } s.logger.ErrorContext(ctx, "failed to get refresh token", "err", err) return nil, newIntrospectInternalServerError() } subjectString, sErr := genSubject(rCtx.storageToken.Claims.UserID, rCtx.storageToken.ConnectorID) if sErr != nil { s.logger.ErrorContext(ctx, "failed to marshal offline session ID", "err", err) return nil, newIntrospectInternalServerError() } return &Introspection{ Active: true, ClientID: rCtx.storageToken.ClientID, IssuedAt: rCtx.storageToken.CreatedAt.Unix(), NotBefore: rCtx.storageToken.CreatedAt.Unix(), Expiry: rCtx.storageToken.CreatedAt.Add(s.refreshTokenPolicy.absoluteLifetime).Unix(), Subject: subjectString, Username: rCtx.storageToken.Claims.PreferredUsername, Audience: getAudience(rCtx.storageToken.ClientID, rCtx.scopes), Issuer: s.issuerURL.String(), Extra: IntrospectionExtra{ Email: rCtx.storageToken.Claims.Email, EmailVerified: &rCtx.storageToken.Claims.EmailVerified, Groups: rCtx.storageToken.Claims.Groups, Name: rCtx.storageToken.Claims.Username, PreferredUsername: rCtx.storageToken.Claims.PreferredUsername, }, TokenType: "Bearer", TokenUse: "refresh_token", }, nil } func (s *Server) introspectAccessToken(ctx context.Context, token string) (*Introspection, error) { verifier := oidc.NewVerifier(s.issuerURL.String(), &signerKeySet{s.signer}, &oidc.Config{SkipClientIDCheck: true}) idToken, err := verifier.Verify(ctx, token) if err != nil { return nil, newIntrospectInactiveTokenError() } var claims IntrospectionExtra if err := idToken.Claims(&claims); err != nil { s.logger.ErrorContext(ctx, "error while fetching token claims", "err", err.Error()) return nil, newIntrospectInternalServerError() } clientID, err := getClientID(idToken.Audience, claims.AuthorizingParty) if err != nil { s.logger.ErrorContext(ctx, "error while fetching client_id from token:", "err", err.Error()) return nil, newIntrospectInternalServerError() } client, err := s.storage.GetClient(ctx, clientID) if err != nil { s.logger.ErrorContext(ctx, "error while fetching client from storage", "err", err.Error()) return nil, newIntrospectInternalServerError() } return &Introspection{ Active: true, ClientID: client.ID, IssuedAt: idToken.IssuedAt.Unix(), NotBefore: idToken.IssuedAt.Unix(), Expiry: idToken.Expiry.Unix(), Subject: idToken.Subject, Username: claims.PreferredUsername, Audience: idToken.Audience, Issuer: s.issuerURL.String(), Extra: claims, TokenType: "Bearer", TokenUse: "access_token", }, nil } func (s *Server) handleIntrospect(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var introspect *Introspection token, tokenType, err := s.getTokenFromRequest(r) if err == nil { switch tokenType { case AccessToken: introspect, err = s.introspectAccessToken(ctx, token) case RefreshToken: introspect, err = s.introspectRefreshToken(ctx, token) default: // Token type is neither handled token types. s.logger.ErrorContext(r.Context(), "unknown token type", "token_type", tokenType) introspectInactiveErr(w) return } } if err != nil { if intErr, ok := err.(*introspectionError); ok { s.introspectErrHelper(w, intErr.typ, intErr.desc, intErr.code) } else { s.logger.ErrorContext(r.Context(), "an unknown error occurred", "err", err.Error()) s.introspectErrHelper(w, errServerError, "An unknown error occurred", http.StatusInternalServerError) } return } rawJSON, jsonErr := json.Marshal(introspect) if jsonErr != nil { s.logger.ErrorContext(r.Context(), "failed to marshal introspection response", "err", jsonErr) s.introspectErrHelper(w, errServerError, "", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(rawJSON) } func (s *Server) introspectErrHelper(w http.ResponseWriter, typ string, description string, statusCode int) { if typ == errInactiveToken { introspectInactiveErr(w) return } if err := tokenErr(w, typ, description, statusCode); err != nil { // TODO(nabokihms): error with context s.logger.Error("introspect error response", "err", err) } } func introspectInactiveErr(w http.ResponseWriter) { w.Header().Set("Cache-Control", "no-store") w.Header().Set("Pragma", "no-cache") w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) json.NewEncoder(w).Encode(struct { Active bool `json:"active"` }{Active: false}) } ================================================ FILE: server/introspectionhandler_test.go ================================================ package server import ( "bytes" "encoding/json" "io" "net/http" "net/http/httptest" "net/url" "path" "strings" "testing" "time" "github.com/stretchr/testify/require" "github.com/dexidp/dex/server/internal" "github.com/dexidp/dex/storage" ) func toJSON(a interface{}) string { b, err := json.Marshal(a) if err != nil { return "" } return string(b) } func mockTestStorage(t *testing.T, s storage.Storage) { ctx := t.Context() c := storage.Client{ ID: "test", Secret: "barfoo", RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"}, Name: "dex client", LogoURL: "https://goo.gl/JIyzIC", } err := s.CreateClient(ctx, c) require.NoError(t, err) c1 := storage.Connector{ ID: "test", Type: "mockPassword", Name: "mockPassword", Config: []byte(`{ "username": "test", "password": "test" }`), } err = s.CreateConnector(ctx, c1) require.NoError(t, err) err = s.CreateRefresh(ctx, storage.RefreshToken{ ID: "test", Token: "bar", ObsoleteToken: "", Nonce: "foo", ClientID: "test", ConnectorID: "test", Scopes: []string{"openid", "email", "profile"}, CreatedAt: time.Now().UTC().Round(time.Millisecond), LastUsed: time.Now().UTC().Round(time.Millisecond), Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, ConnectorData: []byte(`{"some":"data"}`), }) require.NoError(t, err) err = s.CreateRefresh(ctx, storage.RefreshToken{ ID: "expired", Token: "bar", ObsoleteToken: "", Nonce: "foo", ClientID: "test", ConnectorID: "test", Scopes: []string{"openid", "email", "profile"}, CreatedAt: time.Now().AddDate(-1, 0, 0).UTC().Round(time.Millisecond), LastUsed: time.Now().AddDate(-1, 0, 0).UTC().Round(time.Millisecond), Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, ConnectorData: []byte(`{"some":"data"}`), }) require.NoError(t, err) err = s.CreateOfflineSessions(ctx, storage.OfflineSessions{ UserID: "1", ConnID: "test", Refresh: map[string]*storage.RefreshTokenRef{ "test": {ID: "test", ClientID: "test"}, "expired": {ID: "expired", ClientID: "test"}, }, ConnectorData: nil, }) require.NoError(t, err) } func getIntrospectionValue(issuerURL url.URL, issuedAt time.Time, expiry time.Time, tokenUse string) *Introspection { trueValue := true return &Introspection{ Active: true, ClientID: "test", Subject: "CgExEgR0ZXN0", Expiry: expiry.Unix(), IssuedAt: issuedAt.Unix(), NotBefore: issuedAt.Unix(), Audience: []string{ "test", }, Issuer: issuerURL.String(), TokenType: "Bearer", TokenUse: tokenUse, Extra: IntrospectionExtra{ Email: "jane.doe@example.com", EmailVerified: &trueValue, Groups: []string{ "a", "b", }, Name: "jane", }, } } func TestGetTokenFromRequestSuccess(t *testing.T) { t0 := time.Now() ctx := t.Context() now := func() time.Time { return t0 } // Setup a dex server. httpServer, s := newTestServer(t, func(c *Config) { c.Issuer += "/non-root-path" c.Now = now }) defer httpServer.Close() mockTestStorage(t, s.storage) // Generate a valid RS256-signed access token accessToken, _, err := s.newIDToken(ctx, "test", storage.Claims{ UserID: "1", Username: "jane", }, []string{"openid"}, "nonce", "", "", "test", time.Time{}) require.NoError(t, err) tests := []struct { testName string expectedToken string expectedTokenType TokenTypeEnum }{ // Access Token { testName: "Access Token", expectedToken: accessToken, expectedTokenType: AccessToken, }, // Refresh Token { testName: "Refresh token", expectedToken: "CgR0ZXN0EgNiYXI", expectedTokenType: RefreshToken, }, // Unknown token { testName: "Unknown token", expectedToken: "AaAaAaA", expectedTokenType: RefreshToken, }, } for _, tc := range tests { t.Run(tc.testName, func(t *testing.T) { data := url.Values{} data.Set("token", tc.expectedToken) req := httptest.NewRequest(http.MethodPost, "https://test.tech/token/introspect", bytes.NewBufferString(data.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") token, tokenType, err := s.getTokenFromRequest(req) if err != nil { t.Fatalf("Error returned: %s", err.Error()) } if token != tc.expectedToken { t.Fatalf("Wrong token returned. Expected %v got %v", tc.expectedToken, token) } if tokenType != tc.expectedTokenType { t.Fatalf("Wrong token type returned. Expected %v got %v", tc.expectedTokenType, tokenType) } }) } } func TestGetTokenFromRequestFailure(t *testing.T) { t0 := time.Now() now := func() time.Time { return t0 } // Setup a dex server. httpServer, s := newTestServer(t, func(c *Config) { c.Issuer += "/non-root-path" c.Now = now }) defer httpServer.Close() _, _, err := s.getTokenFromRequest(httptest.NewRequest(http.MethodGet, "https://test.tech/token/introspect", nil)) require.ErrorIs(t, err, &introspectionError{ typ: errInvalidRequest, desc: "HTTP method is \"GET\", expected \"POST\".", code: http.StatusBadRequest, }) _, _, err = s.getTokenFromRequest(httptest.NewRequest(http.MethodPost, "https://test.tech/token/introspect", nil)) require.ErrorIs(t, err, &introspectionError{ typ: errInvalidRequest, desc: "The POST body can not be empty.", code: http.StatusBadRequest, }) req := httptest.NewRequest(http.MethodPost, "https://test.tech/token/introspect", strings.NewReader("token_type_hint=access_token")) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") _, _, err = s.getTokenFromRequest(req) require.ErrorIs(t, err, &introspectionError{ typ: errInvalidRequest, desc: "The POST body doesn't contain 'token' parameter.", code: http.StatusBadRequest, }) } func TestHandleIntrospect(t *testing.T) { t0 := time.Now() ctx := t.Context() // Setup a dex server. now := func() time.Time { return t0 } logger := newLogger(t) refreshTokenPolicy, err := NewRefreshTokenPolicy(logger, false, "", "24h", "") if err != nil { t.Fatalf("failed to prepare rotation policy: %v", err) } refreshTokenPolicy.now = now httpServer, s := newTestServer(t, func(c *Config) { c.Issuer += "/non-root-path" c.RefreshTokenPolicy = refreshTokenPolicy c.Now = now }) defer httpServer.Close() mockTestStorage(t, s.storage) activeAccessToken, expiry, err := s.newIDToken(ctx, "test", storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, []string{"openid", "email", "profile", "groups"}, "foo", "", "", "test", time.Time{}) require.NoError(t, err) activeRefreshToken, err := internal.Marshal(&internal.RefreshToken{RefreshId: "test", Token: "bar"}) require.NoError(t, err) expiredRefreshToken, err := internal.Marshal(&internal.RefreshToken{RefreshId: "expired", Token: "bar"}) require.NoError(t, err) inactiveResponse := "{\"active\":false}\n" badRequestResponse := `{"error":"invalid_request","error_description":"The POST body can not be empty."}` tests := []struct { testName string token string tokenType string response string responseStatusCode int }{ // No token { testName: "No token", response: badRequestResponse, responseStatusCode: 400, }, // Access token tests { testName: "Access Token: active", token: activeAccessToken, response: toJSON(getIntrospectionValue(s.issuerURL, t0, expiry, "access_token")), responseStatusCode: 200, }, { testName: "Access Token: wrong", token: "fake-token", response: inactiveResponse, responseStatusCode: 200, }, // Refresh token tests { testName: "Refresh Token: active", token: activeRefreshToken, response: toJSON(getIntrospectionValue(s.issuerURL, t0, t0.Add(s.refreshTokenPolicy.absoluteLifetime), "refresh_token")), responseStatusCode: 200, }, { testName: "Refresh Token: expired", token: expiredRefreshToken, response: inactiveResponse, responseStatusCode: 200, }, { testName: "Refresh Token: active => false (wrong)", token: "fake-token", response: inactiveResponse, responseStatusCode: 200, }, } for _, tc := range tests { t.Run(tc.testName, func(t *testing.T) { data := url.Values{} if tc.token != "" { data.Set("token", tc.token) } if tc.tokenType != "" { data.Set("token_type_hint", tc.tokenType) } u, err := url.Parse(s.issuerURL.String()) if err != nil { t.Fatalf("Could not parse issuer URL %v", err) } u.Path = path.Join(u.Path, "token", "introspect") req, _ := http.NewRequest("POST", u.String(), bytes.NewBufferString(data.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rr := httptest.NewRecorder() s.ServeHTTP(rr, req) if rr.Code != tc.responseStatusCode { t.Errorf("%s: Unexpected Response Type. Expected %v got %v", tc.testName, tc.responseStatusCode, rr.Code) } result, _ := io.ReadAll(rr.Body) if string(result) != tc.response { t.Errorf("%s: Unexpected Response. Expected %q got %q", tc.testName, tc.response, result) } }) } } func TestIntrospectErrHelper(t *testing.T) { t0 := time.Now() now := func() time.Time { return t0 } // Setup a dex server. httpServer, s := newTestServer(t, func(c *Config) { c.Issuer += "/non-root-path" c.Now = now }) defer httpServer.Close() tests := []struct { testName string err *introspectionError resStatusCode int resBody string }{ { testName: "Inactive Token", err: newIntrospectInactiveTokenError(), resStatusCode: http.StatusOK, resBody: "{\"active\":false}\n", }, { testName: "Bad Request", err: newIntrospectBadRequestError("This is a bad request"), resStatusCode: http.StatusBadRequest, resBody: `{"error":"invalid_request","error_description":"This is a bad request"}`, }, { testName: "Internal Server Error", err: newIntrospectInternalServerError(), resStatusCode: http.StatusInternalServerError, resBody: `{"error":"server_error"}`, }, } for _, tc := range tests { t.Run(tc.testName, func(t *testing.T) { w1 := httptest.NewRecorder() s.introspectErrHelper(w1, tc.err.typ, tc.err.desc, tc.err.code) res := w1.Result() require.Equal(t, tc.resStatusCode, res.StatusCode) require.Equal(t, "application/json", res.Header.Get("Content-Type")) data, err := io.ReadAll(res.Body) defer res.Body.Close() require.NoError(t, err) require.Equal(t, tc.resBody, string(data)) }) } } ================================================ FILE: server/mfa.go ================================================ package server import ( "bytes" "context" "crypto/hmac" "crypto/sha256" "encoding/base64" "fmt" "image/png" "net/http" "net/url" "path" "github.com/pquerna/otp" "github.com/pquerna/otp/totp" "github.com/dexidp/dex/storage" ) // MFAProvider is a pluggable multi-factor authentication method. type MFAProvider interface { // Type returns the authenticator type identifier (e.g., "TOTP"). Type() string // EnabledForConnectorType returns true if this provider applies to the given connector type. // If no connector types are configured, the provider applies to all. EnabledForConnectorType(connectorType string) bool } // TOTPProvider implements TOTP-based multi-factor authentication. type TOTPProvider struct { issuer string connectorTypes map[string]struct{} } // NewTOTPProvider creates a new TOTP MFA provider. func NewTOTPProvider(issuer string, connectorTypes []string) *TOTPProvider { m := make(map[string]struct{}, len(connectorTypes)) for _, t := range connectorTypes { m[t] = struct{}{} } return &TOTPProvider{issuer: issuer, connectorTypes: m} } func (p *TOTPProvider) EnabledForConnectorType(connectorType string) bool { if len(p.connectorTypes) == 0 { return true } _, ok := p.connectorTypes[connectorType] return ok } func (p *TOTPProvider) Type() string { return "TOTP" } func (p *TOTPProvider) generate(connID, email string) (*otp.Key, error) { return totp.Generate(totp.GenerateOpts{ Issuer: p.issuer, AccountName: fmt.Sprintf("(%s) %s", connID, email), }) } func (s *Server) handleMFAVerify(w http.ResponseWriter, r *http.Request) { macEncoded := r.FormValue("hmac") if macEncoded == "" { s.renderError(r, w, http.StatusUnauthorized, "Unauthorized request.") return } mac, err := base64.RawURLEncoding.DecodeString(macEncoded) if err != nil { s.renderError(r, w, http.StatusUnauthorized, "Unauthorized request.") return } ctx := r.Context() authReq, err := s.storage.GetAuthRequest(ctx, r.FormValue("req")) if err != nil { s.logger.ErrorContext(ctx, "failed to get auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Database error.") return } if !authReq.LoggedIn { s.logger.ErrorContext(ctx, "auth request does not have an identity for MFA verification") s.renderError(r, w, http.StatusInternalServerError, "Login process not yet finalized.") return } authenticatorID := r.FormValue("authenticator") // Verify HMAC — includes authenticatorID to prevent skipping steps in the MFA chain. h := hmac.New(sha256.New, authReq.HMACKey) h.Write([]byte(authReq.ID + "|" + authenticatorID)) if !hmac.Equal(mac, h.Sum(nil)) { s.renderError(r, w, http.StatusUnauthorized, "Unauthorized request.") return } provider, ok := s.mfaProviders[authenticatorID] if !ok { s.renderError(r, w, http.StatusBadRequest, "Unknown authenticator.") return } totpProvider, ok := provider.(*TOTPProvider) if !ok { s.renderError(r, w, http.StatusInternalServerError, "Unsupported authenticator type.") return } identity, err := s.storage.GetUserIdentity(ctx, authReq.Claims.UserID, authReq.ConnectorID) if err != nil { s.logger.ErrorContext(ctx, "failed to get user identity", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Database error.") return } // Build approval URL with an HMAC that covers only the request ID // (MFA HMAC includes authenticatorID and is not valid for approval). approvalH := hmac.New(sha256.New, authReq.HMACKey) approvalH.Write([]byte(authReq.ID)) returnURL := path.Join(s.issuerURL.Path, "/approval") + "?req=" + authReq.ID + "&hmac=" + base64.RawURLEncoding.EncodeToString(approvalH.Sum(nil)) if authReq.MFAValidated { http.Redirect(w, r, returnURL, http.StatusSeeOther) return } secret := identity.MFASecrets[authenticatorID] switch r.Method { case http.MethodGet: if secret == nil { // First-time enrollment: generate a new TOTP key. // TODO(nabokihms): clean up stale unconfirmed secrets. If a user starts // enrollment multiple times without completing it, old secrets accumulate. generated, err := totpProvider.generate(authReq.ConnectorID, authReq.Claims.Email) if err != nil { s.logger.ErrorContext(ctx, "failed to generate TOTP key", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } secret = &storage.MFASecret{ AuthenticatorID: authenticatorID, Type: "TOTP", Secret: generated.String(), Confirmed: false, CreatedAt: s.now(), } if err := s.storage.UpdateUserIdentity(ctx, authReq.Claims.UserID, authReq.ConnectorID, func(old storage.UserIdentity) (storage.UserIdentity, error) { if old.MFASecrets == nil { old.MFASecrets = make(map[string]*storage.MFASecret) } old.MFASecrets[authenticatorID] = secret return old, nil }); err != nil { s.logger.ErrorContext(ctx, "failed to store MFA secret", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } } s.renderTOTPPage(secret, false, totpProvider.issuer, authReq.ConnectorID, w, r) case http.MethodPost: // TODO(nabokihms): this endpoint should be protected with a rate limit (like the auth endpoint). // TOTP has a limited keyspace (6 digits) with a 30-second validity window, // making it particularly vulnerable to brute-force without rate limiting. // // For now the best way is to use external rate limiting solutions. if secret == nil || secret.Secret == "" { s.renderError(r, w, http.StatusBadRequest, "MFA not enrolled.") return } code := r.FormValue("totp") generated, err := otp.NewKeyFromURL(secret.Secret) if err != nil { s.logger.ErrorContext(ctx, "failed to load TOTP key", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } if !totp.Validate(code, generated.Secret()) { s.renderTOTPPage(secret, true, totpProvider.issuer, authReq.ConnectorID, w, r) return } // Mark MFA secret as confirmed. if !secret.Confirmed { if err := s.storage.UpdateUserIdentity(ctx, authReq.Claims.UserID, authReq.ConnectorID, func(old storage.UserIdentity) (storage.UserIdentity, error) { if s := old.MFASecrets[authenticatorID]; s != nil { s.Confirmed = true } return old, nil }); err != nil { s.logger.ErrorContext(ctx, "failed to confirm MFA secret", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } } // Check if there are more authenticators in the MFA chain. mfaChain, err := s.mfaChainForClient(ctx, authReq.ClientID, authReq.ConnectorID) if err != nil { s.logger.ErrorContext(ctx, "failed to get MFA chain", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } // Find the next authenticator in the chain after the current one. var nextAuthenticator string for i, id := range mfaChain { if id == authenticatorID && i+1 < len(mfaChain) { nextAuthenticator = mfaChain[i+1] break } } if nextAuthenticator != "" { // Redirect to the next authenticator in the chain. h := hmac.New(sha256.New, authReq.HMACKey) h.Write([]byte(authReq.ID + "|" + nextAuthenticator)) v := url.Values{} v.Set("req", authReq.ID) v.Set("hmac", base64.RawURLEncoding.EncodeToString(h.Sum(nil))) v.Set("authenticator", nextAuthenticator) nextURL := path.Join(s.issuerURL.Path, "/mfa/verify") + "?" + v.Encode() http.Redirect(w, r, nextURL, http.StatusSeeOther) return } // All authenticators in the chain completed — mark as validated. if err := s.storage.UpdateAuthRequest(ctx, authReq.ID, func(old storage.AuthRequest) (storage.AuthRequest, error) { old.MFAValidated = true return old, nil }); err != nil { s.logger.ErrorContext(ctx, "failed to update auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } s.sendCodeOrRedirectToApproval(w, r, authReq, returnURL) default: s.renderError(r, w, http.StatusBadRequest, "Unsupported request method.") } } func (s *Server) renderTOTPPage(secret *storage.MFASecret, lastFail bool, issuer, connectorID string, w http.ResponseWriter, r *http.Request) { // Prevent browser from caching the TOTP page (contains QR code with secret). w.Header().Set("Cache-Control", "no-store") var qrCode string if !secret.Confirmed { var err error qrCode, err = generateTOTPQRCode(secret.Secret) if err != nil { s.logger.ErrorContext(r.Context(), "failed to generate QR code", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } } if err := s.templates.totpVerify(r, w, r.URL.String(), issuer, connectorID, qrCode, lastFail); err != nil { s.logger.ErrorContext(r.Context(), "server template error", "err", err) } } // sendCodeOrRedirectToApproval checks skipApproval and stored consent, // sending a code response directly if possible, or redirecting to the approval page. func (s *Server) sendCodeOrRedirectToApproval(w http.ResponseWriter, r *http.Request, authReq storage.AuthRequest, approvalURL string) { ctx := r.Context() if !authReq.ForceApprovalPrompt { if s.skipApproval { authReq, err := s.storage.GetAuthRequest(ctx, authReq.ID) if err != nil { s.logger.ErrorContext(ctx, "failed to get auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Login error.") return } s.sendCodeResponse(w, r, authReq) return } ui, err := s.storage.GetUserIdentity(ctx, authReq.Claims.UserID, authReq.ConnectorID) if err == nil && scopesCoveredByConsent(ui.Consents[authReq.ClientID], authReq.Scopes) { authReq, err := s.storage.GetAuthRequest(ctx, authReq.ID) if err != nil { s.logger.ErrorContext(ctx, "failed to get auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Login error.") return } s.sendCodeResponse(w, r, authReq) return } } http.Redirect(w, r, approvalURL, http.StatusSeeOther) } func generateTOTPQRCode(keyURL string) (string, error) { generated, err := otp.NewKeyFromURL(keyURL) if err != nil { return "", fmt.Errorf("failed to load TOTP key: %w", err) } qrCodeImage, err := generated.Image(300, 300) if err != nil { return "", fmt.Errorf("failed to generate TOTP QR code: %w", err) } var buf bytes.Buffer if err := png.Encode(&buf, qrCodeImage); err != nil { return "", fmt.Errorf("failed to encode TOTP QR code: %w", err) } return base64.StdEncoding.EncodeToString(buf.Bytes()), nil } // mfaChainForClient returns the MFA chain for a client filtered by connector type, // falling back to the server's defaultMFAChain if the client has none. // Returns nil if no MFA is configured/applicable. func (s *Server) mfaChainForClient(ctx context.Context, clientID, connectorID string) ([]string, error) { if len(s.mfaProviders) == 0 { return nil, nil } client, err := s.storage.GetClient(ctx, clientID) if err != nil { return nil, err } // nil means "not set" — fall back to default. // Explicit empty slice ([]string{}) means "no MFA" — don't fall back. source := client.MFAChain if source == nil { source = s.defaultMFAChain } // Resolve connector type from connector ID. connectorType, err := s.getConnectorType(ctx, connectorID) if err != nil { return nil, err } var chain []string for _, authID := range source { provider, ok := s.mfaProviders[authID] if ok && provider.EnabledForConnectorType(connectorType) { chain = append(chain, authID) } } return chain, nil } // getConnectorType returns the type of the connector with the given ID. func (s *Server) getConnectorType(ctx context.Context, connectorID string) (string, error) { conn, err := s.getConnector(ctx, connectorID) if err != nil { return "", fmt.Errorf("get connector %q: %w", connectorID, err) } return conn.Type, nil } ================================================ FILE: server/oauth2.go ================================================ package server import ( "context" "crypto" "crypto/sha256" "crypto/sha512" "encoding/base64" "encoding/json" "errors" "fmt" "hash" "io" "net" "net/http" "net/url" "slices" "strconv" "strings" "time" "github.com/coreos/go-oidc/v3/oidc" "github.com/go-jose/go-jose/v4" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/server/internal" "github.com/dexidp/dex/server/signer" "github.com/dexidp/dex/storage" ) // TODO(ericchiang): clean this file up and figure out more idiomatic error handling. // See: https://tools.ietf.org/html/rfc6749#section-4.1.2.1 // displayedAuthErr is an error that should be displayed to the user as a web page type displayedAuthErr struct { Status int Description string } func (err *displayedAuthErr) Error() string { return err.Description } func newDisplayedErr(status int, format string, a ...interface{}) *displayedAuthErr { return &displayedAuthErr{status, fmt.Sprintf(format, a...)} } // redirectWithError redirects back to the client with an OAuth2 error response. // Used for prompt=none when login or consent is required. func (s *Server) redirectWithError(w http.ResponseWriter, r *http.Request, authReq *storage.AuthRequest, errType, description string) { err := &redirectedAuthErr{ State: authReq.State, RedirectURI: authReq.RedirectURI, Type: errType, Description: description, } err.Handler().ServeHTTP(w, r) } // redirectedAuthErr is an error that should be reported back to the client by 302 redirect type redirectedAuthErr struct { State string RedirectURI string Type string Description string } func (err *redirectedAuthErr) Error() string { return err.Description } func (err *redirectedAuthErr) Handler() http.Handler { hf := func(w http.ResponseWriter, r *http.Request) { v := url.Values{} v.Add("state", err.State) v.Add("error", err.Type) if err.Description != "" { v.Add("error_description", err.Description) } // Parse the redirect URI to ensure it's valid before redirecting u, parseErr := url.Parse(err.RedirectURI) if parseErr != nil { // If URI parsing fails, respond with an error instead of redirecting http.Error(w, "Invalid redirect URI", http.StatusBadRequest) return } // Add error parameters to the URL query := u.Query() for key, values := range v { for _, value := range values { query.Add(key, value) } } u.RawQuery = query.Encode() http.Redirect(w, r, u.String(), http.StatusSeeOther) } return http.HandlerFunc(hf) } func tokenErr(w http.ResponseWriter, typ, description string, statusCode int) error { data := struct { Error string `json:"error"` Description string `json:"error_description,omitempty"` }{typ, description} body, err := json.Marshal(data) if err != nil { return fmt.Errorf("failed to marshal token error response: %v", err) } w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Length", strconv.Itoa(len(body))) w.WriteHeader(statusCode) w.Write(body) return nil } const ( errInvalidRequest = "invalid_request" errUnauthorizedClient = "unauthorized_client" errAccessDenied = "access_denied" errUnsupportedResponseType = "unsupported_response_type" errRequestNotSupported = "request_not_supported" errInvalidScope = "invalid_scope" errServerError = "server_error" errTemporarilyUnavailable = "temporarily_unavailable" errUnsupportedGrantType = "unsupported_grant_type" errInvalidGrant = "invalid_grant" errInvalidClient = "invalid_client" errInactiveToken = "inactive_token" errLoginRequired = "login_required" errInteractionRequired = "interaction_required" errConsentRequired = "consent_required" ) const ( scopeOfflineAccess = "offline_access" // Request a refresh token. scopeOpenID = "openid" scopeGroups = "groups" scopeEmail = "email" scopeProfile = "profile" scopeFederatedID = "federated:id" scopeCrossClientPrefix = "audience:server:client_id:" ) const ( deviceCallbackURI = "/device/callback" ) const ( redirectURIOOB = "urn:ietf:wg:oauth:2.0:oob" ) const ( grantTypeAuthorizationCode = "authorization_code" grantTypeRefreshToken = "refresh_token" grantTypeImplicit = "implicit" grantTypePassword = "password" grantTypeDeviceCode = "urn:ietf:params:oauth:grant-type:device_code" grantTypeTokenExchange = "urn:ietf:params:oauth:grant-type:token-exchange" grantTypeClientCredentials = "client_credentials" ) // ConnectorGrantTypes is the set of grant types that can be restricted per connector. var ConnectorGrantTypes = map[string]bool{ grantTypeAuthorizationCode: true, grantTypeRefreshToken: true, grantTypeImplicit: true, grantTypePassword: true, grantTypeDeviceCode: true, grantTypeTokenExchange: true, } const ( // https://www.rfc-editor.org/rfc/rfc8693.html#section-3 tokenTypeAccess = "urn:ietf:params:oauth:token-type:access_token" tokenTypeRefresh = "urn:ietf:params:oauth:token-type:refresh_token" tokenTypeID = "urn:ietf:params:oauth:token-type:id_token" tokenTypeSAML1 = "urn:ietf:params:oauth:token-type:saml1" tokenTypeSAML2 = "urn:ietf:params:oauth:token-type:saml2" tokenTypeJWT = "urn:ietf:params:oauth:token-type:jwt" ) const ( responseTypeCode = "code" // "Regular" flow responseTypeToken = "token" // Implicit flow for frontend apps. responseTypeIDToken = "id_token" // ID Token in url fragment responseTypeCodeToken = "code token" // "Regular" flow + Implicit flow responseTypeCodeIDToken = "code id_token" // "Regular" flow + ID Token responseTypeIDTokenToken = "id_token token" // ID Token + Implicit flow responseTypeCodeIDTokenToken = "code id_token token" // "Regular" flow + ID Token + Implicit flow ) const ( deviceTokenPending = "authorization_pending" deviceTokenComplete = "complete" deviceTokenSlowDown = "slow_down" deviceTokenExpired = "expired_token" ) func parseScopes(scopes []string) connector.Scopes { var s connector.Scopes for _, scope := range scopes { switch scope { case scopeOfflineAccess: s.OfflineAccess = true case scopeGroups: s.Groups = true } } return s } // The hash algorithm for the at_hash is determined by the signing // algorithm used for the id_token. From the spec: // // ...the hash algorithm used is the hash algorithm used in the alg Header // Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, // hash the access_token value with SHA-256 // // https://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken var hashForSigAlg = map[jose.SignatureAlgorithm]func() hash.Hash{ jose.RS256: sha256.New, jose.RS384: sha512.New384, jose.RS512: sha512.New, jose.ES256: sha256.New, jose.ES384: sha512.New384, jose.ES512: sha512.New, } // Compute an at_hash from a raw access token and a signature algorithm // // See: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken func accessTokenHash(alg jose.SignatureAlgorithm, accessToken string) (string, error) { newHash, ok := hashForSigAlg[alg] if !ok { return "", fmt.Errorf("unsupported signature algorithm: %s", alg) } hashFunc := newHash() if _, err := io.WriteString(hashFunc, accessToken); err != nil { return "", fmt.Errorf("computing hash: %v", err) } sum := hashFunc.Sum(nil) return base64.RawURLEncoding.EncodeToString(sum[:len(sum)/2]), nil } type audience []string func (a audience) contains(aud string) bool { for _, e := range a { if aud == e { return true } } return false } func (a audience) MarshalJSON() ([]byte, error) { if len(a) == 1 { return json.Marshal(a[0]) } return json.Marshal([]string(a)) } type idTokenClaims struct { Issuer string `json:"iss"` Subject string `json:"sub"` Audience audience `json:"aud"` Expiry int64 `json:"exp"` IssuedAt int64 `json:"iat"` AuthorizingParty string `json:"azp,omitempty"` Nonce string `json:"nonce,omitempty"` AuthTime int64 `json:"auth_time,omitempty"` AccessTokenHash string `json:"at_hash,omitempty"` CodeHash string `json:"c_hash,omitempty"` Email string `json:"email,omitempty"` EmailVerified *bool `json:"email_verified,omitempty"` Groups []string `json:"groups,omitempty"` Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` FederatedIDClaims *federatedIDClaims `json:"federated_claims,omitempty"` } type federatedIDClaims struct { ConnectorID string `json:"connector_id,omitempty"` UserID string `json:"user_id,omitempty"` } func (s *Server) newAccessToken(ctx context.Context, clientID string, claims storage.Claims, scopes []string, nonce, connID string, authTime time.Time) (accessToken string, expiry time.Time, err error) { return s.newIDToken(ctx, clientID, claims, scopes, nonce, storage.NewID(), "", connID, authTime) } func getClientID(aud audience, azp string) (string, error) { switch len(aud) { case 0: return "", fmt.Errorf("no audience is set, could not find ClientID") case 1: return aud[0], nil default: return azp, nil } } func getAudience(clientID string, scopes []string) audience { var aud audience for _, scope := range scopes { if peerID, ok := parseCrossClientScope(scope); ok { aud = append(aud, peerID) } } if len(aud) == 0 { // Client didn't ask for cross client audience. Set the current // client as the audience. aud = audience{clientID} // Client asked for cross client audience: // if the current client was not requested explicitly } else if !aud.contains(clientID) { // by default it becomes one of entries in Audience aud = append(aud, clientID) } return aud } func genSubject(userID string, connID string) (string, error) { sub := &internal.IDTokenSubject{ UserId: userID, ConnId: connID, } return internal.Marshal(sub) } func (s *Server) newIDToken(ctx context.Context, clientID string, claims storage.Claims, scopes []string, nonce, accessToken, code, connID string, authTime time.Time) (idToken string, expiry time.Time, err error) { issuedAt := s.now() expiry = issuedAt.Add(s.idTokensValidFor) subjectString, err := genSubject(claims.UserID, connID) if err != nil { s.logger.ErrorContext(ctx, "failed to marshal offline session ID", "err", err) return "", expiry, fmt.Errorf("failed to marshal offline session ID: %v", err) } tok := idTokenClaims{ Issuer: s.issuerURL.String(), Subject: subjectString, Nonce: nonce, Expiry: expiry.Unix(), IssuedAt: issuedAt.Unix(), } // Include auth_time when sessions are enabled and the value is available. if !authTime.IsZero() { tok.AuthTime = authTime.Unix() } // Determine signing algorithm from signer signingAlg, err := s.signer.Algorithm(ctx) if err != nil { s.logger.ErrorContext(ctx, "failed to get signing algorithm", "err", err) return "", expiry, fmt.Errorf("failed to get signing algorithm: %v", err) } if accessToken != "" { atHash, err := accessTokenHash(signingAlg, accessToken) if err != nil { s.logger.ErrorContext(ctx, "error computing at_hash", "err", err) return "", expiry, fmt.Errorf("error computing at_hash: %v", err) } tok.AccessTokenHash = atHash } if code != "" { cHash, err := accessTokenHash(signingAlg, code) if err != nil { s.logger.ErrorContext(ctx, "error computing c_hash", "err", err) return "", expiry, fmt.Errorf("error computing c_hash: #{err}") } tok.CodeHash = cHash } for _, scope := range scopes { switch { case scope == scopeEmail: tok.Email = claims.Email tok.EmailVerified = &claims.EmailVerified case scope == scopeGroups: tok.Groups = claims.Groups case scope == scopeProfile: tok.Name = claims.Username tok.PreferredUsername = claims.PreferredUsername case scope == scopeFederatedID: tok.FederatedIDClaims = &federatedIDClaims{ ConnectorID: connID, UserID: claims.UserID, } default: peerID, ok := parseCrossClientScope(scope) if !ok { // Ignore unknown scopes. These are already validated during the // initial auth request. continue } isTrusted, err := s.validateCrossClientTrust(ctx, clientID, peerID) if err != nil { return "", expiry, err } if !isTrusted { // TODO(ericchiang): propagate this error to the client. return "", expiry, fmt.Errorf("peer (%s) does not trust client", peerID) } } } tok.Audience = getAudience(clientID, scopes) if len(tok.Audience) > 1 { // The current client becomes the authorizing party. tok.AuthorizingParty = clientID } payload, err := json.Marshal(tok) if err != nil { return "", expiry, fmt.Errorf("could not serialize claims: %v", err) } if idToken, err = s.signer.Sign(ctx, payload); err != nil { return "", expiry, fmt.Errorf("failed to sign payload: %v", err) } return idToken, expiry, nil } // validateIDTokenHint verifies the signature and issuer of an id_token_hint. // Expired tokens are accepted per OIDC Core 1.0 §3.1.2.1. // Returns the raw subject claim from the token. func (s *Server) validateIDTokenHint(ctx context.Context, hint string) (string, error) { verifier := oidc.NewVerifier(s.issuerURL.String(), &signerKeySet{s.signer}, &oidc.Config{ SkipExpiryCheck: true, // SkipClientIDCheck is set because the hint may originate from any client that // Dex issued a token to — the caller does not know the expected audience in advance. // The signature verification via signerKeySet already guarantees the token was // issued by this server, which is sufficient for a hint. // Dex does the client id check later in the scope of the session validation. SkipClientIDCheck: true, }) idToken, err := verifier.Verify(ctx, hint) if err != nil { return "", err } return idToken.Subject, nil } // sessionMatchesHint checks whether the session's user identity matches the // subject from an id_token_hint by encoding the session's (userID, connectorID) // via genSubject and doing a string comparison. func sessionMatchesHint(session *storage.AuthSession, hintSubject string) bool { if session == nil { return false } encoded, err := genSubject(session.UserID, session.ConnectorID) if err != nil { return false } return encoded == hintSubject } // parseAuthorizationRequest parses the initial request from the OAuth2 client. // Returns the auth request, the raw subject from id_token_hint (empty if not provided), and any error. func (s *Server) parseAuthorizationRequest(r *http.Request) (*storage.AuthRequest, string, error) { ctx := r.Context() if err := r.ParseForm(); err != nil { return nil, "", newDisplayedErr(http.StatusBadRequest, "Failed to parse request.") } q := r.Form redirectURI, err := url.QueryUnescape(q.Get("redirect_uri")) if err != nil { return nil, "", newDisplayedErr(http.StatusBadRequest, "No redirect_uri provided.") } clientID := q.Get("client_id") state := q.Get("state") nonce := q.Get("nonce") connectorID := q.Get("connector_id") // Some clients, like the old go-oidc, provide extra whitespace. Tolerate this. scopes := strings.Fields(q.Get("scope")) responseTypes := strings.Fields(q.Get("response_type")) codeChallenge := q.Get("code_challenge") codeChallengeMethod := q.Get("code_challenge_method") if codeChallengeMethod == "" { codeChallengeMethod = codeChallengeMethodPlain } client, err := s.storage.GetClient(ctx, clientID) if err != nil { if err == storage.ErrNotFound { s.logger.ErrorContext(r.Context(), "invalid client_id provided", "client_id", clientID) return nil, "", newDisplayedErr(http.StatusNotFound, "Invalid client_id.") } s.logger.ErrorContext(r.Context(), "failed to get client", "err", err) return nil, "", newDisplayedErr(http.StatusInternalServerError, "Database error.") } if !validateRedirectURI(client, redirectURI) { s.logger.ErrorContext(r.Context(), "unregistered redirect_uri", "redirect_uri", redirectURI, "client_id", clientID) return nil, "", newDisplayedErr(http.StatusBadRequest, "Unregistered redirect_uri.") } if redirectURI == deviceCallbackURI && client.Public { redirectURI = s.absPath(deviceCallbackURI) } // From here on out, we want to redirect back to the client with an error. newRedirectedErr := func(typ, format string, a ...interface{}) *redirectedAuthErr { return &redirectedAuthErr{state, redirectURI, typ, fmt.Sprintf(format, a...)} } if connectorID != "" { connectors, err := s.storage.ListConnectors(ctx) if err != nil { s.logger.ErrorContext(r.Context(), "failed to list connectors", "err", err) return nil, "", newRedirectedErr(errServerError, "Unable to retrieve connectors") } if !validateConnectorID(connectors, connectorID) { return nil, "", newRedirectedErr(errInvalidRequest, "Invalid ConnectorID") } if !isConnectorAllowed(client.AllowedConnectors, connectorID) { return nil, "", newRedirectedErr(errInvalidRequest, "Connector not allowed for this client") } } // dex doesn't support request parameter and must return request_not_supported error // https://openid.net/specs/openid-connect-core-1_0.html#6.1 if q.Get("request") != "" { return nil, "", newRedirectedErr(errRequestNotSupported, "Server does not support request parameter.") } if codeChallenge != "" && !slices.Contains(s.pkce.CodeChallengeMethodsSupported, codeChallengeMethod) { return nil, "", newRedirectedErr(errInvalidRequest, "Unsupported PKCE challenge method (%q).", codeChallengeMethod) } // Enforce PKCE if configured. // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12#section-4.1.1 if s.pkce.Enforce && codeChallenge == "" { return nil, "", newRedirectedErr(errInvalidRequest, "PKCE is required. The code_challenge parameter must be provided.") } var ( unrecognized []string invalidScopes []string ) hasOpenIDScope := false for _, scope := range scopes { switch scope { case scopeOpenID: hasOpenIDScope = true case scopeOfflineAccess, scopeEmail, scopeProfile, scopeGroups, scopeFederatedID: default: peerID, ok := parseCrossClientScope(scope) if !ok { unrecognized = append(unrecognized, scope) continue } isTrusted, err := s.validateCrossClientTrust(r.Context(), clientID, peerID) if err != nil { return nil, "", newRedirectedErr(errServerError, "Internal server error.") } if !isTrusted { invalidScopes = append(invalidScopes, scope) } } } if !hasOpenIDScope { return nil, "", newRedirectedErr(errInvalidScope, `Missing required scope(s) ["openid"].`) } if len(unrecognized) > 0 { return nil, "", newRedirectedErr(errInvalidScope, "Unrecognized scope(s) %q", unrecognized) } if len(invalidScopes) > 0 { return nil, "", newRedirectedErr(errInvalidScope, "Client can't request scope(s) %q", invalidScopes) } var rt struct { code bool idToken bool token bool } for _, responseType := range responseTypes { switch responseType { case responseTypeCode: rt.code = true case responseTypeIDToken: rt.idToken = true case responseTypeToken: rt.token = true default: return nil, "", newRedirectedErr(errInvalidRequest, "Invalid response type %q", responseType) } if !s.supportedResponseTypes[responseType] { return nil, "", newRedirectedErr(errUnsupportedResponseType, "Unsupported response type %q", responseType) } } if len(responseTypes) == 0 { return nil, "", newRedirectedErr(errInvalidRequest, "No response_type provided") } if rt.token && !rt.code && !rt.idToken { // "token" can't be provided by its own. // // https://openid.net/specs/openid-connect-core-1_0.html#Authentication return nil, "", newRedirectedErr(errInvalidRequest, "Response type 'token' must be provided with type 'id_token' and/or 'code'") } if !rt.code { // Either "id_token token" or "id_token" has been provided which implies the // implicit flow. Implicit flow requires a nonce value. // // https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest if nonce == "" { return nil, "", newRedirectedErr(errInvalidRequest, "Response type 'token' requires a 'nonce' value.") } } if rt.token { if redirectURI == redirectURIOOB { return nil, "", newRedirectedErr(errInvalidRequest, "Cannot use response type 'token' with redirect_uri '%s'.", redirectURIOOB) } } prompt, err := ParsePrompt(q.Get("prompt")) if err != nil { return nil, "", newRedirectedErr(errInvalidRequest, "Invalid prompt parameter: %v", err) } // Parse max_age: -1 means not specified. maxAge := -1 if maxAgeStr := q.Get("max_age"); maxAgeStr != "" { v, err := strconv.Atoi(maxAgeStr) if err != nil || v < 0 { return nil, "", newRedirectedErr(errInvalidRequest, "Invalid max_age value %q", maxAgeStr) } maxAge = v } // OIDC prompt=consent implies force approval. forceApproval := q.Get("approval_prompt") == "force" || prompt.Consent() // Validate id_token_hint if provided (OIDC Core 1.0 §3.1.2.1). var idTokenHintSubject string if hint := q.Get("id_token_hint"); hint != "" { sub, err := s.validateIDTokenHint(ctx, hint) if err != nil { return nil, "", newRedirectedErr(errInvalidRequest, "Invalid id_token_hint.") } idTokenHintSubject = sub } return &storage.AuthRequest{ ID: storage.NewID(), ClientID: client.ID, State: state, Nonce: nonce, ForceApprovalPrompt: forceApproval, Prompt: prompt.String(), MaxAge: maxAge, Scopes: scopes, RedirectURI: redirectURI, ResponseTypes: responseTypes, ConnectorID: connectorID, PKCE: storage.PKCE{ CodeChallenge: codeChallenge, CodeChallengeMethod: codeChallengeMethod, }, HMACKey: storage.NewHMACKey(crypto.SHA256), }, idTokenHintSubject, nil } func parseCrossClientScope(scope string) (peerID string, ok bool) { if ok = strings.HasPrefix(scope, scopeCrossClientPrefix); ok { peerID = scope[len(scopeCrossClientPrefix):] } return } func (s *Server) validateCrossClientTrust(ctx context.Context, clientID, peerID string) (trusted bool, err error) { if peerID == clientID { return true, nil } peer, err := s.storage.GetClient(ctx, peerID) if err != nil { if err != storage.ErrNotFound { s.logger.ErrorContext(ctx, "failed to get client", "err", err) return false, err } return false, nil } for _, id := range peer.TrustedPeers { if id == clientID { return true, nil } } return false, nil } func validateRedirectURI(client storage.Client, redirectURI string) bool { // Allow named RedirectURIs for both public and non-public clients. // This is required make PKCE-enabled web apps work, when configured as public clients. for _, uri := range client.RedirectURIs { if redirectURI == uri { return true } } // For non-public clients or when RedirectURIs is set, we allow only explicitly named RedirectURIs. // Otherwise, we check below for special URIs used for desktop or mobile apps. if !client.Public || len(client.RedirectURIs) > 0 { return false } if redirectURI == redirectURIOOB || redirectURI == deviceCallbackURI { return true } // verify that the host is of form "http://localhost:(port)(path)", "http://localhost(path)" or numeric form like // "http://127.0.0.1:(port)(path)" u, err := url.Parse(redirectURI) if err != nil { return false } if u.Scheme != "http" { return false } return isHostLocal(u.Host) } func isHostLocal(host string) bool { if host == "localhost" || net.ParseIP(host).IsLoopback() { return true } host, _, err := net.SplitHostPort(host) if err != nil { return false } return host == "localhost" || net.ParseIP(host).IsLoopback() } func validateConnectorID(connectors []storage.Connector, connectorID string) bool { for _, c := range connectors { if c.ID == connectorID { return true } } return false } // signerKeySet implements the oidc.KeySet interface backed by the Dex signer type signerKeySet struct { signer signer.Signer } func (s *signerKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) { jws, err := jose.ParseSigned(jwt, []jose.SignatureAlgorithm{jose.RS256, jose.RS384, jose.RS512, jose.ES256, jose.ES384, jose.ES512}) if err != nil { return nil, err } keyID := "" for _, sig := range jws.Signatures { keyID = sig.Header.KeyID break } keys, err := s.signer.ValidationKeys(ctx) if err != nil { return nil, err } for _, key := range keys { if keyID == "" || key.KeyID == keyID { if payload, err := jws.Verify(key); err == nil { return payload, nil } } } return nil, errors.New("failed to verify id token signature") } ================================================ FILE: server/oauth2_test.go ================================================ package server import ( "crypto/rand" "crypto/rsa" "encoding/json" "log/slog" "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" "github.com/go-jose/go-jose/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/dexidp/dex/server/signer" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/memory" ) func TestGetClientID(t *testing.T) { cid, err := getClientID(audience{}, "") require.Equal(t, "", cid) require.Equal(t, "no audience is set, could not find ClientID", err.Error()) cid, err = getClientID(audience{"a"}, "") require.Equal(t, "a", cid) require.NoError(t, err) cid, err = getClientID(audience{"a", "b"}, "azp") require.Equal(t, "azp", cid) require.NoError(t, err) } func TestGetAudience(t *testing.T) { aud := getAudience("client-id", []string{}) require.Equal(t, aud, audience{"client-id"}) aud = getAudience("client-id", []string{"ascope"}) require.Equal(t, aud, audience{"client-id"}) aud = getAudience("client-id", []string{"ascope", "audience:server:client_id:aa", "audience:server:client_id:bb"}) require.Equal(t, aud, audience{"aa", "bb", "client-id"}) } func TestGetSubject(t *testing.T) { sub, err := genSubject("foo", "bar") require.Equal(t, "CgNmb28SA2Jhcg", sub) require.NoError(t, err) } func TestParseAuthorizationRequest(t *testing.T) { tests := []struct { name string clients []storage.Client supportedResponseTypes []string pkce PKCEConfig usePOST bool queryParams map[string]string expectedError error }{ { name: "normal request", clients: []storage.Client{ { ID: "foo", RedirectURIs: []string{"https://example.com/foo"}, }, }, supportedResponseTypes: []string{"code"}, queryParams: map[string]string{ "client_id": "foo", "redirect_uri": "https://example.com/foo", "response_type": "code", "scope": "openid email profile", }, }, { name: "POST request", clients: []storage.Client{ { ID: "foo", RedirectURIs: []string{"https://example.com/foo"}, }, }, supportedResponseTypes: []string{"code"}, queryParams: map[string]string{ "client_id": "foo", "redirect_uri": "https://example.com/foo", "response_type": "code", "scope": "openid email profile", }, usePOST: true, }, { name: "invalid client id", clients: []storage.Client{ { ID: "foo", RedirectURIs: []string{"https://example.com/foo"}, }, }, supportedResponseTypes: []string{"code"}, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/foo", "response_type": "code", "scope": "openid email profile", }, expectedError: &displayedAuthErr{Status: http.StatusNotFound}, }, { name: "invalid redirect uri", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code"}, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/foo", "response_type": "code", "scope": "openid email profile", }, expectedError: &displayedAuthErr{Status: http.StatusBadRequest}, }, { name: "implicit flow", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code", "id_token", "token"}, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code id_token", "scope": "openid email profile", }, }, { name: "unsupported response type", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code"}, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code id_token", "scope": "openid email profile", }, expectedError: &redirectedAuthErr{Type: errUnsupportedResponseType}, }, { name: "only token response type", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code", "id_token", "token"}, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "token", "scope": "openid email profile", }, expectedError: &redirectedAuthErr{Type: errInvalidRequest}, }, { name: "choose connector_id", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code", "id_token", "token"}, queryParams: map[string]string{ "connector_id": "mock", "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code id_token", "scope": "openid email profile", }, }, { name: "choose second connector_id", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code", "id_token", "token"}, queryParams: map[string]string{ "connector_id": "mock2", "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code id_token", "scope": "openid email profile", }, }, { name: "choose invalid connector_id", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code", "id_token", "token"}, queryParams: map[string]string{ "connector_id": "bogus", "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code id_token", "scope": "openid email profile", }, expectedError: &redirectedAuthErr{Type: errInvalidRequest}, }, { name: "PKCE code_challenge_method plain", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code"}, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code", "code_challenge": "123", "code_challenge_method": "plain", "scope": "openid email profile", }, }, { name: "PKCE code_challenge_method default plain", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code"}, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code", "code_challenge": "123", "scope": "openid email profile", }, }, { name: "PKCE code_challenge_method S256", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code"}, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code", "code_challenge": "123", "code_challenge_method": "S256", "scope": "openid email profile", }, }, { name: "PKCE invalid code_challenge_method", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code"}, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code", "code_challenge": "123", "code_challenge_method": "invalid_method", "scope": "openid email profile", }, expectedError: &redirectedAuthErr{Type: errInvalidRequest}, }, { name: "No response type", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code"}, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/bar", "code_challenge": "123", "code_challenge_method": "plain", "scope": "openid email profile", }, expectedError: &redirectedAuthErr{Type: errInvalidRequest}, }, { name: "PKCE enforced, no code_challenge provided", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code"}, pkce: PKCEConfig{ Enforce: true, CodeChallengeMethodsSupported: []string{"S256", "plain"}, }, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code", "scope": "openid email profile", }, expectedError: &redirectedAuthErr{Type: errInvalidRequest}, }, { name: "PKCE enforced, code_challenge provided", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code"}, pkce: PKCEConfig{ Enforce: true, CodeChallengeMethodsSupported: []string{"S256", "plain"}, }, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code", "code_challenge": "123", "code_challenge_method": "S256", "scope": "openid email profile", }, }, { name: "PKCE only S256 allowed, plain rejected", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code"}, pkce: PKCEConfig{ CodeChallengeMethodsSupported: []string{"S256"}, }, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code", "code_challenge": "123", "code_challenge_method": "plain", "scope": "openid email profile", }, expectedError: &redirectedAuthErr{Type: errInvalidRequest}, }, { name: "PKCE only S256 allowed, S256 accepted", clients: []storage.Client{ { ID: "bar", RedirectURIs: []string{"https://example.com/bar"}, }, }, supportedResponseTypes: []string{"code"}, pkce: PKCEConfig{ CodeChallengeMethodsSupported: []string{"S256"}, }, queryParams: map[string]string{ "client_id": "bar", "redirect_uri": "https://example.com/bar", "response_type": "code", "code_challenge": "123", "code_challenge_method": "S256", "scope": "openid email profile", }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { httpServer, server := newTestServerMultipleConnectors(t, func(c *Config) { c.SupportedResponseTypes = tc.supportedResponseTypes c.Storage = storage.WithStaticClients(c.Storage, tc.clients) if len(tc.pkce.CodeChallengeMethodsSupported) > 0 || tc.pkce.Enforce { c.PKCE = tc.pkce } }) defer httpServer.Close() params := url.Values{} for k, v := range tc.queryParams { params.Set(k, v) } var req *http.Request if tc.usePOST { body := strings.NewReader(params.Encode()) req = httptest.NewRequest("POST", httpServer.URL+"/auth", body) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } else { req = httptest.NewRequest("GET", httpServer.URL+"/auth?"+params.Encode(), nil) } _, _, err := server.parseAuthorizationRequest(req) if tc.expectedError == nil { if err != nil { t.Errorf("%s: expected no error", tc.name) } } else { switch expectedErr := tc.expectedError.(type) { case *redirectedAuthErr: e, ok := err.(*redirectedAuthErr) if !ok { t.Fatalf("%s: expected redirectedAuthErr error", tc.name) } if e.Type != expectedErr.Type { t.Errorf("%s: expected error type %v, got %v", tc.name, expectedErr.Type, e.Type) } if e.RedirectURI != tc.queryParams["redirect_uri"] { t.Errorf("%s: expected error to be returned in redirect to %v", tc.name, tc.queryParams["redirect_uri"]) } case *displayedAuthErr: e, ok := err.(*displayedAuthErr) if !ok { t.Fatalf("%s: expected displayedAuthErr error", tc.name) } if e.Status != expectedErr.Status { t.Errorf("%s: expected http status %v, got %v", tc.name, expectedErr.Status, e.Status) } default: t.Fatalf("%s: unsupported error type", tc.name) } } }) } } const ( // at_hash value and access_token returned by Google. googleAccessTokenHash = "piwt8oCH-K2D9pXlaS1Y-w" googleAccessToken = "ya29.CjHSA1l5WUn8xZ6HanHFzzdHdbXm-14rxnC7JHch9eFIsZkQEGoWzaYG4o7k5f6BnPLj" googleSigningAlg = jose.RS256 ) func TestAccessTokenHash(t *testing.T) { atHash, err := accessTokenHash(googleSigningAlg, googleAccessToken) if err != nil { t.Fatal(err) } if atHash != googleAccessTokenHash { t.Errorf("expected %q got %q", googleAccessTokenHash, atHash) } } func TestValidRedirectURI(t *testing.T) { tests := []struct { client storage.Client redirectURI string wantValid bool }{ { client: storage.Client{ RedirectURIs: []string{"http://foo.com/bar"}, }, redirectURI: "http://foo.com/bar", wantValid: true, }, { client: storage.Client{ RedirectURIs: []string{"http://foo.com/bar"}, }, redirectURI: "http://foo.com/bar/baz", wantValid: false, }, // These special desktop + device + localhost URIs are allowed by default. { client: storage.Client{ Public: true, }, redirectURI: "urn:ietf:wg:oauth:2.0:oob", wantValid: true, }, { client: storage.Client{ Public: true, }, redirectURI: "/device/callback", wantValid: true, }, { client: storage.Client{ Public: true, }, redirectURI: "http://localhost:8080/", wantValid: true, }, { client: storage.Client{ Public: true, }, redirectURI: "http://localhost:991/bar", wantValid: true, }, { client: storage.Client{ Public: true, }, redirectURI: "http://localhost", wantValid: true, }, { client: storage.Client{ Public: true, }, redirectURI: "http://127.0.0.1:8080/", wantValid: true, }, { client: storage.Client{ Public: true, }, redirectURI: "http://127.0.0.1:991/bar", wantValid: true, }, { client: storage.Client{ Public: true, }, redirectURI: "http://127.0.0.1", wantValid: true, }, // Both Public + RedirectURIs configured: Could e.g. be a PKCE-enabled web app. { client: storage.Client{ Public: true, RedirectURIs: []string{"http://foo.com/bar"}, }, redirectURI: "http://foo.com/bar", wantValid: true, }, { client: storage.Client{ Public: true, RedirectURIs: []string{"http://foo.com/bar"}, }, redirectURI: "http://foo.com/bar/baz", wantValid: false, }, // These special desktop + device + localhost URIs are not allowed implicitly when RedirectURIs is non-empty. { client: storage.Client{ Public: true, RedirectURIs: []string{"http://foo.com/bar"}, }, redirectURI: "urn:ietf:wg:oauth:2.0:oob", wantValid: false, }, { client: storage.Client{ Public: true, RedirectURIs: []string{"http://foo.com/bar"}, }, redirectURI: "/device/callback", wantValid: false, }, { client: storage.Client{ Public: true, RedirectURIs: []string{"http://foo.com/bar"}, }, redirectURI: "http://localhost:8080/", wantValid: false, }, { client: storage.Client{ Public: true, RedirectURIs: []string{"http://foo.com/bar"}, }, redirectURI: "http://localhost:991/bar", wantValid: false, }, { client: storage.Client{ Public: true, RedirectURIs: []string{"http://foo.com/bar"}, }, redirectURI: "http://localhost", wantValid: false, }, // These special desktop + device + localhost URIs can still be specified explicitly. { client: storage.Client{ Public: true, RedirectURIs: []string{"http://foo.com/bar", "urn:ietf:wg:oauth:2.0:oob"}, }, redirectURI: "urn:ietf:wg:oauth:2.0:oob", wantValid: true, }, { client: storage.Client{ Public: true, RedirectURIs: []string{"http://foo.com/bar", "/device/callback"}, }, redirectURI: "/device/callback", wantValid: true, }, { client: storage.Client{ Public: true, RedirectURIs: []string{"http://foo.com/bar", "http://localhost:8080/"}, }, redirectURI: "http://localhost:8080/", wantValid: true, }, { client: storage.Client{ Public: true, RedirectURIs: []string{"http://foo.com/bar", "http://localhost:991/bar"}, }, redirectURI: "http://localhost:991/bar", wantValid: true, }, { client: storage.Client{ Public: true, RedirectURIs: []string{"http://foo.com/bar", "http://localhost"}, }, redirectURI: "http://localhost", wantValid: true, }, // Non-localhost URIs are not allowed implicitly. { client: storage.Client{ Public: true, }, redirectURI: "http://foo.com/bar", wantValid: false, }, { client: storage.Client{ Public: true, }, redirectURI: "http://localhost.localhost:8080/", wantValid: false, }, } for _, test := range tests { got := validateRedirectURI(test.client, test.redirectURI) if got != test.wantValid { t.Errorf("client=%#v, redirectURI=%q, wanted valid=%t, got=%t", test.client, test.redirectURI, test.wantValid, got) } } } func TestSignerKeySet(t *testing.T) { logger := newLogger(t) s := memory.New(logger) if err := s.UpdateKeys(t.Context(), func(keys storage.Keys) (storage.Keys, error) { keys.SigningKey = &jose.JSONWebKey{ Key: testKey, KeyID: "testkey", Algorithm: "RS256", Use: "sig", } keys.SigningKeyPub = &jose.JSONWebKey{ Key: testKey.Public(), KeyID: "testkey", Algorithm: "RS256", Use: "sig", } return keys, nil }); err != nil { t.Fatal(err) } tests := []struct { name string tokenGenerator func() (jwt string, err error) wantErr bool }{ { name: "valid token", tokenGenerator: func() (string, error) { signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: testKey}, nil) if err != nil { return "", err } jws, err := signer.Sign([]byte("payload")) if err != nil { return "", err } return jws.CompactSerialize() }, wantErr: false, }, { name: "token signed by different key", tokenGenerator: func() (string, error) { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return "", err } signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: key}, nil) if err != nil { return "", err } jws, err := signer.Sign([]byte("payload")) if err != nil { return "", err } return jws.CompactSerialize() }, wantErr: true, }, } for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { jwt, err := tc.tokenGenerator() if err != nil { t.Fatal(err) } // Create a mock signer for testing sig, err := signer.NewMockSigner(testKey) if err != nil { t.Fatal(err) } keySet := &signerKeySet{ signer: sig, } _, err = keySet.VerifySignature(t.Context(), jwt) if (err != nil && !tc.wantErr) || (err == nil && tc.wantErr) { t.Fatalf("wantErr = %v, but got err = %v", tc.wantErr, err) } }) } } func TestRedirectedAuthErrHandler(t *testing.T) { tests := []struct { name string redirectURI string state string errType string description string wantStatus int wantErr bool }{ { name: "valid redirect uri with error parameters", redirectURI: "https://example.com/callback", state: "state123", errType: errInvalidRequest, description: "Invalid request parameter", wantStatus: http.StatusSeeOther, wantErr: false, }, { name: "valid redirect uri with query params", redirectURI: "https://example.com/callback?existing=param&another=value", state: "state456", errType: errAccessDenied, description: "User denied access", wantStatus: http.StatusSeeOther, wantErr: false, }, { name: "valid redirect uri without description", redirectURI: "https://example.com/callback", state: "state789", errType: errServerError, description: "", wantStatus: http.StatusSeeOther, wantErr: false, }, { name: "invalid redirect uri", redirectURI: "not a valid url ://", state: "state", errType: errInvalidRequest, description: "Test error", wantStatus: http.StatusBadRequest, wantErr: true, }, } for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { err := &redirectedAuthErr{ State: tc.state, RedirectURI: tc.redirectURI, Type: tc.errType, Description: tc.description, } handler := err.Handler() w := httptest.NewRecorder() r := httptest.NewRequest("GET", "/", nil) handler.ServeHTTP(w, r) if w.Code != tc.wantStatus { t.Errorf("expected status %d, got %d", tc.wantStatus, w.Code) } if tc.wantStatus == http.StatusSeeOther { // Verify the redirect location is a valid URL location := w.Header().Get("Location") if location == "" { t.Fatalf("expected Location header, got empty string") } // Parse the redirect URL to verify it's valid redirectURL, parseErr := url.Parse(location) if parseErr != nil { t.Fatalf("invalid redirect URL: %v", parseErr) } // Verify error parameters are present in the query string query := redirectURL.Query() if query.Get("state") != tc.state { t.Errorf("expected state %q, got %q", tc.state, query.Get("state")) } if query.Get("error") != tc.errType { t.Errorf("expected error type %q, got %q", tc.errType, query.Get("error")) } if tc.description != "" && query.Get("error_description") != tc.description { t.Errorf("expected error_description %q, got %q", tc.description, query.Get("error_description")) } // Verify that existing query parameters are preserved if tc.name == "valid redirect uri with query params" { if query.Get("existing") != "param" { t.Errorf("expected existing parameter 'param', got %q", query.Get("existing")) } if query.Get("another") != "value" { t.Errorf("expected another parameter 'value', got %q", query.Get("another")) } } } }) } } // signTestIDToken creates a signed JWT with the given claims using the test key. func signTestIDToken(t *testing.T, claims interface{}) string { t.Helper() payload, err := json.Marshal(claims) require.NoError(t, err) joseSigner, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: testKey}, nil) require.NoError(t, err) jws, err := joseSigner.Sign(payload) require.NoError(t, err) token, err := jws.CompactSerialize() require.NoError(t, err) return token } func TestValidateIDTokenHint(t *testing.T) { sig, err := signer.NewMockSigner(testKey) require.NoError(t, err) issuerURL, err := url.Parse("https://issuer.example.com") require.NoError(t, err) s := &Server{ signer: sig, issuerURL: *issuerURL, logger: slog.Default(), } now := time.Now() t.Run("valid hint (not expired)", func(t *testing.T) { token := signTestIDToken(t, idTokenClaims{ Issuer: "https://issuer.example.com", Subject: "CgNmb28SA2Jhcg", Expiry: now.Add(1 * time.Hour).Unix(), }) sub, err := s.validateIDTokenHint(t.Context(), token) require.NoError(t, err) assert.Equal(t, "CgNmb28SA2Jhcg", sub) }) t.Run("valid hint (expired)", func(t *testing.T) { token := signTestIDToken(t, idTokenClaims{ Issuer: "https://issuer.example.com", Subject: "CgNmb28SA2Jhcg", Expiry: now.Add(-1 * time.Hour).Unix(), }) sub, err := s.validateIDTokenHint(t.Context(), token) require.NoError(t, err) assert.Equal(t, "CgNmb28SA2Jhcg", sub) }) t.Run("invalid signature", func(t *testing.T) { otherKey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) payload, err := json.Marshal(idTokenClaims{ Issuer: "https://issuer.example.com", Subject: "CgNmb28SA2Jhcg", Expiry: now.Add(1 * time.Hour).Unix(), }) require.NoError(t, err) joseSigner, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: otherKey}, nil) require.NoError(t, err) jws, err := joseSigner.Sign(payload) require.NoError(t, err) token, err := jws.CompactSerialize() require.NoError(t, err) _, err = s.validateIDTokenHint(t.Context(), token) assert.Error(t, err) }) t.Run("wrong issuer", func(t *testing.T) { token := signTestIDToken(t, idTokenClaims{ Issuer: "https://wrong-issuer.example.com", Subject: "CgNmb28SA2Jhcg", Expiry: now.Add(1 * time.Hour).Unix(), }) _, err := s.validateIDTokenHint(t.Context(), token) assert.Error(t, err) }) t.Run("malformed token", func(t *testing.T) { _, err := s.validateIDTokenHint(t.Context(), "not-a-valid-jwt") assert.Error(t, err) }) } func TestSessionMatchesHint(t *testing.T) { // genSubject("foo", "bar") == "CgNmb28SA2Jhcg" (from TestGetSubject) assert.True(t, sessionMatchesHint(&storage.AuthSession{UserID: "foo", ConnectorID: "bar"}, "CgNmb28SA2Jhcg")) assert.False(t, sessionMatchesHint(&storage.AuthSession{UserID: "other", ConnectorID: "bar"}, "CgNmb28SA2Jhcg")) assert.False(t, sessionMatchesHint(&storage.AuthSession{UserID: "foo", ConnectorID: "other"}, "CgNmb28SA2Jhcg")) assert.False(t, sessionMatchesHint(nil, "CgNmb28SA2Jhcg")) } func TestParseAuthorizationRequest_IDTokenHint(t *testing.T) { sig, err := signer.NewMockSigner(testKey) require.NoError(t, err) now := time.Now() t.Run("valid id_token_hint populates subject", func(t *testing.T) { httpServer, server := newTestServerMultipleConnectors(t, func(c *Config) { c.SupportedResponseTypes = []string{"code"} c.Storage = storage.WithStaticClients(c.Storage, []storage.Client{ {ID: "foo", RedirectURIs: []string{"https://example.com/foo"}}, }) c.Signer = sig }) defer httpServer.Close() token := signTestIDToken(t, idTokenClaims{ Issuer: httpServer.URL, Subject: "CgNmb28SA2Jhcg", Expiry: now.Add(1 * time.Hour).Unix(), }) params := url.Values{ "client_id": {"foo"}, "redirect_uri": {"https://example.com/foo"}, "response_type": {"code"}, "scope": {"openid"}, "id_token_hint": {token}, } req := httptest.NewRequest("GET", httpServer.URL+"/auth?"+params.Encode(), nil) _, hintSubject, err := server.parseAuthorizationRequest(req) require.NoError(t, err) assert.Equal(t, "CgNmb28SA2Jhcg", hintSubject) }) t.Run("invalid id_token_hint returns error", func(t *testing.T) { httpServer, server := newTestServerMultipleConnectors(t, func(c *Config) { c.SupportedResponseTypes = []string{"code"} c.Storage = storage.WithStaticClients(c.Storage, []storage.Client{ {ID: "foo", RedirectURIs: []string{"https://example.com/foo"}}, }) c.Signer = sig }) defer httpServer.Close() params := url.Values{ "client_id": {"foo"}, "redirect_uri": {"https://example.com/foo"}, "response_type": {"code"}, "scope": {"openid"}, "id_token_hint": {"invalid-token"}, } req := httptest.NewRequest("GET", httpServer.URL+"/auth?"+params.Encode(), nil) _, _, err := server.parseAuthorizationRequest(req) require.Error(t, err) redirectErr, ok := err.(*redirectedAuthErr) require.True(t, ok) assert.Equal(t, errInvalidRequest, redirectErr.Type) }) t.Run("no id_token_hint leaves subject empty", func(t *testing.T) { httpServer, server := newTestServerMultipleConnectors(t, func(c *Config) { c.SupportedResponseTypes = []string{"code"} c.Storage = storage.WithStaticClients(c.Storage, []storage.Client{ {ID: "foo", RedirectURIs: []string{"https://example.com/foo"}}, }) }) defer httpServer.Close() params := url.Values{ "client_id": {"foo"}, "redirect_uri": {"https://example.com/foo"}, "response_type": {"code"}, "scope": {"openid"}, } req := httptest.NewRequest("GET", httpServer.URL+"/auth?"+params.Encode(), nil) _, hintSubject, err := server.parseAuthorizationRequest(req) require.NoError(t, err) assert.Equal(t, "", hintSubject) }) } ================================================ FILE: server/prompt.go ================================================ package server import ( "fmt" "strings" ) // Prompt represents the parsed OIDC "prompt" parameter (RFC 6749 / OpenID Connect Core 3.1.2.1). // The parameter is space-separated and may contain: "none", "login", "consent", "select_account". // "none" must not be combined with any other value. type Prompt struct { none bool login bool consent bool } // ParsePrompt parses and validates the raw prompt query parameter. // Returns an error suitable for returning as an OAuth2 invalid_request if the value is invalid. func ParsePrompt(raw string) (Prompt, error) { raw = strings.TrimSpace(raw) if raw == "" { return Prompt{}, nil } var p Prompt seen := make(map[string]bool) for _, v := range strings.Fields(raw) { if seen[v] { continue } seen[v] = true switch v { case "none": p.none = true case "login": p.login = true case "consent": p.consent = true case "select_account": // Dex does not support account selection; ignore per spec recommendation. default: return Prompt{}, fmt.Errorf("invalid prompt value %q", v) } } if p.none && (p.login || p.consent) { return Prompt{}, fmt.Errorf("prompt=none must not be combined with other values") } return p, nil } // None returns true if the caller requested no interactive UI. func (p Prompt) None() bool { return p.none } // Login returns true if the caller requested forced re-authentication. func (p Prompt) Login() bool { return p.login } // Consent returns true if the caller requested forced consent screen. func (p Prompt) Consent() bool { return p.consent } // String returns the canonical space-separated representation stored in the database. func (p Prompt) String() string { var parts []string if p.none { return "none" } if p.login { parts = append(parts, "login") } if p.consent { parts = append(parts, "consent") } return strings.Join(parts, " ") } ================================================ FILE: server/prompt_test.go ================================================ package server import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestParsePrompt(t *testing.T) { tests := []struct { name string raw string want Prompt wantErr bool }{ {name: "empty", raw: "", want: Prompt{}}, {name: "none", raw: "none", want: Prompt{none: true}}, {name: "login", raw: "login", want: Prompt{login: true}}, {name: "consent", raw: "consent", want: Prompt{consent: true}}, {name: "login consent", raw: "login consent", want: Prompt{login: true, consent: true}}, {name: "consent login", raw: "consent login", want: Prompt{login: true, consent: true}}, {name: "select_account ignored", raw: "select_account", want: Prompt{}}, {name: "login select_account", raw: "login select_account", want: Prompt{login: true}}, {name: "duplicate values", raw: "login login", want: Prompt{login: true}}, {name: "whitespace padding", raw: " login ", want: Prompt{login: true}}, // Errors. {name: "none with login", raw: "none login", wantErr: true}, {name: "none with consent", raw: "none consent", wantErr: true}, {name: "unknown value", raw: "bogus", wantErr: true}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got, err := ParsePrompt(tc.raw) if tc.wantErr { require.Error(t, err) return } require.NoError(t, err) assert.Equal(t, tc.want, got) }) } } func TestPromptString(t *testing.T) { tests := []struct { prompt Prompt want string }{ {Prompt{}, ""}, {Prompt{none: true}, "none"}, {Prompt{login: true}, "login"}, {Prompt{consent: true}, "consent"}, {Prompt{login: true, consent: true}, "login consent"}, } for _, tc := range tests { t.Run(tc.want, func(t *testing.T) { assert.Equal(t, tc.want, tc.prompt.String()) }) } } ================================================ FILE: server/refreshhandlers.go ================================================ package server import ( "context" "errors" "fmt" "log/slog" "net/http" "strings" "time" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/server/internal" "github.com/dexidp/dex/storage" ) type RefreshTokenPolicy struct { rotateRefreshTokens bool // enable rotation absoluteLifetime time.Duration // interval from token creation to the end of its life validIfNotUsedFor time.Duration // interval from last token update to the end of its life reuseInterval time.Duration // interval within which old refresh token is allowed to be reused now func() time.Time logger *slog.Logger } func NewRefreshTokenPolicy(logger *slog.Logger, rotation bool, validIfNotUsedFor, absoluteLifetime, reuseInterval string) (*RefreshTokenPolicy, error) { r := RefreshTokenPolicy{now: time.Now, logger: logger} var err error if validIfNotUsedFor != "" { r.validIfNotUsedFor, err = time.ParseDuration(validIfNotUsedFor) if err != nil { return nil, fmt.Errorf("invalid config value %q for refresh token valid if not used for: %v", validIfNotUsedFor, err) } logger.Info("config refresh tokens", "valid_if_not_used_for", validIfNotUsedFor) } if absoluteLifetime != "" { r.absoluteLifetime, err = time.ParseDuration(absoluteLifetime) if err != nil { return nil, fmt.Errorf("invalid config value %q for refresh tokens absolute lifetime: %v", absoluteLifetime, err) } logger.Info("config refresh tokens", "absolute_lifetime", absoluteLifetime) } if reuseInterval != "" { r.reuseInterval, err = time.ParseDuration(reuseInterval) if err != nil { return nil, fmt.Errorf("invalid config value %q for refresh tokens reuse interval: %v", reuseInterval, err) } logger.Info("config refresh tokens", "reuse_interval", reuseInterval) } r.rotateRefreshTokens = !rotation logger.Info("config refresh tokens rotation", "enabled", r.rotateRefreshTokens) return &r, nil } func (r *RefreshTokenPolicy) RotationEnabled() bool { return r.rotateRefreshTokens } func (r *RefreshTokenPolicy) CompletelyExpired(lastUsed time.Time) bool { if r.absoluteLifetime == 0 { return false // expiration disabled } return r.now().After(lastUsed.Add(r.absoluteLifetime)) } func (r *RefreshTokenPolicy) ExpiredBecauseUnused(lastUsed time.Time) bool { if r.validIfNotUsedFor == 0 { return false // expiration disabled } return r.now().After(lastUsed.Add(r.validIfNotUsedFor)) } func (r *RefreshTokenPolicy) AllowedToReuse(lastUsed time.Time) bool { if r.reuseInterval == 0 { return false // expiration disabled } return !r.now().After(lastUsed.Add(r.reuseInterval)) } func contains(arr []string, item string) bool { for _, itemFromArray := range arr { if itemFromArray == item { return true } } return false } type refreshError struct { msg string code int desc string } func (r *refreshError) Error() string { return fmt.Sprintf("refresh token error: status %d, %q %s", r.code, r.msg, r.desc) } func newInternalServerError() *refreshError { return &refreshError{msg: errInvalidRequest, desc: "", code: http.StatusInternalServerError} } func newBadRequestError(desc string) *refreshError { return &refreshError{msg: errInvalidRequest, desc: desc, code: http.StatusBadRequest} } var ( invalidErr = newBadRequestError("Refresh token is invalid or has already been claimed by another client.") expiredErr = newBadRequestError("Refresh token expired.") ) func (s *Server) refreshTokenErrHelper(w http.ResponseWriter, err *refreshError) { s.tokenErrHelper(w, err.msg, err.desc, err.code) } func (s *Server) extractRefreshTokenFromRequest(r *http.Request) (*internal.RefreshToken, *refreshError) { code := r.PostFormValue("refresh_token") if code == "" { return nil, newBadRequestError("No refresh token is found in request.") } token := new(internal.RefreshToken) if err := internal.Unmarshal(code, token); err != nil { // For backward compatibility, assume the refresh_token is a raw refresh token ID // if it fails to decode. // // Because refresh_token values that aren't unmarshable were generated by servers // that don't have a Token value, we'll still reject any attempts to claim a // refresh_token twice. token = &internal.RefreshToken{RefreshId: code, Token: ""} } return token, nil } type refreshContext struct { storageToken *storage.RefreshToken requestToken *internal.RefreshToken connector Connector connectorData []byte scopes []string } // getRefreshTokenFromStorage checks that refresh token is valid and exists in the storage and gets its info func (s *Server) getRefreshTokenFromStorage(ctx context.Context, clientID *string, token *internal.RefreshToken) (*refreshContext, *refreshError) { refreshCtx := refreshContext{requestToken: token} // Get RefreshToken refresh, err := s.storage.GetRefresh(ctx, token.RefreshId) if err != nil { if err != storage.ErrNotFound { s.logger.ErrorContext(ctx, "failed to get refresh token", "err", err) return nil, newInternalServerError() } return nil, invalidErr } // Only check ClientID if it was provided; if clientID != nil && (refresh.ClientID != *clientID) { s.logger.ErrorContext(ctx, "trying to claim token for different client", "client_id", clientID, "refresh_client_id", refresh.ClientID) // According to https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 Dex should respond with an // invalid grant error if token has already been claimed by another client. return nil, &refreshError{msg: errInvalidGrant, desc: invalidErr.desc, code: http.StatusBadRequest} } if refresh.Token != token.Token { switch { case !s.refreshTokenPolicy.AllowedToReuse(refresh.LastUsed): fallthrough case refresh.ObsoleteToken != token.Token: fallthrough case refresh.ObsoleteToken == "": s.logger.ErrorContext(ctx, "refresh token claimed twice", "token_id", refresh.ID) return nil, invalidErr } } if s.refreshTokenPolicy.CompletelyExpired(refresh.CreatedAt) { s.logger.ErrorContext(ctx, "refresh token expired", "token_id", refresh.ID) return nil, expiredErr } if s.refreshTokenPolicy.ExpiredBecauseUnused(refresh.LastUsed) { s.logger.ErrorContext(ctx, "refresh token expired due to inactivity", "token_id", refresh.ID) return nil, expiredErr } refreshCtx.storageToken = &refresh // Get Connector refreshCtx.connector, err = s.getConnector(ctx, refresh.ConnectorID) if err != nil { s.logger.ErrorContext(ctx, "connector not found", "connector_id", refresh.ConnectorID, "err", err) return nil, newInternalServerError() } if !GrantTypeAllowed(refreshCtx.connector.GrantTypes, grantTypeRefreshToken) { s.logger.ErrorContext(ctx, "connector does not allow refresh token grant", "connector_id", refresh.ConnectorID) return nil, &refreshError{msg: errInvalidRequest, desc: "Connector does not support refresh tokens.", code: http.StatusBadRequest} } // Get Connector Data session, err := s.storage.GetOfflineSessions(ctx, refresh.Claims.UserID, refresh.ConnectorID) switch { case err != nil: if err != storage.ErrNotFound { s.logger.ErrorContext(ctx, "failed to get offline session", "err", err) return nil, newInternalServerError() } case len(refresh.ConnectorData) > 0: // Use the old connector data if it exists, should be deleted once used refreshCtx.connectorData = refresh.ConnectorData default: refreshCtx.connectorData = session.ConnectorData } return &refreshCtx, nil } func (s *Server) getRefreshScopes(r *http.Request, refresh *storage.RefreshToken) ([]string, *refreshError) { // Per the OAuth2 spec, if the client has omitted the scopes, default to the original // authorized scopes. // // https://tools.ietf.org/html/rfc6749#section-6 scope := r.PostFormValue("scope") if scope == "" { return refresh.Scopes, nil } requestedScopes := strings.Fields(scope) var unauthorizedScopes []string // Per the OAuth2 spec, if the client has omitted the scopes, default to the original // authorized scopes. // // https://tools.ietf.org/html/rfc6749#section-6 for _, requestScope := range requestedScopes { if !contains(refresh.Scopes, requestScope) { unauthorizedScopes = append(unauthorizedScopes, requestScope) } } if len(unauthorizedScopes) > 0 { desc := fmt.Sprintf("Requested scopes contain unauthorized scope(s): %q.", unauthorizedScopes) return nil, newBadRequestError(desc) } return requestedScopes, nil } func (s *Server) refreshWithConnector(ctx context.Context, rCtx *refreshContext, ident connector.Identity) (connector.Identity, *refreshError) { // Can the connector refresh the identity? If so, attempt to refresh the data // in the connector. // // TODO(ericchiang): We may want a strict mode where connectors that don't implement // this interface can't perform refreshing. if refreshConn, ok := rCtx.connector.Connector.(connector.RefreshConnector); ok { // Set connector data to the one received from an offline session ident.ConnectorData = rCtx.connectorData s.logger.Debug("connector data before refresh", "connector_data", ident.ConnectorData) newIdent, err := refreshConn.Refresh(ctx, parseScopes(rCtx.scopes), ident) if err != nil { s.logger.ErrorContext(ctx, "failed to refresh identity", "err", err) return ident, newInternalServerError() } return newIdent, nil } return ident, nil } // updateOfflineSession updates offline session in the storage func (s *Server) updateOfflineSession(ctx context.Context, refresh *storage.RefreshToken, ident connector.Identity, lastUsed time.Time) *refreshError { offlineSessionUpdater := func(old storage.OfflineSessions) (storage.OfflineSessions, error) { if old.Refresh[refresh.ClientID].ID != refresh.ID { return old, errors.New("refresh token invalid") } old.Refresh[refresh.ClientID].LastUsed = lastUsed if len(ident.ConnectorData) > 0 { old.ConnectorData = ident.ConnectorData } s.logger.DebugContext(ctx, "saved connector data", "user_id", ident.UserID, "connector_data", ident.ConnectorData) return old, nil } // Update LastUsed time stamp in refresh token reference object // in offline session for the user. err := s.storage.UpdateOfflineSessions(ctx, refresh.Claims.UserID, refresh.ConnectorID, offlineSessionUpdater) if err != nil { s.logger.ErrorContext(ctx, "failed to update offline session", "err", err) return newInternalServerError() } return nil } // updateRefreshToken updates refresh token and offline session in the storage func (s *Server) updateRefreshToken(ctx context.Context, rCtx *refreshContext) (*internal.RefreshToken, connector.Identity, *refreshError) { var rerr *refreshError newToken := &internal.RefreshToken{ Token: rCtx.requestToken.Token, RefreshId: rCtx.requestToken.RefreshId, } lastUsed := s.now() ident := connector.Identity{ UserID: rCtx.storageToken.Claims.UserID, Username: rCtx.storageToken.Claims.Username, PreferredUsername: rCtx.storageToken.Claims.PreferredUsername, Email: rCtx.storageToken.Claims.Email, EmailVerified: rCtx.storageToken.Claims.EmailVerified, Groups: rCtx.storageToken.Claims.Groups, } refreshTokenUpdater := func(old storage.RefreshToken) (storage.RefreshToken, error) { rotationEnabled := s.refreshTokenPolicy.RotationEnabled() reusingAllowed := s.refreshTokenPolicy.AllowedToReuse(old.LastUsed) switch { case !rotationEnabled && reusingAllowed: // If rotation is disabled and the offline session was updated not so long ago - skip further actions. old.ConnectorData = nil return old, nil case rotationEnabled && reusingAllowed: if old.Token != rCtx.requestToken.Token && old.ObsoleteToken != rCtx.requestToken.Token { return old, errors.New("refresh token claimed twice") } // Return previously generated token for all requests with an obsolete tokens if old.ObsoleteToken == rCtx.requestToken.Token { newToken.Token = old.Token } // Do not update last used time for offline session if token is allowed to be reused lastUsed = old.LastUsed old.ConnectorData = nil return old, nil case rotationEnabled && !reusingAllowed: if old.Token != rCtx.requestToken.Token { return old, errors.New("refresh token claimed twice") } // Issue new refresh token old.ObsoleteToken = old.Token newToken.Token = storage.NewID() } old.Token = newToken.Token old.LastUsed = lastUsed // ConnectorData has been moved to OfflineSession old.ConnectorData = nil // Call only once if there is a request which is not in the reuse interval. // This is required to avoid multiple calls to the external IdP for concurrent requests. // Dex will call the connector's Refresh method only once if request is not in reuse interval. ident, rerr = s.refreshWithConnector(ctx, rCtx, ident) if rerr != nil { return old, rerr } // Update the claims of the refresh token. // // UserID intentionally ignored for now. old.Claims.Username = ident.Username old.Claims.PreferredUsername = ident.PreferredUsername old.Claims.Email = ident.Email old.Claims.EmailVerified = ident.EmailVerified old.Claims.Groups = ident.Groups return old, nil } // Update refresh token in the storage. err := s.storage.UpdateRefreshToken(ctx, rCtx.storageToken.ID, refreshTokenUpdater) if err != nil { s.logger.ErrorContext(ctx, "failed to update refresh token", "err", err) return nil, ident, newInternalServerError() } rerr = s.updateOfflineSession(ctx, rCtx.storageToken, ident, lastUsed) if rerr != nil { return nil, ident, rerr } return newToken, ident, nil } // handleRefreshToken handles a refresh token request https://tools.ietf.org/html/rfc6749#section-6 // this method is the entrypoint for refresh tokens handling func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, client storage.Client) { token, rerr := s.extractRefreshTokenFromRequest(r) if rerr != nil { s.refreshTokenErrHelper(w, rerr) return } rCtx, rerr := s.getRefreshTokenFromStorage(r.Context(), &client.ID, token) if rerr != nil { s.refreshTokenErrHelper(w, rerr) return } rCtx.scopes, rerr = s.getRefreshScopes(r, rCtx.storageToken) if rerr != nil { s.refreshTokenErrHelper(w, rerr) return } newToken, ident, rerr := s.updateRefreshToken(r.Context(), rCtx) if rerr != nil { s.refreshTokenErrHelper(w, rerr) return } claims := storage.Claims{ UserID: ident.UserID, Username: ident.Username, PreferredUsername: ident.PreferredUsername, Email: ident.Email, EmailVerified: ident.EmailVerified, Groups: ident.Groups, } authTime := time.Time{} if s.sessionConfig != nil { ui, err := s.storage.GetUserIdentity(r.Context(), ident.UserID, rCtx.storageToken.ConnectorID) if err != nil { s.logger.ErrorContext(r.Context(), "failed to get user identity", "err", err) s.refreshTokenErrHelper(w, newInternalServerError()) return } authTime = ui.LastLogin } accessToken, _, err := s.newAccessToken(r.Context(), client.ID, claims, rCtx.scopes, rCtx.storageToken.Nonce, rCtx.storageToken.ConnectorID, authTime) if err != nil { s.logger.ErrorContext(r.Context(), "failed to create new access token", "err", err) s.refreshTokenErrHelper(w, newInternalServerError()) return } idToken, expiry, err := s.newIDToken(r.Context(), client.ID, claims, rCtx.scopes, rCtx.storageToken.Nonce, accessToken, "", rCtx.storageToken.ConnectorID, authTime) if err != nil { s.logger.ErrorContext(r.Context(), "failed to create ID token", "err", err) s.refreshTokenErrHelper(w, newInternalServerError()) return } rawNewToken, err := internal.Marshal(newToken) if err != nil { s.logger.ErrorContext(r.Context(), "failed to marshal refresh token", "err", err) s.refreshTokenErrHelper(w, newInternalServerError()) return } resp := s.toAccessTokenResponse(idToken, accessToken, rawNewToken, expiry) s.writeAccessToken(w, resp) } ================================================ FILE: server/refreshhandlers_test.go ================================================ package server import ( "bytes" "encoding/base64" "encoding/json" "log/slog" "net/http" "net/http/httptest" "net/url" "path" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/dexidp/dex/server/internal" "github.com/dexidp/dex/storage" ) func mockRefreshTokenTestStorage(t *testing.T, s storage.Storage, useObsolete bool) { ctx := t.Context() c := storage.Client{ ID: "test", Secret: "barfoo", RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"}, Name: "dex client", LogoURL: "https://goo.gl/JIyzIC", } err := s.CreateClient(ctx, c) require.NoError(t, err) c1 := storage.Connector{ ID: "test", Type: "mockCallback", Name: "mockCallback", Config: nil, } err = s.CreateConnector(ctx, c1) require.NoError(t, err) refresh := storage.RefreshToken{ ID: "test", Token: "bar", ObsoleteToken: "", Nonce: "foo", ClientID: "test", ConnectorID: "test", Scopes: []string{"openid", "email", "profile"}, CreatedAt: time.Now().UTC().Round(time.Millisecond), LastUsed: time.Now().UTC().Round(time.Millisecond), Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, ConnectorData: []byte(`{"some":"data"}`), } if useObsolete { refresh.Token = "testtest" refresh.ObsoleteToken = "bar" } err = s.CreateRefresh(ctx, refresh) require.NoError(t, err) offlineSessions := storage.OfflineSessions{ UserID: "1", ConnID: "test", Refresh: map[string]*storage.RefreshTokenRef{"test": {ID: "test", ClientID: "test"}}, ConnectorData: nil, } err = s.CreateOfflineSessions(ctx, offlineSessions) require.NoError(t, err) } func TestRefreshTokenExpirationScenarios(t *testing.T) { t0 := time.Now() tests := []struct { name string policy *RefreshTokenPolicy useObsolete bool error string }{ { name: "Normal", policy: &RefreshTokenPolicy{rotateRefreshTokens: true}, error: ``, }, { name: "Not expired because used", policy: &RefreshTokenPolicy{ rotateRefreshTokens: false, validIfNotUsedFor: time.Second * 60, now: func() time.Time { return t0.Add(time.Second * 25) }, }, error: ``, }, { name: "Expired because not used", policy: &RefreshTokenPolicy{ rotateRefreshTokens: false, validIfNotUsedFor: time.Second * 60, now: func() time.Time { return t0.Add(time.Hour) }, }, error: `{"error":"invalid_request","error_description":"Refresh token expired."}`, }, { name: "Absolutely expired", policy: &RefreshTokenPolicy{ rotateRefreshTokens: true, absoluteLifetime: time.Second * 60, now: func() time.Time { return t0.Add(time.Hour) }, }, error: `{"error":"invalid_request","error_description":"Refresh token expired."}`, }, { name: "Obsolete tokens are allowed", useObsolete: true, policy: &RefreshTokenPolicy{ rotateRefreshTokens: true, reuseInterval: time.Second * 30, now: func() time.Time { return t0.Add(time.Second * 25) }, }, error: ``, }, { name: "Obsolete tokens are not allowed", useObsolete: true, policy: &RefreshTokenPolicy{ rotateRefreshTokens: true, now: func() time.Time { return t0.Add(time.Second * 25) }, }, error: `{"error":"invalid_request","error_description":"Refresh token is invalid or has already been claimed by another client."}`, }, { name: "Obsolete tokens are allowed but token is expired globally", useObsolete: true, policy: &RefreshTokenPolicy{ rotateRefreshTokens: true, reuseInterval: time.Second * 30, absoluteLifetime: time.Second * 20, now: func() time.Time { return t0.Add(time.Second * 25) }, }, error: `{"error":"invalid_request","error_description":"Refresh token expired."}`, }, } for _, tc := range tests { t.Run(tc.name, func(*testing.T) { // Setup a dex server. httpServer, s := newTestServer(t, func(c *Config) { c.RefreshTokenPolicy = tc.policy c.Now = func() time.Time { return t0 } }) defer httpServer.Close() mockRefreshTokenTestStorage(t, s.storage, tc.useObsolete) u, err := url.Parse(s.issuerURL.String()) require.NoError(t, err) tokenData, err := internal.Marshal(&internal.RefreshToken{RefreshId: "test", Token: "bar"}) require.NoError(t, err) u.Path = path.Join(u.Path, "/token") v := url.Values{} v.Add("grant_type", "refresh_token") v.Add("refresh_token", tokenData) req, _ := http.NewRequest("POST", u.String(), bytes.NewBufferString(v.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.SetBasicAuth("test", "barfoo") rr := httptest.NewRecorder() s.ServeHTTP(rr, req) if tc.error == "" { require.Equal(t, 200, rr.Code) } else { require.Equal(t, rr.Body.String(), tc.error) return } // Check that we received expected refresh token var ref struct { Token string `json:"refresh_token"` } err = json.Unmarshal(rr.Body.Bytes(), &ref) require.NoError(t, err) if tc.policy.rotateRefreshTokens == false { require.Equal(t, tokenData, ref.Token) } else { require.NotEqual(t, tokenData, ref.Token) } if tc.useObsolete { updatedTokenData, err := internal.Marshal(&internal.RefreshToken{RefreshId: "test", Token: "testtest"}) require.NoError(t, err) require.Equal(t, updatedTokenData, ref.Token) } }) } } // decodeJWTClaims decodes the payload of a JWT token without verifying the signature. func decodeJWTClaims(t *testing.T, token string) map[string]any { t.Helper() parts := strings.SplitN(token, ".", 3) require.Len(t, parts, 3, "JWT should have 3 parts") payload, err := base64.RawURLEncoding.DecodeString(parts[1]) require.NoError(t, err) var claims map[string]any err = json.Unmarshal(payload, &claims) require.NoError(t, err) return claims } func TestRefreshTokenAuthTime(t *testing.T) { t0 := time.Now().UTC().Round(time.Second) loginTime := t0.Add(-10 * time.Minute) tests := []struct { name string sessionConfig *SessionConfig createUserIdentity bool wantAuthTime bool wantHTTPError bool }{ { name: "sessions enabled with user identity", sessionConfig: &SessionConfig{ CookieName: "dex_session", AbsoluteLifetime: 24 * time.Hour, }, createUserIdentity: true, wantAuthTime: true, }, { name: "sessions disabled", sessionConfig: nil, createUserIdentity: false, wantAuthTime: false, }, { name: "sessions enabled but user identity missing", sessionConfig: &SessionConfig{ CookieName: "dex_session", AbsoluteLifetime: 24 * time.Hour, }, createUserIdentity: false, wantHTTPError: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { httpServer, s := newTestServer(t, func(c *Config) { c.Now = func() time.Time { return t0 } }) defer httpServer.Close() s.sessionConfig = tc.sessionConfig mockRefreshTokenTestStorage(t, s.storage, false) if tc.createUserIdentity { // The mock connector returns UserID "0-385-28089-0" on Refresh, // so the UserIdentity must use that ID to be found by handleRefreshToken. err := s.storage.CreateUserIdentity(t.Context(), storage.UserIdentity{ UserID: "0-385-28089-0", ConnectorID: "test", Claims: storage.Claims{ UserID: "0-385-28089-0", Username: "Kilgore Trout", Email: "kilgore@kilgore.trout", EmailVerified: true, Groups: []string{"authors"}, }, CreatedAt: loginTime, LastLogin: loginTime, }) require.NoError(t, err) } u, err := url.Parse(s.issuerURL.String()) require.NoError(t, err) tokenData, err := internal.Marshal(&internal.RefreshToken{RefreshId: "test", Token: "bar"}) require.NoError(t, err) u.Path = path.Join(u.Path, "/token") v := url.Values{} v.Add("grant_type", "refresh_token") v.Add("refresh_token", tokenData) req, _ := http.NewRequest("POST", u.String(), bytes.NewBufferString(v.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.SetBasicAuth("test", "barfoo") rr := httptest.NewRecorder() s.ServeHTTP(rr, req) if tc.wantHTTPError { assert.Equal(t, http.StatusInternalServerError, rr.Code) return } require.Equal(t, http.StatusOK, rr.Code) var resp struct { AccessToken string `json:"access_token"` IDToken string `json:"id_token"` RefreshToken string `json:"refresh_token"` } err = json.Unmarshal(rr.Body.Bytes(), &resp) require.NoError(t, err) accessClaims := decodeJWTClaims(t, resp.AccessToken) if tc.wantAuthTime { assert.Equal(t, float64(loginTime.Unix()), accessClaims["auth_time"], "access token auth_time should match UserIdentity.LastLogin") } else { assert.Nil(t, accessClaims["auth_time"], "access token should not have auth_time when sessions are disabled") } // TODO: newIDToken in handleRefreshToken is currently called with time.Time{}, // so the ID token does not include auth_time. Once fixed, uncomment: // if tc.wantAuthTime { // idClaims := decodeJWTClaims(t, resp.IDToken) // assert.Equal(t, float64(loginTime.Unix()), idClaims["auth_time"], // "id token auth_time should match UserIdentity.LastLogin") // } }) } } func TestRefreshTokenPolicy(t *testing.T) { lastTime := time.Now() l := slog.New(slog.DiscardHandler) r, err := NewRefreshTokenPolicy(l, true, "1m", "1m", "1m") require.NoError(t, err) t.Run("Allowed", func(t *testing.T) { r.now = func() time.Time { return lastTime } require.Equal(t, true, r.AllowedToReuse(lastTime)) require.Equal(t, false, r.ExpiredBecauseUnused(lastTime)) require.Equal(t, false, r.CompletelyExpired(lastTime)) }) t.Run("Expired", func(t *testing.T) { r.now = func() time.Time { return lastTime.Add(2 * time.Minute) } require.Equal(t, false, r.AllowedToReuse(lastTime)) require.Equal(t, true, r.ExpiredBecauseUnused(lastTime)) require.Equal(t, true, r.CompletelyExpired(lastTime)) }) } ================================================ FILE: server/server.go ================================================ package server import ( "context" "encoding/json" "errors" "fmt" "io/fs" "log/slog" "net" "net/http" "net/netip" "net/url" "os" "path" "slices" "sort" "strings" "sync" "sync/atomic" "time" gosundheit "github.com/AppsFlyer/go-sundheit" "github.com/google/uuid" "github.com/gorilla/handlers" "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "golang.org/x/crypto/bcrypt" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/connector/atlassiancrowd" "github.com/dexidp/dex/connector/authproxy" "github.com/dexidp/dex/connector/bitbucketcloud" "github.com/dexidp/dex/connector/gitea" "github.com/dexidp/dex/connector/github" "github.com/dexidp/dex/connector/gitlab" "github.com/dexidp/dex/connector/google" "github.com/dexidp/dex/connector/keystone" "github.com/dexidp/dex/connector/ldap" "github.com/dexidp/dex/connector/linkedin" "github.com/dexidp/dex/connector/microsoft" "github.com/dexidp/dex/connector/mock" "github.com/dexidp/dex/connector/oauth" "github.com/dexidp/dex/connector/oidc" "github.com/dexidp/dex/connector/openshift" "github.com/dexidp/dex/connector/saml" "github.com/dexidp/dex/pkg/featureflags" "github.com/dexidp/dex/server/signer" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/web" ) // LocalConnector is the local passwordDB connector which is an internal // connector maintained by the server. const LocalConnector = "local" // Connector is a connector with resource version metadata. type Connector struct { Type string ResourceVersion string Connector connector.Connector GrantTypes []string } // GrantTypeAllowed checks if the given grant type is allowed for this connector. // If no grant types are configured, all are allowed. func GrantTypeAllowed(configuredTypes []string, grantType string) bool { return len(configuredTypes) == 0 || slices.Contains(configuredTypes, grantType) } // Config holds the server's configuration options. // // Multiple servers using the same storage are expected to be configured identically. type Config struct { Issuer string // The backing persistence layer. Storage storage.Storage AllowedGrantTypes []string // Valid values are "code" to enable the code flow and "token" to enable the implicit // flow. If no response types are supplied this value defaults to "code". SupportedResponseTypes []string // Headers is a map of headers to be added to the all responses. Headers http.Header // Header to extract real ip from. RealIPHeader string TrustedRealIPCIDRs []netip.Prefix // List of allowed origins for CORS requests on discovery, token and keys endpoint. // If none are indicated, CORS requests are disabled. Passing in "*" will allow any // domain. AllowedOrigins []string // List of allowed headers for CORS requests on discovery, token, and keys endpoint. AllowedHeaders []string // If enabled, the server won't prompt the user to approve authorization requests. // Logging in implies approval. SkipApprovalScreen bool // If enabled, the connectors selection page will always be shown even if there's only one AlwaysShowLoginScreen bool IDTokensValidFor time.Duration // Defaults to 24 hours AuthRequestsValidFor time.Duration // Defaults to 24 hours DeviceRequestsValidFor time.Duration // Defaults to 5 minutes // Refresh token expiration settings RefreshTokenPolicy *RefreshTokenPolicy // If set, the server will use this connector to handle password grants PasswordConnector string // PKCE configuration PKCE PKCEConfig GCFrequency time.Duration // Defaults to 5 minutes // If specified, the server will use this function for determining time. Now func() time.Time Web WebConfig Logger *slog.Logger // Signer is used to sign tokens. Signer signer.Signer PrometheusRegistry *prometheus.Registry HealthChecker gosundheit.Health // If enabled, the server will continue starting even if some connectors fail to initialize. // This allows the server to operate with a subset of connectors if some are misconfigured. ContinueOnConnectorFailure bool // SessionConfig holds session settings. Nil when sessions are disabled. SessionConfig *SessionConfig // MFAProviders maps authenticator IDs to their provider implementations. MFAProviders map[string]MFAProvider // DefaultMFAChain is applied to clients that don't specify their own mfaChain. DefaultMFAChain []string } // SessionConfig holds resolved session configuration. type SessionConfig struct { CookieName string AbsoluteLifetime time.Duration ValidIfNotUsedFor time.Duration RememberMeCheckedByDefault bool } // WebConfig holds the server's frontend templates and asset configuration. type WebConfig struct { // A file path to static web assets. // // It is expected to contain the following directories: // // * static - Static static served at "( issuer URL )/static". // * templates - HTML templates controlled by dex. // * themes/(theme) - Static static served at "( issuer URL )/theme". Dir string // Alternative way to programmatically configure static web assets. // If Dir is specified, WebFS is ignored. // It's expected to contain the same files and directories as mentioned above. // // Note: this is experimental. Might get removed without notice! WebFS fs.FS // Defaults to "( issuer URL )/theme/logo.png" LogoURL string // Defaults to "dex" Issuer string // Defaults to "light" Theme string // Map of extra values passed into the templates Extra map[string]string } // PKCEConfig holds PKCE (Proof Key for Code Exchange) settings. type PKCEConfig struct { // If true, PKCE is required for all authorization code flows. Enforce bool // Supported code challenge methods. Defaults to ["S256", "plain"]. CodeChallengeMethodsSupported []string } func value(val, defaultValue time.Duration) time.Duration { if val == 0 { return defaultValue } return val } // Server is the top level object. type Server struct { issuerURL url.URL // mutex for the connectors map. mu sync.Mutex // Map of connector IDs to connectors. connectors map[string]Connector storage storage.Storage mux http.Handler templates *templates // If enabled, don't prompt user for approval after logging in through connector. skipApproval bool // If enabled, show the connector selection screen even if there's only one alwaysShowLogin bool // Used for password grant passwordConnector string supportedResponseTypes map[string]bool supportedGrantTypes []string pkce PKCEConfig now func() time.Time idTokensValidFor time.Duration authRequestsValidFor time.Duration deviceRequestsValidFor time.Duration refreshTokenPolicy *RefreshTokenPolicy logger *slog.Logger signer signer.Signer sessionConfig *SessionConfig mfaProviders map[string]MFAProvider defaultMFAChain []string } // NewServer constructs a server from the provided config. func NewServer(ctx context.Context, c Config) (*Server, error) { return newServer(ctx, c) } func newServer(ctx context.Context, c Config) (*Server, error) { issuerURL, err := url.Parse(c.Issuer) if err != nil { return nil, fmt.Errorf("server: can't parse issuer URL") } if c.Storage == nil { return nil, errors.New("server: storage cannot be nil") } if len(c.SupportedResponseTypes) == 0 { c.SupportedResponseTypes = []string{responseTypeCode} } if len(c.AllowedHeaders) == 0 { c.AllowedHeaders = []string{"Authorization"} } supportedChallengeMethods := map[string]bool{ codeChallengeMethodS256: true, codeChallengeMethodPlain: true, } if len(c.PKCE.CodeChallengeMethodsSupported) == 0 { c.PKCE.CodeChallengeMethodsSupported = []string{codeChallengeMethodS256, codeChallengeMethodPlain} } for _, m := range c.PKCE.CodeChallengeMethodsSupported { if !supportedChallengeMethods[m] { return nil, fmt.Errorf("unsupported PKCE challenge method %q", m) } } allSupportedGrants := map[string]bool{ grantTypeAuthorizationCode: true, grantTypeRefreshToken: true, grantTypeDeviceCode: true, grantTypeTokenExchange: true, } supportedRes := make(map[string]bool) for _, respType := range c.SupportedResponseTypes { switch respType { case responseTypeCode, responseTypeIDToken, responseTypeCodeIDToken: // continue case responseTypeToken, responseTypeCodeToken, responseTypeIDTokenToken, responseTypeCodeIDTokenToken: // response_type=token is an implicit flow, let's add it to the discovery info // https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.1 allSupportedGrants[grantTypeImplicit] = true default: return nil, fmt.Errorf("unsupported response_type %q", respType) } supportedRes[respType] = true } if c.PasswordConnector != "" { allSupportedGrants[grantTypePassword] = true } allSupportedGrants[grantTypeClientCredentials] = true var supportedGrants []string if len(c.AllowedGrantTypes) > 0 { for _, grant := range c.AllowedGrantTypes { if allSupportedGrants[grant] { supportedGrants = append(supportedGrants, grant) } } } else { for grant := range allSupportedGrants { supportedGrants = append(supportedGrants, grant) } } sort.Strings(supportedGrants) webFS := web.FS() if c.Web.Dir != "" { webFS = os.DirFS(c.Web.Dir) } else if c.Web.WebFS != nil { webFS = c.Web.WebFS } web := webConfig{ webFS: webFS, logoURL: c.Web.LogoURL, issuerURL: c.Issuer, issuer: c.Web.Issuer, theme: c.Web.Theme, extra: c.Web.Extra, } static, theme, robots, tmpls, err := loadWebConfig(web) if err != nil { return nil, fmt.Errorf("server: failed to load web static: %v", err) } now := c.Now if now == nil { now = time.Now } s := &Server{ issuerURL: *issuerURL, connectors: make(map[string]Connector), storage: newKeyCacher(c.Storage, now), supportedResponseTypes: supportedRes, supportedGrantTypes: supportedGrants, pkce: c.PKCE, idTokensValidFor: value(c.IDTokensValidFor, 24*time.Hour), authRequestsValidFor: value(c.AuthRequestsValidFor, 24*time.Hour), deviceRequestsValidFor: value(c.DeviceRequestsValidFor, 5*time.Minute), refreshTokenPolicy: c.RefreshTokenPolicy, skipApproval: c.SkipApprovalScreen, alwaysShowLogin: c.AlwaysShowLoginScreen, now: now, templates: tmpls, passwordConnector: c.PasswordConnector, logger: c.Logger, signer: c.Signer, sessionConfig: c.SessionConfig, mfaProviders: c.MFAProviders, defaultMFAChain: c.DefaultMFAChain, } // Retrieves connector objects in backend storage. This list includes the static connectors // defined in the ConfigMap and dynamic connectors retrieved from the storage. storageConnectors, err := c.Storage.ListConnectors(ctx) if err != nil { return nil, fmt.Errorf("server: failed to list connector objects from storage: %v", err) } if len(storageConnectors) == 0 && len(s.connectors) == 0 { return nil, errors.New("server: no connectors specified") } var failedCount int for _, conn := range storageConnectors { if _, err := s.OpenConnector(conn); err != nil { failedCount++ if c.ContinueOnConnectorFailure { s.logger.Error("server: Failed to open connector", "id", conn.ID, "err", err) continue } return nil, fmt.Errorf("server: Failed to open connector %s: %v", conn.ID, err) } } if c.ContinueOnConnectorFailure && failedCount == len(storageConnectors) { return nil, fmt.Errorf("server: failed to open all connectors (%d/%d)", failedCount, len(storageConnectors)) } if featureflags.SessionsEnabled.Enabled() { s.logger.InfoContext(ctx, "sessions feature flag is enabled") } instrumentHandler := func(_ string, handler http.Handler) http.HandlerFunc { return handler.ServeHTTP } if c.PrometheusRegistry != nil { requestCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "http_requests_total", Help: "Count of all HTTP requests.", }, []string{"code", "method", "handler"}) durationHist := prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: "request_duration_seconds", Help: "A histogram of latencies for requests.", Buckets: []float64{.25, .5, 1, 2.5, 5, 10}, }, []string{"code", "method", "handler"}) sizeHist := prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: "response_size_bytes", Help: "A histogram of response sizes for requests.", Buckets: []float64{200, 500, 900, 1500}, }, []string{"code", "method", "handler"}) c.PrometheusRegistry.MustRegister(requestCounter, durationHist, sizeHist) instrumentHandler = func(handlerName string, handler http.Handler) http.HandlerFunc { return promhttp.InstrumentHandlerDuration(durationHist.MustCurryWith(prometheus.Labels{"handler": handlerName}), promhttp.InstrumentHandlerCounter(requestCounter.MustCurryWith(prometheus.Labels{"handler": handlerName}), promhttp.InstrumentHandlerResponseSize(sizeHist.MustCurryWith(prometheus.Labels{"handler": handlerName}), handler), ), ) } } parseRealIP := func(r *http.Request) (string, error) { remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return "", err } remoteIP, err := netip.ParseAddr(remoteAddr) if err != nil { return "", err } for _, n := range c.TrustedRealIPCIDRs { if !n.Contains(remoteIP) { return remoteAddr, nil // Fallback to the address from the request if the header is provided } } ipVal := r.Header.Get(c.RealIPHeader) if ipVal != "" { ip, err := netip.ParseAddr(ipVal) if err == nil { return ip.String(), nil } } return remoteAddr, nil } handlerWithHeaders := func(handlerName string, handler http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { for k, v := range c.Headers { w.Header()[k] = v } // Context values are used for logging purposes with the log/slog logger. rCtx := r.Context() rCtx = WithRequestID(rCtx) if c.RealIPHeader != "" { realIP, err := parseRealIP(r) if err == nil { rCtx = WithRemoteIP(rCtx, realIP) } } r = r.WithContext(rCtx) instrumentHandler(handlerName, handler)(w, r) } } r := mux.NewRouter().SkipClean(true).UseEncodedPath() handle := func(p string, h http.Handler) { r.Handle(path.Join(issuerURL.Path, p), handlerWithHeaders(p, h)) } handleFunc := func(p string, h http.HandlerFunc) { handle(p, h) } handlePrefix := func(p string, h http.Handler) { prefix := path.Join(issuerURL.Path, p) r.PathPrefix(prefix).Handler(http.StripPrefix(prefix, h)) } handleWithCORS := func(p string, h http.HandlerFunc) { var handler http.Handler = h if len(c.AllowedOrigins) > 0 { cors := handlers.CORS( handlers.AllowedOrigins(c.AllowedOrigins), handlers.AllowedHeaders(c.AllowedHeaders), ) handler = cors(handler) } r.Handle(path.Join(issuerURL.Path, p), handlerWithHeaders(p, handler)) } r.NotFoundHandler = http.NotFoundHandler() discoveryHandler, err := s.discoveryHandler(ctx) if err != nil { return nil, err } handleWithCORS("/.well-known/openid-configuration", discoveryHandler) // Handle the root path for the better user experience. handleWithCORS("/", func(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintf(w, ` Dex

Dex IdP

A Federated OpenID Connect Provider

Discovery

`, s.issuerURL.JoinPath(".well-known", "openid-configuration").String()) if err != nil { s.logger.Error("failed to write response", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Handling the / path error.") return } }) // TODO(ericchiang): rate limit certain paths based on IP. handleWithCORS("/token", s.handleToken) handleWithCORS("/keys", s.handlePublicKeys) handleWithCORS("/userinfo", s.handleUserInfo) handleWithCORS("/token/introspect", s.handleIntrospect) handleFunc("/auth", s.handleAuthorization) handleFunc("/auth/{connector}", s.handleConnectorLogin) handleFunc("/auth/{connector}/login", s.handlePasswordLogin) handleFunc("/device", s.handleDeviceExchange) handleFunc("/device/auth/verify_code", s.verifyUserCode) handleFunc("/device/code", s.handleDeviceCode) // TODO(nabokihms): "/device/token" endpoint is deprecated, consider using /token endpoint instead handleFunc("/device/token", s.handleDeviceTokenDeprecated) handleFunc(deviceCallbackURI, s.handleDeviceCallback) handleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { // Strip the X-Remote-* headers to prevent security issues on // misconfigured authproxy connector setups. for key := range r.Header { if strings.HasPrefix(strings.ToLower(key), "x-remote-") { r.Header.Del(key) } } s.handleConnectorCallback(w, r) }) // For easier connector-specific web server configuration, e.g. for the // "authproxy" connector. handleFunc("/callback/{connector}", s.handleConnectorCallback) handleFunc("/approval", s.handleApproval) handleFunc("/mfa/verify", s.handleMFAVerify) handle("/healthz", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !c.HealthChecker.IsHealthy() { s.renderError(r, w, http.StatusInternalServerError, "Health check failed.") return } fmt.Fprintf(w, "Health check passed") })) handlePrefix("/static", static) handlePrefix("/theme", theme) handleFunc("/robots.txt", robots) s.mux = r s.signer.Start(ctx) s.startGarbageCollection(ctx, value(c.GCFrequency, 5*time.Minute), now) return s, nil } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.mux.ServeHTTP(w, r) } func (s *Server) absPath(pathItems ...string) string { paths := make([]string, len(pathItems)+1) paths[0] = s.issuerURL.Path copy(paths[1:], pathItems) return path.Join(paths...) } func (s *Server) absURL(pathItems ...string) string { u := s.issuerURL u.Path = s.absPath(pathItems...) return u.String() } func newPasswordDB(s storage.Storage) interface { connector.Connector connector.PasswordConnector } { return passwordDB{s} } type passwordDB struct { s storage.Storage } func resolvePasswordName(p storage.Password) string { if p.Name != "" { return p.Name } return p.Username } func resolvePasswordEmailVerified(p storage.Password) bool { if p.EmailVerified != nil { return *p.EmailVerified } return true } func (db passwordDB) Login(ctx context.Context, s connector.Scopes, email, password string) (connector.Identity, bool, error) { p, err := db.s.GetPassword(ctx, email) if err != nil { if err != storage.ErrNotFound { return connector.Identity{}, false, fmt.Errorf("get password: %v", err) } return connector.Identity{}, false, nil } // This check prevents dex users from logging in using static passwords // configured with hash costs that are too high or low. if err := checkCost(p.Hash); err != nil { return connector.Identity{}, false, err } if err := bcrypt.CompareHashAndPassword(p.Hash, []byte(password)); err != nil { return connector.Identity{}, false, nil } return connector.Identity{ UserID: p.UserID, Username: resolvePasswordName(p), PreferredUsername: p.PreferredUsername, Email: p.Email, EmailVerified: resolvePasswordEmailVerified(p), Groups: p.Groups, }, true, nil } func (db passwordDB) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) { // If the user has been deleted, the refresh token will be rejected. p, err := db.s.GetPassword(ctx, identity.Email) if err != nil { if err == storage.ErrNotFound { return connector.Identity{}, errors.New("user not found") } return connector.Identity{}, fmt.Errorf("get password: %v", err) } // User removed but a new user with the same email exists. if p.UserID != identity.UserID { return connector.Identity{}, errors.New("user not found") } // If a user has updated their username, that will be reflected in the // refreshed token. // // No other fields are expected to be refreshable as email is effectively used // as an ID. identity.Username = resolvePasswordName(p) identity.PreferredUsername = p.PreferredUsername identity.EmailVerified = resolvePasswordEmailVerified(p) identity.Groups = p.Groups return identity, nil } func (db passwordDB) Prompt() string { return "Email Address" } // newKeyCacher returns a storage which caches keys so long as the next func newKeyCacher(s storage.Storage, now func() time.Time) storage.Storage { if now == nil { now = time.Now } return &keyCacher{Storage: s, now: now} } type keyCacher struct { storage.Storage now func() time.Time keys atomic.Value // Always holds nil or type *storage.Keys. } func (k *keyCacher) GetKeys(ctx context.Context) (storage.Keys, error) { keys, ok := k.keys.Load().(*storage.Keys) if ok && keys != nil && k.now().Before(keys.NextRotation) { return *keys, nil } storageKeys, err := k.Storage.GetKeys(ctx) if err != nil { return storageKeys, err } if k.now().Before(storageKeys.NextRotation) { k.keys.Store(&storageKeys) } return storageKeys, nil } func (s *Server) startGarbageCollection(ctx context.Context, frequency time.Duration, now func() time.Time) { go func() { for { select { case <-ctx.Done(): return case <-time.After(frequency): if r, err := s.storage.GarbageCollect(ctx, now()); err != nil { s.logger.ErrorContext(ctx, "garbage collection failed", "err", err) } else if !r.IsEmpty() { s.logger.InfoContext(ctx, "garbage collection run, delete auth", "requests", r.AuthRequests, "auth_codes", r.AuthCodes, "device_requests", r.DeviceRequests, "device_tokens", r.DeviceTokens, "auth_sessions", r.AuthSessions) } } } }() } // ConnectorConfig is a configuration that can open a connector. type ConnectorConfig interface { Open(id string, logger *slog.Logger) (connector.Connector, error) } // ConnectorsConfig variable provides an easy way to return a config struct // depending on the connector type. var ConnectorsConfig = map[string]func() ConnectorConfig{ "keystone": func() ConnectorConfig { return new(keystone.Config) }, "mockCallback": func() ConnectorConfig { return new(mock.CallbackConfig) }, "mockPassword": func() ConnectorConfig { return new(mock.PasswordConfig) }, "ldap": func() ConnectorConfig { return new(ldap.Config) }, "gitea": func() ConnectorConfig { return new(gitea.Config) }, "github": func() ConnectorConfig { return new(github.Config) }, "gitlab": func() ConnectorConfig { return new(gitlab.Config) }, "google": func() ConnectorConfig { return new(google.Config) }, "oidc": func() ConnectorConfig { return new(oidc.Config) }, "oauth": func() ConnectorConfig { return new(oauth.Config) }, "saml": func() ConnectorConfig { return new(saml.Config) }, "authproxy": func() ConnectorConfig { return new(authproxy.Config) }, "linkedin": func() ConnectorConfig { return new(linkedin.Config) }, "microsoft": func() ConnectorConfig { return new(microsoft.Config) }, "bitbucket-cloud": func() ConnectorConfig { return new(bitbucketcloud.Config) }, "openshift": func() ConnectorConfig { return new(openshift.Config) }, "atlassian-crowd": func() ConnectorConfig { return new(atlassiancrowd.Config) }, // Keep around for backwards compatibility. "samlExperimental": func() ConnectorConfig { return new(saml.Config) }, } // openConnector will parse the connector config and open the connector. func openConnector(logger *slog.Logger, conn storage.Connector) (connector.Connector, error) { var c connector.Connector f, ok := ConnectorsConfig[conn.Type] if !ok { return c, fmt.Errorf("unknown connector type %q", conn.Type) } connConfig := f() if len(conn.Config) != 0 { data := []byte(string(conn.Config)) if err := json.Unmarshal(data, connConfig); err != nil { return c, fmt.Errorf("parse connector config: %v", err) } } c, err := connConfig.Open(conn.ID, logger) if err != nil { return c, fmt.Errorf("failed to create connector %s: %v", conn.ID, err) } return c, nil } // OpenConnector updates server connector map with specified connector object. func (s *Server) OpenConnector(conn storage.Connector) (Connector, error) { var c connector.Connector if conn.Type == LocalConnector { c = newPasswordDB(s.storage) } else { var err error c, err = openConnector(s.logger, conn) if err != nil { return Connector{}, fmt.Errorf("failed to open connector: %v", err) } } connector := Connector{ Type: conn.Type, ResourceVersion: conn.ResourceVersion, Connector: c, GrantTypes: conn.GrantTypes, } s.mu.Lock() s.connectors[conn.ID] = connector s.mu.Unlock() return connector, nil } // CloseConnector removes the connector from the server's in-memory map. func (s *Server) CloseConnector(id string) { s.mu.Lock() delete(s.connectors, id) s.mu.Unlock() } // getConnector retrieves the connector object with the given id from the storage // and updates the connector list for server if necessary. func (s *Server) getConnector(ctx context.Context, id string) (Connector, error) { storageConnector, err := s.storage.GetConnector(ctx, id) if err != nil { return Connector{}, fmt.Errorf("failed to get connector object from storage: %v", err) } var conn Connector var ok bool s.mu.Lock() conn, ok = s.connectors[id] s.mu.Unlock() if !ok || storageConnector.ResourceVersion != conn.ResourceVersion { // Connector object does not exist in server connectors map or // has been updated in the storage. Need to get latest. conn, err := s.OpenConnector(storageConnector) if err != nil { return Connector{}, fmt.Errorf("failed to open connector: %v", err) } return conn, nil } return conn, nil } type logRequestKey string const ( RequestKeyRequestID logRequestKey = "request_id" RequestKeyRemoteIP logRequestKey = "client_remote_addr" ) func WithRequestID(ctx context.Context) context.Context { return context.WithValue(ctx, RequestKeyRequestID, uuid.NewString()) } func WithRemoteIP(ctx context.Context, ip string) context.Context { return context.WithValue(ctx, RequestKeyRemoteIP, ip) } ================================================ FILE: server/server_test.go ================================================ package server import ( "context" "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" "errors" "fmt" "io" "net/http" "net/http/httptest" "net/http/httputil" "net/url" "path" "reflect" "sort" "strings" "testing" "time" gosundheit "github.com/AppsFlyer/go-sundheit" "github.com/coreos/go-oidc/v3/oidc" "github.com/go-jose/go-jose/v4" "github.com/kylelemons/godebug/pretty" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" "golang.org/x/crypto/bcrypt" "golang.org/x/oauth2" "github.com/dexidp/dex/connector" "github.com/dexidp/dex/connector/mock" "github.com/dexidp/dex/server/signer" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/memory" ) func mustLoad(s string) *rsa.PrivateKey { block, _ := pem.Decode([]byte(s)) if block == nil { panic("no pem data found") } key, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { panic(err) } return key } var testKey = mustLoad(`-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEArmoiX5G36MKPiVGS1sicruEaGRrbhPbIKOf97aGGQRjXVngo Knwd2L4T9CRyABgQm3tLHHcT5crODoy46wX2g9onTZWViWWuhJ5wxXNmUbCAPWHb j9SunW53WuLYZ/IJLNZt5XYCAFPjAakWp8uMuuDwWo5EyFaw85X3FSMhVmmaYDd0 cn+1H4+NS/52wX7tWmyvGUNJ8lzjFAnnOtBJByvkyIC7HDphkLQV4j//sMNY1mPX HbsYgFv2J/LIJtkjdYO2UoDhZG3Gvj16fMy2JE2owA8IX4/s+XAmA2PiTfd0J5b4 drAKEcdDl83G6L3depEkTkfvp0ZLsh9xupAvIwIDAQABAoIBABKGgWonPyKA7+AF AxS/MC0/CZebC6/+ylnV8lm4K1tkuRKdJp8EmeL4pYPsDxPFepYZLWwzlbB1rxdK iSWld36fwEb0WXLDkxrQ/Wdrj3Wjyqs6ZqjLTVS5dAH6UEQSKDlT+U5DD4lbX6RA goCGFUeQNtdXfyTMWHU2+4yKM7NKzUpczFky+0d10Mg0ANj3/4IILdr3hqkmMSI9 1TB9ksWBXJxt3nGxAjzSFihQFUlc231cey/HhYbvAX5fN0xhLxOk88adDcdXE7br 3Ser1q6XaaFQSMj4oi1+h3RAT9MUjJ6johEqjw0PbEZtOqXvA1x5vfFdei6SqgKn Am3BspkCgYEA2lIiKEkT/Je6ZH4Omhv9atbGoBdETAstL3FnNQjkyVau9f6bxQkl 4/sz985JpaiasORQBiTGY8JDT/hXjROkut91agi2Vafhr29L/mto7KZglfDsT4b2 9z/EZH8wHw7eYhvdoBbMbqNDSI8RrGa4mpLpuN+E0wsFTzSZEL+QMQUCgYEAzIQh xnreQvDAhNradMqLmxRpayn1ORaPReD4/off+mi7hZRLKtP0iNgEVEWHJ6HEqqi1 r38XAc8ap/lfOVMar2MLyCFOhYspdHZ+TGLZfr8gg/Fzeq9IRGKYadmIKVwjMeyH REPqg1tyrvMOE0HI5oqkko8JTDJ0OyVC0Vc6+AcCgYAqCzkywugLc/jcU35iZVOH WLdFq1Vmw5w/D7rNdtoAgCYPj6nV5y4Z2o2mgl6ifXbU7BMRK9Hc8lNeOjg6HfdS WahV9DmRA1SuIWPkKjE5qczd81i+9AHpmakrpWbSBF4FTNKAewOBpwVVGuBPcDTK 59IE3V7J+cxa9YkotYuCNQKBgCwGla7AbHBEm2z+H+DcaUktD7R+B8gOTzFfyLoi Tdj+CsAquDO0BQQgXG43uWySql+CifoJhc5h4v8d853HggsXa0XdxaWB256yk2Wm MePTCRDePVm/ufLetqiyp1kf+IOaw1Oyux0j5oA62mDS3Iikd+EE4Z+BjPvefY/L E2qpAoGAZo5Wwwk7q8b1n9n/ACh4LpE+QgbFdlJxlfFLJCKstl37atzS8UewOSZj FDWV28nTP9sqbtsmU8Tem2jzMvZ7C/Q0AuDoKELFUpux8shm8wfIhyaPnXUGZoAZ Np4vUwMSYV5mopESLWOg3loBxKyLGFtgGKVCjGiQvy6zISQ4fQo= -----END RSA PRIVATE KEY-----`) func newTestServer(t *testing.T, updateConfig func(c *Config)) (*httptest.Server, *Server) { var server *Server s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { server.ServeHTTP(w, r) })) logger := newLogger(t) ctx := t.Context() sig, err := signer.NewMockSigner(testKey) if err != nil { t.Fatalf("failed to create mock signer: %v", err) } config := Config{ Issuer: s.URL, Storage: memory.New(logger), Web: WebConfig{ Dir: "../web", }, Logger: logger, PrometheusRegistry: prometheus.NewRegistry(), HealthChecker: gosundheit.New(), SkipApprovalScreen: true, // Don't prompt for approval, just immediately redirect with code. AllowedGrantTypes: []string{ // all implemented types grantTypeDeviceCode, grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypeRefreshToken, grantTypeTokenExchange, grantTypeImplicit, grantTypePassword, }, Signer: sig, } if updateConfig != nil { updateConfig(&config) } s.URL = config.Issuer connector := storage.Connector{ ID: "mock", Type: "mockCallback", Name: "Mock", ResourceVersion: "1", } if err := config.Storage.CreateConnector(ctx, connector); err != nil { t.Fatalf("create connector: %v", err) } if server, err = newServer(ctx, config); err != nil { t.Fatal(err) } // Default rotation policy if server.refreshTokenPolicy == nil { server.refreshTokenPolicy, err = NewRefreshTokenPolicy(logger, false, "", "", "") if err != nil { t.Fatalf("failed to prepare rotation policy: %v", err) } server.refreshTokenPolicy.now = config.Now } return s, server } func newTestServerMultipleConnectors(t *testing.T, updateConfig func(c *Config)) (*httptest.Server, *Server) { var server *Server s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { server.ServeHTTP(w, r) })) logger := newLogger(t) ctx := t.Context() sig, err := signer.NewMockSigner(testKey) if err != nil { t.Fatalf("failed to create mock signer: %v", err) } config := Config{ Issuer: s.URL, Storage: memory.New(logger), Web: WebConfig{ Dir: "../web", }, Logger: logger, PrometheusRegistry: prometheus.NewRegistry(), Signer: sig, } if updateConfig != nil { updateConfig(&config) } s.URL = config.Issuer connector := storage.Connector{ ID: "mock", Type: "mockCallback", Name: "Mock", ResourceVersion: "1", } connector2 := storage.Connector{ ID: "mock2", Type: "mockCallback", Name: "Mock", ResourceVersion: "1", } if err := config.Storage.CreateConnector(ctx, connector); err != nil { t.Fatalf("create connector: %v", err) } if err := config.Storage.CreateConnector(ctx, connector2); err != nil { t.Fatalf("create connector: %v", err) } if server, err = newServer(ctx, config); err != nil { t.Fatal(err) } server.skipApproval = true // Don't prompt for approval, just immediately redirect with code. return s, server } func TestNewTestServer(t *testing.T) { newTestServer(t, nil) } func TestDiscovery(t *testing.T) { httpServer, _ := newTestServer(t, func(c *Config) { c.Issuer += "/non-root-path" }) defer httpServer.Close() p, err := oidc.NewProvider(t.Context(), httpServer.URL) if err != nil { t.Fatalf("failed to get provider: %v", err) } var got map[string]*json.RawMessage if err := p.Claims(&got); err != nil { t.Fatalf("failed to decode claims: %v", err) } required := []string{ "issuer", "authorization_endpoint", "token_endpoint", "jwks_uri", "userinfo_endpoint", } for _, field := range required { if _, ok := got[field]; !ok { t.Errorf("server discovery is missing required field %q", field) } } } type oauth2Tests struct { clientID string tests []test } type test struct { name string // If specified these set of scopes will be used during the test case. scopes []string // handleToken provides the OAuth2 token response for the integration test. handleToken func(context.Context, *oidc.Provider, *oauth2.Config, *oauth2.Token, *mock.Callback) error // extra parameters to pass when requesting auth_code authCodeOptions []oauth2.AuthCodeOption // extra parameters to pass when retrieving id token retrieveTokenOptions []oauth2.AuthCodeOption // define an error response, when the test expects an error on the auth endpoint authError *OAuth2ErrorResponse // define an error response, when the test expects an error on the token endpoint tokenError ErrorResponse } // Defines an expected error by HTTP Status Code and // the OAuth2 error int the response json type ErrorResponse struct { Error string StatusCode int } // https://tools.ietf.org/html/rfc6749#section-5.2 type OAuth2ErrorResponse struct { Error string `json:"error"` ErrorDescription string `json:"error_description"` ErrorURI string `json:"error_uri"` } func makeOAuth2Tests(clientID string, clientSecret string, now func() time.Time) oauth2Tests { requestedScopes := []string{oidc.ScopeOpenID, "email", "profile", "groups", "offline_access"} // Used later when configuring test servers to set how long id_tokens will be valid for. // // The actual value of 30s is completely arbitrary. We just need to set a value // so tests can compute the expected "expires_in" field. idTokensValidFor := time.Second * 30 oidcConfig := &oidc.Config{SkipClientIDCheck: true} basicIDTokenVerify := func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error { idToken, ok := token.Extra("id_token").(string) if !ok { return fmt.Errorf("no id token found") } if _, err := p.Verifier(oidcConfig).Verify(ctx, idToken); err != nil { return fmt.Errorf("failed to verify id token: %v", err) } return nil } return oauth2Tests{ clientID: clientID, tests: []test{ { name: "verify ID Token", handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error { idToken, ok := token.Extra("id_token").(string) if !ok { return fmt.Errorf("no id token found") } if _, err := p.Verifier(oidcConfig).Verify(ctx, idToken); err != nil { return fmt.Errorf("failed to verify id token: %v", err) } return nil }, }, { name: "fetch userinfo", handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error { ui, err := p.UserInfo(ctx, config.TokenSource(ctx, token)) if err != nil { return fmt.Errorf("failed to fetch userinfo: %v", err) } if conn.Identity.Email != ui.Email { return fmt.Errorf("expected email to be %v, got %v", conn.Identity.Email, ui.Email) } return nil }, }, { name: "verify id token and oauth2 token expiry", handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error { expectedExpiry := now().Add(idTokensValidFor) timeEq := func(t1, t2 time.Time, within time.Duration) bool { return t1.Sub(t2) < within } // TODO: This is a flaky test. We need something better (eg. clockwork). if !timeEq(token.Expiry, expectedExpiry, 2*time.Second) { return fmt.Errorf("expected expired_in to be %s, got %s", expectedExpiry, token.Expiry) } rawIDToken, ok := token.Extra("id_token").(string) if !ok { return fmt.Errorf("no id token found") } idToken, err := p.Verifier(oidcConfig).Verify(ctx, rawIDToken) if err != nil { return fmt.Errorf("failed to verify id token: %v", err) } if !timeEq(idToken.Expiry, expectedExpiry, time.Second) { return fmt.Errorf("expected id token expiry to be %s, got %s", expectedExpiry, token.Expiry) } return nil }, }, { name: "verify at_hash", handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error { rawIDToken, ok := token.Extra("id_token").(string) if !ok { return fmt.Errorf("no id token found") } idToken, err := p.Verifier(oidcConfig).Verify(ctx, rawIDToken) if err != nil { return fmt.Errorf("failed to verify id token: %v", err) } var claims struct { AtHash string `json:"at_hash"` } if err := idToken.Claims(&claims); err != nil { return fmt.Errorf("failed to decode raw claims: %v", err) } if claims.AtHash == "" { return errors.New("no at_hash value in id_token") } wantAtHash, err := accessTokenHash(jose.RS256, token.AccessToken) if err != nil { return fmt.Errorf("computed expected at hash: %v", err) } if wantAtHash != claims.AtHash { return fmt.Errorf("expected at_hash=%q got=%q", wantAtHash, claims.AtHash) } return nil }, }, { name: "refresh token", handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error { // have to use time.Now because the OAuth2 package uses it. token.Expiry = time.Now().Add(time.Second * -10) if token.Valid() { return errors.New("token shouldn't be valid") } newToken, err := config.TokenSource(ctx, token).Token() if err != nil { return fmt.Errorf("failed to refresh token: %v", err) } if token.RefreshToken == newToken.RefreshToken { return fmt.Errorf("old refresh token was the same as the new token %q", token.RefreshToken) } if _, err := config.TokenSource(ctx, token).Token(); err == nil { return errors.New("was able to redeem the same refresh token twice") } return nil }, }, { name: "refresh with explicit scopes", handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error { v := url.Values{} v.Add("client_id", clientID) v.Add("client_secret", clientSecret) v.Add("grant_type", "refresh_token") v.Add("refresh_token", token.RefreshToken) v.Add("scope", strings.Join(requestedScopes, " ")) resp, err := http.PostForm(p.Endpoint().TokenURL, v) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { dump, err := httputil.DumpResponse(resp, true) if err != nil { panic(err) } return fmt.Errorf("unexpected response: %s", dump) } if resp.Header.Get("Cache-Control") != "no-store" { return fmt.Errorf("cache-control header doesn't included in token response") } if resp.Header.Get("Pragma") != "no-cache" { return fmt.Errorf("pragma header doesn't included in token response") } return nil }, }, { name: "refresh with extra spaces", handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error { v := url.Values{} v.Add("client_id", clientID) v.Add("client_secret", clientSecret) v.Add("grant_type", "refresh_token") v.Add("refresh_token", token.RefreshToken) // go-oidc adds an additional space before scopes when refreshing. // Since we support that client we choose to be more relaxed about // scope parsing, disregarding extra whitespace. v.Add("scope", " "+strings.Join(requestedScopes, " ")) resp, err := http.PostForm(p.Endpoint().TokenURL, v) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { dump, err := httputil.DumpResponse(resp, true) if err != nil { panic(err) } return fmt.Errorf("unexpected response: %s", dump) } if resp.Header.Get("Cache-Control") != "no-store" { return fmt.Errorf("cache-control header doesn't included in token response") } if resp.Header.Get("Pragma") != "no-cache" { return fmt.Errorf("pragma header doesn't included in token response") } return nil }, }, { name: "refresh with unauthorized scopes", scopes: []string{"openid", "email"}, handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error { v := url.Values{} v.Add("client_id", clientID) v.Add("client_secret", clientSecret) v.Add("grant_type", "refresh_token") v.Add("refresh_token", token.RefreshToken) // Request a scope that wasn't requested initially. v.Add("scope", "oidc email profile") resp, err := http.PostForm(p.Endpoint().TokenURL, v) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { dump, err := httputil.DumpResponse(resp, true) if err != nil { panic(err) } return fmt.Errorf("unexpected response: %s", dump) } return nil }, }, { name: "refresh with different client id", scopes: []string{"openid", "email"}, handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error { v := url.Values{} v.Add("client_id", clientID) v.Add("client_secret", clientSecret) v.Add("grant_type", "refresh_token") v.Add("refresh_token", "existedrefrestoken") v.Add("scope", "oidc email") resp, err := http.PostForm(p.Endpoint().TokenURL, v) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusBadRequest { return fmt.Errorf("expected status code %d, got %d", http.StatusBadRequest, resp.StatusCode) } var respErr struct { Error string `json:"error"` Description string `json:"error_description"` } if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil { return fmt.Errorf("cannot decode token response: %v", err) } if respErr.Error != errInvalidGrant { return fmt.Errorf("expected error %q, got %q", errInvalidGrant, respErr.Error) } expectedMsg := "Refresh token is invalid or has already been claimed by another client." if respErr.Description != expectedMsg { return fmt.Errorf("expected error description %q, got %q", expectedMsg, respErr.Description) } return nil }, }, { // This test ensures that the connector.RefreshConnector interface is being // used when clients request a refresh token. name: "refresh with identity changes", handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error { // have to use time.Now because the OAuth2 package uses it. token.Expiry = time.Now().Add(time.Second * -10) if token.Valid() { return errors.New("token shouldn't be valid") } ident := connector.Identity{ UserID: "fooid", Username: "foo", Email: "foo@bar.com", EmailVerified: true, Groups: []string{"foo", "bar"}, } conn.Identity = ident type claims struct { Username string `json:"name"` Email string `json:"email"` EmailVerified bool `json:"email_verified"` Groups []string `json:"groups"` } want := claims{ident.Username, ident.Email, ident.EmailVerified, ident.Groups} newToken, err := config.TokenSource(ctx, token).Token() if err != nil { return fmt.Errorf("failed to refresh token: %v", err) } rawIDToken, ok := newToken.Extra("id_token").(string) if !ok { return fmt.Errorf("no id_token in refreshed token") } idToken, err := p.Verifier(oidcConfig).Verify(ctx, rawIDToken) if err != nil { return fmt.Errorf("failed to verify id token: %v", err) } var got claims if err := idToken.Claims(&got); err != nil { return fmt.Errorf("failed to unmarshal claims: %v", err) } if diff := pretty.Compare(want, got); diff != "" { return fmt.Errorf("got identity != want identity: %s", diff) } return nil }, }, { name: "unsupported grant type", retrieveTokenOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("grant_type", "unsupported"), }, handleToken: basicIDTokenVerify, tokenError: ErrorResponse{ Error: errUnsupportedGrantType, StatusCode: http.StatusBadRequest, }, }, { // This test ensures that PKCE work in "plain" mode (no code_challenge_method specified) name: "PKCE with plain", authCodeOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_challenge", "challenge123"), }, retrieveTokenOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_verifier", "challenge123"), }, handleToken: basicIDTokenVerify, }, { // This test ensures that PKCE works in "S256" mode name: "PKCE with S256", authCodeOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_challenge", "lyyl-X4a69qrqgEfUL8wodWic3Be9ZZ5eovBgIKKi-w"), oauth2.SetAuthURLParam("code_challenge_method", "S256"), }, retrieveTokenOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_verifier", "challenge123"), }, handleToken: basicIDTokenVerify, }, { // This test ensures that PKCE does fail with wrong code_verifier in "plain" mode name: "PKCE with plain and wrong code_verifier", authCodeOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_challenge", "challenge123"), }, retrieveTokenOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_verifier", "challenge124"), }, handleToken: basicIDTokenVerify, tokenError: ErrorResponse{ Error: errInvalidGrant, StatusCode: http.StatusBadRequest, }, }, { // This test ensures that PKCE fail with wrong code_verifier in "S256" mode name: "PKCE with S256 and wrong code_verifier", authCodeOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_challenge", "lyyl-X4a69qrqgEfUL8wodWic3Be9ZZ5eovBgIKKi-w"), oauth2.SetAuthURLParam("code_challenge_method", "S256"), }, retrieveTokenOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_verifier", "challenge124"), }, handleToken: basicIDTokenVerify, tokenError: ErrorResponse{ Error: errInvalidGrant, StatusCode: http.StatusBadRequest, }, }, { // Ensure that, when PKCE flow started on /auth // we stay in PKCE flow on /token name: "PKCE flow expected on /token", authCodeOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_challenge", "lyyl-X4a69qrqgEfUL8wodWic3Be9ZZ5eovBgIKKi-w"), oauth2.SetAuthURLParam("code_challenge_method", "S256"), }, retrieveTokenOptions: []oauth2.AuthCodeOption{ // No PKCE call on /token }, handleToken: basicIDTokenVerify, tokenError: ErrorResponse{ Error: errInvalidGrant, StatusCode: http.StatusBadRequest, }, }, { // Ensure that when no PKCE flow was started on /auth // we cannot switch to PKCE on /token name: "No PKCE flow started on /auth", authCodeOptions: []oauth2.AuthCodeOption{ // No PKCE call on /auth }, retrieveTokenOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_verifier", "challenge123"), }, handleToken: basicIDTokenVerify, tokenError: ErrorResponse{ Error: errInvalidRequest, StatusCode: http.StatusBadRequest, }, }, { // Make sure that, when we start with "S256" on /auth, we cannot downgrade to "plain" on /token name: "PKCE with S256 and try to downgrade to plain", authCodeOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_challenge", "lyyl-X4a69qrqgEfUL8wodWic3Be9ZZ5eovBgIKKi-w"), oauth2.SetAuthURLParam("code_challenge_method", "S256"), }, retrieveTokenOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("code_verifier", "lyyl-X4a69qrqgEfUL8wodWic3Be9ZZ5eovBgIKKi-w"), oauth2.SetAuthURLParam("code_challenge_method", "plain"), }, handleToken: basicIDTokenVerify, tokenError: ErrorResponse{ Error: errInvalidGrant, StatusCode: http.StatusBadRequest, }, }, { name: "Request parameter in authorization query", authCodeOptions: []oauth2.AuthCodeOption{ oauth2.SetAuthURLParam("request", "anything"), }, authError: &OAuth2ErrorResponse{ Error: errRequestNotSupported, ErrorDescription: "Server does not support request parameter.", }, handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error { return nil }, }, }, } } // TestOAuth2CodeFlow runs integration tests against a test server. The tests stand up a server // which requires no interaction to login, logs in through a test client, then passes the client // and returned token to the test. func TestOAuth2CodeFlow(t *testing.T) { clientID := "testclient" clientSecret := "testclientsecret" requestedScopes := []string{oidc.ScopeOpenID, "email", "profile", "groups", "offline_access"} t0 := time.Now() // Always have the time function used by the server return the same time so // we can predict expected values of "expires_in" fields exactly. now := func() time.Time { return t0 } // Used later when configuring test servers to set how long id_tokens will be valid for. // // The actual value of 30s is completely arbitrary. We just need to set a value // so tests can compute the expected "expires_in" field. idTokensValidFor := time.Second * 30 // Connector used by the tests. var conn *mock.Callback tests := makeOAuth2Tests(clientID, clientSecret, now) for _, tc := range tests.tests { t.Run(tc.name, func(t *testing.T) { ctx := t.Context() // Setup a dex server. httpServer, s := newTestServer(t, func(c *Config) { c.Issuer += "/non-root-path" c.Now = now c.IDTokensValidFor = idTokensValidFor }) defer httpServer.Close() mockConn := s.connectors["mock"] conn = mockConn.Connector.(*mock.Callback) // Query server's provider metadata. p, err := oidc.NewProvider(ctx, httpServer.URL) if err != nil { t.Fatalf("failed to get provider: %v", err) } var ( // If the OAuth2 client didn't get a response, we need // to print the requests the user saw. gotCode bool reqDump, respDump []byte // Auth step, not token. state = "a_state" ) defer func() { if !gotCode && tc.authError == nil { t.Errorf("never got a code in callback\n%s\n%s", reqDump, respDump) } }() // Setup OAuth2 client. var oauth2Config *oauth2.Config oauth2Client := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/callback" { // User is visiting app first time. Redirect to dex. http.Redirect(w, r, oauth2Config.AuthCodeURL(state, tc.authCodeOptions...), http.StatusSeeOther) return } // User is at '/callback' so they were just redirected _from_ dex. q := r.URL.Query() // Did dex return an error? if errType := q.Get("error"); errType != "" { description := q.Get("error_description") if tc.authError == nil { if description != "" { t.Errorf("got error from server %s: %s", errType, description) } else { t.Errorf("got error from server %s", errType) } w.WriteHeader(http.StatusInternalServerError) return } require.Equal(t, *tc.authError, OAuth2ErrorResponse{Error: errType, ErrorDescription: description}) return } // Grab code, exchange for token. if code := q.Get("code"); code != "" { gotCode = true token, err := oauth2Config.Exchange(ctx, code, tc.retrieveTokenOptions...) if tc.tokenError.StatusCode != 0 { checkErrorResponse(err, t, tc) return } if err != nil { t.Errorf("failed to exchange code for token: %v", err) return } err = tc.handleToken(ctx, p, oauth2Config, token, conn) if err != nil { t.Errorf("%s: %v", tc.name, err) } return } // Ensure state matches. if gotState := q.Get("state"); gotState != state { t.Errorf("state did not match, want=%q got=%q", state, gotState) } w.WriteHeader(http.StatusOK) })) defer oauth2Client.Close() // Register the client above with dex. redirectURL := oauth2Client.URL + "/callback" client := storage.Client{ ID: clientID, Secret: clientSecret, RedirectURIs: []string{redirectURL}, } if err := s.storage.CreateClient(ctx, client); err != nil { t.Fatalf("failed to create client: %v", err) } if err := s.storage.CreateRefresh(ctx, storage.RefreshToken{ ID: "existedrefrestoken", ClientID: "unexcistedclientid", }); err != nil { t.Fatalf("failed to create existed refresh token: %v", err) } // Create the OAuth2 config. oauth2Config = &oauth2.Config{ ClientID: client.ID, ClientSecret: client.Secret, Endpoint: p.Endpoint(), Scopes: requestedScopes, RedirectURL: redirectURL, } if len(tc.scopes) != 0 { oauth2Config.Scopes = tc.scopes } // Login! // // 1. First request to client, redirects to dex. // 2. Dex "logs in" the user, redirects to client with "code". // 3. Client exchanges "code" for "token" (id_token, refresh_token, etc.). // 4. Test is run with OAuth2 token response. // resp, err := http.Get(oauth2Client.URL + "/login") if err != nil { t.Fatalf("get failed: %v", err) } defer resp.Body.Close() if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil { t.Fatal(err) } if respDump, err = httputil.DumpResponse(resp, true); err != nil { t.Fatal(err) } tokens, err := s.storage.ListRefreshTokens(ctx) if err != nil { t.Fatalf("failed to get existed refresh token: %v", err) } for _, token := range tokens { if /* token was updated */ token.ObsoleteToken != "" && token.ConnectorData != nil { t.Fatalf("token connectorData with id %q field is not nil: %s", token.ID, token.ConnectorData) } } }) } } func TestOAuth2ImplicitFlow(t *testing.T) { ctx := t.Context() httpServer, s := newTestServer(t, func(c *Config) { // Enable support for the implicit flow. c.SupportedResponseTypes = []string{"code", "token", "id_token"} }) defer httpServer.Close() p, err := oidc.NewProvider(ctx, httpServer.URL) if err != nil { t.Fatalf("failed to get provider: %v", err) } var ( reqDump, respDump []byte gotIDToken bool state = "a_state" nonce = "a_nonce" ) defer func() { if !gotIDToken { t.Errorf("never got a id token in fragment\n%s\n%s", reqDump, respDump) } }() var oauth2Config *oauth2.Config oauth2Server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/callback" { q := r.URL.Query() if errType := q.Get("error"); errType != "" { if desc := q.Get("error_description"); desc != "" { t.Errorf("got error from server %s: %s", errType, desc) } else { t.Errorf("got error from server %s", errType) } w.WriteHeader(http.StatusInternalServerError) return } // Fragment is checked by the client since net/http servers don't preserve URL fragments. // E.g. // // r.URL.Fragment // // Will always be empty. w.WriteHeader(http.StatusOK) return } u := oauth2Config.AuthCodeURL(state, oauth2.SetAuthURLParam("response_type", "id_token token"), oidc.Nonce(nonce)) http.Redirect(w, r, u, http.StatusSeeOther) })) defer oauth2Server.Close() redirectURL := oauth2Server.URL + "/callback" client := storage.Client{ ID: "testclient", Secret: "testclientsecret", RedirectURIs: []string{redirectURL}, } if err := s.storage.CreateClient(ctx, client); err != nil { t.Fatalf("failed to create client: %v", err) } idTokenVerifier := p.Verifier(&oidc.Config{ ClientID: client.ID, }) oauth2Config = &oauth2.Config{ ClientID: client.ID, ClientSecret: client.Secret, Endpoint: p.Endpoint(), Scopes: []string{oidc.ScopeOpenID, "profile", "email", "offline_access"}, RedirectURL: redirectURL, } checkIDToken := func(u *url.URL) error { if u.Fragment == "" { return fmt.Errorf("url has no fragment: %s", u) } v, err := url.ParseQuery(u.Fragment) if err != nil { return fmt.Errorf("failed to parse fragment: %v", err) } rawIDToken := v.Get("id_token") if rawIDToken == "" { return errors.New("no id_token in fragment") } idToken, err := idTokenVerifier.Verify(ctx, rawIDToken) if err != nil { return fmt.Errorf("failed to verify id_token: %v", err) } if idToken.Nonce != nonce { return fmt.Errorf("failed to verify id_token: nonce was %v, but want %v", idToken.Nonce, nonce) } return nil } httpClient := &http.Client{ // net/http servers don't preserve URL fragments when passing the request to // handlers. The only way to get at that values is to check the redirect on // the client side. CheckRedirect: func(req *http.Request, via []*http.Request) error { if len(via) > 10 { return errors.New("too many redirects") } // If we're being redirected back to the client server, inspect the URL fragment // for an ID Token. u := req.URL.String() if strings.HasPrefix(u, oauth2Server.URL) { if err := checkIDToken(req.URL); err == nil { gotIDToken = true } else { t.Error(err) } } return nil }, } resp, err := httpClient.Get(oauth2Server.URL + "/login") if err != nil { t.Fatalf("get failed: %v", err) } defer resp.Body.Close() if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil { t.Fatal(err) } if respDump, err = httputil.DumpResponse(resp, true); err != nil { t.Fatal(err) } } func TestCrossClientScopes(t *testing.T) { ctx := t.Context() httpServer, s := newTestServer(t, func(c *Config) { c.Issuer += "/non-root-path" }) defer httpServer.Close() p, err := oidc.NewProvider(ctx, httpServer.URL) if err != nil { t.Fatalf("failed to get provider: %v", err) } var ( reqDump, respDump []byte gotCode bool state = "a_state" ) defer func() { if !gotCode { t.Errorf("never got a code in callback\n%s\n%s", reqDump, respDump) } }() testClientID := "testclient" peerID := "peer" var oauth2Config *oauth2.Config oauth2Server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/callback" { q := r.URL.Query() if errType := q.Get("error"); errType != "" { if desc := q.Get("error_description"); desc != "" { t.Errorf("got error from server %s: %s", errType, desc) } else { t.Errorf("got error from server %s", errType) } w.WriteHeader(http.StatusInternalServerError) return } if code := q.Get("code"); code != "" { gotCode = true token, err := oauth2Config.Exchange(ctx, code) if err != nil { t.Errorf("failed to exchange code for token: %v", err) return } rawIDToken, ok := token.Extra("id_token").(string) if !ok { t.Errorf("no id token found: %v", err) return } idToken, err := p.Verifier(&oidc.Config{ClientID: testClientID}).Verify(ctx, rawIDToken) if err != nil { t.Errorf("failed to parse ID Token: %v", err) return } sort.Strings(idToken.Audience) expAudience := []string{peerID, testClientID} if !reflect.DeepEqual(idToken.Audience, expAudience) { t.Errorf("expected audience %q, got %q", expAudience, idToken.Audience) } } if gotState := q.Get("state"); gotState != state { t.Errorf("state did not match, want=%q got=%q", state, gotState) } w.WriteHeader(http.StatusOK) return } http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusSeeOther) })) defer oauth2Server.Close() redirectURL := oauth2Server.URL + "/callback" client := storage.Client{ ID: testClientID, Secret: "testclientsecret", RedirectURIs: []string{redirectURL}, } if err := s.storage.CreateClient(ctx, client); err != nil { t.Fatalf("failed to create client: %v", err) } peer := storage.Client{ ID: peerID, Secret: "foobar", TrustedPeers: []string{"testclient"}, } if err := s.storage.CreateClient(ctx, peer); err != nil { t.Fatalf("failed to create client: %v", err) } oauth2Config = &oauth2.Config{ ClientID: client.ID, ClientSecret: client.Secret, Endpoint: p.Endpoint(), Scopes: []string{ oidc.ScopeOpenID, "profile", "email", "audience:server:client_id:" + client.ID, "audience:server:client_id:" + peer.ID, }, RedirectURL: redirectURL, } resp, err := http.Get(oauth2Server.URL + "/login") if err != nil { t.Fatalf("get failed: %v", err) } defer resp.Body.Close() if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil { t.Fatal(err) } if respDump, err = httputil.DumpResponse(resp, true); err != nil { t.Fatal(err) } } func TestCrossClientScopesWithAzpInAudienceByDefault(t *testing.T) { ctx := t.Context() httpServer, s := newTestServer(t, func(c *Config) { c.Issuer += "/non-root-path" }) defer httpServer.Close() p, err := oidc.NewProvider(ctx, httpServer.URL) if err != nil { t.Fatalf("failed to get provider: %v", err) } var ( reqDump, respDump []byte gotCode bool state = "a_state" ) defer func() { if !gotCode { t.Errorf("never got a code in callback\n%s\n%s", reqDump, respDump) } }() testClientID := "testclient" peerID := "peer" var oauth2Config *oauth2.Config oauth2Server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/callback" { q := r.URL.Query() if errType := q.Get("error"); errType != "" { if desc := q.Get("error_description"); desc != "" { t.Errorf("got error from server %s: %s", errType, desc) } else { t.Errorf("got error from server %s", errType) } w.WriteHeader(http.StatusInternalServerError) return } if code := q.Get("code"); code != "" { gotCode = true token, err := oauth2Config.Exchange(ctx, code) if err != nil { t.Errorf("failed to exchange code for token: %v", err) return } rawIDToken, ok := token.Extra("id_token").(string) if !ok { t.Errorf("no id token found: %v", err) return } idToken, err := p.Verifier(&oidc.Config{ClientID: testClientID}).Verify(ctx, rawIDToken) if err != nil { t.Errorf("failed to parse ID Token: %v", err) return } sort.Strings(idToken.Audience) expAudience := []string{peerID, testClientID} if !reflect.DeepEqual(idToken.Audience, expAudience) { t.Errorf("expected audience %q, got %q", expAudience, idToken.Audience) } } if gotState := q.Get("state"); gotState != state { t.Errorf("state did not match, want=%q got=%q", state, gotState) } w.WriteHeader(http.StatusOK) return } http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusSeeOther) })) defer oauth2Server.Close() redirectURL := oauth2Server.URL + "/callback" client := storage.Client{ ID: testClientID, Secret: "testclientsecret", RedirectURIs: []string{redirectURL}, } if err := s.storage.CreateClient(ctx, client); err != nil { t.Fatalf("failed to create client: %v", err) } peer := storage.Client{ ID: peerID, Secret: "foobar", TrustedPeers: []string{"testclient"}, } if err := s.storage.CreateClient(ctx, peer); err != nil { t.Fatalf("failed to create client: %v", err) } oauth2Config = &oauth2.Config{ ClientID: client.ID, ClientSecret: client.Secret, Endpoint: p.Endpoint(), Scopes: []string{ oidc.ScopeOpenID, "profile", "email", "audience:server:client_id:" + peer.ID, }, RedirectURL: redirectURL, } resp, err := http.Get(oauth2Server.URL + "/login") if err != nil { t.Fatalf("get failed: %v", err) } defer resp.Body.Close() if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil { t.Fatal(err) } if respDump, err = httputil.DumpResponse(resp, true); err != nil { t.Fatal(err) } } func TestPasswordDB(t *testing.T) { ctx := t.Context() logger := newLogger(t) s := memory.New(logger) conn := newPasswordDB(s) pw := "hi" h, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost) if err != nil { t.Fatal(err) } s.CreatePassword(ctx, storage.Password{ Email: "jane@example.com", Username: "jane", Name: "Jane Doe", PreferredUsername: "jane-public", EmailVerified: boolPtr(false), UserID: "foobar", Groups: []string{"team-a", "team-a/admins"}, Hash: h, }) tests := []struct { name string username string password string wantIdentity connector.Identity wantInvalid bool wantErr bool }{ { name: "valid password", username: "jane@example.com", password: pw, wantIdentity: connector.Identity{ Email: "jane@example.com", Username: "Jane Doe", PreferredUsername: "jane-public", UserID: "foobar", EmailVerified: false, Groups: []string{"team-a", "team-a/admins"}, }, }, { name: "unknown user", username: "john@example.com", password: pw, wantInvalid: true, }, { name: "invalid password", username: "jane@example.com", password: "not the correct password", wantInvalid: true, }, } for _, tc := range tests { ident, valid, err := conn.Login(t.Context(), connector.Scopes{}, tc.username, tc.password) if err != nil { if !tc.wantErr { t.Errorf("%s: %v", tc.name, err) } continue } if tc.wantErr { t.Errorf("%s: expected error", tc.name) continue } if !valid { if !tc.wantInvalid { t.Errorf("%s: expected valid password", tc.name) } continue } if tc.wantInvalid { t.Errorf("%s: expected invalid password", tc.name) continue } if diff := pretty.Compare(tc.wantIdentity, ident); diff != "" { t.Errorf("%s: %s", tc.name, diff) } } } func TestPasswordDBUsernamePrompt(t *testing.T) { logger := newLogger(t) s := memory.New(logger) conn := newPasswordDB(s) expected := "Email Address" if actual := conn.Prompt(); actual != expected { t.Errorf("expected %v, got %v", expected, actual) } } type storageWithKeysTrigger struct { storage.Storage f func() } func (s storageWithKeysTrigger) GetKeys(ctx context.Context) (storage.Keys, error) { s.f() return s.Storage.GetKeys(ctx) } func TestKeyCacher(t *testing.T) { tNow := time.Now() now := func() time.Time { return tNow } ctx := t.Context() logger := newLogger(t) s := memory.New(logger) tests := []struct { before func() wantCallToStorage bool }{ { before: func() {}, wantCallToStorage: true, }, { before: func() { s.UpdateKeys(ctx, func(old storage.Keys) (storage.Keys, error) { old.NextRotation = tNow.Add(time.Minute) return old, nil }) }, wantCallToStorage: true, }, { before: func() {}, wantCallToStorage: false, }, { before: func() { tNow = tNow.Add(time.Hour) }, wantCallToStorage: true, }, { before: func() { tNow = tNow.Add(time.Hour) s.UpdateKeys(ctx, func(old storage.Keys) (storage.Keys, error) { old.NextRotation = tNow.Add(time.Minute) return old, nil }) }, wantCallToStorage: true, }, { before: func() {}, wantCallToStorage: false, }, } gotCall := false s = newKeyCacher(storageWithKeysTrigger{s, func() { gotCall = true }}, now) for i, tc := range tests { gotCall = false tc.before() s.GetKeys(t.Context()) if gotCall != tc.wantCallToStorage { t.Errorf("case %d: expected call to storage=%t got call to storage=%t", i, tc.wantCallToStorage, gotCall) } } } func checkErrorResponse(err error, t *testing.T, tc test) { if err == nil { t.Errorf("%s: DANGEROUS! got a token when we should not get one!", tc.name) return } if rErr, ok := err.(*oauth2.RetrieveError); ok { if rErr.Response.StatusCode != tc.tokenError.StatusCode { t.Errorf("%s: got wrong StatusCode from server %d. expected %d", tc.name, rErr.Response.StatusCode, tc.tokenError.StatusCode) } details := new(OAuth2ErrorResponse) if err := json.Unmarshal(rErr.Body, details); err != nil { t.Errorf("%s: could not parse return json: %s", tc.name, err) return } if tc.tokenError.Error != "" && details.Error != tc.tokenError.Error { t.Errorf("%s: got wrong Error in response: %s (%s). expected %s", tc.name, details.Error, details.ErrorDescription, tc.tokenError.Error) } } else { t.Errorf("%s: unexpected error type: %s. expected *oauth2.RetrieveError", tc.name, reflect.TypeOf(err)) } } type oauth2Client struct { config *oauth2.Config token *oauth2.Token server *httptest.Server } // TestRefreshTokenFlow tests the refresh token code flow for oauth2. The test verifies // that only valid refresh tokens can be used to refresh an expired token. func TestRefreshTokenFlow(t *testing.T) { state := "state" now := time.Now ctx := t.Context() httpServer, s := newTestServer(t, func(c *Config) { c.Now = now }) defer httpServer.Close() p, err := oidc.NewProvider(ctx, httpServer.URL) if err != nil { t.Fatalf("failed to get provider: %v", err) } var oauth2Client oauth2Client oauth2Client.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/callback" { // User is visiting app first time. Redirect to dex. http.Redirect(w, r, oauth2Client.config.AuthCodeURL(state), http.StatusSeeOther) return } // User is at '/callback' so they were just redirected _from_ dex. q := r.URL.Query() if errType := q.Get("error"); errType != "" { if desc := q.Get("error_description"); desc != "" { t.Errorf("got error from server %s: %s", errType, desc) } else { t.Errorf("got error from server %s", errType) } w.WriteHeader(http.StatusInternalServerError) return } // Grab code, exchange for token. if code := q.Get("code"); code != "" { token, err := oauth2Client.config.Exchange(ctx, code) if err != nil { t.Errorf("failed to exchange code for token: %v", err) return } oauth2Client.token = token } // Ensure state matches. if gotState := q.Get("state"); gotState != state { t.Errorf("state did not match, want=%q got=%q", state, gotState) } w.WriteHeader(http.StatusOK) })) defer oauth2Client.server.Close() // Register the client above with dex. redirectURL := oauth2Client.server.URL + "/callback" client := storage.Client{ ID: "testclient", Secret: "testclientsecret", RedirectURIs: []string{redirectURL}, } if err := s.storage.CreateClient(ctx, client); err != nil { t.Fatalf("failed to create client: %v", err) } oauth2Client.config = &oauth2.Config{ ClientID: client.ID, ClientSecret: client.Secret, Endpoint: p.Endpoint(), Scopes: []string{oidc.ScopeOpenID, "email", "offline_access"}, RedirectURL: redirectURL, } resp, err := http.Get(oauth2Client.server.URL + "/login") if err != nil { t.Fatalf("get failed: %v", err) } defer resp.Body.Close() tok := &oauth2.Token{ RefreshToken: oauth2Client.token.RefreshToken, Expiry: time.Now().Add(-time.Hour), } // Login in again to receive a new token. resp, err = http.Get(oauth2Client.server.URL + "/login") if err != nil { t.Fatalf("get failed: %v", err) } defer resp.Body.Close() // try to refresh expired token with old refresh token. if _, err := oauth2Client.config.TokenSource(ctx, tok).Token(); err == nil { t.Errorf("Token refreshed with invalid refresh token, error expected.") } } // TestOAuth2DeviceFlow runs device flow integration tests against a test server func TestOAuth2DeviceFlow(t *testing.T) { clientID := "testclient" clientSecret := "" requestedScopes := []string{oidc.ScopeOpenID, "email", "profile", "groups", "offline_access"} t0 := time.Now() // Always have the time function used by the server return the same time so // we can predict expected values of "expires_in" fields exactly. now := func() time.Time { return t0 } // Connector used by the tests. var conn *mock.Callback idTokensValidFor := time.Second * 30 tests := makeOAuth2Tests(clientID, clientSecret, now) testCases := []struct { name string tokenEndpoint string oauth2Tests oauth2Tests }{ { name: "Actual token endpoint for devices", tokenEndpoint: "/token", oauth2Tests: tests, }, // TODO(nabokihms): delete temporary tests after removing the deprecated token endpoint support { name: "Deprecated token endpoint for devices", tokenEndpoint: "/device/token", oauth2Tests: tests, }, } for _, testCase := range testCases { for _, tc := range testCase.oauth2Tests.tests { t.Run(tc.name, func(t *testing.T) { ctx := t.Context() // Setup a dex server. httpServer, s := newTestServer(t, func(c *Config) { c.Issuer += "/non-root-path" c.Now = now c.IDTokensValidFor = idTokensValidFor }) defer httpServer.Close() mockConn := s.connectors["mock"] conn = mockConn.Connector.(*mock.Callback) p, err := oidc.NewProvider(ctx, httpServer.URL) if err != nil { t.Fatalf("failed to get provider: %v", err) } // Add the Clients to the test server client := storage.Client{ ID: clientID, RedirectURIs: []string{s.absPath(deviceCallbackURI)}, Public: true, } if err := s.storage.CreateClient(ctx, client); err != nil { t.Fatalf("failed to create client: %v", err) } if err := s.storage.CreateRefresh(ctx, storage.RefreshToken{ ID: "existedrefrestoken", ClientID: "unexcistedclientid", }); err != nil { t.Fatalf("failed to create existed refresh token: %v", err) } // Grab the issuer that we'll reuse for the different endpoints to hit issuer, err := url.Parse(s.issuerURL.String()) if err != nil { t.Errorf("Could not parse issuer URL %v", err) } // Send a new Device Request codeURL, _ := url.Parse(issuer.String()) codeURL.Path = path.Join(codeURL.Path, "device/code") data := url.Values{} data.Set("client_id", clientID) data.Add("scope", strings.Join(requestedScopes, " ")) resp, err := http.PostForm(codeURL.String(), data) if err != nil { t.Errorf("Could not request device code: %v", err) } defer resp.Body.Close() responseBody, err := io.ReadAll(resp.Body) if err != nil { t.Errorf("Could read device code response %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("%v - Unexpected Response Type. Expected 200 got %v. Response: %v", tc.name, resp.StatusCode, string(responseBody)) } if resp.Header.Get("Cache-Control") != "no-store" { t.Errorf("Cache-Control header doesn't exist in Device Code Response") } // Parse the code response var deviceCode deviceCodeResponse if err := json.Unmarshal(responseBody, &deviceCode); err != nil { t.Errorf("Unexpected Device Code Response Format %v", string(responseBody)) } // Mock the user hitting the verification URI and posting the form verifyURL, _ := url.Parse(issuer.String()) verifyURL.Path = path.Join(verifyURL.Path, "/device/auth/verify_code") urlData := url.Values{} urlData.Set("user_code", deviceCode.UserCode) resp, err = http.PostForm(verifyURL.String(), urlData) if err != nil { t.Errorf("Error Posting Form: %v", err) } defer resp.Body.Close() responseBody, err = io.ReadAll(resp.Body) if err != nil { t.Errorf("Could read verification response %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("%v - Unexpected Response Type. Expected 200 got %v. Response: %v", tc.name, resp.StatusCode, string(responseBody)) } // Hit the Token Endpoint, and try and get an access token tokenURL, _ := url.Parse(issuer.String()) tokenURL.Path = path.Join(tokenURL.Path, testCase.tokenEndpoint) v := url.Values{} v.Add("grant_type", grantTypeDeviceCode) v.Add("device_code", deviceCode.DeviceCode) resp, err = http.PostForm(tokenURL.String(), v) if err != nil { t.Errorf("Could not request device token: %v", err) } defer resp.Body.Close() responseBody, err = io.ReadAll(resp.Body) if err != nil { t.Errorf("Could read device token response %v", err) } if resp.StatusCode != http.StatusOK { t.Errorf("%v - Unexpected Token Response Type. Expected 200 got %v. Response: %v", tc.name, resp.StatusCode, string(responseBody)) } // Parse the response var tokenRes accessTokenResponse if err := json.Unmarshal(responseBody, &tokenRes); err != nil { t.Errorf("Unexpected Device Access Token Response Format %v", string(responseBody)) } token := &oauth2.Token{ AccessToken: tokenRes.AccessToken, TokenType: tokenRes.TokenType, RefreshToken: tokenRes.RefreshToken, } raw := make(map[string]interface{}) json.Unmarshal(responseBody, &raw) // no error checks for optional fields token = token.WithExtra(raw) if secs := tokenRes.ExpiresIn; secs > 0 { token.Expiry = time.Now().Add(time.Duration(secs) * time.Second) } // Run token tests to validate info is correct // Create the OAuth2 config. oauth2Config := &oauth2.Config{ ClientID: client.ID, ClientSecret: client.Secret, Endpoint: p.Endpoint(), Scopes: requestedScopes, RedirectURL: s.absURL(deviceCallbackURI), } if len(tc.scopes) != 0 { oauth2Config.Scopes = tc.scopes } err = tc.handleToken(ctx, p, oauth2Config, token, conn) if err != nil { t.Errorf("%s: %v", tc.name, err) } }) } } } func TestServerSupportedGrants(t *testing.T) { tests := []struct { name string config func(c *Config) resGrants []string }{ { name: "Simple", config: func(c *Config) {}, resGrants: []string{grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, }, { name: "Minimal", config: func(c *Config) { c.AllowedGrantTypes = []string{grantTypeTokenExchange} }, resGrants: []string{grantTypeTokenExchange}, }, { name: "With password connector", config: func(c *Config) { c.PasswordConnector = "local" }, resGrants: []string{grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypePassword, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, }, { name: "Without client credentials", config: func(c *Config) { c.AllowedGrantTypes = []string{ grantTypeAuthorizationCode, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange, } }, resGrants: []string{grantTypeAuthorizationCode, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, }, { name: "With token response", config: func(c *Config) { c.SupportedResponseTypes = append(c.SupportedResponseTypes, responseTypeToken) }, resGrants: []string{grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypeImplicit, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, }, { name: "All", config: func(c *Config) { c.PasswordConnector = "local" c.SupportedResponseTypes = append(c.SupportedResponseTypes, responseTypeToken) }, resGrants: []string{grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypeImplicit, grantTypePassword, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { _, srv := newTestServer(t, tc.config) require.Equal(t, tc.resGrants, srv.supportedGrantTypes) }) } } func TestHeaders(t *testing.T) { ctx := t.Context() httpServer, _ := newTestServer(t, func(c *Config) { c.Headers = map[string][]string{ "Strict-Transport-Security": {"max-age=31536000; includeSubDomains"}, } }) defer httpServer.Close() p, err := oidc.NewProvider(ctx, httpServer.URL) if err != nil { t.Fatalf("failed to get provider: %v", err) } resp, err := http.Get(p.Endpoint().TokenURL) require.NoError(t, err) require.Equal(t, "max-age=31536000; includeSubDomains", resp.Header.Get("Strict-Transport-Security")) } func TestConnectorFailureHandling(t *testing.T) { ctx := t.Context() tests := []struct { name string connectors []storage.Connector continueOnConnectorFailure bool wantErr bool wantErrContains string expectConnectors []string // IDs of connectors that should be loaded successfully }{ { name: "all connectors succeed with flag enabled", connectors: []storage.Connector{ { ID: "mock1", Type: "mockCallback", Name: "Mock1", }, { ID: "mock2", Type: "mockCallback", Name: "Mock2", }, }, continueOnConnectorFailure: true, wantErr: false, expectConnectors: []string{"mock1", "mock2"}, }, { name: "all connectors succeed with flag disabled", connectors: []storage.Connector{ { ID: "mock1", Type: "mockCallback", Name: "Mock1", }, { ID: "mock2", Type: "mockCallback", Name: "Mock2", }, }, continueOnConnectorFailure: false, wantErr: false, expectConnectors: []string{"mock1", "mock2"}, }, { name: "partial connector failure with flag enabled", connectors: []storage.Connector{ { ID: "mock-good", Type: "mockCallback", Name: "Good Mock", }, { ID: "bad-connector", Type: "nonexistent", Name: "Bad Connector", }, { ID: "mock-good2", Type: "mockCallback", Name: "Good Mock 2", }, }, continueOnConnectorFailure: true, wantErr: false, expectConnectors: []string{"mock-good", "mock-good2"}, }, { name: "partial connector failure with flag disabled", connectors: []storage.Connector{ { ID: "mock-good", Type: "mockCallback", Name: "Good Mock", }, { ID: "bad-connector", Type: "nonexistent", Name: "Bad Connector", }, { ID: "mock-good2", Type: "mockCallback", Name: "Good Mock 2", }, }, continueOnConnectorFailure: false, wantErr: true, wantErrContains: "Failed to open connector bad-connector", expectConnectors: []string{}, // Server creation should fail }, { name: "all connectors fail with flag enabled", connectors: []storage.Connector{ { ID: "bad1", Type: "nonexistent1", Name: "Bad 1", }, { ID: "bad2", Type: "nonexistent2", Name: "Bad 2", }, }, continueOnConnectorFailure: true, wantErr: true, wantErrContains: "failed to open all connectors (2/2)", }, { name: "all connectors fail with flag disabled", connectors: []storage.Connector{ { ID: "bad1", Type: "nonexistent1", Name: "Bad 1", }, { ID: "bad2", Type: "nonexistent2", Name: "Bad 2", }, }, continueOnConnectorFailure: false, wantErr: true, wantErrContains: "Failed to open connector", }, { name: "no connectors", connectors: []storage.Connector{}, continueOnConnectorFailure: true, wantErr: true, wantErrContains: "no connectors specified", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { logger := newLogger(t) sig, err := signer.NewMockSigner(testKey) if err != nil { t.Fatalf("failed to create mock signer: %v", err) } config := Config{ Issuer: "http://localhost", Storage: memory.New(logger), Web: WebConfig{ Dir: "../web", }, Logger: logger, PrometheusRegistry: prometheus.NewRegistry(), HealthChecker: gosundheit.New(), ContinueOnConnectorFailure: tc.continueOnConnectorFailure, Signer: sig, } // Create connectors in storage for _, conn := range tc.connectors { if err := config.Storage.CreateConnector(ctx, conn); err != nil { t.Fatalf("failed to create connector: %v", err) } } server, err := newServer(ctx, config) if tc.wantErr { if err == nil { t.Errorf("expected error but got none") } else if tc.wantErrContains != "" && !strings.Contains(err.Error(), tc.wantErrContains) { t.Errorf("expected error containing %q, got %q", tc.wantErrContains, err.Error()) } } else { if err != nil { t.Errorf("unexpected error: %v", err) } else { // Verify expected connectors are loaded for _, id := range tc.expectConnectors { if _, exists := server.connectors[id]; !exists { t.Errorf("expected connector %q to be loaded", id) } } // Verify failed connectors are not loaded for _, conn := range tc.connectors { _, shouldExist := false, false for _, expectedID := range tc.expectConnectors { if conn.ID == expectedID { shouldExist = true break } } _, exists := server.connectors[conn.ID] if shouldExist && !exists { t.Errorf("connector %q should have been loaded but wasn't", conn.ID) } else if !shouldExist && exists { t.Errorf("connector %q should not have been loaded but was", conn.ID) } } } } }) } } ================================================ FILE: server/session.go ================================================ package server import ( "context" "crypto/hmac" "crypto/sha256" "encoding/base64" "errors" "fmt" "net/http" "path" "strings" "time" "github.com/dexidp/dex/storage" ) // rememberMeDefault returns a pointer to the default remember-me value if sessions are enabled, nil otherwise. func (s *Server) rememberMeDefault() *bool { if s.sessionConfig == nil { return nil } v := s.sessionConfig.RememberMeCheckedByDefault return &v } // remoteIP returns the real IP from context (set by parseRealIP middleware) or falls back to r.RemoteAddr. func remoteIP(r *http.Request) string { if ip, ok := r.Context().Value(RequestKeyRemoteIP).(string); ok && ip != "" { return ip } return r.RemoteAddr } // sessionCookieValue encodes session identity into a cookie value. // Format: base64url(userID) + "." + base64url(connectorID) + "." + nonce // TODO(nabokihms): consider cookie encoding func sessionCookieValue(userID, connectorID, nonce string) string { return base64.RawURLEncoding.EncodeToString([]byte(userID)) + "." + base64.RawURLEncoding.EncodeToString([]byte(connectorID)) + "." + nonce } // parseSessionCookie decodes a session cookie value into its components. func parseSessionCookie(value string) (userID, connectorID, nonce string, err error) { parts := strings.SplitN(value, ".", 3) if len(parts) != 3 { return "", "", "", fmt.Errorf("invalid session cookie format") } userIDBytes, err := base64.RawURLEncoding.DecodeString(parts[0]) if err != nil { return "", "", "", fmt.Errorf("decode userID: %w", err) } connectorIDBytes, err := base64.RawURLEncoding.DecodeString(parts[1]) if err != nil { return "", "", "", fmt.Errorf("decode connectorID: %w", err) } return string(userIDBytes), string(connectorIDBytes), parts[2], nil } func (s *Server) sessionCookiePath() string { if s.issuerURL.Path == "" { return "/" } return s.issuerURL.Path } func (s *Server) setSessionCookie(w http.ResponseWriter, userID, connectorID, nonce string, rememberMe bool) { cookie := &http.Cookie{ Name: s.sessionConfig.CookieName, Value: sessionCookieValue(userID, connectorID, nonce), Path: s.sessionCookiePath(), HttpOnly: true, Secure: s.issuerURL.Scheme == "https", SameSite: http.SameSiteLaxMode, } if rememberMe { cookie.MaxAge = int(s.sessionConfig.AbsoluteLifetime.Seconds()) } http.SetCookie(w, cookie) } func (s *Server) clearSessionCookie(w http.ResponseWriter) { http.SetCookie(w, &http.Cookie{ Name: s.sessionConfig.CookieName, Value: "", Path: s.sessionCookiePath(), HttpOnly: true, Secure: s.issuerURL.Scheme == "https", SameSite: http.SameSiteLaxMode, MaxAge: -1, }) } // getValidAuthSession returns a valid, non-expired session or nil. // It parses the session cookie to extract (userID, connectorID, nonce), // looks up the session by composite key, and verifies the nonce. // Invalid or expired session cookies are cleared automatically. func (s *Server) getValidAuthSession(ctx context.Context, w http.ResponseWriter, r *http.Request, authReq *storage.AuthRequest) *storage.AuthSession { if s.sessionConfig == nil { return nil } cookie, err := r.Cookie(s.sessionConfig.CookieName) if err != nil || cookie.Value == "" { return nil } userID, connectorID, nonce, err := parseSessionCookie(cookie.Value) if err != nil { s.logger.DebugContext(ctx, "invalid session cookie format", "err", err) s.clearSessionCookie(w) return nil } session, err := s.storage.GetAuthSession(ctx, userID, connectorID) if err != nil { if !errors.Is(err, storage.ErrNotFound) { s.logger.ErrorContext(ctx, "failed to get auth session", "err", err) } s.clearSessionCookie(w) return nil } // Verify nonce to prevent cookie forgery. if session.Nonce != nonce { s.logger.DebugContext(ctx, "auth session nonce mismatch") s.clearSessionCookie(w) return nil } now := s.now() // Check absolute lifetime using the stored expiry (set once at creation). if !session.AbsoluteExpiry.IsZero() && now.After(session.AbsoluteExpiry) { s.logger.InfoContext(ctx, "auth session expired (absolute lifetime)", "user_id", session.UserID, "connector_id", session.ConnectorID) if err := s.storage.DeleteAuthSession(ctx, session.UserID, session.ConnectorID); err != nil { s.logger.DebugContext(ctx, "failed to delete expired auth session", "err", err) } s.clearSessionCookie(w) return nil } // Check idle timeout using the stored expiry (updated on every activity). if !session.IdleExpiry.IsZero() && now.After(session.IdleExpiry) { s.logger.InfoContext(ctx, "auth session expired (idle timeout)", "user_id", session.UserID, "connector_id", session.ConnectorID) if err := s.storage.DeleteAuthSession(ctx, session.UserID, session.ConnectorID); err != nil { s.logger.DebugContext(ctx, "failed to delete expired auth session", "err", err) } s.clearSessionCookie(w) return nil } // Only reuse sessions from the same connector. if session.ConnectorID != authReq.ConnectorID { return nil } return &session } // createOrUpdateAuthSession creates a new session or updates an existing one // after a successful login, and sets the session cookie. // rememberMe controls whether the cookie is persistent (survives browser close). func (s *Server) createOrUpdateAuthSession(ctx context.Context, r *http.Request, w http.ResponseWriter, authReq storage.AuthRequest, rememberMe bool) error { if s.sessionConfig == nil { return nil } now := s.now() userID := authReq.Claims.UserID connectorID := authReq.ConnectorID clientState := &storage.ClientAuthState{ Active: true, ExpiresAt: now.Add(s.sessionConfig.AbsoluteLifetime), LastActivity: now, } // Try to reuse existing session for this (userID, connectorID). session, err := s.storage.GetAuthSession(ctx, userID, connectorID) if err == nil { // Session exists, update it. s.logger.DebugContext(ctx, "updating existing auth session", "user_id", userID, "connector_id", connectorID, "client_id", authReq.ClientID) if err := s.storage.UpdateAuthSession(ctx, userID, connectorID, func(old storage.AuthSession) (storage.AuthSession, error) { old.LastActivity = now old.IdleExpiry = now.Add(s.sessionConfig.ValidIfNotUsedFor) if old.ClientStates == nil { old.ClientStates = make(map[string]*storage.ClientAuthState) } old.ClientStates[authReq.ClientID] = clientState return old, nil }); err != nil { return fmt.Errorf("update auth session: %w", err) } s.setSessionCookie(w, userID, connectorID, session.Nonce, rememberMe) return nil } // Unexpected error, exit the method. if !errors.Is(err, storage.ErrNotFound) { return fmt.Errorf("get auth session: %w", err) } nonce := storage.NewID() newSession := storage.AuthSession{ UserID: userID, ConnectorID: connectorID, Nonce: nonce, ClientStates: map[string]*storage.ClientAuthState{ authReq.ClientID: clientState, }, CreatedAt: now, LastActivity: now, IPAddress: remoteIP(r), UserAgent: r.UserAgent(), AbsoluteExpiry: now.Add(s.sessionConfig.AbsoluteLifetime), IdleExpiry: now.Add(s.sessionConfig.ValidIfNotUsedFor), } if err := s.storage.CreateAuthSession(ctx, newSession); err != nil { return fmt.Errorf("create auth session: %w", err) } s.logger.DebugContext(ctx, "created new auth session", "user_id", userID, "connector_id", connectorID, "client_id", authReq.ClientID) s.setSessionCookie(w, userID, connectorID, nonce, rememberMe) return nil } // trySessionLogin checks if the user has a valid session for the same connector. // If so, it finalizes login from the stored identity and returns a redirect URL. // Returns ("", false) if session-based login is not possible. func (s *Server) trySessionLogin(ctx context.Context, r *http.Request, w http.ResponseWriter, authReq *storage.AuthRequest) (string, bool) { session := s.getValidAuthSession(ctx, w, r, authReq) return s.trySessionLoginWithSession(ctx, r, w, authReq, session) } // trySessionLoginWithSession is like trySessionLogin but accepts a pre-retrieved session. // This allows callers to inspect the session (e.g., for id_token_hint comparison) before // attempting session-based login. func (s *Server) trySessionLoginWithSession(ctx context.Context, r *http.Request, w http.ResponseWriter, authReq *storage.AuthRequest, session *storage.AuthSession) (string, bool) { if session == nil { return "", false } clientState, ok := session.ClientStates[authReq.ClientID] if !ok || !clientState.Active { return "", false } now := s.now() if now.After(clientState.ExpiresAt) { return "", false } // Load identity from storage. ui, err := s.storage.GetUserIdentity(ctx, session.UserID, session.ConnectorID) if err != nil { s.logger.ErrorContext(ctx, "session: failed to get user identity", "err", err) return "", false } // Check max_age: if the user's last authentication is too old, force re-auth. if authReq.MaxAge >= 0 { if now.Sub(ui.LastLogin) > time.Duration(authReq.MaxAge)*time.Second { return "", false } } claims := storage.Claims{ UserID: ui.Claims.UserID, Username: ui.Claims.Username, PreferredUsername: ui.Claims.PreferredUsername, Email: ui.Claims.Email, EmailVerified: ui.Claims.EmailVerified, Groups: ui.Claims.Groups, } // Update AuthRequest with stored identity and auth_time from last login. if err := s.storage.UpdateAuthRequest(ctx, authReq.ID, func(a storage.AuthRequest) (storage.AuthRequest, error) { a.LoggedIn = true a.Claims = claims a.ConnectorID = session.ConnectorID a.AuthTime = ui.LastLogin return a, nil }); err != nil { s.logger.ErrorContext(ctx, "session: failed to update auth request", "err", err) return "", false } s.logger.DebugContext(ctx, "session: re-authenticated from session", "user_id", session.UserID, "connector_id", session.ConnectorID) // Update session activity. _ = s.storage.UpdateAuthSession(ctx, session.UserID, session.ConnectorID, func(old storage.AuthSession) (storage.AuthSession, error) { old.LastActivity = now old.IdleExpiry = now.Add(s.sessionConfig.ValidIfNotUsedFor) if cs, ok := old.ClientStates[authReq.ClientID]; ok { cs.LastActivity = now } return old, nil }) // Build HMAC for approval URL. h := hmac.New(sha256.New, authReq.HMACKey) h.Write([]byte(authReq.ID)) mac := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) // Skip approval if globally configured or user already consented to the requested scopes. if !authReq.ForceApprovalPrompt && (s.skipApproval || scopesCoveredByConsent(ui.Consents[authReq.ClientID], authReq.Scopes)) { // Re-read to get the updated AuthRequest (LoggedIn, Claims, ConnectorID set above). updated, err := s.storage.GetAuthRequest(ctx, authReq.ID) if err != nil { s.logger.ErrorContext(ctx, "session: failed to get auth request", "err", err) return "", false } s.sendCodeResponse(w, r, updated) return "", true } returnURL := path.Join(s.issuerURL.Path, "/approval") + "?req=" + authReq.ID + "&hmac=" + mac return returnURL, true } // updateSessionTokenIssuedAt updates the session's LastTokenIssuedAt for the given client. func (s *Server) updateSessionTokenIssuedAt(r *http.Request, clientID string) { if s.sessionConfig == nil { return } cookie, err := r.Cookie(s.sessionConfig.CookieName) if err != nil || cookie.Value == "" { return } userID, connectorID, _, err := parseSessionCookie(cookie.Value) if err != nil { return } now := s.now() _ = s.storage.UpdateAuthSession(r.Context(), userID, connectorID, func(old storage.AuthSession) (storage.AuthSession, error) { old.LastActivity = now old.IdleExpiry = now.Add(s.sessionConfig.ValidIfNotUsedFor) if cs, ok := old.ClientStates[clientID]; ok { cs.LastTokenIssuedAt = now cs.LastActivity = now } return old, nil }) } ================================================ FILE: server/session_test.go ================================================ package server import ( "crypto" "log/slog" "net/http" "net/http/httptest" "net/url" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/memory" ) func newTestSessionServer(t *testing.T) *Server { t.Helper() now := time.Date(2026, 3, 16, 12, 0, 0, 0, time.UTC) issuerURL, err := url.Parse("https://example.com/dex") require.NoError(t, err) return &Server{ storage: memory.New(nil), logger: slog.Default(), now: func() time.Time { return now }, sessionConfig: &SessionConfig{ CookieName: "dex_session", AbsoluteLifetime: 24 * time.Hour, ValidIfNotUsedFor: 1 * time.Hour, }, issuerURL: *issuerURL, } } func TestSetSessionCookie(t *testing.T) { s := newTestSessionServer(t) w := httptest.NewRecorder() s.setSessionCookie(w, "user1", "conn1", "nonce123", false) cookies := w.Result().Cookies() require.Len(t, cookies, 1) c := cookies[0] assert.Equal(t, "dex_session", c.Name) assert.Equal(t, sessionCookieValue("user1", "conn1", "nonce123"), c.Value) assert.Equal(t, "/dex", c.Path) assert.True(t, c.HttpOnly) assert.True(t, c.Secure) assert.Equal(t, http.SameSiteLaxMode, c.SameSite) } func TestSetSessionCookie_HTTP(t *testing.T) { s := newTestSessionServer(t) u, _ := url.Parse("http://localhost:5556/dex") s.issuerURL = *u w := httptest.NewRecorder() s.setSessionCookie(w, "user1", "conn1", "nonce123", false) cookies := w.Result().Cookies() require.Len(t, cookies, 1) assert.False(t, cookies[0].Secure) } func TestClearSessionCookie(t *testing.T) { s := newTestSessionServer(t) w := httptest.NewRecorder() s.clearSessionCookie(w) cookies := w.Result().Cookies() require.Len(t, cookies, 1) assert.Equal(t, -1, cookies[0].MaxAge) assert.Equal(t, "", cookies[0].Value) } func TestSessionCookieValueRoundtrip(t *testing.T) { tests := []struct { name string userID string connectorID string nonce string }{ {"simple", "user1", "ldap", "abc123"}, {"with special chars", "user@example.com", "oidc-provider", "xyz789"}, {"unicode", "юзер", "коннектор", "nonce"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { value := sessionCookieValue(tt.userID, tt.connectorID, tt.nonce) gotUser, gotConn, gotNonce, err := parseSessionCookie(value) require.NoError(t, err) assert.Equal(t, tt.userID, gotUser) assert.Equal(t, tt.connectorID, gotConn) assert.Equal(t, tt.nonce, gotNonce) }) } } func TestParseSessionCookie_Invalid(t *testing.T) { //nolint:dogsled // only for tests _, _, _, err := parseSessionCookie("invalid") assert.Error(t, err) //nolint:dogsled // only for tests _, _, _, err = parseSessionCookie("a.b") assert.Error(t, err) } func TestGetValidAuthSession(t *testing.T) { ctx := t.Context() authReq := &storage.AuthRequest{ConnectorID: "conn1"} t.Run("no session config", func(t *testing.T) { s := newTestSessionServer(t) s.sessionConfig = nil r := httptest.NewRequest(http.MethodGet, "/", nil) assert.Nil(t, s.getValidAuthSession(ctx, httptest.NewRecorder(), r, authReq)) }) t.Run("no cookie", func(t *testing.T) { s := newTestSessionServer(t) r := httptest.NewRequest(http.MethodGet, "/", nil) assert.Nil(t, s.getValidAuthSession(ctx, httptest.NewRecorder(), r, authReq)) }) t.Run("invalid cookie format", func(t *testing.T) { s := newTestSessionServer(t) r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: "invalid-format"}) w := httptest.NewRecorder() assert.Nil(t, s.getValidAuthSession(ctx, w, r, authReq)) // Cookie should be cleared. assert.Equal(t, -1, w.Result().Cookies()[0].MaxAge) }) t.Run("session not found", func(t *testing.T) { s := newTestSessionServer(t) r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("nouser", "noconn", "nonce")}) w := httptest.NewRecorder() assert.Nil(t, s.getValidAuthSession(ctx, w, r, authReq)) // Cookie should be cleared. assert.Equal(t, -1, w.Result().Cookies()[0].MaxAge) }) t.Run("valid session", func(t *testing.T) { s := newTestSessionServer(t) now := s.now() nonce := "test-nonce" session := storage.AuthSession{ UserID: "user1", ConnectorID: "conn1", Nonce: nonce, ClientStates: map[string]*storage.ClientAuthState{}, CreatedAt: now.Add(-30 * time.Minute), LastActivity: now.Add(-5 * time.Minute), IPAddress: "127.0.0.1", UserAgent: "test", AbsoluteExpiry: now.Add(24 * time.Hour), IdleExpiry: now.Add(1 * time.Hour), } require.NoError(t, s.storage.CreateAuthSession(ctx, session)) r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user1", "conn1", nonce)}) result := s.getValidAuthSession(ctx, httptest.NewRecorder(), r, authReq) require.NotNil(t, result) assert.Equal(t, "user1", result.UserID) assert.Equal(t, "conn1", result.ConnectorID) }) t.Run("connector mismatch", func(t *testing.T) { s := newTestSessionServer(t) now := s.now() nonce := "test-nonce-conn" session := storage.AuthSession{ UserID: "user1", ConnectorID: "ldap", Nonce: nonce, ClientStates: map[string]*storage.ClientAuthState{}, CreatedAt: now.Add(-30 * time.Minute), LastActivity: now.Add(-5 * time.Minute), IPAddress: "127.0.0.1", UserAgent: "test", AbsoluteExpiry: now.Add(24 * time.Hour), IdleExpiry: now.Add(1 * time.Hour), } require.NoError(t, s.storage.CreateAuthSession(ctx, session)) r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user1", "ldap", nonce)}) githubReq := &storage.AuthRequest{ConnectorID: "github"} assert.Nil(t, s.getValidAuthSession(ctx, httptest.NewRecorder(), r, githubReq)) }) t.Run("nonce mismatch", func(t *testing.T) { s := newTestSessionServer(t) now := s.now() session := storage.AuthSession{ UserID: "user2", ConnectorID: "conn2", Nonce: "correct-nonce", ClientStates: map[string]*storage.ClientAuthState{}, CreatedAt: now.Add(-30 * time.Minute), LastActivity: now.Add(-5 * time.Minute), IPAddress: "127.0.0.1", UserAgent: "test", AbsoluteExpiry: now.Add(24 * time.Hour), IdleExpiry: now.Add(1 * time.Hour), } require.NoError(t, s.storage.CreateAuthSession(ctx, session)) r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user2", "conn2", "wrong-nonce")}) conn2Req := &storage.AuthRequest{ConnectorID: "conn2"} w := httptest.NewRecorder() assert.Nil(t, s.getValidAuthSession(ctx, w, r, conn2Req)) assert.Equal(t, -1, w.Result().Cookies()[0].MaxAge) }) t.Run("expired absolute lifetime", func(t *testing.T) { s := newTestSessionServer(t) now := s.now() nonce := "expired-nonce" session := storage.AuthSession{ UserID: "user3", ConnectorID: "conn3", Nonce: nonce, ClientStates: map[string]*storage.ClientAuthState{}, CreatedAt: now.Add(-25 * time.Hour), LastActivity: now.Add(-1 * time.Minute), IPAddress: "127.0.0.1", UserAgent: "test", AbsoluteExpiry: now.Add(-1 * time.Hour), IdleExpiry: now.Add(1 * time.Hour), } require.NoError(t, s.storage.CreateAuthSession(ctx, session)) r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user3", "conn3", nonce)}) conn3Req := &storage.AuthRequest{ConnectorID: "conn3"} w := httptest.NewRecorder() assert.Nil(t, s.getValidAuthSession(ctx, w, r, conn3Req)) assert.Equal(t, -1, w.Result().Cookies()[0].MaxAge) // Session should be deleted. _, err := s.storage.GetAuthSession(ctx, "user3", "conn3") assert.ErrorIs(t, err, storage.ErrNotFound) }) t.Run("expired idle timeout", func(t *testing.T) { s := newTestSessionServer(t) now := s.now() nonce := "idle-nonce" session := storage.AuthSession{ UserID: "user4", ConnectorID: "conn4", Nonce: nonce, ClientStates: map[string]*storage.ClientAuthState{}, CreatedAt: now.Add(-2 * time.Hour), LastActivity: now.Add(-2 * time.Hour), IPAddress: "127.0.0.1", UserAgent: "test", AbsoluteExpiry: now.Add(22 * time.Hour), IdleExpiry: now.Add(-1 * time.Hour), } require.NoError(t, s.storage.CreateAuthSession(ctx, session)) r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user4", "conn4", nonce)}) conn4Req := &storage.AuthRequest{ConnectorID: "conn4"} w := httptest.NewRecorder() assert.Nil(t, s.getValidAuthSession(ctx, w, r, conn4Req)) assert.Equal(t, -1, w.Result().Cookies()[0].MaxAge) // Session should be deleted. _, err := s.storage.GetAuthSession(ctx, "user4", "conn4") assert.ErrorIs(t, err, storage.ErrNotFound) }) } func TestCreateOrUpdateAuthSession(t *testing.T) { ctx := t.Context() t.Run("create new session", func(t *testing.T) { s := newTestSessionServer(t) w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodGet, "/", nil) authReq := storage.AuthRequest{ ID: "auth-1", ClientID: "client-1", Claims: storage.Claims{UserID: "user-1"}, ConnectorID: "mock", } err := s.createOrUpdateAuthSession(ctx, r, w, authReq, false) require.NoError(t, err) // Cookie should be set. cookies := w.Result().Cookies() require.Len(t, cookies, 1) userID, connectorID, nonce, err := parseSessionCookie(cookies[0].Value) require.NoError(t, err) assert.Equal(t, "user-1", userID) assert.Equal(t, "mock", connectorID) assert.NotEmpty(t, nonce) // Session should exist in storage. session, err := s.storage.GetAuthSession(ctx, "user-1", "mock") require.NoError(t, err) assert.Equal(t, "user-1", session.UserID) assert.Equal(t, "mock", session.ConnectorID) require.Contains(t, session.ClientStates, "client-1") assert.True(t, session.ClientStates["client-1"].Active) }) t.Run("update existing session", func(t *testing.T) { s := newTestSessionServer(t) now := s.now() nonce := "existing-nonce" existingSession := storage.AuthSession{ UserID: "user-1", ConnectorID: "mock", Nonce: nonce, ClientStates: map[string]*storage.ClientAuthState{ "client-1": { Active: true, ExpiresAt: now.Add(24 * time.Hour), LastActivity: now.Add(-10 * time.Minute), }, }, CreatedAt: now.Add(-30 * time.Minute), LastActivity: now.Add(-10 * time.Minute), IPAddress: "127.0.0.1", UserAgent: "test", AbsoluteExpiry: now.Add(24 * time.Hour), IdleExpiry: now.Add(50 * time.Minute), } require.NoError(t, s.storage.CreateAuthSession(ctx, existingSession)) w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodGet, "/", nil) authReq := storage.AuthRequest{ ID: "auth-2", ClientID: "client-2", Claims: storage.Claims{UserID: "user-1"}, ConnectorID: "mock", } err := s.createOrUpdateAuthSession(ctx, r, w, authReq, false) require.NoError(t, err) // Cookie should be set with existing nonce. cookies := w.Result().Cookies() require.Len(t, cookies, 1) _, _, gotNonce, err := parseSessionCookie(cookies[0].Value) require.NoError(t, err) assert.Equal(t, nonce, gotNonce) // Session should have both clients. session, err := s.storage.GetAuthSession(ctx, "user-1", "mock") require.NoError(t, err) assert.Len(t, session.ClientStates, 2) assert.Contains(t, session.ClientStates, "client-1") assert.Contains(t, session.ClientStates, "client-2") }) t.Run("nil session config", func(t *testing.T) { s := newTestSessionServer(t) s.sessionConfig = nil w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodGet, "/", nil) err := s.createOrUpdateAuthSession(ctx, r, w, storage.AuthRequest{}, false) assert.NoError(t, err) assert.Empty(t, w.Result().Cookies()) }) } // setupSessionLoginFixture creates the necessary storage objects for trySessionLogin tests. func setupSessionLoginFixture(t *testing.T, s *Server) storage.AuthRequest { t.Helper() ctx := t.Context() now := s.now() require.NoError(t, s.storage.CreateAuthSession(ctx, storage.AuthSession{ UserID: "user-1", ConnectorID: "mock", Nonce: "test-nonce", ClientStates: map[string]*storage.ClientAuthState{ "client-1": { Active: true, ExpiresAt: now.Add(24 * time.Hour), LastActivity: now.Add(-1 * time.Minute), }, }, CreatedAt: now.Add(-30 * time.Minute), LastActivity: now.Add(-1 * time.Minute), IPAddress: "127.0.0.1", UserAgent: "test", AbsoluteExpiry: now.Add(24 * time.Hour), IdleExpiry: now.Add(59 * time.Minute), })) require.NoError(t, s.storage.CreateUserIdentity(ctx, storage.UserIdentity{ UserID: "user-1", ConnectorID: "mock", Claims: storage.Claims{ UserID: "user-1", Username: "testuser", Email: "test@example.com", }, Consents: map[string][]string{"client-1": {"openid", "email"}}, CreatedAt: now.Add(-1 * time.Hour), LastLogin: now.Add(-30 * time.Minute), })) authReq := storage.AuthRequest{ ID: storage.NewID(), ClientID: "client-1", ConnectorID: "mock", Scopes: []string{"openid", "email"}, RedirectURI: "http://localhost/callback", MaxAge: -1, HMACKey: storage.NewHMACKey(crypto.SHA256), Expiry: now.Add(10 * time.Minute), } require.NoError(t, s.storage.CreateAuthRequest(ctx, authReq)) return authReq } func sessionCookieRequest(userID, connectorID, nonce string) *http.Request { r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue(userID, connectorID, nonce)}) return r } func TestTrySessionLogin(t *testing.T) { ctx := t.Context() t.Run("no session", func(t *testing.T) { s := newTestSessionServer(t) authReq := storage.AuthRequest{ConnectorID: "mock"} r := httptest.NewRequest(http.MethodGet, "/", nil) w := httptest.NewRecorder() _, ok := s.trySessionLogin(ctx, r, w, &authReq) assert.False(t, ok) }) t.Run("successful login with skipApproval", func(t *testing.T) { s := newTestSessionServer(t) s.skipApproval = true authReq := setupSessionLoginFixture(t, s) r := sessionCookieRequest("user-1", "mock", "test-nonce") w := httptest.NewRecorder() _, ok := s.trySessionLogin(ctx, r, w, &authReq) assert.True(t, ok) }) t.Run("successful login redirects to approval", func(t *testing.T) { s := newTestSessionServer(t) s.skipApproval = false authReq := setupSessionLoginFixture(t, s) authReq.ForceApprovalPrompt = true require.NoError(t, s.storage.UpdateAuthRequest(ctx, authReq.ID, func(a storage.AuthRequest) (storage.AuthRequest, error) { a.ForceApprovalPrompt = true return a, nil })) r := sessionCookieRequest("user-1", "mock", "test-nonce") w := httptest.NewRecorder() redirectURL, ok := s.trySessionLogin(ctx, r, w, &authReq) assert.True(t, ok) assert.Contains(t, redirectURL, "/approval") assert.Contains(t, redirectURL, "req="+authReq.ID) }) t.Run("skips approval when consent already given", func(t *testing.T) { s := newTestSessionServer(t) s.skipApproval = false authReq := setupSessionLoginFixture(t, s) r := sessionCookieRequest("user-1", "mock", "test-nonce") w := httptest.NewRecorder() _, ok := s.trySessionLogin(ctx, r, w, &authReq) assert.True(t, ok) }) t.Run("connector mismatch returns false", func(t *testing.T) { s := newTestSessionServer(t) authReq := setupSessionLoginFixture(t, s) authReq.ConnectorID = "github" r := sessionCookieRequest("user-1", "mock", "test-nonce") w := httptest.NewRecorder() _, ok := s.trySessionLogin(ctx, r, w, &authReq) assert.False(t, ok) }) t.Run("no client state for requested client", func(t *testing.T) { s := newTestSessionServer(t) authReq := setupSessionLoginFixture(t, s) authReq.ClientID = "unknown-client" r := sessionCookieRequest("user-1", "mock", "test-nonce") w := httptest.NewRecorder() _, ok := s.trySessionLogin(ctx, r, w, &authReq) assert.False(t, ok) }) t.Run("expired client state returns false", func(t *testing.T) { s := newTestSessionServer(t) now := s.now() require.NoError(t, s.storage.CreateAuthSession(t.Context(), storage.AuthSession{ UserID: "user-exp", ConnectorID: "mock", Nonce: "nonce-exp", ClientStates: map[string]*storage.ClientAuthState{ "client-1": { Active: true, ExpiresAt: now.Add(-1 * time.Hour), }, }, CreatedAt: now.Add(-2 * time.Hour), LastActivity: now.Add(-1 * time.Minute), AbsoluteExpiry: now.Add(22 * time.Hour), IdleExpiry: now.Add(59 * time.Minute), })) require.NoError(t, s.storage.CreateUserIdentity(t.Context(), storage.UserIdentity{ UserID: "user-exp", ConnectorID: "mock", Claims: storage.Claims{UserID: "user-exp"}, Consents: make(map[string][]string), CreatedAt: now, LastLogin: now, })) authReq := storage.AuthRequest{ ID: storage.NewID(), ClientID: "client-1", ConnectorID: "mock", MaxAge: -1, HMACKey: storage.NewHMACKey(crypto.SHA256), Expiry: now.Add(10 * time.Minute), } require.NoError(t, s.storage.CreateAuthRequest(t.Context(), authReq)) r := sessionCookieRequest("user-exp", "mock", "nonce-exp") w := httptest.NewRecorder() _, ok := s.trySessionLogin(ctx, r, w, &authReq) assert.False(t, ok) }) t.Run("updates session activity", func(t *testing.T) { s := newTestSessionServer(t) s.skipApproval = true authReq := setupSessionLoginFixture(t, s) r := sessionCookieRequest("user-1", "mock", "test-nonce") w := httptest.NewRecorder() _, ok := s.trySessionLogin(ctx, r, w, &authReq) require.True(t, ok) session, err := s.storage.GetAuthSession(ctx, "user-1", "mock") require.NoError(t, err) assert.Equal(t, s.now(), session.LastActivity) }) } // setupSessionWithIdentity creates an AuthSession, UserIdentity, and AuthRequest in storage // for use in trySessionLogin tests. Returns the authReq. func setupSessionWithIdentity(t *testing.T, s *Server, now time.Time, lastLogin time.Time) storage.AuthRequest { t.Helper() ctx := t.Context() nonce := "test-nonce" session := storage.AuthSession{ UserID: "user-1", ConnectorID: "mock", Nonce: nonce, ClientStates: map[string]*storage.ClientAuthState{ "client-1": { Active: true, ExpiresAt: now.Add(24 * time.Hour), LastActivity: now.Add(-1 * time.Minute), }, }, CreatedAt: now.Add(-30 * time.Minute), LastActivity: now.Add(-1 * time.Minute), IPAddress: "127.0.0.1", UserAgent: "test", } require.NoError(t, s.storage.CreateAuthSession(ctx, session)) ui := storage.UserIdentity{ UserID: "user-1", ConnectorID: "mock", Claims: storage.Claims{ UserID: "user-1", Username: "testuser", Email: "test@example.com", }, Consents: make(map[string][]string), CreatedAt: now.Add(-1 * time.Hour), LastLogin: lastLogin, } require.NoError(t, s.storage.CreateUserIdentity(ctx, ui)) authReq := storage.AuthRequest{ ID: storage.NewID(), ClientID: "client-1", ConnectorID: "mock", Scopes: []string{"openid"}, RedirectURI: "http://localhost/callback", MaxAge: -1, HMACKey: storage.NewHMACKey(crypto.SHA256), Expiry: now.Add(10 * time.Minute), } require.NoError(t, s.storage.CreateAuthRequest(ctx, authReq)) return authReq } func TestTrySessionLogin_MaxAge(t *testing.T) { ctx := t.Context() t.Run("max_age not specified, session reused", func(t *testing.T) { s := newTestSessionServer(t) now := s.now() authReq := setupSessionWithIdentity(t, s, now, now.Add(-2*time.Hour)) authReq.MaxAge = -1 // not specified r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user-1", "mock", "test-nonce")}) w := httptest.NewRecorder() _, ok := s.trySessionLogin(ctx, r, w, &authReq) assert.True(t, ok, "session should be reused when max_age is not specified") }) t.Run("max_age satisfied, session reused", func(t *testing.T) { s := newTestSessionServer(t) now := s.now() // User logged in 10 minutes ago, max_age=3600 (1 hour) authReq := setupSessionWithIdentity(t, s, now, now.Add(-10*time.Minute)) authReq.MaxAge = 3600 r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user-1", "mock", "test-nonce")}) w := httptest.NewRecorder() _, ok := s.trySessionLogin(ctx, r, w, &authReq) assert.True(t, ok, "session should be reused when max_age is satisfied") }) t.Run("max_age exceeded, force re-auth", func(t *testing.T) { s := newTestSessionServer(t) now := s.now() // User logged in 2 hours ago, max_age=3600 (1 hour) authReq := setupSessionWithIdentity(t, s, now, now.Add(-2*time.Hour)) authReq.MaxAge = 3600 r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user-1", "mock", "test-nonce")}) w := httptest.NewRecorder() _, ok := s.trySessionLogin(ctx, r, w, &authReq) assert.False(t, ok, "session should NOT be reused when max_age is exceeded") }) t.Run("max_age=0, always force re-auth", func(t *testing.T) { s := newTestSessionServer(t) now := s.now() // User logged in 1 second ago, max_age=0 authReq := setupSessionWithIdentity(t, s, now, now.Add(-1*time.Second)) authReq.MaxAge = 0 r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user-1", "mock", "test-nonce")}) w := httptest.NewRecorder() _, ok := s.trySessionLogin(ctx, r, w, &authReq) assert.False(t, ok, "max_age=0 should always force re-authentication") }) t.Run("auth_time is set from UserIdentity.LastLogin", func(t *testing.T) { s := newTestSessionServer(t) s.skipApproval = false now := s.now() lastLogin := now.Add(-10 * time.Minute) authReq := setupSessionWithIdentity(t, s, now, lastLogin) authReq.ForceApprovalPrompt = true // force approval so AuthRequest is not deleted require.NoError(t, s.storage.UpdateAuthRequest(ctx, authReq.ID, func(a storage.AuthRequest) (storage.AuthRequest, error) { a.ForceApprovalPrompt = true return a, nil })) r := httptest.NewRequest(http.MethodGet, "/", nil) r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user-1", "mock", "test-nonce")}) w := httptest.NewRecorder() redirectURL, ok := s.trySessionLogin(ctx, r, w, &authReq) require.True(t, ok) assert.Contains(t, redirectURL, "/approval") // Verify AuthTime was set on the auth request. updated, err := s.storage.GetAuthRequest(ctx, authReq.ID) require.NoError(t, err) assert.Equal(t, lastLogin.Unix(), updated.AuthTime.Unix()) }) } func TestTrySessionLoginWithSession_IDTokenHint(t *testing.T) { ctx := t.Context() // genSubject("user-1", "mock") produces a deterministic subject string. hintSubjectForUser1Mock, err := genSubject("user-1", "mock") require.NoError(t, err) hintSubjectOther, err := genSubject("other-user", "mock") require.NoError(t, err) t.Run("hint matches session user - session login succeeds", func(t *testing.T) { s := newTestSessionServer(t) s.skipApproval = true authReq := setupSessionLoginFixture(t, s) session := s.getValidAuthSession(ctx, httptest.NewRecorder(), sessionCookieRequest("user-1", "mock", "test-nonce"), &authReq) require.NotNil(t, session) // Verify hint matches. assert.True(t, sessionMatchesHint(session, hintSubjectForUser1Mock)) r := sessionCookieRequest("user-1", "mock", "test-nonce") w := httptest.NewRecorder() _, ok := s.trySessionLoginWithSession(ctx, r, w, &authReq, session) assert.True(t, ok) }) t.Run("hint does not match session user - session invalidated", func(t *testing.T) { s := newTestSessionServer(t) s.skipApproval = true authReq := setupSessionLoginFixture(t, s) session := s.getValidAuthSession(ctx, httptest.NewRecorder(), sessionCookieRequest("user-1", "mock", "test-nonce"), &authReq) require.NotNil(t, session) // Verify hint does NOT match. assert.False(t, sessionMatchesHint(session, hintSubjectOther)) // Simulating the hint mismatch logic from handleConnectorLogin: // when hint doesn't match and prompt is not none, session is set to nil. var nilSession *storage.AuthSession r := sessionCookieRequest("user-1", "mock", "test-nonce") w := httptest.NewRecorder() _, ok := s.trySessionLoginWithSession(ctx, r, w, &authReq, nilSession) assert.False(t, ok, "session login should fail when session is invalidated due to hint mismatch") }) t.Run("hint with no session - trySessionLoginWithSession returns false", func(t *testing.T) { s := newTestSessionServer(t) s.skipApproval = true authReq := setupSessionLoginFixture(t, s) r := httptest.NewRequest(http.MethodGet, "/", nil) w := httptest.NewRecorder() _, ok := s.trySessionLoginWithSession(ctx, r, w, &authReq, nil) assert.False(t, ok) }) t.Run("no hint - unchanged behavior", func(t *testing.T) { s := newTestSessionServer(t) s.skipApproval = true authReq := setupSessionLoginFixture(t, s) session := s.getValidAuthSession(ctx, httptest.NewRecorder(), sessionCookieRequest("user-1", "mock", "test-nonce"), &authReq) require.NotNil(t, session) r := sessionCookieRequest("user-1", "mock", "test-nonce") w := httptest.NewRecorder() _, ok := s.trySessionLoginWithSession(ctx, r, w, &authReq, session) assert.True(t, ok) }) } func TestParseAuthRequest_PromptAndMaxAge(t *testing.T) { t.Run("prompt=consent sets ForceApprovalPrompt", func(t *testing.T) { authReq := storage.AuthRequest{ Prompt: "consent", ForceApprovalPrompt: true, } assert.True(t, authReq.ForceApprovalPrompt) assert.Equal(t, "consent", authReq.Prompt) }) t.Run("max_age default is -1", func(t *testing.T) { authReq := storage.AuthRequest{ MaxAge: -1, } assert.Equal(t, -1, authReq.MaxAge) }) } ================================================ FILE: server/signer/local.go ================================================ package signer import ( "context" "fmt" "log/slog" "time" "github.com/go-jose/go-jose/v4" "github.com/dexidp/dex/storage" ) // LocalConfig holds configuration for the local signer. type LocalConfig struct { // KeysRotationPeriod defines the duration of time after which the signing keys will be rotated. KeysRotationPeriod string `json:"keysRotationPeriod"` } // Open creates a new local signer. func (c *LocalConfig) Open(_ context.Context, s storage.Storage, idTokenValidFor time.Duration, now func() time.Time, logger *slog.Logger) (Signer, error) { rotateKeysAfter, err := time.ParseDuration(c.KeysRotationPeriod) if err != nil { return nil, fmt.Errorf("invalid config value %q for local signer rotation period: %v", c.KeysRotationPeriod, err) } strategy := defaultRotationStrategy(rotateKeysAfter, idTokenValidFor) r := &keyRotator{s, strategy, now, logger} return &localSigner{ storage: s, rotator: r, logger: logger, }, nil } // localSigner signs payloads using keys stored in the Dex storage. // It manages key rotation and storage using the existing keyRotator logic. type localSigner struct { storage storage.Storage rotator *keyRotator logger *slog.Logger } // Start begins key rotation in a new goroutine, closing once the context is canceled. // // The method blocks until after the first attempt to rotate keys has completed. That way // healthy storages will return from this call with valid keys. func (l *localSigner) Start(ctx context.Context) { // Try to rotate immediately so properly configured storages will have keys. if err := l.rotator.rotate(); err != nil { if err == errAlreadyRotated { l.logger.Info("key rotation not needed", "err", err) } else { l.logger.Error("failed to rotate keys", "err", err) } } go func() { for { select { case <-ctx.Done(): return case <-time.After(time.Second * 30): if err := l.rotator.rotate(); err != nil { l.logger.Error("failed to rotate keys", "err", err) } } } }() } func (l *localSigner) Sign(ctx context.Context, payload []byte) (string, error) { keys, err := l.storage.GetKeys(ctx) if err != nil { return "", fmt.Errorf("failed to get keys: %v", err) } signingKey := keys.SigningKey if signingKey == nil { return "", fmt.Errorf("no key to sign payload with") } signingAlg, err := signatureAlgorithm(signingKey) if err != nil { return "", err } return signPayload(signingKey, signingAlg, payload) } func (l *localSigner) ValidationKeys(ctx context.Context) ([]*jose.JSONWebKey, error) { keys, err := l.storage.GetKeys(ctx) if err != nil { return nil, fmt.Errorf("failed to get keys: %v", err) } if keys.SigningKeyPub == nil { return nil, fmt.Errorf("no public keys found") } jwks := make([]*jose.JSONWebKey, len(keys.VerificationKeys)+1) jwks[0] = keys.SigningKeyPub for i, verificationKey := range keys.VerificationKeys { jwks[i+1] = verificationKey.PublicKey } return jwks, nil } func (l *localSigner) Algorithm(_ context.Context) (jose.SignatureAlgorithm, error) { // Local signer always uses RSA keys (see rotationStrategy.key). // TODO(nabokihms): add support for other key types and algorithms in the future. return jose.RS256, nil } ================================================ FILE: server/signer/local_test.go ================================================ package signer import ( "context" "log/slog" "testing" "time" "github.com/go-jose/go-jose/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/dexidp/dex/storage/memory" ) func newTestLocalSigner(t *testing.T) *localSigner { t.Helper() logger := slog.New(slog.DiscardHandler) s := memory.New(logger) r := &keyRotator{ Storage: s, strategy: defaultRotationStrategy(time.Hour, time.Hour), now: time.Now, logger: logger, } return &localSigner{ storage: s, rotator: r, logger: logger, } } func TestLocalSignerAlgorithm(t *testing.T) { ls := newTestLocalSigner(t) // Algorithm should return RS256 even before keys are rotated (empty storage). alg, err := ls.Algorithm(context.Background()) require.NoError(t, err) assert.Equal(t, jose.RS256, alg) } func TestLocalSignerSignAndValidate(t *testing.T) { ls := newTestLocalSigner(t) ctx := context.Background() // Rotate keys so we have a signing key. require.NoError(t, ls.rotator.rotate()) payload := []byte(`{"sub":"test-user"}`) signed, err := ls.Sign(ctx, payload) require.NoError(t, err) assert.NotEmpty(t, signed) // Validation keys should be available. keys, err := ls.ValidationKeys(ctx) require.NoError(t, err) assert.NotEmpty(t, keys) } ================================================ FILE: server/signer/mock.go ================================================ package signer import ( "context" "crypto/rand" "crypto/rsa" "encoding/hex" "io" "github.com/go-jose/go-jose/v4" ) // MockConfig creates a mock signer with a static key for testing. type MockConfig struct { Key *rsa.PrivateKey } // Open creates a new mock signer. func (c *MockConfig) Open(_ context.Context) (Signer, error) { if c.Key == nil { // Generate a new key if not provided key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, err } c.Key = key } // Generate a key ID b := make([]byte, 20) if _, err := io.ReadFull(rand.Reader, b); err != nil { panic(err) } keyID := hex.EncodeToString(b) return &mockSigner{ key: &jose.JSONWebKey{ Key: c.Key, KeyID: keyID, Algorithm: "RS256", Use: "sig", }, pubKey: &jose.JSONWebKey{ Key: c.Key.Public(), KeyID: keyID, Algorithm: "RS256", Use: "sig", }, }, nil } // mockSigner is a simple signer that uses a static RSA key for testing. type mockSigner struct { key *jose.JSONWebKey pubKey *jose.JSONWebKey } func (m *mockSigner) Sign(_ context.Context, payload []byte) (string, error) { return signPayload(m.key, jose.RS256, payload) } func (m *mockSigner) ValidationKeys(_ context.Context) ([]*jose.JSONWebKey, error) { return []*jose.JSONWebKey{m.pubKey}, nil } func (m *mockSigner) Algorithm(_ context.Context) (jose.SignatureAlgorithm, error) { return jose.RS256, nil } func (m *mockSigner) Start(_ context.Context) { // Nothing to do for mock signer } // NewMockSigner creates a mock signer with the provided key for testing. // If key is nil, a new one will be generated. func NewMockSigner(key *rsa.PrivateKey) (Signer, error) { return (&MockConfig{Key: key}).Open(context.Background()) } ================================================ FILE: server/signer/rotation.go ================================================ package signer import ( "context" "crypto/rand" "crypto/rsa" "encoding/hex" "errors" "fmt" "io" "log/slog" "time" "github.com/go-jose/go-jose/v4" "github.com/dexidp/dex/storage" ) var errAlreadyRotated = errors.New("keys already rotated by another server instance") // rotationStrategy describes a strategy for generating cryptographic keys, how // often to rotate them, and how long they can validate signatures after rotation. type rotationStrategy struct { // Time between rotations. rotationFrequency time.Duration // After being rotated how long should the key be kept around for validating // signatures? idTokenValidFor time.Duration // Keys are always RSA keys. Though cryptopasta recommends ECDSA keys, not every // client may support these (e.g. github.com/coreos/go-oidc/oidc). key func() (*rsa.PrivateKey, error) } // defaultRotationStrategy returns a strategy which rotates keys every provided period, // holding onto the public parts for some specified amount of time. func defaultRotationStrategy(rotationFrequency, idTokenValidFor time.Duration) rotationStrategy { return rotationStrategy{ rotationFrequency: rotationFrequency, idTokenValidFor: idTokenValidFor, key: func() (*rsa.PrivateKey, error) { return rsa.GenerateKey(rand.Reader, 2048) }, } } type keyRotator struct { storage.Storage strategy rotationStrategy now func() time.Time logger *slog.Logger } func (k keyRotator) rotate() error { keys, err := k.GetKeys(context.Background()) if err != nil && err != storage.ErrNotFound { return fmt.Errorf("get keys: %v", err) } if k.now().Before(keys.NextRotation) { return nil } k.logger.Info("keys expired, rotating") // Generate the key outside of a storage transaction. key, err := k.strategy.key() if err != nil { return fmt.Errorf("generate key: %v", err) } b := make([]byte, 20) if _, err := io.ReadFull(rand.Reader, b); err != nil { panic(err) } keyID := hex.EncodeToString(b) priv := &jose.JSONWebKey{ Key: key, KeyID: keyID, Algorithm: "RS256", Use: "sig", } pub := &jose.JSONWebKey{ Key: key.Public(), KeyID: keyID, Algorithm: "RS256", Use: "sig", } var nextRotation time.Time err = k.Storage.UpdateKeys(context.Background(), func(keys storage.Keys) (storage.Keys, error) { tNow := k.now() // if you are running multiple instances of dex, another instance // could have already rotated the keys. if tNow.Before(keys.NextRotation) { return storage.Keys{}, errAlreadyRotated } expired := func(key storage.VerificationKey) bool { return tNow.After(key.Expiry) } // Remove any verification keys that have expired. i := 0 for _, key := range keys.VerificationKeys { if !expired(key) { keys.VerificationKeys[i] = key i++ } } keys.VerificationKeys = keys.VerificationKeys[:i] if keys.SigningKeyPub != nil { // Move current signing key to a verification only key, throwing // away the private part. verificationKey := storage.VerificationKey{ PublicKey: keys.SigningKeyPub, // After demoting the signing key, keep the token around for at least // the amount of time an ID Token is valid for. This ensures the // verification key won't expire until all ID Tokens it's signed // expired as well. Expiry: tNow.Add(k.strategy.idTokenValidFor), } keys.VerificationKeys = append(keys.VerificationKeys, verificationKey) } nextRotation = k.now().Add(k.strategy.rotationFrequency) keys.SigningKey = priv keys.SigningKeyPub = pub keys.NextRotation = nextRotation return keys, nil }) if err != nil { return err } k.logger.Info("keys rotated", "next_rotation", nextRotation) return nil } ================================================ FILE: server/signer/rotation_test.go ================================================ package signer import ( "context" "log/slog" "sort" "testing" "time" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/memory" ) func signingKeyID(t *testing.T, s storage.Storage) string { keys, err := s.GetKeys(context.TODO()) if err != nil { t.Fatal(err) } return keys.SigningKey.KeyID } func verificationKeyIDs(t *testing.T, s storage.Storage) (ids []string) { keys, err := s.GetKeys(context.TODO()) if err != nil { t.Fatal(err) } for _, key := range keys.VerificationKeys { ids = append(ids, key.PublicKey.KeyID) } return ids } // slicesEq compare two string slices without modifying the ordering // of the slices. func slicesEq(s1, s2 []string) bool { if len(s1) != len(s2) { return false } cp := func(s []string) []string { c := make([]string, len(s)) copy(c, s) return c } cp1 := cp(s1) cp2 := cp(s2) sort.Strings(cp1) sort.Strings(cp2) for i, el := range cp1 { if el != cp2[i] { return false } } return true } func TestKeyRotator(t *testing.T) { now := time.Now() delta := time.Millisecond rotationFrequency := time.Second * 5 validFor := time.Second * 21 // Only the last 5 verification keys are expected to be kept around. maxVerificationKeys := 5 l := slog.New(slog.DiscardHandler) r := &keyRotator{ Storage: memory.New(l), strategy: defaultRotationStrategy(rotationFrequency, validFor), now: func() time.Time { return now }, logger: l, } var expVerificationKeys []string for i := 0; i < 10; i++ { now = now.Add(rotationFrequency + delta) if err := r.rotate(); err != nil { t.Fatal(err) } got := verificationKeyIDs(t, r.Storage) if !slicesEq(expVerificationKeys, got) { t.Errorf("after %d rotation, expected verification keys %q, got %q", i+1, expVerificationKeys, got) } expVerificationKeys = append(expVerificationKeys, signingKeyID(t, r.Storage)) if n := len(expVerificationKeys); n > maxVerificationKeys { expVerificationKeys = expVerificationKeys[n-maxVerificationKeys:] } } } ================================================ FILE: server/signer/signer.go ================================================ package signer import ( "context" "github.com/go-jose/go-jose/v4" ) // Signer is an interface for signing payloads and retrieving validation keys. type Signer interface { // Sign signs the provided payload. Sign(ctx context.Context, payload []byte) (string, error) // ValidationKeys returns the current public keys used for signature validation. ValidationKeys(ctx context.Context) ([]*jose.JSONWebKey, error) // Algorithm returns the signing algorithm used by this signer. Algorithm(ctx context.Context) (jose.SignatureAlgorithm, error) // Start starts any background tasks required by the signer (e.g., key rotation). Start(ctx context.Context) } ================================================ FILE: server/signer/utils.go ================================================ package signer import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "errors" "fmt" "github.com/go-jose/go-jose/v4" ) func signatureAlgorithm(jwk *jose.JSONWebKey) (alg jose.SignatureAlgorithm, err error) { if jwk.Key == nil { return alg, errors.New("no signing key") } switch key := jwk.Key.(type) { case *rsa.PrivateKey: // Because OIDC mandates that we support RS256, we always return that // value. In the future, we might want to make this configurable on a // per client basis. For example allowing PS256 or ECDSA variants. // // See https://github.com/dexidp/dex/issues/692 return jose.RS256, nil case *ecdsa.PrivateKey: // We don't actually support ECDSA keys yet, but they're tested for // in case we want to in the future. // // These values are prescribed depending on the ECDSA key type. We // can't return different values. switch key.Params() { case elliptic.P256().Params(): return jose.ES256, nil case elliptic.P384().Params(): return jose.ES384, nil case elliptic.P521().Params(): return jose.ES512, nil default: return alg, errors.New("unsupported ecdsa curve") } default: return alg, fmt.Errorf("unsupported signing key type %T", key) } } func signPayload(key *jose.JSONWebKey, alg jose.SignatureAlgorithm, payload []byte) (jws string, err error) { signingKey := jose.SigningKey{Key: key, Algorithm: alg} signer, err := jose.NewSigner(signingKey, &jose.SignerOptions{}) if err != nil { return "", fmt.Errorf("new signer: %v", err) } signature, err := signer.Sign(payload) if err != nil { return "", fmt.Errorf("signing payload: %v", err) } return signature.CompactSerialize() } ================================================ FILE: server/signer/vault.go ================================================ package signer import ( "context" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rsa" "crypto/sha256" "crypto/sha512" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "fmt" "hash" "os" "github.com/go-jose/go-jose/v4" vault "github.com/openbao/openbao/api/v2" ) // VaultConfig holds configuration for the Vault signer. type VaultConfig struct { Addr string `json:"addr"` Token string `json:"token"` KeyName string `json:"keyName"` } // UnmarshalJSON unmarshals a VaultConfig and applies environment variables. // If Addr or Token are not provided in the config, they are read from VAULT_ADDR // and VAULT_TOKEN environment variables respectively. func (c *VaultConfig) UnmarshalJSON(data []byte) error { type Alias VaultConfig aux := &struct { *Alias }{ Alias: (*Alias)(c), } if err := json.Unmarshal(data, &aux); err != nil { return err } // Apply environment variables if config values are empty if c.Addr == "" { if addr := os.Getenv("VAULT_ADDR"); addr != "" { c.Addr = addr } } if c.Token == "" { if token := os.Getenv("VAULT_TOKEN"); token != "" { c.Token = token } } return nil } // Open creates a new Vault signer. func (c *VaultConfig) Open(_ context.Context) (Signer, error) { return newVaultSigner(*c) } // vaultSigner signs payloads using HashiCorp Vault's Transit backend. type vaultSigner struct { client *vault.Client keyName string } // newVaultSigner creates a new Vault signer that uses Transit backend for signing. func newVaultSigner(c VaultConfig) (*vaultSigner, error) { config := vault.DefaultConfig() config.Address = c.Addr client, err := vault.NewClient(config) if err != nil { return nil, fmt.Errorf("failed to create vault client: %v", err) } if c.Token != "" { client.SetToken(c.Token) } return &vaultSigner{ client: client, keyName: c.KeyName, }, nil } func (v *vaultSigner) Start(_ context.Context) { // Vault signer does not need background rotation tasks } func (v *vaultSigner) Sign(ctx context.Context, payload []byte) (string, error) { // 1. Fetch keys to determine the key to use (latest version) and its ID. keysMap, latestVersion, err := v.getTransitKeysMap(ctx) if err != nil { return "", fmt.Errorf("failed to get keys for signing context: %v", err) } // Determine the key version and ID to use // We use the latest version by default signingJWK, ok := keysMap[latestVersion] if !ok { return "", fmt.Errorf("latest key version %d not found in public keys", latestVersion) } // 2. Construct JWS Header and Payload first (Signing Input) header := map[string]interface{}{ "alg": signingJWK.Algorithm, "kid": signingJWK.KeyID, } headerBytes, err := json.Marshal(header) if err != nil { return "", fmt.Errorf("failed to marshal header: %v", err) } headerB64 := base64.RawURLEncoding.EncodeToString(headerBytes) payloadB64 := base64.RawURLEncoding.EncodeToString(payload) // The input to the signature is "header.payload" signingInput := fmt.Sprintf("%s.%s", headerB64, payloadB64) // 3. Sign the signingInput using Vault var vaultInput string data := map[string]interface{}{} // Determine Vault params based on JWS algorithm params, err := getVaultParams(signingJWK.Algorithm) if err != nil { return "", err } // Apply params to data map for k, v := range params.extraParams { data[k] = v } // Hash input if needed if params.hasher != nil { params.hasher.Write([]byte(signingInput)) hash := params.hasher.Sum(nil) vaultInput = base64.StdEncoding.EncodeToString(hash) } else { // No pre-hashing (EdDSA) vaultInput = base64.StdEncoding.EncodeToString([]byte(signingInput)) } data["input"] = vaultInput signPath := fmt.Sprintf("transit/sign/%s", v.keyName) signSecret, err := v.client.Logical().WriteWithContext(ctx, signPath, data) if err != nil { return "", fmt.Errorf("vault sign: %v", err) } signatureString, ok := signSecret.Data["signature"].(string) if !ok { return "", fmt.Errorf("vault response missing signature") } // Parse vault signature: "vault:v1:base64sig" var signatureB64 []byte if len(signatureString) > 8 && signatureString[:6] == "vault:" { parts := splitVaultSignature(signatureString) if len(parts) == 3 { // part 1 is "vault", part 2 is "v1", part 3 is signature // The signature is already base64 encoded, decoding it is not needed and // will make the code failing. signatureB64 = []byte(parts[2]) } } else { return "", fmt.Errorf("unexpected signature format: %s", signatureString) } return fmt.Sprintf("%s.%s.%s", headerB64, payloadB64, signatureB64), nil } func (v *vaultSigner) ValidationKeys(ctx context.Context) ([]*jose.JSONWebKey, error) { keysMap, _, err := v.getTransitKeysMap(ctx) if err != nil { return nil, err } keys := make([]*jose.JSONWebKey, 0, len(keysMap)) for _, k := range keysMap { keys = append(keys, k) } return keys, nil } // getTransitKeysMap returns a map of key_version -> JWK and the latest version number func (v *vaultSigner) getTransitKeysMap(ctx context.Context) (map[int64]*jose.JSONWebKey, int64, error) { path := fmt.Sprintf("transit/keys/%s", v.keyName) secret, err := v.client.Logical().ReadWithContext(ctx, path) if err != nil { return nil, 0, fmt.Errorf("failed to read key from vault: %v", err) } if secret == nil { return nil, 0, fmt.Errorf("key %q not found in vault", v.keyName) } latestVersion, ok := secret.Data["latest_version"].(json.Number) if !ok { // Try float64 which is default for unmarshal interface{} if lv, ok := secret.Data["latest_version"].(float64); ok { latestVersion = json.Number(fmt.Sprintf("%d", int(lv))) } else if lv, ok := secret.Data["latest_version"].(int); ok { latestVersion = json.Number(fmt.Sprintf("%d", lv)) } } latestVerInt, err := latestVersion.Int64() if err != nil { return nil, 0, fmt.Errorf("failed to get latest version: %v", err) } keysObj, ok := secret.Data["keys"].(map[string]interface{}) if !ok { return nil, 0, fmt.Errorf("invalid response from vault") } jwksMap := make(map[int64]*jose.JSONWebKey) for verStr, data := range keysObj { d, ok := data.(map[string]interface{}) if !ok { continue } var ver int64 fmt.Sscanf(verStr, "%d", &ver) pemStr, ok := d["public_key"].(string) if !ok { continue } jwk, err := parsePEMToJWK(pemStr) if err != nil { continue } jwksMap[ver] = jwk } return jwksMap, latestVerInt, nil } func parsePEMToJWK(pemStr string) (*jose.JSONWebKey, error) { block, _ := pem.Decode([]byte(pemStr)) if block == nil { // OpenBao may return ED25519 keys as raw base64-encoded strings instead of PEM // Try to decode as raw base64 ED25519 key keyBytes, err := base64.StdEncoding.DecodeString(pemStr) if err != nil { return nil, fmt.Errorf("failed to parse PEM block or base64: %v", err) } // Check if it's a raw 32-byte ED25519 key var ed25519Key ed25519.PublicKey if len(keyBytes) == 32 { ed25519Key = ed25519.PublicKey(keyBytes) } else { // Try to parse as PKIX public key pub, err := x509.ParsePKIXPublicKey(keyBytes) if err != nil { return nil, fmt.Errorf("failed to parse raw key: %v", err) } // Create JWK for ED25519 key var ok bool ed25519Key, ok = pub.(ed25519.PublicKey) if !ok { return nil, fmt.Errorf("expected ED25519 key, got %T", pub) } } jwk := &jose.JSONWebKey{ Key: ed25519Key, Algorithm: "EdDSA", Use: "sig", } thumbprint, err := jwk.Thumbprint(crypto.SHA256) if err != nil { return nil, err } jwk.KeyID = base64.RawURLEncoding.EncodeToString(thumbprint) return jwk, nil } pub, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse public key: %v", err) } alg := "" switch k := pub.(type) { case *rsa.PublicKey: alg = "RS256" case *ecdsa.PublicKey: switch k.Curve { case elliptic.P256(): alg = "ES256" case elliptic.P384(): alg = "ES384" case elliptic.P521(): alg = "ES512" default: return nil, fmt.Errorf("unsupported ECDSA curve") } case ed25519.PublicKey: alg = "EdDSA" default: return nil, fmt.Errorf("unsupported key type %T", pub) } jwk := &jose.JSONWebKey{ Key: pub, Algorithm: alg, Use: "sig", } thumbprint, err := jwk.Thumbprint(crypto.SHA256) if err != nil { return nil, err } jwk.KeyID = base64.RawURLEncoding.EncodeToString(thumbprint) return jwk, nil } func splitVaultSignature(sig string) []string { // Basic split implementation // "vault:v1:signature" var parts []string start := 0 for i := 0; i < len(sig); i++ { if sig[i] == ':' { parts = append(parts, sig[start:i]) start = i + 1 } } parts = append(parts, sig[start:]) return parts } func (v *vaultSigner) Algorithm(ctx context.Context) (jose.SignatureAlgorithm, error) { keysMap, latestVersion, err := v.getTransitKeysMap(ctx) if err != nil { return "", fmt.Errorf("failed to get keys: %v", err) } signingJWK, ok := keysMap[latestVersion] if !ok { return "", fmt.Errorf("latest key version %d not found", latestVersion) } return jose.SignatureAlgorithm(signingJWK.Algorithm), nil } type vaultAlgoParams struct { hasher hash.Hash extraParams map[string]interface{} } func getVaultParams(alg string) (vaultAlgoParams, error) { params := vaultAlgoParams{ extraParams: map[string]interface{}{ "marshaling_algorithm": "jws", "signature_algorithm": "pkcs1v15", }, } switch alg { case "RS256": params.hasher = sha256.New() params.extraParams["prehashed"] = true params.extraParams["hash_algorithm"] = "sha2-256" case "ES256": params.hasher = sha256.New() params.extraParams["prehashed"] = true params.extraParams["hash_algorithm"] = "sha2-256" case "ES384": params.hasher = sha512.New384() params.extraParams["prehashed"] = true params.extraParams["hash_algorithm"] = "sha2-384" case "ES512": params.hasher = sha512.New() params.extraParams["prehashed"] = true params.extraParams["hash_algorithm"] = "sha2-512" case "EdDSA": // No hashing params.hasher = nil default: return params, fmt.Errorf("unsupported signing algorithm: %s", alg) } return params, nil } ================================================ FILE: server/signer/vault_integration_test.go ================================================ package signer import ( "context" "crypto/rand" "encoding/base64" "encoding/json" "fmt" "os" "testing" "time" "github.com/go-jose/go-jose/v4" vault "github.com/openbao/openbao/api/v2" ) // Conformance tests verify that Vault and OpenBao behave identically with the signer. // These tests use a single SDK (OpenBao API) that works with both systems. // // To run tests for a specific system, set the environment variables: // // For Vault: // DEX_VAULT_ADDR=http://localhost:8200 // DEX_VAULT_TOKEN=root-token // go test -v -run TestVaultSignerConformance // // For OpenBao: // DEX_OPENBAO_ADDR=http://localhost:8210 // DEX_OPENBAO_TOKEN=root-token // go test -v -run TestVaultSignerConformance // // To test both systems in parallel, set both sets of environment variables. type conformanceTestConfig struct { name string addr string token string } // getTestConfigs returns list of test configs based on environment variables func getTestConfigs(t *testing.T) []conformanceTestConfig { var configs []conformanceTestConfig // Check for Vault vaultAddr := os.Getenv("DEX_VAULT_ADDR") vaultToken := os.Getenv("DEX_VAULT_TOKEN") if vaultAddr != "" && vaultToken != "" { configs = append(configs, conformanceTestConfig{ name: "Vault", addr: vaultAddr, token: vaultToken, }) } // Check for OpenBao openbaoAddr := os.Getenv("DEX_OPENBAO_ADDR") openbaoToken := os.Getenv("DEX_OPENBAO_TOKEN") if openbaoAddr != "" && openbaoToken != "" { configs = append(configs, conformanceTestConfig{ name: "OpenBao", addr: openbaoAddr, token: openbaoToken, }) } if len(configs) == 0 { t.Skip("Skipping conformance tests. Set DEX_VAULT_TOKEN+DEX_VAULT_ADDR or DEX_OPENBAO_TOKEN+DEX_OPENBAO_ADDR to run.") } return configs } // TestVaultSignerConformance_SigningAndVerification tests that signing and verification work the same way // across Vault and OpenBao implementations. func TestVaultSignerConformance_SigningAndVerification(t *testing.T) { configs := getTestConfigs(t) testCases := []struct { name string keyType string alg string }{ { name: "RSA-2048", keyType: "rsa-2048", alg: "RS256", }, { name: "ECDSA-P256", keyType: "ecdsa-p256", alg: "ES256", }, { name: "ECDSA-P384", keyType: "ecdsa-p384", alg: "ES384", }, { name: "ED25519", keyType: "ed25519", alg: "EdDSA", }, } for _, config := range configs { t.Run(config.name, func(t *testing.T) { ctx := context.Background() // Create client vaultConfig := vault.DefaultConfig() vaultConfig.Address = config.addr client, err := vault.NewClient(vaultConfig) if err != nil { t.Fatalf("failed to create client: %v", err) } client.SetToken(config.token) // Enable transit engine if err := enableTransitEngine(client); err != nil { t.Fatalf("failed to enable transit engine: %v", err) } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { keyName := fmt.Sprintf("test-key-%s-%s-%d", config.name, tc.keyType, time.Now().Unix()) // Create key keyData := map[string]interface{}{ "type": tc.keyType, } _, err := client.Logical().WriteWithContext(ctx, fmt.Sprintf("transit/keys/%s", keyName), keyData) if err != nil { t.Fatalf("failed to create key: %v", err) } defer cleanupTests(t, ctx, client, keyName) // Create signer signerConfig := VaultConfig{ Addr: config.addr, Token: config.token, KeyName: keyName, } signer, err := newVaultSigner(signerConfig) if err != nil { t.Fatalf("failed to create signer: %v", err) } // Test 1: Verify algorithm alg, err := signer.Algorithm(ctx) if err != nil { t.Fatalf("failed to get algorithm: %v", err) } if string(alg) != tc.alg { t.Errorf("expected algorithm %s, got %s", tc.alg, alg) } // Test 2: Get validation keys keys, err := signer.ValidationKeys(ctx) if err != nil { t.Fatalf("failed to get validation keys: %v", err) } if len(keys) == 0 { t.Fatal("expected at least one validation key") } if keys[0].Algorithm != tc.alg { t.Errorf("expected key algorithm %s, got %s", tc.alg, keys[0].Algorithm) } if keys[0].Use != "sig" { t.Errorf("expected key use 'sig', got %s", keys[0].Use) } // Test 3: Sign and verify JWT payload := map[string]interface{}{ "iss": "https://dex.example.com", "sub": "user123", "aud": "client-app", "exp": time.Now().Add(time.Hour).Unix(), "iat": time.Now().Unix(), } payloadBytes, err := json.Marshal(payload) if err != nil { t.Fatalf("failed to marshal payload: %v", err) } jwtString, err := signer.Sign(ctx, payloadBytes) if err != nil { t.Fatalf("failed to sign payload: %v", err) } // Verify JWT signature jws, err := jose.ParseSigned(jwtString, []jose.SignatureAlgorithm{jose.SignatureAlgorithm(tc.alg)}) if err != nil { t.Fatalf("failed to parse signed JWT: %v", err) } verifiedPayload, err := jws.Verify(keys[0]) if err != nil { t.Fatalf("failed to verify JWT signature: %v", err) } var decodedPayload map[string]interface{} if err := json.Unmarshal(verifiedPayload, &decodedPayload); err != nil { t.Fatalf("failed to unmarshal verified payload: %v", err) } if decodedPayload["sub"] != payload["sub"] { t.Errorf("payload mismatch: expected sub=%s, got %s", payload["sub"], decodedPayload["sub"]) } // Test 4: Multiple signatures with same key for i := 0; i < 3; i++ { randomPayload := make([]byte, 32) _, err := rand.Read(randomPayload) if err != nil { t.Fatalf("failed to generate random payload: %v", err) } payloadData := map[string]interface{}{ "data": base64.StdEncoding.EncodeToString(randomPayload), "iat": time.Now().Unix(), } payloadBytes, err := json.Marshal(payloadData) if err != nil { t.Fatalf("failed to marshal payload: %v", err) } jwtString, err := signer.Sign(ctx, payloadBytes) if err != nil { t.Fatalf("sign attempt %d failed: %v", i+1, err) } jws, err := jose.ParseSigned(jwtString, []jose.SignatureAlgorithm{jose.SignatureAlgorithm(tc.alg)}) if err != nil { t.Fatalf("parse attempt %d failed: %v", i+1, err) } _, err = jws.Verify(keys[0]) if err != nil { t.Fatalf("verify attempt %d failed: %v", i+1, err) } } }) } }) } } // TestVaultSignerConformance_KeyRotation tests that key rotation works identically // across Vault and OpenBao implementations. func TestVaultSignerConformance_KeyRotation(t *testing.T) { configs := getTestConfigs(t) for _, config := range configs { t.Run(config.name, func(t *testing.T) { ctx := context.Background() // Create client vaultConfig := vault.DefaultConfig() vaultConfig.Address = config.addr client, err := vault.NewClient(vaultConfig) if err != nil { t.Fatalf("failed to create client: %v", err) } client.SetToken(config.token) // Enable transit engine if err := enableTransitEngine(client); err != nil { t.Fatalf("failed to enable transit engine: %v", err) } keyName := fmt.Sprintf("test-rotation-key-%s-%d", config.name, time.Now().Unix()) // Create initial key keyData := map[string]interface{}{ "type": "ecdsa-p256", } _, err = client.Logical().WriteWithContext(ctx, fmt.Sprintf("transit/keys/%s", keyName), keyData) if err != nil { t.Fatalf("failed to create key: %v", err) } defer cleanupTests(t, ctx, client, keyName) // Create signer signerConfig := VaultConfig{ Addr: config.addr, Token: config.token, KeyName: keyName, } signer, err := newVaultSigner(signerConfig) if err != nil { t.Fatalf("failed to create signer: %v", err) } // Sign with initial key version payload1 := map[string]interface{}{"version": "v1", "iat": time.Now().Unix()} payload1Bytes, err := json.Marshal(payload1) if err != nil { t.Fatalf("failed to marshal payload: %v", err) } jwt1, err := signer.Sign(ctx, payload1Bytes) if err != nil { t.Fatalf("failed to sign with v1: %v", err) } // Get keys before rotation keysBefore, err := signer.ValidationKeys(ctx) if err != nil { t.Fatalf("failed to get keys before rotation: %v", err) } if len(keysBefore) != 1 { t.Errorf("expected 1 key before rotation, got %d", len(keysBefore)) } // Rotate key _, err = client.Logical().WriteWithContext(ctx, fmt.Sprintf("transit/keys/%s/rotate", keyName), nil) if err != nil { t.Fatalf("failed to rotate key: %v", err) } // Sign with new key version payload2 := map[string]interface{}{"version": "v2", "iat": time.Now().Unix()} payload2Bytes, err := json.Marshal(payload2) if err != nil { t.Fatalf("failed to marshal payload: %v", err) } jwt2, err := signer.Sign(ctx, payload2Bytes) if err != nil { t.Fatalf("failed to sign with v2: %v", err) } // Get keys after rotation keysAfter, err := signer.ValidationKeys(ctx) if err != nil { t.Fatalf("failed to get keys after rotation: %v", err) } if len(keysAfter) != 2 { t.Errorf("expected 2 keys after rotation, got %d", len(keysAfter)) } // Verify both JWTs can be validated with the current keyset jws1, err := jose.ParseSigned(jwt1, []jose.SignatureAlgorithm{jose.ES256}) if err != nil { t.Fatalf("failed to parse jwt1: %v", err) } jws2, err := jose.ParseSigned(jwt2, []jose.SignatureAlgorithm{jose.ES256}) if err != nil { t.Fatalf("failed to parse jwt2: %v", err) } // Find matching keys and verify verified1 := false verified2 := false for _, key := range keysAfter { if _, err := jws1.Verify(key); err == nil { verified1 = true } if _, err := jws2.Verify(key); err == nil { verified2 = true } } if !verified1 { t.Error("failed to verify JWT signed with version 1") } if !verified2 { t.Error("failed to verify JWT signed with version 2") } }) } } // TestVaultSignerConformance_PublicKeyDiscovery tests that public key discovery works identically // across Vault and OpenBao implementations. func TestVaultSignerConformance_PublicKeyDiscovery(t *testing.T) { configs := getTestConfigs(t) for _, config := range configs { t.Run(config.name, func(t *testing.T) { ctx := context.Background() // Create client vaultConfig := vault.DefaultConfig() vaultConfig.Address = config.addr client, err := vault.NewClient(vaultConfig) if err != nil { t.Fatalf("failed to create client: %v", err) } client.SetToken(config.token) // Enable transit engine if err := enableTransitEngine(client); err != nil { t.Fatalf("failed to enable transit engine: %v", err) } keyName := fmt.Sprintf("test-discovery-key-%s-%d", config.name, time.Now().Unix()) // Create key keyData := map[string]interface{}{ "type": "rsa-2048", } _, err = client.Logical().WriteWithContext(ctx, fmt.Sprintf("transit/keys/%s", keyName), keyData) if err != nil { t.Fatalf("failed to create key: %v", err) } defer cleanupTests(t, ctx, client, keyName) // Create signer signerConfig := VaultConfig{ Addr: config.addr, Token: config.token, KeyName: keyName, } signer, err := newVaultSigner(signerConfig) if err != nil { t.Fatalf("failed to create signer: %v", err) } // Get public keys (simulating JWKS endpoint) keys, err := signer.ValidationKeys(ctx) if err != nil { t.Fatalf("failed to get validation keys: %v", err) } // Verify keys have required JWKS fields for i, key := range keys { if key.KeyID == "" { t.Errorf("key %d missing KeyID", i) } if key.Algorithm == "" { t.Errorf("key %d missing Algorithm", i) } if key.Use != "sig" { t.Errorf("key %d has wrong Use field: expected 'sig', got '%s'", i, key.Use) } if key.Key == nil { t.Errorf("key %d missing public key", i) } // Verify key can be marshaled to JWKS format jwksData, err := json.Marshal(key) if err != nil { t.Errorf("key %d cannot be marshaled to JSON: %v", i, err) } var jwksCheck map[string]interface{} if err := json.Unmarshal(jwksData, &jwksCheck); err != nil { t.Errorf("key %d JWKS data is invalid: %v", i, err) } // Check for standard JWKS fields requiredFields := []string{"kty", "use", "kid", "alg"} for _, field := range requiredFields { if _, ok := jwksCheck[field]; !ok { t.Errorf("key %d missing required JWKS field: %s", i, field) } } } // Sign a JWT payload := map[string]interface{}{ "iss": "https://dex.example.com", "sub": "test-user", "aud": "test-client", "exp": time.Now().Add(time.Hour).Unix(), "iat": time.Now().Unix(), } payloadBytes, err := json.Marshal(payload) if err != nil { t.Fatalf("failed to marshal payload: %v", err) } jwtString, err := signer.Sign(ctx, payloadBytes) if err != nil { t.Fatalf("failed to sign JWT: %v", err) } // Parse JWT and verify it has correct kid in header jws, err := jose.ParseSigned(jwtString, []jose.SignatureAlgorithm{jose.RS256}) if err != nil { t.Fatalf("failed to parse JWT: %v", err) } if len(jws.Signatures) == 0 { t.Fatal("JWT has no signatures") } kid := jws.Signatures[0].Header.KeyID if kid == "" { t.Error("JWT header missing kid") } // Verify kid matches one of the public keys kidFound := false for _, key := range keys { if key.KeyID == kid { kidFound = true break } } if !kidFound { t.Errorf("JWT kid '%s' not found in public keys", kid) } }) } } // enableTransitEngine enables the transit secrets engine if not already enabled. func enableTransitEngine(client *vault.Client) error { // Check if already enabled mounts, err := client.Sys().ListMounts() if err != nil { return fmt.Errorf("failed to list mounts: %v", err) } if _, exists := mounts["transit/"]; exists { return nil } // Enable transit engine mountInput := &vault.MountInput{ Type: "transit", } if err := client.Sys().Mount("transit", mountInput); err != nil { return fmt.Errorf("failed to mount transit: %v", err) } return nil } func cleanupTests(t *testing.T, ctx context.Context, client *vault.Client, keyName string) { updateData := map[string]interface{}{ "deletion_allowed": true, } _, err := client.Logical().WriteWithContext(ctx, fmt.Sprintf("transit/keys/%s/config", keyName), updateData) if err != nil { t.Logf("failed to update key config: %v", err) } _, err = client.Logical().DeleteWithContext(ctx, fmt.Sprintf("transit/keys/%s", keyName)) if err != nil { t.Logf("failed to delete key: %v", err) } } ================================================ FILE: server/signer/vault_test.go ================================================ package signer import ( "encoding/json" "os" "testing" ) func TestVaultConfigUnmarshalJSON_WithEnvVars(t *testing.T) { // Save original environment variables originalAddr := os.Getenv("VAULT_ADDR") originalToken := os.Getenv("VAULT_TOKEN") defer func() { os.Setenv("VAULT_ADDR", originalAddr) os.Setenv("VAULT_TOKEN", originalToken) }() // Set environment variables os.Setenv("VAULT_ADDR", "http://vault.example.com:8200") os.Setenv("VAULT_TOKEN", "s.xxxxxxxxxxxxxxxx") tests := []struct { name string json string want VaultConfig wantErr bool }{ { name: "empty config uses env vars", json: `{"keyName": "signing-key"}`, want: VaultConfig{ Addr: "http://vault.example.com:8200", Token: "s.xxxxxxxxxxxxxxxx", KeyName: "signing-key", }, wantErr: false, }, { name: "config values override env vars", json: `{"addr": "http://custom.vault.com:8200", "token": "s.custom", "keyName": "signing-key"}`, want: VaultConfig{ Addr: "http://custom.vault.com:8200", Token: "s.custom", KeyName: "signing-key", }, wantErr: false, }, { name: "partial config uses env vars for missing values", json: `{"addr": "http://custom.vault.com:8200", "keyName": "signing-key"}`, want: VaultConfig{ Addr: "http://custom.vault.com:8200", Token: "s.xxxxxxxxxxxxxxxx", KeyName: "signing-key", }, wantErr: false, }, { name: "empty token in config uses env var", json: `{"addr": "http://custom.vault.com:8200", "token": "", "keyName": "signing-key"}`, want: VaultConfig{ Addr: "http://custom.vault.com:8200", Token: "s.xxxxxxxxxxxxxxxx", KeyName: "signing-key", }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got VaultConfig err := json.Unmarshal([]byte(tt.json), &got) if (err != nil) != tt.wantErr { t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if got.Addr != tt.want.Addr { t.Errorf("Addr: got %q, want %q", got.Addr, tt.want.Addr) } if got.Token != tt.want.Token { t.Errorf("Token: got %q, want %q", got.Token, tt.want.Token) } if got.KeyName != tt.want.KeyName { t.Errorf("KeyName: got %q, want %q", got.KeyName, tt.want.KeyName) } }) } } func TestVaultConfigUnmarshalJSON_WithoutEnvVars(t *testing.T) { // Save original environment variables originalAddr := os.Getenv("VAULT_ADDR") originalToken := os.Getenv("VAULT_TOKEN") defer func() { os.Setenv("VAULT_ADDR", originalAddr) os.Setenv("VAULT_TOKEN", originalToken) }() // Unset environment variables os.Unsetenv("VAULT_ADDR") os.Unsetenv("VAULT_TOKEN") tests := []struct { name string json string want VaultConfig wantErr bool }{ { name: "config values used when env vars not set", json: `{"addr": "http://vault.example.com:8200", "token": "s.xxxxxxxxxxxxxxxx", "keyName": "signing-key"}`, want: VaultConfig{ Addr: "http://vault.example.com:8200", Token: "s.xxxxxxxxxxxxxxxx", KeyName: "signing-key", }, wantErr: false, }, { name: "empty config when env vars not set", json: `{"keyName": "signing-key"}`, want: VaultConfig{ Addr: "", Token: "", KeyName: "signing-key", }, wantErr: false, }, { name: "only keyName required in config", json: `{"keyName": "my-key"}`, want: VaultConfig{ Addr: "", Token: "", KeyName: "my-key", }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got VaultConfig err := json.Unmarshal([]byte(tt.json), &got) if (err != nil) != tt.wantErr { t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if got.Addr != tt.want.Addr { t.Errorf("Addr: got %q, want %q", got.Addr, tt.want.Addr) } if got.Token != tt.want.Token { t.Errorf("Token: got %q, want %q", got.Token, tt.want.Token) } if got.KeyName != tt.want.KeyName { t.Errorf("KeyName: got %q, want %q", got.KeyName, tt.want.KeyName) } }) } } func TestVaultConfigUnmarshalJSON_InvalidJSON(t *testing.T) { tests := []struct { name string json string wantErr bool }{ { name: "invalid json", json: `{invalid json}`, wantErr: true, }, { name: "empty json", json: `{}`, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got VaultConfig err := json.Unmarshal([]byte(tt.json), &got) if (err != nil) != tt.wantErr { t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } }) } } ================================================ FILE: server/templates.go ================================================ package server import ( "fmt" "html/template" "io" "io/fs" "net/http" "net/url" "path" "sort" "strings" "github.com/Masterminds/sprig/v3" ) const ( tmplApproval = "approval.html" tmplLogin = "login.html" tmplPassword = "password.html" tmplOOB = "oob.html" tmplError = "error.html" tmplDevice = "device.html" tmplDeviceSuccess = "device_success.html" tmplTOTPVerify = "totp_verify.html" ) var requiredTmpls = []string{ tmplApproval, tmplLogin, tmplPassword, tmplOOB, tmplError, tmplDevice, tmplDeviceSuccess, } type templates struct { loginTmpl *template.Template approvalTmpl *template.Template passwordTmpl *template.Template oobTmpl *template.Template errorTmpl *template.Template deviceTmpl *template.Template deviceSuccessTmpl *template.Template totpVerifyTmpl *template.Template } type webConfig struct { webFS fs.FS logoURL string issuer string theme string issuerURL string extra map[string]string } func getFuncMap(c webConfig) (template.FuncMap, error) { funcs := sprig.FuncMap() issuerURL, err := url.Parse(c.issuerURL) if err != nil { return nil, fmt.Errorf("error parsing issuerURL: %v", err) } additionalFuncs := map[string]interface{}{ "extra": func(k string) string { return c.extra[k] }, "issuer": func() string { return c.issuer }, "logo": func() string { return c.logoURL }, "url": func(reqPath, assetPath string) string { return relativeURL(issuerURL.Path, reqPath, assetPath) }, } for k, v := range additionalFuncs { funcs[k] = v } return funcs, nil } // loadWebConfig returns static assets, theme assets, and templates used by the frontend by // reading the dir specified in the webConfig. If directory is not specified it will // use the file system specified by webFS. // // The directory layout is expected to be: // // ( web directory ) // |- static // |- themes // | |- (theme name) // |- templates func loadWebConfig(c webConfig) (http.Handler, http.Handler, http.HandlerFunc, *templates, error) { // fallback to the default theme if the legacy theme name is provided if c.theme == "coreos" || c.theme == "tectonic" { c.theme = "" } if c.theme == "" { c.theme = "light" } if c.issuer == "" { c.issuer = "dex" } if c.logoURL == "" { c.logoURL = "theme/logo.png" } staticFiles, err := fs.Sub(c.webFS, "static") if err != nil { return nil, nil, nil, nil, fmt.Errorf("read static dir: %v", err) } themeFiles, err := fs.Sub(c.webFS, path.Join("themes", c.theme)) if err != nil { return nil, nil, nil, nil, fmt.Errorf("read themes dir: %v", err) } robotsContent, err := fs.ReadFile(c.webFS, "robots.txt") if err != nil { return nil, nil, nil, nil, fmt.Errorf("read robots.txt dir: %v", err) } static := http.FileServer(http.FS(staticFiles)) theme := http.FileServer(http.FS(themeFiles)) robots := func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, string(robotsContent)) } templates, err := loadTemplates(c, "templates") return static, theme, robots, templates, err } // loadTemplates parses the expected templates from the provided directory. func loadTemplates(c webConfig, templatesDir string) (*templates, error) { files, err := fs.ReadDir(c.webFS, templatesDir) if err != nil { return nil, fmt.Errorf("read dir: %v", err) } filenames := []string{} for _, file := range files { if file.IsDir() { continue } filenames = append(filenames, path.Join(templatesDir, file.Name())) } if len(filenames) == 0 { return nil, fmt.Errorf("no files in template dir %q", templatesDir) } funcs, err := getFuncMap(c) if err != nil { return nil, err } tmpls, err := template.New("").Funcs(funcs).ParseFS(c.webFS, filenames...) if err != nil { return nil, fmt.Errorf("parse files: %v", err) } missingTmpls := []string{} for _, tmplName := range requiredTmpls { if tmpls.Lookup(tmplName) == nil { missingTmpls = append(missingTmpls, tmplName) } } if len(missingTmpls) > 0 { return nil, fmt.Errorf("missing template(s): %s", missingTmpls) } return &templates{ loginTmpl: tmpls.Lookup(tmplLogin), approvalTmpl: tmpls.Lookup(tmplApproval), passwordTmpl: tmpls.Lookup(tmplPassword), oobTmpl: tmpls.Lookup(tmplOOB), errorTmpl: tmpls.Lookup(tmplError), deviceTmpl: tmpls.Lookup(tmplDevice), deviceSuccessTmpl: tmpls.Lookup(tmplDeviceSuccess), totpVerifyTmpl: tmpls.Lookup(tmplTOTPVerify), }, nil } // relativeURL returns the URL of the asset relative to the URL of the request path. // The serverPath is consulted to trim any prefix due in case it is not listening // to the root path. // // Algorithm: // 1. Remove common prefix of serverPath and reqPath // 2. Remove common prefix of assetPath and reqPath // 3. For each part of reqPath remaining(minus one), go up one level (..) // 4. For each part of assetPath remaining, append it to result // // eg // server listens at localhost/dex so serverPath is dex // reqPath is /dex/auth // assetPath is static/main.css // relativeURL("/dex", "/dex/auth", "static/main.css") = "../static/main.css" func relativeURL(serverPath, reqPath, assetPath string) string { if u, err := url.ParseRequestURI(assetPath); err == nil && u.Scheme != "" { // assetPath points to the external URL, no changes needed return assetPath } splitPath := func(p string) []string { res := []string{} parts := strings.Split(path.Clean(p), "/") for _, part := range parts { if part != "" { res = append(res, part) } } return res } stripCommonParts := func(s1, s2 []string) ([]string, []string) { min := len(s1) if len(s2) < min { min = len(s2) } splitIndex := min for i := 0; i < min; i++ { if s1[i] != s2[i] { splitIndex = i break } } return s1[splitIndex:], s2[splitIndex:] } server, req, asset := splitPath(serverPath), splitPath(reqPath), splitPath(assetPath) // Remove common prefix of request path with server path _, req = stripCommonParts(server, req) // Remove common prefix of request path with asset path asset, req = stripCommonParts(asset, req) // For each part of the request remaining (minus one) -> go up one level (..) // For each part of the asset remaining -> append it var relativeURL string for i := 0; i < len(req)-1; i++ { relativeURL = path.Join("..", relativeURL) } relativeURL = path.Join(relativeURL, path.Join(asset...)) return relativeURL } var scopeDescriptions = map[string]string{ "offline_access": "Have offline access", "profile": "View basic profile information", "email": "View your email address", // 'groups' is not a standard OIDC scope, and Dex only returns groups only if the upstream provider does too. // This warning is added for convenience to show that the user may expose some sensitive data to the application. "groups": "View your groups", } type connectorInfo struct { ID string Name string URL template.URL Type string } type byName []connectorInfo func (n byName) Len() int { return len(n) } func (n byName) Less(i, j int) bool { return n[i].Name < n[j].Name } func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] } func (t *templates) device(r *http.Request, w http.ResponseWriter, postURL string, userCode string, lastWasInvalid bool) error { if lastWasInvalid { w.WriteHeader(http.StatusBadRequest) } data := struct { PostURL string UserCode string Invalid bool ReqPath string }{postURL, userCode, lastWasInvalid, r.URL.Path} return renderTemplate(w, t.deviceTmpl, data) } func (t *templates) deviceSuccess(r *http.Request, w http.ResponseWriter, clientName string) error { data := struct { ClientName string ReqPath string }{clientName, r.URL.Path} return renderTemplate(w, t.deviceSuccessTmpl, data) } func (t *templates) login(r *http.Request, w http.ResponseWriter, connectors []connectorInfo) error { sort.Sort(byName(connectors)) data := struct { Connectors []connectorInfo ReqPath string }{connectors, r.URL.Path} return renderTemplate(w, t.loginTmpl, data) } func (t *templates) password(r *http.Request, w http.ResponseWriter, postURL, lastUsername, usernamePrompt string, lastWasInvalid bool, backLink string, rememberMe *bool) error { if lastWasInvalid { w.WriteHeader(http.StatusUnauthorized) } data := struct { PostURL string BackLink string Username string UsernamePrompt string Invalid bool ReqPath string ShowRememberMe bool RememberMeChecked bool }{ PostURL: postURL, BackLink: backLink, Username: lastUsername, UsernamePrompt: usernamePrompt, Invalid: lastWasInvalid, ReqPath: r.URL.Path, ShowRememberMe: rememberMe != nil, } if rememberMe != nil { data.RememberMeChecked = *rememberMe } return renderTemplate(w, t.passwordTmpl, data) } func (t *templates) approval(r *http.Request, w http.ResponseWriter, authReqID, username, clientName string, scopes []string) error { accesses := []string{} for _, scope := range scopes { access, ok := scopeDescriptions[scope] if ok { accesses = append(accesses, access) } } sort.Strings(accesses) data := struct { User string Client string AuthReqID string Scopes []string ReqPath string }{username, clientName, authReqID, accesses, r.URL.Path} return renderTemplate(w, t.approvalTmpl, data) } func (t *templates) totpVerify(r *http.Request, w http.ResponseWriter, postURL, issuer, connector, qrCode string, lastWasInvalid bool) error { if lastWasInvalid { w.WriteHeader(http.StatusUnauthorized) } data := struct { PostURL string Invalid bool Issuer string Connector string QRCode string ReqPath string }{postURL, lastWasInvalid, issuer, connector, qrCode, r.URL.Path} return renderTemplate(w, t.totpVerifyTmpl, data) } func (t *templates) oob(r *http.Request, w http.ResponseWriter, code string) error { data := struct { Code string ReqPath string }{code, r.URL.Path} return renderTemplate(w, t.oobTmpl, data) } func (t *templates) err(r *http.Request, w http.ResponseWriter, errCode int, errMsg string) error { w.WriteHeader(errCode) data := struct { ErrType string ErrMsg string ReqPath string }{http.StatusText(errCode), errMsg, r.URL.Path} if err := t.errorTmpl.Execute(w, data); err != nil { return fmt.Errorf("rendering template %s failed: %s", t.errorTmpl.Name(), err) } return nil } // small io.Writer utility to determine if executing the template wrote to the underlying response writer. type writeRecorder struct { wrote bool w io.Writer } func (w *writeRecorder) Write(p []byte) (n int, err error) { w.wrote = true return w.w.Write(p) } func renderTemplate(w http.ResponseWriter, tmpl *template.Template, data interface{}) error { wr := &writeRecorder{w: w} if err := tmpl.Execute(wr, data); err != nil { if !wr.wrote { // TODO(ericchiang): replace with better internal server error. http.Error(w, "Internal server error", http.StatusInternalServerError) } return fmt.Errorf("rendering template %s failed: %s", tmpl.Name(), err) } return nil } ================================================ FILE: server/templates_test.go ================================================ package server import "testing" func TestRelativeURL(t *testing.T) { tests := []struct { name string serverPath string reqPath string assetPath string expected string }{ { name: "server-root-req-one-level-asset-two-level", serverPath: "/", reqPath: "/auth", assetPath: "/theme/main.css", expected: "theme/main.css", }, { name: "server-one-level-req-one-level-asset-two-level", serverPath: "/dex", reqPath: "/dex/auth", assetPath: "/theme/main.css", expected: "theme/main.css", }, { name: "server-root-req-two-level-asset-three-level", serverPath: "/dex", reqPath: "/dex/auth/connector", assetPath: "assets/css/main.css", expected: "../assets/css/main.css", }, { name: "external-url", serverPath: "/dex", reqPath: "/dex/auth/connector", assetPath: "https://kubernetes.io/images/favicon.png", expected: "https://kubernetes.io/images/favicon.png", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { actual := relativeURL(test.serverPath, test.reqPath, test.assetPath) if actual != test.expected { t.Fatalf("Got '%s'. Expected '%s'", actual, test.expected) } }) } } ================================================ FILE: storage/conformance/conformance.go ================================================ // Package conformance provides conformance tests for storage implementations. package conformance import ( "context" "reflect" "sort" "testing" "time" jose "github.com/go-jose/go-jose/v4" "github.com/kylelemons/godebug/pretty" "github.com/stretchr/testify/require" "golang.org/x/crypto/bcrypt" "github.com/dexidp/dex/storage" ) // ensure that values being tested on never expire. var neverExpire = time.Now().UTC().Add(time.Hour * 24 * 365 * 100) // defaultAuthTime is a non-zero time used as AuthTime default in tests. // MySQL rejects Go's zero time (0001-01-01), so all test fixtures must use a real value. var defaultAuthTime = time.Now().UTC() type subTest struct { name string run func(t *testing.T, s storage.Storage) } func runTests(t *testing.T, newStorage func(t *testing.T) storage.Storage, tests []subTest) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { s := newStorage(t) test.run(t, s) s.Close() }) } } // RunTests runs a set of conformance tests against a storage. newStorage should // return an initialized but empty storage. The storage will be closed at the // end of each test run. func RunTests(t *testing.T, newStorage func(t *testing.T) storage.Storage) { runTests(t, newStorage, []subTest{ {"AuthCodeCRUD", testAuthCodeCRUD}, {"AuthRequestCRUD", testAuthRequestCRUD}, {"ClientCRUD", testClientCRUD}, {"RefreshTokenCRUD", testRefreshTokenCRUD}, {"PasswordCRUD", testPasswordCRUD}, {"KeysCRUD", testKeysCRUD}, {"OfflineSessionCRUD", testOfflineSessionCRUD}, {"ConnectorCRUD", testConnectorCRUD}, {"GarbageCollection", testGC}, {"TimezoneSupport", testTimezones}, {"DeviceRequestCRUD", testDeviceRequestCRUD}, {"DeviceTokenCRUD", testDeviceTokenCRUD}, {"UserIdentityCRUD", testUserIdentityCRUD}, {"AuthSessionCRUD", testAuthSessionCRUD}, }) } func mustLoadJWK(b string) *jose.JSONWebKey { var jwt jose.JSONWebKey if err := jwt.UnmarshalJSON([]byte(b)); err != nil { panic(err) } return &jwt } func mustBeErrNotFound(t *testing.T, kind string, err error) { switch { case err == nil: t.Errorf("deleting nonexistent %s should return an error", kind) case err != storage.ErrNotFound: t.Errorf("deleting %s expected storage.ErrNotFound, got %v", kind, err) } } func mustBeErrAlreadyExists(t *testing.T, kind string, err error) { switch { case err == nil: t.Errorf("attempting to create an existing %s should return an error", kind) case err != storage.ErrAlreadyExists: t.Errorf("creating an existing %s expected storage.ErrAlreadyExists, got %v", kind, err) } } func testAuthRequestCRUD(t *testing.T, s storage.Storage) { ctx := t.Context() codeChallenge := storage.PKCE{ CodeChallenge: "code_challenge_test", CodeChallengeMethod: "plain", } a1 := storage.AuthRequest{ ID: storage.NewID(), ClientID: "client1", ResponseTypes: []string{"code"}, Scopes: []string{"openid", "email"}, RedirectURI: "https://localhost:80/callback", Nonce: "foo", State: "bar", ForceApprovalPrompt: true, LoggedIn: true, Expiry: neverExpire, AuthTime: defaultAuthTime, ConnectorID: "ldap", ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, PKCE: codeChallenge, HMACKey: []byte("hmac_key"), } identity := storage.Claims{Email: "foobar"} if err := s.CreateAuthRequest(ctx, a1); err != nil { t.Fatalf("failed creating auth request: %v", err) } // Attempt to create same AuthRequest twice. err := s.CreateAuthRequest(ctx, a1) mustBeErrAlreadyExists(t, "auth request", err) a2 := storage.AuthRequest{ ID: storage.NewID(), ClientID: "client2", ResponseTypes: []string{"code"}, Scopes: []string{"openid", "email"}, RedirectURI: "https://localhost:80/callback", Nonce: "bar", State: "foo", ForceApprovalPrompt: true, LoggedIn: true, Expiry: neverExpire, AuthTime: defaultAuthTime, ConnectorID: "ldap", ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "2", Username: "john", Email: "john.doe@example.com", EmailVerified: true, Groups: []string{"a"}, }, HMACKey: []byte("hmac_key"), } if err := s.CreateAuthRequest(ctx, a2); err != nil { t.Fatalf("failed creating auth request: %v", err) } if err := s.UpdateAuthRequest(ctx, a1.ID, func(old storage.AuthRequest) (storage.AuthRequest, error) { old.Claims = identity old.ConnectorID = "connID" return old, nil }); err != nil { t.Fatalf("failed to update auth request: %v", err) } got, err := s.GetAuthRequest(ctx, a1.ID) if err != nil { t.Fatalf("failed to get auth req: %v", err) } if !reflect.DeepEqual(got.Claims, identity) { t.Fatalf("update failed, wanted identity=%#v got %#v", identity, got.Claims) } if !reflect.DeepEqual(got.PKCE, codeChallenge) { t.Fatalf("storage does not support PKCE, wanted challenge=%#v got %#v", codeChallenge, got.PKCE) } if err := s.DeleteAuthRequest(ctx, a1.ID); err != nil { t.Fatalf("failed to delete auth request: %v", err) } if err := s.DeleteAuthRequest(ctx, a2.ID); err != nil { t.Fatalf("failed to delete auth request: %v", err) } _, err = s.GetAuthRequest(ctx, a1.ID) mustBeErrNotFound(t, "auth request", err) } func testAuthCodeCRUD(t *testing.T, s storage.Storage) { ctx := t.Context() a1 := storage.AuthCode{ ID: storage.NewID(), ClientID: "client1", RedirectURI: "https://localhost:80/callback", Nonce: "foobar", Scopes: []string{"openid", "email"}, Expiry: neverExpire, AuthTime: defaultAuthTime, ConnectorID: "ldap", ConnectorData: []byte(`{"some":"data"}`), PKCE: storage.PKCE{ CodeChallenge: "12345", CodeChallengeMethod: "Whatever", }, Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, } if err := s.CreateAuthCode(ctx, a1); err != nil { t.Fatalf("failed creating auth code: %v", err) } a2 := storage.AuthCode{ ID: storage.NewID(), ClientID: "client2", RedirectURI: "https://localhost:80/callback", Nonce: "foobar", Scopes: []string{"openid", "email"}, Expiry: neverExpire, AuthTime: defaultAuthTime, ConnectorID: "ldap", ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "2", Username: "john", Email: "john.doe@example.com", EmailVerified: true, Groups: []string{"a"}, }, } // Attempt to create same AuthCode twice. err := s.CreateAuthCode(ctx, a1) mustBeErrAlreadyExists(t, "auth code", err) if err := s.CreateAuthCode(ctx, a2); err != nil { t.Fatalf("failed creating auth code: %v", err) } got, err := s.GetAuthCode(ctx, a1.ID) if err != nil { t.Fatalf("failed to get auth code: %v", err) } if a1.Expiry.Unix() != got.Expiry.Unix() { t.Errorf("auth code expiry did not match want=%s vs got=%s", a1.Expiry, got.Expiry) } got.Expiry = a1.Expiry // time fields do not compare well got.AuthTime = a1.AuthTime // time fields do not compare well if diff := pretty.Compare(a1, got); diff != "" { t.Errorf("auth code retrieved from storage did not match: %s", diff) } if err := s.DeleteAuthCode(ctx, a1.ID); err != nil { t.Fatalf("delete auth code: %v", err) } if err := s.DeleteAuthCode(ctx, a2.ID); err != nil { t.Fatalf("delete auth code: %v", err) } _, err = s.GetAuthCode(ctx, a1.ID) mustBeErrNotFound(t, "auth code", err) } func testClientCRUD(t *testing.T, s storage.Storage) { ctx := t.Context() id1 := storage.NewID() c1 := storage.Client{ ID: id1, Secret: "foobar", RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"}, Name: "dex client", LogoURL: "https://goo.gl/JIyzIC", AllowedConnectors: []string{"github", "google"}, } err := s.DeleteClient(ctx, id1) mustBeErrNotFound(t, "client", err) if err := s.CreateClient(ctx, c1); err != nil { t.Fatalf("create client: %v", err) } // Attempt to create same Client twice. err = s.CreateClient(ctx, c1) mustBeErrAlreadyExists(t, "client", err) id2 := storage.NewID() c2 := storage.Client{ ID: id2, Secret: "barfoo", RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"}, Name: "dex client", LogoURL: "https://goo.gl/JIyzIC", } if err := s.CreateClient(ctx, c2); err != nil { t.Fatalf("create client: %v", err) } getAndCompare := func(_ string, want storage.Client) { gc, err := s.GetClient(ctx, id1) if err != nil { t.Errorf("get client: %v", err) return } if diff := pretty.Compare(want, gc); diff != "" { t.Errorf("client retrieved from storage did not match: %s", diff) } } getAndCompare(id1, c1) newSecret := "barfoo" err = s.UpdateClient(ctx, id1, func(old storage.Client) (storage.Client, error) { old.Secret = newSecret return old, nil }) if err != nil { t.Errorf("update client: %v", err) } c1.Secret = newSecret getAndCompare(id1, c1) if err := s.DeleteClient(ctx, id1); err != nil { t.Fatalf("delete client: %v", err) } if err := s.DeleteClient(ctx, id2); err != nil { t.Fatalf("delete client: %v", err) } _, err = s.GetClient(ctx, id1) mustBeErrNotFound(t, "client", err) } func testRefreshTokenCRUD(t *testing.T, s storage.Storage) { ctx := t.Context() id := storage.NewID() refresh := storage.RefreshToken{ ID: id, Token: "bar", ObsoleteToken: "", Nonce: "foo", ClientID: "client_id", ConnectorID: "client_secret", Scopes: []string{"openid", "email", "profile"}, CreatedAt: time.Now().UTC().Round(time.Millisecond), LastUsed: time.Now().UTC().Round(time.Millisecond), Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, ConnectorData: []byte(`{"some":"data"}`), } if err := s.CreateRefresh(ctx, refresh); err != nil { t.Fatalf("create refresh token: %v", err) } // Attempt to create same Refresh Token twice. err := s.CreateRefresh(ctx, refresh) mustBeErrAlreadyExists(t, "refresh token", err) getAndCompare := func(id string, want storage.RefreshToken) { gr, err := s.GetRefresh(ctx, id) if err != nil { t.Errorf("get refresh: %v", err) return } if diff := pretty.Compare(gr.CreatedAt.UnixNano(), gr.CreatedAt.UnixNano()); diff != "" { t.Errorf("refresh token created timestamp retrieved from storage did not match: %s", diff) } if diff := pretty.Compare(gr.LastUsed.UnixNano(), gr.LastUsed.UnixNano()); diff != "" { t.Errorf("refresh token last used timestamp retrieved from storage did not match: %s", diff) } gr.CreatedAt = time.Time{} gr.LastUsed = time.Time{} want.CreatedAt = time.Time{} want.LastUsed = time.Time{} if diff := pretty.Compare(want, gr); diff != "" { t.Errorf("refresh token retrieved from storage did not match: %s", diff) } } getAndCompare(id, refresh) id2 := storage.NewID() refresh2 := storage.RefreshToken{ ID: id2, Token: "bar_2", ObsoleteToken: refresh.Token, Nonce: "foo_2", ClientID: "client_id_2", ConnectorID: "client_secret", Scopes: []string{"openid", "email", "profile"}, CreatedAt: time.Now().UTC().Round(time.Millisecond), LastUsed: time.Now().UTC().Round(time.Millisecond), Claims: storage.Claims{ UserID: "2", Username: "john", Email: "john.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, ConnectorData: []byte(`{"some":"data"}`), } if err := s.CreateRefresh(ctx, refresh2); err != nil { t.Fatalf("create second refresh token: %v", err) } getAndCompare(id2, refresh2) updatedAt := time.Now().UTC().Round(time.Millisecond) updater := func(r storage.RefreshToken) (storage.RefreshToken, error) { r.Token = "spam" r.LastUsed = updatedAt return r, nil } if err := s.UpdateRefreshToken(ctx, id, updater); err != nil { t.Errorf("failed to update refresh token: %v", err) } refresh.Token = "spam" refresh.LastUsed = updatedAt getAndCompare(id, refresh) // Ensure that updating the first token doesn't impact the second. Issue #847. getAndCompare(id2, refresh2) if err := s.DeleteRefresh(ctx, id); err != nil { t.Fatalf("failed to delete refresh request: %v", err) } if err := s.DeleteRefresh(ctx, id2); err != nil { t.Fatalf("failed to delete refresh request: %v", err) } _, err = s.GetRefresh(ctx, id) mustBeErrNotFound(t, "refresh token", err) } type byEmail []storage.Password func (n byEmail) Len() int { return len(n) } func (n byEmail) Less(i, j int) bool { return n[i].Email < n[j].Email } func (n byEmail) Swap(i, j int) { n[i], n[j] = n[j], n[i] } func boolPtr(v bool) *bool { return &v } func testPasswordCRUD(t *testing.T, s storage.Storage) { ctx := t.Context() // Use bcrypt.MinCost to keep the tests short. passwordHash1, err := bcrypt.GenerateFromPassword([]byte("secret"), bcrypt.MinCost) if err != nil { t.Fatal(err) } password1 := storage.Password{ Email: "jane@example.com", Hash: passwordHash1, Username: "jane", Name: "Jane Doe", PreferredUsername: "jane-public", EmailVerified: boolPtr(true), UserID: "foobar", Groups: []string{"team-a", "team-a/admins"}, } if err := s.CreatePassword(ctx, password1); err != nil { t.Fatalf("create password token: %v", err) } // Attempt to create same Password twice. err = s.CreatePassword(ctx, password1) mustBeErrAlreadyExists(t, "password", err) passwordHash2, err := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.MinCost) if err != nil { t.Fatal(err) } password2 := storage.Password{ Email: "john@example.com", Hash: passwordHash2, Username: "john", Name: "John Smith", PreferredUsername: "john-public", EmailVerified: boolPtr(false), UserID: "barfoo", Groups: []string{"team-b"}, } if err := s.CreatePassword(ctx, password2); err != nil { t.Fatalf("create password token: %v", err) } getAndCompare := func(id string, want storage.Password) { gr, err := s.GetPassword(ctx, id) if err != nil { t.Errorf("get password %q: %v", id, err) return } if diff := pretty.Compare(want, gr); diff != "" { t.Errorf("password retrieved from storage did not match: %s", diff) } } getAndCompare("jane@example.com", password1) getAndCompare("JANE@example.com", password1) // Emails should be case insensitive if err := s.UpdatePassword(ctx, password1.Email, func(old storage.Password) (storage.Password, error) { old.Username = "jane doe" return old, nil }); err != nil { t.Fatalf("failed to update auth request: %v", err) } password1.Username = "jane doe" getAndCompare("jane@example.com", password1) var passwordList []storage.Password passwordList = append(passwordList, password1, password2) listAndCompare := func(want []storage.Password) { passwords, err := s.ListPasswords(ctx) if err != nil { t.Errorf("list password: %v", err) return } sort.Sort(byEmail(want)) sort.Sort(byEmail(passwords)) if diff := pretty.Compare(want, passwords); diff != "" { t.Errorf("password list retrieved from storage did not match: %s", diff) } } listAndCompare(passwordList) if err := s.DeletePassword(ctx, password1.Email); err != nil { t.Fatalf("failed to delete password: %v", err) } if err := s.DeletePassword(ctx, password2.Email); err != nil { t.Fatalf("failed to delete password: %v", err) } _, err = s.GetPassword(ctx, password1.Email) mustBeErrNotFound(t, "password", err) } func testOfflineSessionCRUD(t *testing.T, s storage.Storage) { ctx := t.Context() userID1 := storage.NewID() session1 := storage.OfflineSessions{ UserID: userID1, ConnID: "Conn1", Refresh: make(map[string]*storage.RefreshTokenRef), ConnectorData: []byte(`{"some":"data"}`), } // Creating an OfflineSession with an empty Refresh list to ensure that // an empty map is translated as expected by the storage. if err := s.CreateOfflineSessions(ctx, session1); err != nil { t.Fatalf("create offline session with UserID = %s: %v", session1.UserID, err) } // Attempt to create same OfflineSession twice. err := s.CreateOfflineSessions(ctx, session1) mustBeErrAlreadyExists(t, "offline session", err) userID2 := storage.NewID() session2 := storage.OfflineSessions{ UserID: userID2, ConnID: "Conn2", Refresh: make(map[string]*storage.RefreshTokenRef), ConnectorData: []byte(`{"some":"data"}`), } if err := s.CreateOfflineSessions(ctx, session2); err != nil { t.Fatalf("create offline session with UserID = %s: %v", session2.UserID, err) } getAndCompare := func(userID string, connID string, want storage.OfflineSessions) { gr, err := s.GetOfflineSessions(ctx, userID, connID) if err != nil { t.Errorf("get offline session: %v", err) return } if diff := pretty.Compare(want, gr); diff != "" { t.Errorf("offline session retrieved from storage did not match: %s", diff) } } getAndCompare(userID1, "Conn1", session1) id := storage.NewID() tokenRef := storage.RefreshTokenRef{ ID: id, ClientID: "client_id", CreatedAt: time.Now().UTC().Round(time.Millisecond), LastUsed: time.Now().UTC().Round(time.Millisecond), } session1.Refresh[tokenRef.ClientID] = &tokenRef if err := s.UpdateOfflineSessions(ctx, session1.UserID, session1.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) { old.Refresh[tokenRef.ClientID] = &tokenRef return old, nil }); err != nil { t.Fatalf("failed to update offline session: %v", err) } getAndCompare(userID1, "Conn1", session1) if err := s.DeleteOfflineSessions(ctx, session1.UserID, session1.ConnID); err != nil { t.Fatalf("failed to delete offline session: %v", err) } if err := s.DeleteOfflineSessions(ctx, session2.UserID, session2.ConnID); err != nil { t.Fatalf("failed to delete offline session: %v", err) } _, err = s.GetOfflineSessions(ctx, session1.UserID, session1.ConnID) mustBeErrNotFound(t, "offline session", err) } func testConnectorCRUD(t *testing.T, s storage.Storage) { ctx := t.Context() id1 := storage.NewID() config1 := []byte(`{"issuer": "https://accounts.google.com"}`) c1 := storage.Connector{ ID: id1, Type: "Default", Name: "Default", Config: config1, GrantTypes: []string{"authorization_code", "refresh_token"}, } if err := s.CreateConnector(ctx, c1); err != nil { t.Fatalf("create connector with ID = %s: %v", c1.ID, err) } // Attempt to create same Connector twice. err := s.CreateConnector(ctx, c1) mustBeErrAlreadyExists(t, "connector", err) id2 := storage.NewID() config2 := []byte(`{"redirectURI": "http://127.0.0.1:5556/dex/callback"}`) c2 := storage.Connector{ ID: id2, Type: "Mock", Name: "Mock", Config: config2, } if err := s.CreateConnector(ctx, c2); err != nil { t.Fatalf("create connector with ID = %s: %v", c2.ID, err) } getAndCompare := func(id string, want storage.Connector) { gr, err := s.GetConnector(ctx, id) if err != nil { t.Errorf("get connector: %v", err) return } // ignore resource version comparison gr.ResourceVersion = "" if diff := pretty.Compare(want, gr); diff != "" { t.Errorf("connector retrieved from storage did not match: %s", diff) } } getAndCompare(id1, c1) if err := s.UpdateConnector(ctx, c1.ID, func(old storage.Connector) (storage.Connector, error) { old.Type = "oidc" old.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:token-exchange"} return old, nil }); err != nil { t.Fatalf("failed to update Connector: %v", err) } c1.Type = "oidc" c1.GrantTypes = []string{"urn:ietf:params:oauth:grant-type:token-exchange"} getAndCompare(id1, c1) connectorList := []storage.Connector{c1, c2} listAndCompare := func(want []storage.Connector) { connectors, err := s.ListConnectors(ctx) if err != nil { t.Errorf("list connectors: %v", err) return } // ignore resource version comparison for i := range connectors { connectors[i].ResourceVersion = "" } sort.Slice(connectors, func(i, j int) bool { return connectors[i].Name < connectors[j].Name }) if diff := pretty.Compare(want, connectors); diff != "" { t.Errorf("connector list retrieved from storage did not match: %s", diff) } } listAndCompare(connectorList) if err := s.DeleteConnector(ctx, c1.ID); err != nil { t.Fatalf("failed to delete connector: %v", err) } if err := s.DeleteConnector(ctx, c2.ID); err != nil { t.Fatalf("failed to delete connector: %v", err) } _, err = s.GetConnector(ctx, c1.ID) mustBeErrNotFound(t, "connector", err) } func testKeysCRUD(t *testing.T, s storage.Storage) { ctx := context.TODO() updateAndCompare := func(k storage.Keys) { err := s.UpdateKeys(ctx, func(oldKeys storage.Keys) (storage.Keys, error) { return k, nil }) if err != nil { t.Errorf("failed to update keys: %v", err) return } if got, err := s.GetKeys(ctx); err != nil { t.Errorf("failed to get keys: %v", err) } else { got.NextRotation = got.NextRotation.UTC() if diff := pretty.Compare(k, got); diff != "" { t.Errorf("got keys did not equal expected: %s", diff) } } } // Postgres isn't as accurate with nano seconds as we'd like n := time.Now().UTC().Round(time.Second) keys1 := storage.Keys{ SigningKey: jsonWebKeys[0].Private, SigningKeyPub: jsonWebKeys[0].Public, NextRotation: n, } keys2 := storage.Keys{ SigningKey: jsonWebKeys[2].Private, SigningKeyPub: jsonWebKeys[2].Public, NextRotation: n.Add(time.Hour), VerificationKeys: []storage.VerificationKey{ { PublicKey: jsonWebKeys[0].Public, Expiry: n.Add(time.Hour), }, { PublicKey: jsonWebKeys[1].Public, Expiry: n.Add(time.Hour * 2), }, }, } updateAndCompare(keys1) updateAndCompare(keys2) } func testGC(t *testing.T, s storage.Storage) { ctx := t.Context() est, err := time.LoadLocation("America/New_York") if err != nil { t.Fatal(err) } pst, err := time.LoadLocation("America/Los_Angeles") if err != nil { t.Fatal(err) } expiry := time.Now().In(est) c := storage.AuthCode{ ID: storage.NewID(), ClientID: "foobar", RedirectURI: "https://localhost:80/callback", Nonce: "foobar", Scopes: []string{"openid", "email"}, Expiry: expiry, AuthTime: defaultAuthTime, ConnectorID: "ldap", ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, } if err := s.CreateAuthCode(ctx, c); err != nil { t.Fatalf("failed creating auth code: %v", err) } for _, tz := range []*time.Location{time.UTC, est, pst} { result, err := s.GarbageCollect(ctx, expiry.Add(-time.Hour).In(tz)) if err != nil { t.Errorf("garbage collection failed: %v", err) } else if result.AuthCodes != 0 || result.AuthRequests != 0 { t.Errorf("expected no garbage collection results, got %#v", result) } if _, err := s.GetAuthCode(ctx, c.ID); err != nil { t.Errorf("expected to be able to get auth code after GC: %v", err) } } if r, err := s.GarbageCollect(ctx, expiry.Add(time.Hour)); err != nil { t.Errorf("garbage collection failed: %v", err) } else if r.AuthCodes != 1 { t.Errorf("expected to garbage collect 1 objects, got %d", r.AuthCodes) } if _, err := s.GetAuthCode(ctx, c.ID); err == nil { t.Errorf("expected auth code to be GC'd") } else if err != storage.ErrNotFound { t.Errorf("expected storage.ErrNotFound, got %v", err) } a := storage.AuthRequest{ ID: storage.NewID(), ClientID: "foobar", ResponseTypes: []string{"code"}, Scopes: []string{"openid", "email"}, RedirectURI: "https://localhost:80/callback", Nonce: "foo", State: "bar", ForceApprovalPrompt: true, LoggedIn: true, Expiry: expiry, AuthTime: defaultAuthTime, ConnectorID: "ldap", ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, HMACKey: []byte("hmac_key"), } if err := s.CreateAuthRequest(ctx, a); err != nil { t.Fatalf("failed creating auth request: %v", err) } for _, tz := range []*time.Location{time.UTC, est, pst} { result, err := s.GarbageCollect(ctx, expiry.Add(-time.Hour).In(tz)) if err != nil { t.Errorf("garbage collection failed: %v", err) } else if result.AuthCodes != 0 || result.AuthRequests != 0 { t.Errorf("expected no garbage collection results, got %#v", result) } if _, err := s.GetAuthRequest(ctx, a.ID); err != nil { t.Errorf("expected to be able to get auth request after GC: %v", err) } } if r, err := s.GarbageCollect(ctx, expiry.Add(time.Hour)); err != nil { t.Errorf("garbage collection failed: %v", err) } else if r.AuthRequests != 1 { t.Errorf("expected to garbage collect 1 objects, got %d", r.AuthRequests) } if _, err := s.GetAuthRequest(ctx, a.ID); err == nil { t.Errorf("expected auth request to be GC'd") } else if err != storage.ErrNotFound { t.Errorf("expected storage.ErrNotFound, got %v", err) } d := storage.DeviceRequest{ UserCode: storage.NewUserCode(), DeviceCode: storage.NewID(), ClientID: "client1", ClientSecret: "secret1", Scopes: []string{"openid", "email"}, Expiry: expiry, } if err := s.CreateDeviceRequest(ctx, d); err != nil { t.Fatalf("failed creating device request: %v", err) } for _, tz := range []*time.Location{time.UTC, est, pst} { result, err := s.GarbageCollect(ctx, expiry.Add(-time.Hour).In(tz)) if err != nil { t.Errorf("garbage collection failed: %v", err) } else if result.DeviceRequests != 0 { t.Errorf("expected no device garbage collection results, got %#v", result) } if _, err := s.GetDeviceRequest(ctx, d.UserCode); err != nil { t.Errorf("expected to be able to get auth request after GC: %v", err) } } if r, err := s.GarbageCollect(ctx, expiry.Add(time.Hour)); err != nil { t.Errorf("garbage collection failed: %v", err) } else if r.DeviceRequests != 1 { t.Errorf("expected to garbage collect 1 device request, got %d", r.DeviceRequests) } if _, err := s.GetDeviceRequest(ctx, d.UserCode); err == nil { t.Errorf("expected device request to be GC'd") } else if err != storage.ErrNotFound { t.Errorf("expected storage.ErrNotFound, got %v", err) } dt := storage.DeviceToken{ DeviceCode: storage.NewID(), Status: "pending", Token: "foo", Expiry: expiry, LastRequestTime: time.Now(), PollIntervalSeconds: 0, PKCE: storage.PKCE{ CodeChallenge: "challenge", CodeChallengeMethod: "S256", }, } if err := s.CreateDeviceToken(ctx, dt); err != nil { t.Fatalf("failed creating device token: %v", err) } for _, tz := range []*time.Location{time.UTC, est, pst} { result, err := s.GarbageCollect(ctx, expiry.Add(-time.Hour).In(tz)) if err != nil { t.Errorf("garbage collection failed: %v", err) } else if result.DeviceTokens != 0 { t.Errorf("expected no device token garbage collection results, got %#v", result) } if _, err := s.GetDeviceToken(ctx, dt.DeviceCode); err != nil { t.Errorf("expected to be able to get device token after GC: %v", err) } } if r, err := s.GarbageCollect(ctx, expiry.Add(time.Hour)); err != nil { t.Errorf("garbage collection failed: %v", err) } else if r.DeviceTokens != 1 { t.Errorf("expected to garbage collect 1 device token, got %d", r.DeviceTokens) } if _, err := s.GetDeviceToken(ctx, dt.DeviceCode); err == nil { t.Errorf("expected device token to be GC'd") } else if err != storage.ErrNotFound { t.Errorf("expected storage.ErrNotFound, got %v", err) } // Test auth session GC. authSession := storage.AuthSession{ UserID: "gc-user", ConnectorID: "gc-conn", Nonce: storage.NewID(), ClientStates: map[string]*storage.ClientAuthState{ "client1": {Active: true, ExpiresAt: expiry.Add(time.Hour), LastActivity: expiry}, }, CreatedAt: expiry.Add(-time.Hour), LastActivity: expiry.Add(-time.Hour), AbsoluteExpiry: expiry, IdleExpiry: expiry, } if err := s.CreateAuthSession(ctx, authSession); err != nil { t.Fatalf("failed creating auth session: %v", err) } // GC before expiry should not delete. for _, tz := range []*time.Location{time.UTC, est, pst} { result, err := s.GarbageCollect(ctx, expiry.Add(-time.Hour).In(tz)) if err != nil { t.Errorf("garbage collection failed: %v", err) } else if result.AuthSessions != 0 { t.Errorf("expected no auth session garbage collection results, got %#v", result) } if _, err := s.GetAuthSession(ctx, authSession.UserID, authSession.ConnectorID); err != nil { t.Errorf("expected to be able to get auth session after GC: %v", err) } } // GC after expiry should delete. if r, err := s.GarbageCollect(ctx, expiry.Add(time.Hour)); err != nil { t.Errorf("garbage collection failed: %v", err) } else if r.AuthSessions != 1 { t.Errorf("expected to garbage collect 1 auth session, got %d", r.AuthSessions) } if _, err := s.GetAuthSession(ctx, authSession.UserID, authSession.ConnectorID); err == nil { t.Errorf("expected auth session to be GC'd") } else if err != storage.ErrNotFound { t.Errorf("expected storage.ErrNotFound, got %v", err) } // Test auth session GC: absolute expired, idle still valid. absExpiredSession := storage.AuthSession{ UserID: "gc-abs-expired", ConnectorID: "gc-conn", Nonce: storage.NewID(), ClientStates: map[string]*storage.ClientAuthState{ "client1": {Active: true, ExpiresAt: expiry.Add(time.Hour), LastActivity: expiry}, }, CreatedAt: expiry.Add(-25 * time.Hour), LastActivity: expiry.Add(-time.Minute), AbsoluteExpiry: expiry.Add(-time.Hour), // expired IdleExpiry: expiry.Add(time.Hour), // still valid } if err := s.CreateAuthSession(ctx, absExpiredSession); err != nil { t.Fatalf("failed creating abs-expired auth session: %v", err) } if r, err := s.GarbageCollect(ctx, expiry); err != nil { t.Errorf("garbage collection failed: %v", err) } else if r.AuthSessions != 1 { t.Errorf("expected to garbage collect 1 auth session (absolute expired), got %d", r.AuthSessions) } if _, err := s.GetAuthSession(ctx, absExpiredSession.UserID, absExpiredSession.ConnectorID); err == nil { t.Errorf("expected abs-expired auth session to be GC'd") } // Test auth session GC: absolute still valid, idle expired. idleExpiredSession := storage.AuthSession{ UserID: "gc-idle-expired", ConnectorID: "gc-conn", Nonce: storage.NewID(), ClientStates: map[string]*storage.ClientAuthState{ "client1": {Active: true, ExpiresAt: expiry.Add(time.Hour), LastActivity: expiry}, }, CreatedAt: expiry.Add(-time.Hour), LastActivity: expiry.Add(-2 * time.Hour), AbsoluteExpiry: expiry.Add(23 * time.Hour), // still valid IdleExpiry: expiry.Add(-time.Hour), // expired } if err := s.CreateAuthSession(ctx, idleExpiredSession); err != nil { t.Fatalf("failed creating idle-expired auth session: %v", err) } if r, err := s.GarbageCollect(ctx, expiry); err != nil { t.Errorf("garbage collection failed: %v", err) } else if r.AuthSessions != 1 { t.Errorf("expected to garbage collect 1 auth session (idle expired), got %d", r.AuthSessions) } if _, err := s.GetAuthSession(ctx, idleExpiredSession.UserID, idleExpiredSession.ConnectorID); err == nil { t.Errorf("expected idle-expired auth session to be GC'd") } } // testTimezones tests that backends either fully support timezones or // do the correct standardization. func testTimezones(t *testing.T, s storage.Storage) { ctx := t.Context() est, err := time.LoadLocation("America/New_York") if err != nil { t.Fatal(err) } // Create an expiry with timezone info. Only expect backends to be // accurate to the millisecond expiry := time.Now().In(est).Round(time.Millisecond) c := storage.AuthCode{ ID: storage.NewID(), ClientID: "foobar", RedirectURI: "https://localhost:80/callback", Nonce: "foobar", Scopes: []string{"openid", "email"}, Expiry: expiry, AuthTime: defaultAuthTime, ConnectorID: "ldap", ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, } if err := s.CreateAuthCode(ctx, c); err != nil { t.Fatalf("failed creating auth code: %v", err) } got, err := s.GetAuthCode(ctx, c.ID) if err != nil { t.Fatalf("failed to get auth code: %v", err) } // Ensure that if the resulting time is converted to the same // timezone, it's the same value. We DO NOT expect timezones // to be preserved. gotTime := got.Expiry.In(est) wantTime := expiry if !gotTime.Equal(wantTime) { t.Fatalf("expected expiry %v got %v", wantTime, gotTime) } } func testDeviceRequestCRUD(t *testing.T, s storage.Storage) { ctx := t.Context() d1 := storage.DeviceRequest{ UserCode: storage.NewUserCode(), DeviceCode: storage.NewID(), ClientID: "client1", ClientSecret: "secret1", Scopes: []string{"openid", "email"}, Expiry: neverExpire.Round(time.Second), } if err := s.CreateDeviceRequest(ctx, d1); err != nil { t.Fatalf("failed creating device request: %v", err) } // Attempt to create same DeviceRequest twice. err := s.CreateDeviceRequest(ctx, d1) mustBeErrAlreadyExists(t, "device request", err) got, err := s.GetDeviceRequest(ctx, d1.UserCode) if err != nil { t.Fatalf("failed to get device request: %v", err) } require.Equal(t, d1, got) // No manual deletes for device requests, will be handled by garbage collection routines // see testGC } func testDeviceTokenCRUD(t *testing.T, s storage.Storage) { ctx := t.Context() codeChallenge := storage.PKCE{ CodeChallenge: "code_challenge_test", CodeChallengeMethod: "plain", } // Create a Token d1 := storage.DeviceToken{ DeviceCode: storage.NewID(), Status: "pending", Token: storage.NewID(), Expiry: neverExpire, LastRequestTime: time.Now(), PollIntervalSeconds: 0, PKCE: codeChallenge, } if err := s.CreateDeviceToken(ctx, d1); err != nil { t.Fatalf("failed creating device token: %v", err) } // Attempt to create same Device Token twice. err := s.CreateDeviceToken(ctx, d1) mustBeErrAlreadyExists(t, "device token", err) // Update the device token, simulate a redemption if err := s.UpdateDeviceToken(ctx, d1.DeviceCode, func(old storage.DeviceToken) (storage.DeviceToken, error) { old.Token = "token data" old.Status = "complete" return old, nil }); err != nil { t.Fatalf("failed to update device token: %v", err) } // Retrieve the device token got, err := s.GetDeviceToken(ctx, d1.DeviceCode) if err != nil { t.Fatalf("failed to get device token: %v", err) } // Validate expected result set if got.Status != "complete" { t.Fatalf("update failed, wanted token status=%v got %v", "complete", got.Status) } if got.Token != "token data" { t.Fatalf("update failed, wanted token %v got %v", "token data", got.Token) } if !reflect.DeepEqual(got.PKCE, codeChallenge) { t.Fatalf("storage does not support PKCE, wanted challenge=%#v got %#v", codeChallenge, got.PKCE) } } func testUserIdentityCRUD(t *testing.T, s storage.Storage) { ctx := t.Context() now := time.Now().UTC().Round(time.Millisecond) u1 := storage.UserIdentity{ UserID: "user1", ConnectorID: "conn1", Claims: storage.Claims{ UserID: "user1", Username: "jane", Email: "jane@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, Consents: make(map[string][]string), CreatedAt: now, LastLogin: now, BlockedUntil: time.Unix(0, 0).UTC(), } // Create with empty Consents map. if err := s.CreateUserIdentity(ctx, u1); err != nil { t.Fatalf("create user identity: %v", err) } // Duplicate create should return ErrAlreadyExists. err := s.CreateUserIdentity(ctx, u1) mustBeErrAlreadyExists(t, "user identity", err) // Get and compare. got, err := s.GetUserIdentity(ctx, u1.UserID, u1.ConnectorID) if err != nil { t.Fatalf("get user identity: %v", err) } got.CreatedAt = got.CreatedAt.UTC().Round(time.Millisecond) got.LastLogin = got.LastLogin.UTC().Round(time.Millisecond) got.BlockedUntil = got.BlockedUntil.UTC().Round(time.Millisecond) u1.BlockedUntil = u1.BlockedUntil.UTC().Round(time.Millisecond) if diff := pretty.Compare(u1, got); diff != "" { t.Errorf("user identity retrieved from storage did not match: %s", diff) } // Update: add consent entry. if err := s.UpdateUserIdentity(ctx, u1.UserID, u1.ConnectorID, func(old storage.UserIdentity) (storage.UserIdentity, error) { old.Consents["client1"] = []string{"openid", "email"} return old, nil }); err != nil { t.Fatalf("update user identity: %v", err) } // Get and verify updated consents. got, err = s.GetUserIdentity(ctx, u1.UserID, u1.ConnectorID) if err != nil { t.Fatalf("get user identity after update: %v", err) } wantConsents := map[string][]string{"client1": {"openid", "email"}} if diff := pretty.Compare(wantConsents, got.Consents); diff != "" { t.Errorf("user identity consents did not match after update: %s", diff) } // List and verify. identities, err := s.ListUserIdentities(ctx) if err != nil { t.Fatalf("list user identities: %v", err) } if len(identities) != 1 { t.Fatalf("expected 1 user identity, got %d", len(identities)) } // Delete. if err := s.DeleteUserIdentity(ctx, u1.UserID, u1.ConnectorID); err != nil { t.Fatalf("delete user identity: %v", err) } // Get deleted should return ErrNotFound. _, err = s.GetUserIdentity(ctx, u1.UserID, u1.ConnectorID) mustBeErrNotFound(t, "user identity", err) } func testAuthSessionCRUD(t *testing.T, s storage.Storage) { ctx := t.Context() now := time.Now().UTC().Round(time.Millisecond) session := storage.AuthSession{ UserID: "user1", ConnectorID: "conn1", Nonce: storage.NewID(), ClientStates: map[string]*storage.ClientAuthState{ "client1": { Active: true, ExpiresAt: now.Add(24 * time.Hour), LastActivity: now, LastTokenIssuedAt: now, }, }, CreatedAt: now, LastActivity: now, IPAddress: "192.168.1.1", UserAgent: "TestBrowser/1.0", AbsoluteExpiry: now.Add(24 * time.Hour), IdleExpiry: now.Add(1 * time.Hour), } // Create. if err := s.CreateAuthSession(ctx, session); err != nil { t.Fatalf("create auth session: %v", err) } // Duplicate create should return ErrAlreadyExists. err := s.CreateAuthSession(ctx, session) mustBeErrAlreadyExists(t, "auth session", err) // Get and compare. got, err := s.GetAuthSession(ctx, session.UserID, session.ConnectorID) if err != nil { t.Fatalf("get auth session: %v", err) } got.CreatedAt = got.CreatedAt.UTC().Round(time.Millisecond) got.LastActivity = got.LastActivity.UTC().Round(time.Millisecond) got.AbsoluteExpiry = got.AbsoluteExpiry.UTC().Round(time.Millisecond) got.IdleExpiry = got.IdleExpiry.UTC().Round(time.Millisecond) for _, cs := range got.ClientStates { cs.ExpiresAt = cs.ExpiresAt.UTC().Round(time.Millisecond) cs.LastActivity = cs.LastActivity.UTC().Round(time.Millisecond) cs.LastTokenIssuedAt = cs.LastTokenIssuedAt.UTC().Round(time.Millisecond) } if diff := pretty.Compare(session, got); diff != "" { t.Errorf("auth session retrieved from storage did not match: %s", diff) } // Update: add a new client state. newNow := now.Add(time.Minute) if err := s.UpdateAuthSession(ctx, session.UserID, session.ConnectorID, func(old storage.AuthSession) (storage.AuthSession, error) { old.ClientStates["client2"] = &storage.ClientAuthState{ Active: true, ExpiresAt: newNow.Add(24 * time.Hour), LastActivity: newNow, } old.LastActivity = newNow return old, nil }); err != nil { t.Fatalf("update auth session: %v", err) } // Get and verify update. got, err = s.GetAuthSession(ctx, session.UserID, session.ConnectorID) if err != nil { t.Fatalf("get auth session after update: %v", err) } if len(got.ClientStates) != 2 { t.Fatalf("expected 2 client states, got %d", len(got.ClientStates)) } if got.ClientStates["client2"] == nil { t.Fatal("expected client2 state to exist") } // List and verify. sessions, err := s.ListAuthSessions(ctx) if err != nil { t.Fatalf("list auth sessions: %v", err) } if len(sessions) != 1 { t.Fatalf("expected 1 auth session, got %d", len(sessions)) } // Delete. if err := s.DeleteAuthSession(ctx, session.UserID, session.ConnectorID); err != nil { t.Fatalf("delete auth session: %v", err) } // Get deleted should return ErrNotFound. _, err = s.GetAuthSession(ctx, session.UserID, session.ConnectorID) mustBeErrNotFound(t, "auth session", err) } ================================================ FILE: storage/conformance/gen_jwks.go ================================================ //go:build ignore // +build ignore // This file is used to generate static JWKs for tests. package main import ( "bytes" "crypto/rand" "crypto/rsa" "encoding/hex" "encoding/json" "go/format" "io" "log" "os" "text/template" "github.com/go-jose/go-jose/v4" ) func newUUID() string { u := make([]byte, 16) if _, err := io.ReadFull(rand.Reader, u); err != nil { panic(err) } u[8] = (u[8] | 0x80) & 0xBF u[6] = (u[6] | 0x40) & 0x4F return hex.EncodeToString(u) } var tmpl = template.Must(template.New("jwks.go").Parse(` // This file was generated by gen_jwks.go package conformance import jose "github.com/go-jose/go-jose/v4" type keyPair struct { Public *jose.JSONWebKey Private *jose.JSONWebKey } // keys are generated beforehand so we don't have to generate RSA keys for every test. var jsonWebKeys = []keyPair{ {{ range $i, $pair := .Keys }} { Public: mustLoadJWK({{ $pair.Public }}), Private: mustLoadJWK({{ $pair.Private }}), }, {{ end }} } `[1:])) // Remove the first newline. type keyPair struct { Public string Private string } func main() { var tmplData struct { Keys []keyPair } for i := 0; i < 5; i++ { // TODO(ericchiang): Test with ECDSA keys. key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { log.Fatalf("gen rsa key: %v", err) } priv := jose.JSONWebKey{ Key: key, KeyID: newUUID(), Algorithm: "RS256", Use: "sig", } pub := jose.JSONWebKey{ Key: key.Public(), KeyID: newUUID(), Algorithm: "RS256", Use: "sig", } privBytes, err := json.MarshalIndent(priv, "\t\t", "\t") if err != nil { log.Fatalf("marshal priv: %v", err) } pubBytes, err := json.MarshalIndent(pub, "\t\t", "\t") if err != nil { log.Fatalf("marshal pub: %v", err) } tmplData.Keys = append(tmplData.Keys, keyPair{ Private: "`" + string(privBytes) + "`", Public: "`" + string(pubBytes) + "`", }) } buff := new(bytes.Buffer) if err := tmpl.Execute(buff, tmplData); err != nil { log.Fatalf("execute tmpl: %v", err) } out, err := format.Source(buff.Bytes()) if err != nil { log.Fatalf("gofmt failed: %v", err) } if err := os.WriteFile("jwks.go", out, 0o644); err != nil { log.Fatal(err) } } ================================================ FILE: storage/conformance/jwks.go ================================================ // This file was generaged by gen_jwks.go package conformance import "github.com/go-jose/go-jose/v4" type keyPair struct { Public *jose.JSONWebKey Private *jose.JSONWebKey } // keys are generated beforehand so we don't have to generate RSA keys for every test. var jsonWebKeys = []keyPair{ { Public: mustLoadJWK(`{ "use": "sig", "kty": "RSA", "kid": "8145b5b9243c41459a8fdaa12acbd371", "alg": "RS256", "n": "34ls8E4onyEU_JKcxl8BMu2N6hK_D6aG2tOuCHJ_ka4rom8NmdJGdOQPC_fvKhcAxWeDktdAPislTT76Q4iMCC7DbM1aQhgRMaecKHBagc5ue2kSPM3oZPLqe6X-CxdxGTfXAvFIZM9JZTbQeJPcXFdn28iZ086xWPMdQKY5QTRKtoHQSN6EAQuuiuZsXrAC3lBZmE4tda6NoeYLb0UayGqiiFmtoIFJQ4NecI-EECT-mcjkPGWG0Ll5dCIUhGDl8sQSUrmBuaTDpPEzLGo-UtM3ay7AN0gOVN0mLIk2oyroXcVOA626LYNLVU0mz9PDpdkhWBeUfLL6i4HjUS3RaQ", "e": "AQAB" }`), Private: mustLoadJWK(`{ "use": "sig", "kty": "RSA", "kid": "f547defc90b34ec08caeb8b294591216", "alg": "RS256", "n": "34ls8E4onyEU_JKcxl8BMu2N6hK_D6aG2tOuCHJ_ka4rom8NmdJGdOQPC_fvKhcAxWeDktdAPislTT76Q4iMCC7DbM1aQhgRMaecKHBagc5ue2kSPM3oZPLqe6X-CxdxGTfXAvFIZM9JZTbQeJPcXFdn28iZ086xWPMdQKY5QTRKtoHQSN6EAQuuiuZsXrAC3lBZmE4tda6NoeYLb0UayGqiiFmtoIFJQ4NecI-EECT-mcjkPGWG0Ll5dCIUhGDl8sQSUrmBuaTDpPEzLGo-UtM3ay7AN0gOVN0mLIk2oyroXcVOA626LYNLVU0mz9PDpdkhWBeUfLL6i4HjUS3RaQ", "e": "AQAB", "d": "3rABHsQ-I4jJZ3SHSfeLMjkFj5JtVCIJZiZK0Y9_Fpn0TjVjz0Fzfy9S7hFo6P1Rf1bH9JkLHuPMnU-H8Y8uMVikxtcse3uOZXEcWAzVnUsRNVBPItPeF_MHNXb_xfzsZrsCL6Q_Am6eJ36b4AMtG7DXflQxKphWhM5s7eKqVxDrkhaDPnALLRFjCvUZ_myQQ3Upn7gMgAbvfIY1fn9rXW_4CfxbxhcPJW5IOcu6bPvpQlfuFkXjF-gGCiNf5kv6Db0lpDOKX5l5T-KFGQ0dIOdasm8vL2GxCKZf55rKRCt0a28fwwH2p94ja-1qtPTc34V8F26LyVRgQgD3e-0aoQ", "p": "_WoAr3sgL5yfaqBL38yqx4hqSPZGdR6xTS64rhgZaVg14_W6xYmlPI7PmVBRW45Fk4tXhXjv9oMZH9HGrH2v4yqXLEq0gJr4VAPvRaN6p_kb_eCfLHCbNCYBAPNVUdFpOTvOmh7m0zYPrku7DZDnZQEN_A9hYcufjy0em-lV6Tc", "q": "4dFfwyYQmns1xwVEPABxpazk6nAluS-7yYSAc9A8D25nqm0mNWdPJvmpJS02xSDjIGfe0FtMr1XlPm3XHdUlIu2Z9Ex-J-kcs3lfs2UKmleQqJRXK4MahAEIV3vp0zG47hAJyzE3Oh4sVLFr3ZK9_-SenolCFv5eIikWa3Xg6l8" }`), }, { Public: mustLoadJWK(`{ "use": "sig", "kty": "RSA", "kid": "3a9365e41b114ec1b9288b214196e158", "alg": "RS256", "n": "t3TrxLN5_z-x5X9kebkoPnoYnGAPqAXOVCGBTxcAqev_P8t6SyyeeITDiePhCctYp5dO-WHRkB7_BkUeHZOgoyCBarDkDifQSG7MCtlYDm0yiSij_0vqzJQx-6zlXb5ypwO0P1sAXrO_nO87u69w5yaKf0yEJMpSjU8BDKQ__nskZP2QJJsYwOeAI9aAM2oP8r7Im8KzLy9-mnFSqypxBnL24hFNzKOS_GyHs0tPLjVY7JNDtDOkwPQIQFzsdZSY88n6uYvV-MGu3O-Y3-xLwUqMlJOXFskhmp1AOUnb4JgQ9wEaZ7088PY3Ak0eZkrg2FQ3XRHSWhUCOb2xL5iTvw", "e": "AQAB" }`), Private: mustLoadJWK(`{ "use": "sig", "kty": "RSA", "kid": "c79418aaf8ee439bb2b0e28672d71584", "alg": "RS256", "n": "t3TrxLN5_z-x5X9kebkoPnoYnGAPqAXOVCGBTxcAqev_P8t6SyyeeITDiePhCctYp5dO-WHRkB7_BkUeHZOgoyCBarDkDifQSG7MCtlYDm0yiSij_0vqzJQx-6zlXb5ypwO0P1sAXrO_nO87u69w5yaKf0yEJMpSjU8BDKQ__nskZP2QJJsYwOeAI9aAM2oP8r7Im8KzLy9-mnFSqypxBnL24hFNzKOS_GyHs0tPLjVY7JNDtDOkwPQIQFzsdZSY88n6uYvV-MGu3O-Y3-xLwUqMlJOXFskhmp1AOUnb4JgQ9wEaZ7088PY3Ak0eZkrg2FQ3XRHSWhUCOb2xL5iTvw", "e": "AQAB", "d": "T7-y0dIXQV8l7RbAza0wkmAvHKMhiy_i7m2WMZRVRIiDb-77HXyq8sb73ZBC_if4RPogaYYdPCJNSCN5oO_Qz7jMqV119bVW9HW9myW6AqNzaW5SRCNzUTVGuRoCpwqn-nRAwZ3EfmZy8DyK4d61HLaDVC0l8HxHAIiMcztfWjbfD2LjwWF2hF5VRG2-haDfT6Kwtz0zEXblvYxyPqVyKOFtuWDlzX8iP8_ryWaChpR-jTmwtm7663wcu4M9teMkdgubCIqkz0LLtd-97ZUM2ti70WO7AEqE6p1evnjfYt4HZpQlsn0psrgGLvX2oCIvmPQMfTjzmtsEC51F5CU-yQ", "p": "4xi5OdCP9n1ivD3CuMhcaoMrwkC1yVdYnJwaNXjIyuSUT0i_QmuRpViydpZsfiYEoNNczL_PwxlDNdl2ccbelBuoEDbrvAfz0G0-YVYuLJoEKQs_OjenIn_6AZlmn7zSQ0LjoZ1tTjOaKuueB2b8RVtF2pbZ_o1ApyWd3q6QjyU", "q": "zs5SF-jdzP9xThPTEmAa2yh6SI48KuwVwWXGjOQZThXVEfwo-iZNevPjg3b6gwY9fKi71-J75c1ng0QrgdDuRIackHFpSLaWgcIpN31-uyZl5X-uxpBZON1HeiYT8J2JhgbA9ZJ0_SUq3j4YSrFEGKSpBi741mqwS9CZ6NSN5BM" }`), }, { Public: mustLoadJWK(`{ "use": "sig", "kty": "RSA", "kid": "4c267fc23c7b44d6973a1722b7201849", "alg": "RS256", "n": "yeZexEF1gOXd71iz9jRQR3EhgM2-o3mVO4O1fJYYQTh5APfrrbMhOGLvgK06vytREiY9_1awL7YfEnZzQynq9WTZpkwlAhYujHYf1RbGPeoXJS2cXKThfIhbeITEyhfepqzwU_f-RhvaLS3bydDi7F74oTO9njtLkGV2qNHH3B2uTFBy2G8VmDeHNQrUa868LQ9omrmWFkLnoZOoVPiLZD-5aZXOKJ0In5sg9B1EX1oaF-xejCTBX_8EJvvvKXH-GUZnHc3g3Rf3k4iXCJi8VMyjA8we3fgP8jp2P3Ofv6VOKG3vh8j5lI3ys_rctc2fu6CaNWNNZs9wbjpDVPuc0w", "e": "AQAB" }`), Private: mustLoadJWK(`{ "use": "sig", "kty": "RSA", "kid": "eec6ee158cb34d699be4baff419da383", "alg": "RS256", "n": "yeZexEF1gOXd71iz9jRQR3EhgM2-o3mVO4O1fJYYQTh5APfrrbMhOGLvgK06vytREiY9_1awL7YfEnZzQynq9WTZpkwlAhYujHYf1RbGPeoXJS2cXKThfIhbeITEyhfepqzwU_f-RhvaLS3bydDi7F74oTO9njtLkGV2qNHH3B2uTFBy2G8VmDeHNQrUa868LQ9omrmWFkLnoZOoVPiLZD-5aZXOKJ0In5sg9B1EX1oaF-xejCTBX_8EJvvvKXH-GUZnHc3g3Rf3k4iXCJi8VMyjA8we3fgP8jp2P3Ofv6VOKG3vh8j5lI3ys_rctc2fu6CaNWNNZs9wbjpDVPuc0w", "e": "AQAB", "d": "IOFck5eZfElzMFSA0lrIrCnXa_OV1WeqjwuvFcAX6R86TZcSkbI3echa-ti7VYDHbi4-MIQ8oziErOwPb2O3OQmYjIWgDUvxfryKCJjx5glmhY59BXVwp2hJhUISDlt-ziQh63ratS46BNuQDLjxC8-XrCESA1_iuXxcq7emVclRKN2DpGehf2bZyjcZy-OEwvL1jLsvoY2jmY_2JOT4nFLqoelg5vENj69p8IR9Bpdzp0urngLZJ4-HqFGyfx3tEo4ZUF1M5xnoycBc5LMZjmElK66rjBRWPq9UwZwfqaeQh6wEA9siYw1V9yrNRUkq3Q6BErbXNDKBV36bRIiaIQ", "p": "_YirCr3Sfs9FkEFFMNsTZ2Wv8e5napONPtg1WUYOxG36k65EkPtlmZLWmiwmBk6592oND_S5WvbW4BbX5lRbEvNiRy9coVPst6lOOnLe69GJoI_GxoRyu_94qIS-VNPSQkyw4gfA1M-lMdfKpaTMv7fvVolvmDs5xN_fmXpl06M", "q": "y90gdyUcYzDX1u3-fCINzXbDcr80QEO3bjuG8p7feaYY2MP51t6j6MisNsQqcGKY7xFhpc-z8_cEIg1HJ3FSly-yejPj8RGavPX6NVGVHDNGwxxnm_i3kf-4MuDxwRSSHMlgVNAXuoH-3iicz-bNTVYM-5bYucZMvZHC6Ur2JRE" }`), }, { Public: mustLoadJWK(`{ "use": "sig", "kty": "RSA", "kid": "e10385c5384046f395fc6d9027db2f35", "alg": "RS256", "n": "299cgJgPiu9CK8hGgQw3j8e-Y_u4-Tm6WXKOFHdjCUPV5EAWMOa34cQNt75KN8pxlIcnujnU6TpH4OPRCw1gA44rrk_uczIEULsTnt6UFuMtUY2r-2UW2BWg5rEHyLcNX_QCA80T9DVSxsWeN8S23YcVk9fVputIRU7ee7auOx3b6K3pkoQJBVUk-_ndaqwlX-JU2CQG52CH91CrDzN0WGUPrhMZOdL7ybv94l5ztBrnjaQupkt0FxTA1_m_tXTvxIgzzegaqXrJ1mJM-z2TxPUJUc_04JaGilPUkxU780jk_03d46Op-pdElgbZ52C9JT9b8nRnA-vHq4e2whY8Yw", "e": "AQAB" }`), Private: mustLoadJWK(`{ "use": "sig", "kty": "RSA", "kid": "8165cc507cd1492394be64575dfa8261", "alg": "RS256", "n": "299cgJgPiu9CK8hGgQw3j8e-Y_u4-Tm6WXKOFHdjCUPV5EAWMOa34cQNt75KN8pxlIcnujnU6TpH4OPRCw1gA44rrk_uczIEULsTnt6UFuMtUY2r-2UW2BWg5rEHyLcNX_QCA80T9DVSxsWeN8S23YcVk9fVputIRU7ee7auOx3b6K3pkoQJBVUk-_ndaqwlX-JU2CQG52CH91CrDzN0WGUPrhMZOdL7ybv94l5ztBrnjaQupkt0FxTA1_m_tXTvxIgzzegaqXrJ1mJM-z2TxPUJUc_04JaGilPUkxU780jk_03d46Op-pdElgbZ52C9JT9b8nRnA-vHq4e2whY8Yw", "e": "AQAB", "d": "xh587o6WKr2uZV8gUHXettroLpWKtl-TD7hOWBi_j4ClgfdRR50NggwzxCZeH-l18LzcSkyEEefnDriZC5lws6NurrHtjbU6-Dep1VSAIiNwGXVLy8nqDKlog5ZvCigPkC-BhUVMPpexz9QP3faORAzNn5szNCX7yB_qD5WrZy20AUEoWtGPgxGW6xf5Lgu6zg2uQEEB1Z0hKjHV9seIiuQooMrSzpS1D7BLSTHOvM2Y2lXvQQokc3uQXnyT_soHPjHl00bcuJLJaRCmyHRTol7uh9MNe67eMy7pHYmmlwOvTDfW6meKCgoEXd1wKIrS9VRY7WP36ZRpJH6qv8vceQ", "p": "7sWEsknUaSlAJ-bGhsuFr_j15zupV9O-DLnLobASm4Z7Ylt1HhtPN1NCVzYFTCtltPBE_CXGaAPqw3wiERK3tgYSLV8yk57sU1H28Zsq65A1B-vdlO69-F_6djiGegYKTOO4CXt0VYB4hJ6Trwx_BNJmrAD_Ykjqsp5sR0gOrqU", "q": "67y_hzbi81IH2DxmHTQOfHgcLYe-TnrEQLGLQtfx8J0J_REf_fLBDL-pt_jy6WIvTAb-LgUIcieiXfhni1nPUw0f_I1SDNv02EYvP0vkfyQdJBR6sLi4jv0mpqyQxvGif9B4eM9Qjngm2Jclj3-el-RkMZOUyf3zGTNGLI3MmGc" }`), }, { Public: mustLoadJWK(`{ "use": "sig", "kty": "RSA", "kid": "941861b40500430da0d09ec213e00832", "alg": "RS256", "n": "ub3SiNK-uIvSrUTyIPm1cITzuqPX_CIa6nZTDTP1tJ6PP_KufYz2eGLj9jppWLo_J7XQfKfIAKvET8Mq4HEcLQpNRN90KNyGML17JJtSgYJeLuB38BnalVUxpnycPKeGgoNJMu6t8tKYOtOfxtqTA6x8MnqMeify1cvEc5Tr4QmKjcLLHKcr1yMR7kG48i586bLdchtIBYeB298WXbQaKrgsEjZA0E1exfMnYHyvN12lMBxwhOJtcFu3mngZ7vTh179UKsP3yD8IdO5ITe_RIOmnUKuynW3PdkRUzCK5gS-xuqueGqEzJVIKBv0Hfom3eyDW5DjxpIZxlqkGhGyeNw", "e": "AQAB" }`), Private: mustLoadJWK(`{ "use": "sig", "kty": "RSA", "kid": "c4c09817da9a42ae8d850aaba7b7cd82", "alg": "RS256", "n": "ub3SiNK-uIvSrUTyIPm1cITzuqPX_CIa6nZTDTP1tJ6PP_KufYz2eGLj9jppWLo_J7XQfKfIAKvET8Mq4HEcLQpNRN90KNyGML17JJtSgYJeLuB38BnalVUxpnycPKeGgoNJMu6t8tKYOtOfxtqTA6x8MnqMeify1cvEc5Tr4QmKjcLLHKcr1yMR7kG48i586bLdchtIBYeB298WXbQaKrgsEjZA0E1exfMnYHyvN12lMBxwhOJtcFu3mngZ7vTh179UKsP3yD8IdO5ITe_RIOmnUKuynW3PdkRUzCK5gS-xuqueGqEzJVIKBv0Hfom3eyDW5DjxpIZxlqkGhGyeNw", "e": "AQAB", "d": "29bQWSEWm1bjBDGWY3EqTwMNdtp1yPaU5O0nX3kgV6dT5VxXKkKtdc-WANkh1uKZ3WZUXTY4gpLKx504Im2965FF4z6XPcXFDes21R0BikfDMbh8PLJdBGLRYTwbr66YheDdwmq9d6nKg9X2RmZtmuuMFDL4EZ02zdVfr22TwcSCghC2gnV6CpHHeEatJBWbK1yE6cHqCeY9UTc_QnXmbZ0TYsQi4qCV1HqTJKZDtkzqZMPvMB5EP_my_SCxcfcIzt6qqujmuXCFiS658Up-Z4W5s0RINLoPmePG8zJVFBmWrQ8xiykCeL8z9XSvXoEo6ZJJC-KSjI6s-KsCfQqZ", "p": "8LzUJM2YgP7zG618rrFTav3gB2t1yMwFJy9d3J-pOkVFUq-4-74qEZz6H2RTUw7Ae5XEYdVIbRRQInpo0qO2MfLW8vtRexUNFFt1pBiVykq-KdkWcwPETyRD-huEEqswBhg33lFTUrY7BXRukbfNmVY7YfdagIJ5LZU0I-nGMqs", "q": "xYRoIFTTiXitKBFo0vvHAadqVV8gJq8bCxJZ4lFMpADlU-S8Me7aPmkhPmCDaw-ii940S46bTp9ueh6EJCttmG3cJm8r4YzjK-H1dnqeF_3dpq2pimVFlFILBKWojUHHWC4n0d1IVwdf8-xnDSiUzl9roFZV5IPy4mW1HMTZ4qU" }`), }, } ================================================ FILE: storage/conformance/transactions.go ================================================ package conformance import ( "context" "strconv" "sync" "testing" "time" "github.com/stretchr/testify/require" "golang.org/x/crypto/bcrypt" "github.com/dexidp/dex/storage" ) // RunTransactionTests runs a test suite aimed a verifying the transaction // guarantees of the storage interface. Atomic updates, deletes, etc. The // storage returned by newStorage will be closed at the end of each test run. // // This call is separate from RunTests because some storage perform extremely // poorly under deadlocks, such as SQLite3, while others may be working towards // conformance. func RunTransactionTests(t *testing.T, newStorage func(t *testing.T) storage.Storage) { runTests(t, newStorage, []subTest{ {"AuthRequestConcurrentUpdate", testAuthRequestConcurrentUpdate}, {"ClientConcurrentUpdate", testClientConcurrentUpdate}, {"PasswordConcurrentUpdate", testPasswordConcurrentUpdate}, {"KeysConcurrentUpdate", testKeysConcurrentUpdate}, }) } // RunConcurrencyTests runs tests that verify storage implementations handle // high-contention parallel updates correctly. Unlike RunTransactionTests, // these tests use real goroutine-based parallelism rather than nested calls, // and are safe to run on all storage backends (including those with non-reentrant locks). func RunConcurrencyTests(t *testing.T, newStorage func(t *testing.T) storage.Storage) { runTests(t, newStorage, []subTest{ {"RefreshTokenParallelUpdate", testRefreshTokenParallelUpdate}, }) } func testClientConcurrentUpdate(t *testing.T, s storage.Storage) { ctx := t.Context() c := storage.Client{ ID: storage.NewID(), Secret: "foobar", RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"}, Name: "dex client", LogoURL: "https://goo.gl/JIyzIC", } if err := s.CreateClient(ctx, c); err != nil { t.Fatalf("create client: %v", err) } var err1, err2 error err1 = s.UpdateClient(ctx, c.ID, func(old storage.Client) (storage.Client, error) { old.Secret = "new secret 1" err2 = s.UpdateClient(ctx, c.ID, func(old storage.Client) (storage.Client, error) { old.Secret = "new secret 2" return old, nil }) return old, nil }) if (err1 == nil) == (err2 == nil) { t.Errorf("update client:\nupdate1: %v\nupdate2: %v\n", err1, err2) } } func testAuthRequestConcurrentUpdate(t *testing.T, s storage.Storage) { ctx := t.Context() a := storage.AuthRequest{ ID: storage.NewID(), ClientID: "foobar", ResponseTypes: []string{"code"}, Scopes: []string{"openid", "email"}, RedirectURI: "https://localhost:80/callback", Nonce: "foo", State: "bar", ForceApprovalPrompt: true, LoggedIn: true, Expiry: neverExpire, AuthTime: defaultAuthTime, ConnectorID: "ldap", ConnectorData: []byte(`{"some":"data"}`), Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, HMACKey: []byte("hmac_key"), } if err := s.CreateAuthRequest(ctx, a); err != nil { t.Fatalf("failed creating auth request: %v", err) } var err1, err2 error err1 = s.UpdateAuthRequest(ctx, a.ID, func(old storage.AuthRequest) (storage.AuthRequest, error) { old.State = "state 1" err2 = s.UpdateAuthRequest(ctx, a.ID, func(old storage.AuthRequest) (storage.AuthRequest, error) { old.State = "state 2" return old, nil }) return old, nil }) if (err1 == nil) == (err2 == nil) { t.Errorf("update auth request:\nupdate1: %v\nupdate2: %v\n", err1, err2) } } func testPasswordConcurrentUpdate(t *testing.T, s storage.Storage) { ctx := t.Context() // Use bcrypt.MinCost to keep the tests short. passwordHash, err := bcrypt.GenerateFromPassword([]byte("secret"), bcrypt.MinCost) if err != nil { t.Fatal(err) } password := storage.Password{ Email: "jane@example.com", Hash: passwordHash, Username: "jane", Name: "Jane Doe", PreferredUsername: "jane-public", EmailVerified: boolPtr(true), UserID: "foobar", Groups: []string{"team-a"}, } if err := s.CreatePassword(ctx, password); err != nil { t.Fatalf("create password token: %v", err) } var err1, err2 error err1 = s.UpdatePassword(ctx, password.Email, func(old storage.Password) (storage.Password, error) { old.Username = "user 1" err2 = s.UpdatePassword(ctx, password.Email, func(old storage.Password) (storage.Password, error) { old.Username = "user 2" return old, nil }) return old, nil }) if (err1 == nil) == (err2 == nil) { t.Errorf("update password: concurrent updates both returned no error") } } func testKeysConcurrentUpdate(t *testing.T, s storage.Storage) { // Test twice. Once for a create, once for an update. for i := 0; i < 2; i++ { n := time.Now().UTC().Round(time.Second) keys1 := storage.Keys{ SigningKey: jsonWebKeys[i].Private, SigningKeyPub: jsonWebKeys[i].Public, NextRotation: n, } keys2 := storage.Keys{ SigningKey: jsonWebKeys[2].Private, SigningKeyPub: jsonWebKeys[2].Public, NextRotation: n.Add(time.Hour), VerificationKeys: []storage.VerificationKey{ { PublicKey: jsonWebKeys[0].Public, Expiry: n.Add(time.Hour), }, { PublicKey: jsonWebKeys[1].Public, Expiry: n.Add(time.Hour * 2), }, }, } var err1, err2 error ctx := context.TODO() err1 = s.UpdateKeys(ctx, func(old storage.Keys) (storage.Keys, error) { err2 = s.UpdateKeys(ctx, func(old storage.Keys) (storage.Keys, error) { return keys1, nil }) return keys2, nil }) if (err1 == nil) == (err2 == nil) { t.Errorf("update keys: concurrent updates both returned no error") } } } // testRefreshTokenParallelUpdate tests that many parallel updates to the same // refresh token are serialized correctly by the storage and no updates are lost. // // Each goroutine atomically increments a counter stored in the Token field. // After all goroutines finish, the counter must equal the number of successful updates. // A mismatch indicates lost updates due to broken atomicity. func testRefreshTokenParallelUpdate(t *testing.T, s storage.Storage) { ctx := t.Context() id := storage.NewID() refresh := storage.RefreshToken{ ID: id, Token: "0", Nonce: "foo", ClientID: "client_id", ConnectorID: "connector_id", Scopes: []string{"openid"}, CreatedAt: time.Now().UTC().Round(time.Millisecond), LastUsed: time.Now().UTC().Round(time.Millisecond), Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane@example.com", }, } require.NoError(t, s.CreateRefresh(ctx, refresh)) const numWorkers = 100 type updateResult struct { err error newToken string // token value written by this worker's updater } var wg sync.WaitGroup results := make([]updateResult, numWorkers) for i := range numWorkers { wg.Add(1) go func() { defer wg.Done() results[i].err = s.UpdateRefreshToken(ctx, id, func(old storage.RefreshToken) (storage.RefreshToken, error) { counter, _ := strconv.Atoi(old.Token) old.Token = strconv.Itoa(counter + 1) results[i].newToken = old.Token return old, nil }) }() } wg.Wait() errCounts := map[string]int{} var successes int writtenTokens := map[string]int{} for _, r := range results { if r.err == nil { successes++ writtenTokens[r.newToken]++ } else { errCounts[r.err.Error()]++ } } for msg, count := range errCounts { t.Logf("error (x%d): %s", count, msg) } stored, err := s.GetRefresh(ctx, id) require.NoError(t, err) counter, err := strconv.Atoi(stored.Token) require.NoError(t, err) t.Logf("parallel refresh token updates: %d/%d succeeded, final counter: %d", successes, numWorkers, counter) if successes < numWorkers { t.Errorf("not all updates succeeded: %d/%d (some failed under contention)", successes, numWorkers) } if counter != successes { t.Errorf("lost updates detected: %d successful updates but counter is %d", successes, counter) } // Each successful updater must have seen a unique counter value. // Duplicates would mean two updaters read the same state — a sign of broken atomicity. for token, count := range writtenTokens { if count > 1 { t.Errorf("token %q was written by %d updaters — concurrent updaters saw the same state", token, count) } } // Successful updaters must have produced a contiguous sequence 1..N. // A gap would mean an updater saw stale state even though the write succeeded. for i := 1; i <= successes; i++ { if writtenTokens[strconv.Itoa(i)] != 1 { t.Errorf("expected token %q to be written exactly once, got %d", strconv.Itoa(i), writtenTokens[strconv.Itoa(i)]) } } // The token stored in the database must match the highest value written. // This confirms that the last successful update is the one persisted. if stored.Token != strconv.Itoa(successes) { t.Errorf("stored token %q does not match expected final value %q", stored.Token, strconv.Itoa(successes)) } } ================================================ FILE: storage/doc.go ================================================ // Package storage defines the storage interface and types used by the server. package storage ================================================ FILE: storage/ent/client/authcode.go ================================================ package client import ( "context" "github.com/dexidp/dex/storage" ) // CreateAuthCode saves provided auth code into the database. func (d *Database) CreateAuthCode(ctx context.Context, code storage.AuthCode) error { _, err := d.client.AuthCode.Create(). SetID(code.ID). SetClientID(code.ClientID). SetScopes(code.Scopes). SetRedirectURI(code.RedirectURI). SetNonce(code.Nonce). SetClaimsUserID(code.Claims.UserID). SetClaimsEmail(code.Claims.Email). SetClaimsEmailVerified(code.Claims.EmailVerified). SetClaimsUsername(code.Claims.Username). SetClaimsPreferredUsername(code.Claims.PreferredUsername). SetClaimsGroups(code.Claims.Groups). SetCodeChallenge(code.PKCE.CodeChallenge). SetCodeChallengeMethod(code.PKCE.CodeChallengeMethod). // Save utc time into database because ent doesn't support comparing dates with different timezones SetExpiry(code.Expiry.UTC()). SetConnectorID(code.ConnectorID). SetConnectorData(code.ConnectorData). SetAuthTime(code.AuthTime). Save(ctx) if err != nil { return convertDBError("create auth code: %w", err) } return nil } // GetAuthCode extracts an auth code from the database by id. func (d *Database) GetAuthCode(ctx context.Context, id string) (storage.AuthCode, error) { authCode, err := d.client.AuthCode.Get(ctx, id) if err != nil { return storage.AuthCode{}, convertDBError("get auth code: %w", err) } return toStorageAuthCode(authCode), nil } // DeleteAuthCode deletes an auth code from the database by id. func (d *Database) DeleteAuthCode(ctx context.Context, id string) error { err := d.client.AuthCode.DeleteOneID(id).Exec(ctx) if err != nil { return convertDBError("delete auth code: %w", err) } return nil } ================================================ FILE: storage/ent/client/authrequest.go ================================================ package client import ( "context" "fmt" "github.com/dexidp/dex/storage" ) // CreateAuthRequest saves provided auth request into the database. func (d *Database) CreateAuthRequest(ctx context.Context, authRequest storage.AuthRequest) error { _, err := d.client.AuthRequest.Create(). SetID(authRequest.ID). SetClientID(authRequest.ClientID). SetScopes(authRequest.Scopes). SetResponseTypes(authRequest.ResponseTypes). SetRedirectURI(authRequest.RedirectURI). SetState(authRequest.State). SetNonce(authRequest.Nonce). SetForceApprovalPrompt(authRequest.ForceApprovalPrompt). SetLoggedIn(authRequest.LoggedIn). SetClaimsUserID(authRequest.Claims.UserID). SetClaimsEmail(authRequest.Claims.Email). SetClaimsEmailVerified(authRequest.Claims.EmailVerified). SetClaimsUsername(authRequest.Claims.Username). SetClaimsPreferredUsername(authRequest.Claims.PreferredUsername). SetClaimsGroups(authRequest.Claims.Groups). SetCodeChallenge(authRequest.PKCE.CodeChallenge). SetCodeChallengeMethod(authRequest.PKCE.CodeChallengeMethod). // Save utc time into database because ent doesn't support comparing dates with different timezones SetExpiry(authRequest.Expiry.UTC()). SetConnectorID(authRequest.ConnectorID). SetConnectorData(authRequest.ConnectorData). SetHmacKey(authRequest.HMACKey). SetMfaValidated(authRequest.MFAValidated). SetPrompt(authRequest.Prompt). SetMaxAge(authRequest.MaxAge). SetAuthTime(authRequest.AuthTime). Save(ctx) if err != nil { return convertDBError("create auth request: %w", err) } return nil } // GetAuthRequest extracts an auth request from the database by id. func (d *Database) GetAuthRequest(ctx context.Context, id string) (storage.AuthRequest, error) { authRequest, err := d.client.AuthRequest.Get(ctx, id) if err != nil { return storage.AuthRequest{}, convertDBError("get auth request: %w", err) } return toStorageAuthRequest(authRequest), nil } // DeleteAuthRequest deletes an auth request from the database by id. func (d *Database) DeleteAuthRequest(ctx context.Context, id string) error { err := d.client.AuthRequest.DeleteOneID(id).Exec(ctx) if err != nil { return convertDBError("delete auth request: %w", err) } return nil } // UpdateAuthRequest changes an auth request by id using an updater function and saves it to the database. func (d *Database) UpdateAuthRequest(ctx context.Context, id string, updater func(old storage.AuthRequest) (storage.AuthRequest, error)) error { tx, err := d.BeginTx(ctx) if err != nil { return fmt.Errorf("update auth request tx: %w", err) } authRequest, err := tx.AuthRequest.Get(context.TODO(), id) if err != nil { return rollback(tx, "update auth request database: %w", err) } newAuthRequest, err := updater(toStorageAuthRequest(authRequest)) if err != nil { return rollback(tx, "update auth request updating: %w", err) } _, err = tx.AuthRequest.UpdateOneID(newAuthRequest.ID). SetClientID(newAuthRequest.ClientID). SetScopes(newAuthRequest.Scopes). SetResponseTypes(newAuthRequest.ResponseTypes). SetRedirectURI(newAuthRequest.RedirectURI). SetState(newAuthRequest.State). SetNonce(newAuthRequest.Nonce). SetForceApprovalPrompt(newAuthRequest.ForceApprovalPrompt). SetLoggedIn(newAuthRequest.LoggedIn). SetClaimsUserID(newAuthRequest.Claims.UserID). SetClaimsEmail(newAuthRequest.Claims.Email). SetClaimsEmailVerified(newAuthRequest.Claims.EmailVerified). SetClaimsUsername(newAuthRequest.Claims.Username). SetClaimsPreferredUsername(newAuthRequest.Claims.PreferredUsername). SetClaimsGroups(newAuthRequest.Claims.Groups). SetCodeChallenge(newAuthRequest.PKCE.CodeChallenge). SetCodeChallengeMethod(newAuthRequest.PKCE.CodeChallengeMethod). // Save utc time into database because ent doesn't support comparing dates with different timezones SetExpiry(newAuthRequest.Expiry.UTC()). SetConnectorID(newAuthRequest.ConnectorID). SetConnectorData(newAuthRequest.ConnectorData). SetHmacKey(newAuthRequest.HMACKey). SetMfaValidated(newAuthRequest.MFAValidated). SetPrompt(newAuthRequest.Prompt). SetMaxAge(newAuthRequest.MaxAge). SetAuthTime(newAuthRequest.AuthTime). Save(context.TODO()) if err != nil { return rollback(tx, "update auth request uploading: %w", err) } if err = tx.Commit(); err != nil { return rollback(tx, "update auth request commit: %w", err) } return nil } ================================================ FILE: storage/ent/client/authsession.go ================================================ package client import ( "context" "encoding/json" "fmt" "github.com/dexidp/dex/storage" ) // CreateAuthSession saves provided auth session into the database. func (d *Database) CreateAuthSession(ctx context.Context, session storage.AuthSession) error { if session.ClientStates == nil { session.ClientStates = make(map[string]*storage.ClientAuthState) } encodedStates, err := json.Marshal(session.ClientStates) if err != nil { return fmt.Errorf("encode client states auth session: %w", err) } id := compositeKeyID(session.UserID, session.ConnectorID, d.hasher) _, err = d.client.AuthSession.Create(). SetID(id). SetUserID(session.UserID). SetConnectorID(session.ConnectorID). SetNonce(session.Nonce). SetClientStates(encodedStates). SetCreatedAt(session.CreatedAt). SetLastActivity(session.LastActivity). SetIPAddress(session.IPAddress). SetUserAgent(session.UserAgent). SetAbsoluteExpiry(session.AbsoluteExpiry.UTC()). SetIdleExpiry(session.IdleExpiry.UTC()). Save(ctx) if err != nil { return convertDBError("create auth session: %w", err) } return nil } // GetAuthSession extracts an auth session from the database by user ID and connector ID. func (d *Database) GetAuthSession(ctx context.Context, userID, connectorID string) (storage.AuthSession, error) { id := compositeKeyID(userID, connectorID, d.hasher) authSession, err := d.client.AuthSession.Get(ctx, id) if err != nil { return storage.AuthSession{}, convertDBError("get auth session: %w", err) } return toStorageAuthSession(authSession), nil } // ListAuthSessions extracts all auth sessions from the database. func (d *Database) ListAuthSessions(ctx context.Context) ([]storage.AuthSession, error) { authSessions, err := d.client.AuthSession.Query().All(ctx) if err != nil { return nil, convertDBError("list auth sessions: %w", err) } storageAuthSessions := make([]storage.AuthSession, 0, len(authSessions)) for _, s := range authSessions { storageAuthSessions = append(storageAuthSessions, toStorageAuthSession(s)) } return storageAuthSessions, nil } // DeleteAuthSession deletes an auth session from the database by user ID and connector ID. func (d *Database) DeleteAuthSession(ctx context.Context, userID, connectorID string) error { id := compositeKeyID(userID, connectorID, d.hasher) err := d.client.AuthSession.DeleteOneID(id).Exec(ctx) if err != nil { return convertDBError("delete auth session: %w", err) } return nil } // UpdateAuthSession changes an auth session using an updater function. func (d *Database) UpdateAuthSession(ctx context.Context, userID, connectorID string, updater func(s storage.AuthSession) (storage.AuthSession, error)) error { id := compositeKeyID(userID, connectorID, d.hasher) tx, err := d.BeginTx(ctx) if err != nil { return convertDBError("update auth session tx: %w", err) } authSession, err := tx.AuthSession.Get(ctx, id) if err != nil { return rollback(tx, "update auth session database: %w", err) } newSession, err := updater(toStorageAuthSession(authSession)) if err != nil { return rollback(tx, "update auth session updating: %w", err) } if newSession.ClientStates == nil { newSession.ClientStates = make(map[string]*storage.ClientAuthState) } encodedStates, err := json.Marshal(newSession.ClientStates) if err != nil { return rollback(tx, "encode client states auth session: %w", err) } _, err = tx.AuthSession.UpdateOneID(id). SetClientStates(encodedStates). SetLastActivity(newSession.LastActivity). SetIPAddress(newSession.IPAddress). SetUserAgent(newSession.UserAgent). SetAbsoluteExpiry(newSession.AbsoluteExpiry.UTC()). SetIdleExpiry(newSession.IdleExpiry.UTC()). Save(ctx) if err != nil { return rollback(tx, "update auth session updating: %w", err) } if err = tx.Commit(); err != nil { return rollback(tx, "update auth session commit: %w", err) } return nil } ================================================ FILE: storage/ent/client/client.go ================================================ package client import ( "context" "github.com/dexidp/dex/storage" ) // CreateClient saves provided oauth2 client settings into the database. func (d *Database) CreateClient(ctx context.Context, client storage.Client) error { _, err := d.client.OAuth2Client.Create(). SetID(client.ID). SetName(client.Name). SetSecret(client.Secret). SetPublic(client.Public). SetLogoURL(client.LogoURL). SetRedirectUris(client.RedirectURIs). SetTrustedPeers(client.TrustedPeers). SetAllowedConnectors(client.AllowedConnectors). SetMfaChain(client.MFAChain). Save(ctx) if err != nil { return convertDBError("create oauth2 client: %w", err) } return nil } // ListClients extracts an array of oauth2 clients from the database. func (d *Database) ListClients(ctx context.Context) ([]storage.Client, error) { clients, err := d.client.OAuth2Client.Query().All(ctx) if err != nil { return nil, convertDBError("list clients: %w", err) } storageClients := make([]storage.Client, 0, len(clients)) for _, c := range clients { storageClients = append(storageClients, toStorageClient(c)) } return storageClients, nil } // GetClient extracts an oauth2 client from the database by id. func (d *Database) GetClient(ctx context.Context, id string) (storage.Client, error) { client, err := d.client.OAuth2Client.Get(ctx, id) if err != nil { return storage.Client{}, convertDBError("get client: %w", err) } return toStorageClient(client), nil } // DeleteClient deletes an oauth2 client from the database by id. func (d *Database) DeleteClient(ctx context.Context, id string) error { err := d.client.OAuth2Client.DeleteOneID(id).Exec(ctx) if err != nil { return convertDBError("delete client: %w", err) } return nil } // UpdateClient changes an oauth2 client by id using an updater function and saves it to the database. func (d *Database) UpdateClient(ctx context.Context, id string, updater func(old storage.Client) (storage.Client, error)) error { tx, err := d.BeginTx(ctx) if err != nil { return convertDBError("update client tx: %w", err) } client, err := tx.OAuth2Client.Get(ctx, id) if err != nil { return rollback(tx, "update client database: %w", err) } newClient, err := updater(toStorageClient(client)) if err != nil { return rollback(tx, "update client updating: %w", err) } _, err = tx.OAuth2Client.UpdateOneID(newClient.ID). SetName(newClient.Name). SetSecret(newClient.Secret). SetPublic(newClient.Public). SetLogoURL(newClient.LogoURL). SetRedirectUris(newClient.RedirectURIs). SetTrustedPeers(newClient.TrustedPeers). SetAllowedConnectors(newClient.AllowedConnectors). SetMfaChain(newClient.MFAChain). Save(ctx) if err != nil { return rollback(tx, "update client uploading: %w", err) } if err = tx.Commit(); err != nil { return rollback(tx, "update auth request commit: %w", err) } return nil } ================================================ FILE: storage/ent/client/connector.go ================================================ package client import ( "context" "github.com/dexidp/dex/storage" ) // CreateConnector saves a connector into the database. func (d *Database) CreateConnector(ctx context.Context, connector storage.Connector) error { _, err := d.client.Connector.Create(). SetID(connector.ID). SetName(connector.Name). SetType(connector.Type). SetResourceVersion(connector.ResourceVersion). SetConfig(connector.Config). SetGrantTypes(connector.GrantTypes). Save(ctx) if err != nil { return convertDBError("create connector: %w", err) } return nil } // ListConnectors extracts an array of connectors from the database. func (d *Database) ListConnectors(ctx context.Context) ([]storage.Connector, error) { connectors, err := d.client.Connector.Query().All(ctx) if err != nil { return nil, convertDBError("list connectors: %w", err) } storageConnectors := make([]storage.Connector, 0, len(connectors)) for _, c := range connectors { storageConnectors = append(storageConnectors, toStorageConnector(c)) } return storageConnectors, nil } // GetConnector extracts a connector from the database by id. func (d *Database) GetConnector(ctx context.Context, id string) (storage.Connector, error) { connector, err := d.client.Connector.Get(ctx, id) if err != nil { return storage.Connector{}, convertDBError("get connector: %w", err) } return toStorageConnector(connector), nil } // DeleteConnector deletes a connector from the database by id. func (d *Database) DeleteConnector(ctx context.Context, id string) error { err := d.client.Connector.DeleteOneID(id).Exec(ctx) if err != nil { return convertDBError("delete connector: %w", err) } return nil } // UpdateConnector changes a connector by id using an updater function and saves it to the database. func (d *Database) UpdateConnector(ctx context.Context, id string, updater func(old storage.Connector) (storage.Connector, error)) error { tx, err := d.BeginTx(ctx) if err != nil { return convertDBError("update connector tx: %w", err) } connector, err := tx.Connector.Get(ctx, id) if err != nil { return rollback(tx, "update connector database: %w", err) } newConnector, err := updater(toStorageConnector(connector)) if err != nil { return rollback(tx, "update connector updating: %w", err) } _, err = tx.Connector.UpdateOneID(newConnector.ID). SetName(newConnector.Name). SetType(newConnector.Type). SetResourceVersion(newConnector.ResourceVersion). SetConfig(newConnector.Config). SetGrantTypes(newConnector.GrantTypes). Save(ctx) if err != nil { return rollback(tx, "update connector uploading: %w", err) } if err = tx.Commit(); err != nil { return rollback(tx, "update connector commit: %w", err) } return nil } ================================================ FILE: storage/ent/client/devicerequest.go ================================================ package client import ( "context" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db/devicerequest" ) // CreateDeviceRequest saves provided device request into the database. func (d *Database) CreateDeviceRequest(ctx context.Context, request storage.DeviceRequest) error { _, err := d.client.DeviceRequest.Create(). SetClientID(request.ClientID). SetClientSecret(request.ClientSecret). SetScopes(request.Scopes). SetUserCode(request.UserCode). SetDeviceCode(request.DeviceCode). // Save utc time into database because ent doesn't support comparing dates with different timezones SetExpiry(request.Expiry.UTC()). Save(ctx) if err != nil { return convertDBError("create device request: %w", err) } return nil } // GetDeviceRequest extracts a device request from the database by user code. func (d *Database) GetDeviceRequest(ctx context.Context, userCode string) (storage.DeviceRequest, error) { deviceRequest, err := d.client.DeviceRequest.Query(). Where(devicerequest.UserCode(userCode)). Only(ctx) if err != nil { return storage.DeviceRequest{}, convertDBError("get device request: %w", err) } return toStorageDeviceRequest(deviceRequest), nil } ================================================ FILE: storage/ent/client/devicetoken.go ================================================ package client import ( "context" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db/devicetoken" ) // CreateDeviceToken saves provided token into the database. func (d *Database) CreateDeviceToken(ctx context.Context, token storage.DeviceToken) error { _, err := d.client.DeviceToken.Create(). SetDeviceCode(token.DeviceCode). SetToken([]byte(token.Token)). SetPollInterval(token.PollIntervalSeconds). // Save utc time into database because ent doesn't support comparing dates with different timezones SetExpiry(token.Expiry.UTC()). SetLastRequest(token.LastRequestTime.UTC()). SetStatus(token.Status). SetCodeChallenge(token.PKCE.CodeChallenge). SetCodeChallengeMethod(token.PKCE.CodeChallengeMethod). Save(ctx) if err != nil { return convertDBError("create device token: %w", err) } return nil } // GetDeviceToken extracts a token from the database by device code. func (d *Database) GetDeviceToken(ctx context.Context, deviceCode string) (storage.DeviceToken, error) { deviceToken, err := d.client.DeviceToken.Query(). Where(devicetoken.DeviceCode(deviceCode)). Only(ctx) if err != nil { return storage.DeviceToken{}, convertDBError("get device token: %w", err) } return toStorageDeviceToken(deviceToken), nil } // UpdateDeviceToken changes a token by device code using an updater function and saves it to the database. func (d *Database) UpdateDeviceToken(ctx context.Context, deviceCode string, updater func(old storage.DeviceToken) (storage.DeviceToken, error)) error { tx, err := d.BeginTx(ctx) if err != nil { return convertDBError("update device token tx: %w", err) } token, err := tx.DeviceToken.Query(). Where(devicetoken.DeviceCode(deviceCode)). Only(ctx) if err != nil { return rollback(tx, "update device token database: %w", err) } newToken, err := updater(toStorageDeviceToken(token)) if err != nil { return rollback(tx, "update device token updating: %w", err) } _, err = tx.DeviceToken.Update(). Where(devicetoken.DeviceCode(newToken.DeviceCode)). SetDeviceCode(newToken.DeviceCode). SetToken([]byte(newToken.Token)). SetPollInterval(newToken.PollIntervalSeconds). // Save utc time into database because ent doesn't support comparing dates with different timezones SetExpiry(newToken.Expiry.UTC()). SetLastRequest(newToken.LastRequestTime.UTC()). SetStatus(newToken.Status). SetCodeChallenge(newToken.PKCE.CodeChallenge). SetCodeChallengeMethod(newToken.PKCE.CodeChallengeMethod). Save(ctx) if err != nil { return rollback(tx, "update device token uploading: %w", err) } if err = tx.Commit(); err != nil { return rollback(tx, "update device token commit: %w", err) } return nil } ================================================ FILE: storage/ent/client/keys.go ================================================ package client import ( "context" "errors" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db" ) func getKeys(ctx context.Context, client *db.KeysClient) (storage.Keys, error) { rawKeys, err := client.Get(ctx, keysRowID) if err != nil { return storage.Keys{}, convertDBError("get keys: %w", err) } return toStorageKeys(rawKeys), nil } // GetKeys returns signing keys, public keys and verification keys from the database. func (d *Database) GetKeys(ctx context.Context) (storage.Keys, error) { return getKeys(ctx, d.client.Keys) } // UpdateKeys rotates keys using updater function. func (d *Database) UpdateKeys(ctx context.Context, updater func(old storage.Keys) (storage.Keys, error)) error { firstUpdate := false tx, err := d.BeginTx(ctx) if err != nil { return convertDBError("update keys tx: %w", err) } storageKeys, err := getKeys(ctx, tx.Keys) if err != nil { if !errors.Is(err, storage.ErrNotFound) { return rollback(tx, "update keys get: %w", err) } firstUpdate = true } newKeys, err := updater(storageKeys) if err != nil { return rollback(tx, "update keys updating: %w", err) } // ent doesn't have an upsert support yet // https://github.com/facebook/ent/issues/139 if firstUpdate { _, err = tx.Keys.Create(). SetID(keysRowID). SetNextRotation(newKeys.NextRotation). SetSigningKey(*newKeys.SigningKey). SetSigningKeyPub(*newKeys.SigningKeyPub). SetVerificationKeys(newKeys.VerificationKeys). Save(ctx) if err != nil { return rollback(tx, "create keys: %w", err) } if err = tx.Commit(); err != nil { return rollback(tx, "update keys commit: %w", err) } return nil } err = tx.Keys.UpdateOneID(keysRowID). SetNextRotation(newKeys.NextRotation.UTC()). SetSigningKey(*newKeys.SigningKey). SetSigningKeyPub(*newKeys.SigningKeyPub). SetVerificationKeys(newKeys.VerificationKeys). Exec(ctx) if err != nil { return rollback(tx, "update keys uploading: %w", err) } if err = tx.Commit(); err != nil { return rollback(tx, "update keys commit: %w", err) } return nil } ================================================ FILE: storage/ent/client/main.go ================================================ package client import ( "context" "database/sql" "hash" "time" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db" "github.com/dexidp/dex/storage/ent/db/authcode" "github.com/dexidp/dex/storage/ent/db/authrequest" "github.com/dexidp/dex/storage/ent/db/authsession" "github.com/dexidp/dex/storage/ent/db/devicerequest" "github.com/dexidp/dex/storage/ent/db/devicetoken" "github.com/dexidp/dex/storage/ent/db/migrate" ) var _ storage.Storage = (*Database)(nil) type Database struct { client *db.Client txOptions *sql.TxOptions hasher func() hash.Hash } // NewDatabase returns new database client with set options. func NewDatabase(opts ...func(*Database)) *Database { database := &Database{} for _, f := range opts { f(database) } return database } // WithClient sets client option of a Database object. func WithClient(c *db.Client) func(*Database) { return func(s *Database) { s.client = c } } // WithHasher sets client option of a Database object. func WithHasher(h func() hash.Hash) func(*Database) { return func(s *Database) { s.hasher = h } } // WithTxIsolationLevel sets correct isolation level for database transactions. func WithTxIsolationLevel(level sql.IsolationLevel) func(*Database) { return func(s *Database) { s.txOptions = &sql.TxOptions{Isolation: level} } } // Schema exposes migration schema to perform migrations. func (d *Database) Schema() *migrate.Schema { return d.client.Schema } // Close calls the corresponding method of the ent database client. func (d *Database) Close() error { return d.client.Close() } // BeginTx is a wrapper to begin transaction with defined options. func (d *Database) BeginTx(ctx context.Context) (*db.Tx, error) { return d.client.BeginTx(ctx, d.txOptions) } // GarbageCollect removes expired entities from the database. func (d *Database) GarbageCollect(ctx context.Context, now time.Time) (storage.GCResult, error) { result := storage.GCResult{} utcNow := now.UTC() q, err := d.client.AuthRequest.Delete(). Where(authrequest.ExpiryLT(utcNow)). Exec(ctx) if err != nil { return result, convertDBError("gc auth request: %w", err) } result.AuthRequests = int64(q) q, err = d.client.AuthCode.Delete(). Where(authcode.ExpiryLT(utcNow)). Exec(ctx) if err != nil { return result, convertDBError("gc auth code: %w", err) } result.AuthCodes = int64(q) q, err = d.client.DeviceRequest.Delete(). Where(devicerequest.ExpiryLT(utcNow)). Exec(ctx) if err != nil { return result, convertDBError("gc device request: %w", err) } result.DeviceRequests = int64(q) q, err = d.client.DeviceToken.Delete(). Where(devicetoken.ExpiryLT(utcNow)). Exec(ctx) if err != nil { return result, convertDBError("gc device token: %w", err) } result.DeviceTokens = int64(q) q, err = d.client.AuthSession.Delete(). Where( authsession.Or( authsession.AbsoluteExpiryLT(utcNow), authsession.IdleExpiryLT(utcNow), ), ). Exec(ctx) if err != nil { return result, convertDBError("gc auth session: %w", err) } result.AuthSessions = int64(q) return result, err } ================================================ FILE: storage/ent/client/offlinesession.go ================================================ package client import ( "context" "encoding/json" "fmt" "github.com/dexidp/dex/storage" ) // CreateOfflineSessions saves provided offline session into the database. func (d *Database) CreateOfflineSessions(ctx context.Context, session storage.OfflineSessions) error { encodedRefresh, err := json.Marshal(session.Refresh) if err != nil { return fmt.Errorf("encode refresh offline session: %w", err) } id := compositeKeyID(session.UserID, session.ConnID, d.hasher) _, err = d.client.OfflineSession.Create(). SetID(id). SetUserID(session.UserID). SetConnID(session.ConnID). SetConnectorData(session.ConnectorData). SetRefresh(encodedRefresh). Save(ctx) if err != nil { return convertDBError("create offline session: %w", err) } return nil } // GetOfflineSessions extracts an offline session from the database by user id and connector id. func (d *Database) GetOfflineSessions(ctx context.Context, userID, connID string) (storage.OfflineSessions, error) { id := compositeKeyID(userID, connID, d.hasher) offlineSession, err := d.client.OfflineSession.Get(ctx, id) if err != nil { return storage.OfflineSessions{}, convertDBError("get offline session: %w", err) } return toStorageOfflineSession(offlineSession), nil } // DeleteOfflineSessions deletes an offline session from the database by user id and connector id. func (d *Database) DeleteOfflineSessions(ctx context.Context, userID, connID string) error { id := compositeKeyID(userID, connID, d.hasher) err := d.client.OfflineSession.DeleteOneID(id).Exec(ctx) if err != nil { return convertDBError("delete offline session: %w", err) } return nil } // UpdateOfflineSessions changes an offline session by user id and connector id using an updater function. func (d *Database) UpdateOfflineSessions(ctx context.Context, userID string, connID string, updater func(s storage.OfflineSessions) (storage.OfflineSessions, error)) error { id := compositeKeyID(userID, connID, d.hasher) tx, err := d.BeginTx(ctx) if err != nil { return convertDBError("update offline session tx: %w", err) } offlineSession, err := tx.OfflineSession.Get(ctx, id) if err != nil { return rollback(tx, "update offline session database: %w", err) } newOfflineSession, err := updater(toStorageOfflineSession(offlineSession)) if err != nil { return rollback(tx, "update offline session updating: %w", err) } encodedRefresh, err := json.Marshal(newOfflineSession.Refresh) if err != nil { return rollback(tx, "encode refresh offline session: %w", err) } _, err = tx.OfflineSession.UpdateOneID(id). SetUserID(newOfflineSession.UserID). SetConnID(newOfflineSession.ConnID). SetConnectorData(newOfflineSession.ConnectorData). SetRefresh(encodedRefresh). Save(ctx) if err != nil { return rollback(tx, "update offline session uploading: %w", err) } if err = tx.Commit(); err != nil { return rollback(tx, "update offline session commit: %w", err) } return nil } ================================================ FILE: storage/ent/client/password.go ================================================ package client import ( "context" "strings" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db/password" ) // CreatePassword saves provided password into the database. func (d *Database) CreatePassword(ctx context.Context, password storage.Password) error { _, err := d.client.Password.Create(). SetEmail(password.Email). SetHash(password.Hash). SetUsername(password.Username). SetName(password.Name). SetPreferredUsername(password.PreferredUsername). SetNillableEmailVerified(password.EmailVerified). SetUserID(password.UserID). SetGroups(password.Groups). Save(ctx) if err != nil { return convertDBError("create password: %w", err) } return nil } // ListPasswords extracts an array of passwords from the database. func (d *Database) ListPasswords(ctx context.Context) ([]storage.Password, error) { passwords, err := d.client.Password.Query().All(ctx) if err != nil { return nil, convertDBError("list passwords: %w", err) } storagePasswords := make([]storage.Password, 0, len(passwords)) for _, p := range passwords { storagePasswords = append(storagePasswords, toStoragePassword(p)) } return storagePasswords, nil } // GetPassword extracts a password from the database by email. func (d *Database) GetPassword(ctx context.Context, email string) (storage.Password, error) { email = strings.ToLower(email) passwordFromStorage, err := d.client.Password.Query(). Where(password.Email(email)). Only(ctx) if err != nil { return storage.Password{}, convertDBError("get password: %w", err) } return toStoragePassword(passwordFromStorage), nil } // DeletePassword deletes a password from the database by email. func (d *Database) DeletePassword(ctx context.Context, email string) error { email = strings.ToLower(email) _, err := d.client.Password.Delete(). Where(password.Email(email)). Exec(ctx) if err != nil { return convertDBError("delete password: %w", err) } return nil } // UpdatePassword changes a password by email using an updater function and saves it to the database. func (d *Database) UpdatePassword(ctx context.Context, email string, updater func(old storage.Password) (storage.Password, error)) error { email = strings.ToLower(email) tx, err := d.BeginTx(ctx) if err != nil { return convertDBError("update connector tx: %w", err) } passwordToUpdate, err := tx.Password.Query(). Where(password.Email(email)). Only(ctx) if err != nil { return rollback(tx, "update password database: %w", err) } newPassword, err := updater(toStoragePassword(passwordToUpdate)) if err != nil { return rollback(tx, "update password updating: %w", err) } _, err = tx.Password.Update(). Where(password.Email(newPassword.Email)). SetEmail(newPassword.Email). SetHash(newPassword.Hash). SetUsername(newPassword.Username). SetName(newPassword.Name). SetPreferredUsername(newPassword.PreferredUsername). SetNillableEmailVerified(newPassword.EmailVerified). SetUserID(newPassword.UserID). SetGroups(newPassword.Groups). Save(ctx) if err != nil { return rollback(tx, "update password uploading: %w", err) } if err = tx.Commit(); err != nil { return rollback(tx, "update password commit: %w", err) } return nil } ================================================ FILE: storage/ent/client/refreshtoken.go ================================================ package client import ( "context" "github.com/dexidp/dex/storage" ) // CreateRefresh saves provided refresh token into the database. func (d *Database) CreateRefresh(ctx context.Context, refresh storage.RefreshToken) error { _, err := d.client.RefreshToken.Create(). SetID(refresh.ID). SetClientID(refresh.ClientID). SetScopes(refresh.Scopes). SetNonce(refresh.Nonce). SetClaimsUserID(refresh.Claims.UserID). SetClaimsEmail(refresh.Claims.Email). SetClaimsEmailVerified(refresh.Claims.EmailVerified). SetClaimsUsername(refresh.Claims.Username). SetClaimsPreferredUsername(refresh.Claims.PreferredUsername). SetClaimsGroups(refresh.Claims.Groups). SetConnectorID(refresh.ConnectorID). SetConnectorData(refresh.ConnectorData). SetToken(refresh.Token). SetObsoleteToken(refresh.ObsoleteToken). // Save utc time into database because ent doesn't support comparing dates with different timezones SetLastUsed(refresh.LastUsed.UTC()). SetCreatedAt(refresh.CreatedAt.UTC()). Save(ctx) if err != nil { return convertDBError("create refresh token: %w", err) } return nil } // ListRefreshTokens extracts an array of refresh tokens from the database. func (d *Database) ListRefreshTokens(ctx context.Context) ([]storage.RefreshToken, error) { refreshTokens, err := d.client.RefreshToken.Query().All(ctx) if err != nil { return nil, convertDBError("list refresh tokens: %w", err) } storageRefreshTokens := make([]storage.RefreshToken, 0, len(refreshTokens)) for _, r := range refreshTokens { storageRefreshTokens = append(storageRefreshTokens, toStorageRefreshToken(r)) } return storageRefreshTokens, nil } // GetRefresh extracts a refresh token from the database by id. func (d *Database) GetRefresh(ctx context.Context, id string) (storage.RefreshToken, error) { refreshToken, err := d.client.RefreshToken.Get(ctx, id) if err != nil { return storage.RefreshToken{}, convertDBError("get refresh token: %w", err) } return toStorageRefreshToken(refreshToken), nil } // DeleteRefresh deletes a refresh token from the database by id. func (d *Database) DeleteRefresh(ctx context.Context, id string) error { err := d.client.RefreshToken.DeleteOneID(id).Exec(ctx) if err != nil { return convertDBError("delete refresh token: %w", err) } return nil } // UpdateRefreshToken changes a refresh token by id using an updater function and saves it to the database. func (d *Database) UpdateRefreshToken(ctx context.Context, id string, updater func(old storage.RefreshToken) (storage.RefreshToken, error)) error { tx, err := d.BeginTx(ctx) if err != nil { return convertDBError("update refresh token tx: %w", err) } token, err := tx.RefreshToken.Get(ctx, id) if err != nil { return rollback(tx, "update refresh token database: %w", err) } newtToken, err := updater(toStorageRefreshToken(token)) if err != nil { return rollback(tx, "update refresh token updating: %w", err) } _, err = tx.RefreshToken.UpdateOneID(newtToken.ID). SetClientID(newtToken.ClientID). SetScopes(newtToken.Scopes). SetNonce(newtToken.Nonce). SetClaimsUserID(newtToken.Claims.UserID). SetClaimsEmail(newtToken.Claims.Email). SetClaimsEmailVerified(newtToken.Claims.EmailVerified). SetClaimsUsername(newtToken.Claims.Username). SetClaimsPreferredUsername(newtToken.Claims.PreferredUsername). SetClaimsGroups(newtToken.Claims.Groups). SetConnectorID(newtToken.ConnectorID). SetConnectorData(newtToken.ConnectorData). SetToken(newtToken.Token). SetObsoleteToken(newtToken.ObsoleteToken). // Save utc time into database because ent doesn't support comparing dates with different timezones SetLastUsed(newtToken.LastUsed.UTC()). SetCreatedAt(newtToken.CreatedAt.UTC()). Save(ctx) if err != nil { return rollback(tx, "update refresh token uploading: %w", err) } if err = tx.Commit(); err != nil { return rollback(tx, "update refresh token commit: %w", err) } return nil } ================================================ FILE: storage/ent/client/types.go ================================================ package client import ( "encoding/json" "strings" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db" ) const keysRowID = "keys" func toStorageKeys(keys *db.Keys) storage.Keys { return storage.Keys{ SigningKey: &keys.SigningKey, SigningKeyPub: &keys.SigningKeyPub, VerificationKeys: keys.VerificationKeys, NextRotation: keys.NextRotation, } } func toStorageAuthRequest(a *db.AuthRequest) storage.AuthRequest { return storage.AuthRequest{ ID: a.ID, ClientID: a.ClientID, ResponseTypes: a.ResponseTypes, Scopes: a.Scopes, RedirectURI: a.RedirectURI, Nonce: a.Nonce, State: a.State, ForceApprovalPrompt: a.ForceApprovalPrompt, LoggedIn: a.LoggedIn, ConnectorID: a.ConnectorID, ConnectorData: *a.ConnectorData, Expiry: a.Expiry, Claims: storage.Claims{ UserID: a.ClaimsUserID, Username: a.ClaimsUsername, PreferredUsername: a.ClaimsPreferredUsername, Email: a.ClaimsEmail, EmailVerified: a.ClaimsEmailVerified, Groups: a.ClaimsGroups, }, PKCE: storage.PKCE{ CodeChallenge: a.CodeChallenge, CodeChallengeMethod: a.CodeChallengeMethod, }, HMACKey: a.HmacKey, MFAValidated: a.MfaValidated, Prompt: a.Prompt, MaxAge: a.MaxAge, AuthTime: a.AuthTime, } } func toStorageAuthCode(a *db.AuthCode) storage.AuthCode { return storage.AuthCode{ ID: a.ID, ClientID: a.ClientID, Scopes: a.Scopes, RedirectURI: a.RedirectURI, Nonce: a.Nonce, ConnectorID: a.ConnectorID, ConnectorData: *a.ConnectorData, Expiry: a.Expiry, Claims: storage.Claims{ UserID: a.ClaimsUserID, Username: a.ClaimsUsername, PreferredUsername: a.ClaimsPreferredUsername, Email: a.ClaimsEmail, EmailVerified: a.ClaimsEmailVerified, Groups: a.ClaimsGroups, }, PKCE: storage.PKCE{ CodeChallenge: a.CodeChallenge, CodeChallengeMethod: a.CodeChallengeMethod, }, AuthTime: a.AuthTime, } } func toStorageClient(c *db.OAuth2Client) storage.Client { return storage.Client{ ID: c.ID, Secret: c.Secret, RedirectURIs: c.RedirectUris, TrustedPeers: c.TrustedPeers, Public: c.Public, Name: c.Name, LogoURL: c.LogoURL, AllowedConnectors: c.AllowedConnectors, MFAChain: c.MfaChain, } } func toStorageConnector(c *db.Connector) storage.Connector { return storage.Connector{ ID: c.ID, Type: c.Type, Name: c.Name, Config: c.Config, GrantTypes: c.GrantTypes, } } func toStorageOfflineSession(o *db.OfflineSession) storage.OfflineSessions { s := storage.OfflineSessions{ UserID: o.UserID, ConnID: o.ConnID, ConnectorData: *o.ConnectorData, } if o.Refresh != nil { if err := json.Unmarshal(o.Refresh, &s.Refresh); err != nil { // Correctness of json structure if guaranteed on uploading panic(err) } } else { // Server code assumes this will be non-nil. s.Refresh = make(map[string]*storage.RefreshTokenRef) } return s } func toStorageRefreshToken(r *db.RefreshToken) storage.RefreshToken { return storage.RefreshToken{ ID: r.ID, Token: r.Token, ObsoleteToken: r.ObsoleteToken, CreatedAt: r.CreatedAt, LastUsed: r.LastUsed, ClientID: r.ClientID, ConnectorID: r.ConnectorID, ConnectorData: *r.ConnectorData, Scopes: r.Scopes, Nonce: r.Nonce, Claims: storage.Claims{ UserID: r.ClaimsUserID, Username: r.ClaimsUsername, PreferredUsername: r.ClaimsPreferredUsername, Email: r.ClaimsEmail, EmailVerified: r.ClaimsEmailVerified, Groups: r.ClaimsGroups, }, } } func toStoragePassword(p *db.Password) storage.Password { return storage.Password{ Email: p.Email, Hash: p.Hash, Username: p.Username, Name: p.Name, PreferredUsername: p.PreferredUsername, EmailVerified: p.EmailVerified, UserID: p.UserID, Groups: p.Groups, } } func toStorageDeviceRequest(r *db.DeviceRequest) storage.DeviceRequest { return storage.DeviceRequest{ UserCode: strings.ToUpper(r.UserCode), DeviceCode: r.DeviceCode, ClientID: r.ClientID, ClientSecret: r.ClientSecret, Scopes: r.Scopes, Expiry: r.Expiry, } } func toStorageUserIdentity(u *db.UserIdentity) storage.UserIdentity { s := storage.UserIdentity{ UserID: u.UserID, ConnectorID: u.ConnectorID, Claims: storage.Claims{ UserID: u.ClaimsUserID, Username: u.ClaimsUsername, PreferredUsername: u.ClaimsPreferredUsername, Email: u.ClaimsEmail, EmailVerified: u.ClaimsEmailVerified, Groups: u.ClaimsGroups, }, CreatedAt: u.CreatedAt, LastLogin: u.LastLogin, BlockedUntil: u.BlockedUntil, } if u.Consents != nil { if err := json.Unmarshal(u.Consents, &s.Consents); err != nil { // Correctness of json structure is guaranteed on uploading panic(err) } if s.Consents == nil { // Ensure Consents is non-nil even if JSON was "null". s.Consents = make(map[string][]string) } } else { // Server code assumes this will be non-nil. s.Consents = make(map[string][]string) } if u.MfaSecrets != nil { if err := json.Unmarshal(*u.MfaSecrets, &s.MFASecrets); err != nil { // Correctness of json structure is guaranteed on uploading panic(err) } if s.MFASecrets == nil { s.MFASecrets = make(map[string]*storage.MFASecret) } } else { s.MFASecrets = make(map[string]*storage.MFASecret) } return s } func toStorageAuthSession(s *db.AuthSession) storage.AuthSession { result := storage.AuthSession{ UserID: s.UserID, ConnectorID: s.ConnectorID, Nonce: s.Nonce, CreatedAt: s.CreatedAt, LastActivity: s.LastActivity, IPAddress: s.IPAddress, UserAgent: s.UserAgent, AbsoluteExpiry: s.AbsoluteExpiry, IdleExpiry: s.IdleExpiry, } if s.ClientStates != nil { if err := json.Unmarshal(s.ClientStates, &result.ClientStates); err != nil { panic(err) } if result.ClientStates == nil { result.ClientStates = make(map[string]*storage.ClientAuthState) } } else { result.ClientStates = make(map[string]*storage.ClientAuthState) } return result } func toStorageDeviceToken(t *db.DeviceToken) storage.DeviceToken { return storage.DeviceToken{ DeviceCode: t.DeviceCode, Status: t.Status, Token: string(*t.Token), Expiry: t.Expiry, LastRequestTime: t.LastRequest, PollIntervalSeconds: t.PollInterval, PKCE: storage.PKCE{ CodeChallenge: t.CodeChallenge, CodeChallengeMethod: t.CodeChallengeMethod, }, } } ================================================ FILE: storage/ent/client/useridentity.go ================================================ package client import ( "context" "encoding/json" "fmt" "github.com/dexidp/dex/storage" ) // CreateUserIdentity saves provided user identity into the database. func (d *Database) CreateUserIdentity(ctx context.Context, identity storage.UserIdentity) error { if identity.Consents == nil { identity.Consents = make(map[string][]string) } encodedConsents, err := json.Marshal(identity.Consents) if err != nil { return fmt.Errorf("encode consents user identity: %w", err) } if identity.MFASecrets == nil { identity.MFASecrets = make(map[string]*storage.MFASecret) } encodedMFASecrets, err := json.Marshal(identity.MFASecrets) if err != nil { return fmt.Errorf("encode mfa secrets user identity: %w", err) } id := compositeKeyID(identity.UserID, identity.ConnectorID, d.hasher) _, err = d.client.UserIdentity.Create(). SetID(id). SetUserID(identity.UserID). SetConnectorID(identity.ConnectorID). SetClaimsUserID(identity.Claims.UserID). SetClaimsUsername(identity.Claims.Username). SetClaimsPreferredUsername(identity.Claims.PreferredUsername). SetClaimsEmail(identity.Claims.Email). SetClaimsEmailVerified(identity.Claims.EmailVerified). SetClaimsGroups(identity.Claims.Groups). SetConsents(encodedConsents). SetMfaSecrets(encodedMFASecrets). SetCreatedAt(identity.CreatedAt). SetLastLogin(identity.LastLogin). SetBlockedUntil(identity.BlockedUntil). Save(ctx) if err != nil { return convertDBError("create user identity: %w", err) } return nil } // GetUserIdentity extracts a user identity from the database by user id and connector id. func (d *Database) GetUserIdentity(ctx context.Context, userID, connectorID string) (storage.UserIdentity, error) { id := compositeKeyID(userID, connectorID, d.hasher) userIdentity, err := d.client.UserIdentity.Get(ctx, id) if err != nil { return storage.UserIdentity{}, convertDBError("get user identity: %w", err) } return toStorageUserIdentity(userIdentity), nil } // DeleteUserIdentity deletes a user identity from the database by user id and connector id. func (d *Database) DeleteUserIdentity(ctx context.Context, userID, connectorID string) error { id := compositeKeyID(userID, connectorID, d.hasher) err := d.client.UserIdentity.DeleteOneID(id).Exec(ctx) if err != nil { return convertDBError("delete user identity: %w", err) } return nil } // UpdateUserIdentity changes a user identity by user id and connector id using an updater function. func (d *Database) UpdateUserIdentity(ctx context.Context, userID string, connectorID string, updater func(u storage.UserIdentity) (storage.UserIdentity, error)) error { id := compositeKeyID(userID, connectorID, d.hasher) tx, err := d.BeginTx(ctx) if err != nil { return convertDBError("update user identity tx: %w", err) } userIdentity, err := tx.UserIdentity.Get(ctx, id) if err != nil { return rollback(tx, "update user identity database: %w", err) } newUserIdentity, err := updater(toStorageUserIdentity(userIdentity)) if err != nil { return rollback(tx, "update user identity updating: %w", err) } if newUserIdentity.Consents == nil { newUserIdentity.Consents = make(map[string][]string) } encodedConsents, err := json.Marshal(newUserIdentity.Consents) if err != nil { return rollback(tx, "encode consents user identity: %w", err) } if newUserIdentity.MFASecrets == nil { newUserIdentity.MFASecrets = make(map[string]*storage.MFASecret) } encodedMFASecrets, err := json.Marshal(newUserIdentity.MFASecrets) if err != nil { return rollback(tx, "encode mfa secrets user identity: %w", err) } _, err = tx.UserIdentity.UpdateOneID(id). SetUserID(newUserIdentity.UserID). SetConnectorID(newUserIdentity.ConnectorID). SetClaimsUserID(newUserIdentity.Claims.UserID). SetClaimsUsername(newUserIdentity.Claims.Username). SetClaimsPreferredUsername(newUserIdentity.Claims.PreferredUsername). SetClaimsEmail(newUserIdentity.Claims.Email). SetClaimsEmailVerified(newUserIdentity.Claims.EmailVerified). SetClaimsGroups(newUserIdentity.Claims.Groups). SetConsents(encodedConsents). SetMfaSecrets(encodedMFASecrets). SetCreatedAt(newUserIdentity.CreatedAt). SetLastLogin(newUserIdentity.LastLogin). SetBlockedUntil(newUserIdentity.BlockedUntil). Save(ctx) if err != nil { return rollback(tx, "update user identity uploading: %w", err) } if err = tx.Commit(); err != nil { return rollback(tx, "update user identity commit: %w", err) } return nil } // ListUserIdentities lists all user identities in the database. func (d *Database) ListUserIdentities(ctx context.Context) ([]storage.UserIdentity, error) { userIdentities, err := d.client.UserIdentity.Query().All(ctx) if err != nil { return nil, convertDBError("list user identities: %w", err) } storageUserIdentities := make([]storage.UserIdentity, 0, len(userIdentities)) for _, u := range userIdentities { storageUserIdentities = append(storageUserIdentities, toStorageUserIdentity(u)) } return storageUserIdentities, nil } ================================================ FILE: storage/ent/client/utils.go ================================================ package client import ( "fmt" "hash" "github.com/pkg/errors" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db" ) func rollback(tx *db.Tx, t string, err error) error { rerr := tx.Rollback() err = convertDBError(t, err) if rerr == nil { return err } return errors.Wrapf(err, "rolling back transaction: %v", rerr) } func convertDBError(t string, err error) error { if db.IsNotFound(err) { return storage.ErrNotFound } if db.IsConstraintError(err) { return storage.ErrAlreadyExists } return fmt.Errorf(t, err) } // compositeKeyID composes a hashed id from two key parts to use as primary key. // ent doesn't support multi-key primary yet // https://github.com/facebook/ent/issues/400 func compositeKeyID(first string, second string, hasher func() hash.Hash) string { h := hasher() h.Write([]byte(first)) h.Write([]byte(second)) return fmt.Sprintf("%x", h.Sum(nil)) } ================================================ FILE: storage/ent/db/authcode/authcode.go ================================================ // Code generated by ent, DO NOT EDIT. package authcode import ( "entgo.io/ent/dialect/sql" ) const ( // Label holds the string label denoting the authcode type in the database. Label = "auth_code" // FieldID holds the string denoting the id field in the database. FieldID = "id" // FieldClientID holds the string denoting the client_id field in the database. FieldClientID = "client_id" // FieldScopes holds the string denoting the scopes field in the database. FieldScopes = "scopes" // FieldNonce holds the string denoting the nonce field in the database. FieldNonce = "nonce" // FieldRedirectURI holds the string denoting the redirect_uri field in the database. FieldRedirectURI = "redirect_uri" // FieldClaimsUserID holds the string denoting the claims_user_id field in the database. FieldClaimsUserID = "claims_user_id" // FieldClaimsUsername holds the string denoting the claims_username field in the database. FieldClaimsUsername = "claims_username" // FieldClaimsEmail holds the string denoting the claims_email field in the database. FieldClaimsEmail = "claims_email" // FieldClaimsEmailVerified holds the string denoting the claims_email_verified field in the database. FieldClaimsEmailVerified = "claims_email_verified" // FieldClaimsGroups holds the string denoting the claims_groups field in the database. FieldClaimsGroups = "claims_groups" // FieldClaimsPreferredUsername holds the string denoting the claims_preferred_username field in the database. FieldClaimsPreferredUsername = "claims_preferred_username" // FieldConnectorID holds the string denoting the connector_id field in the database. FieldConnectorID = "connector_id" // FieldConnectorData holds the string denoting the connector_data field in the database. FieldConnectorData = "connector_data" // FieldExpiry holds the string denoting the expiry field in the database. FieldExpiry = "expiry" // FieldCodeChallenge holds the string denoting the code_challenge field in the database. FieldCodeChallenge = "code_challenge" // FieldCodeChallengeMethod holds the string denoting the code_challenge_method field in the database. FieldCodeChallengeMethod = "code_challenge_method" // FieldAuthTime holds the string denoting the auth_time field in the database. FieldAuthTime = "auth_time" // Table holds the table name of the authcode in the database. Table = "auth_codes" ) // Columns holds all SQL columns for authcode fields. var Columns = []string{ FieldID, FieldClientID, FieldScopes, FieldNonce, FieldRedirectURI, FieldClaimsUserID, FieldClaimsUsername, FieldClaimsEmail, FieldClaimsEmailVerified, FieldClaimsGroups, FieldClaimsPreferredUsername, FieldConnectorID, FieldConnectorData, FieldExpiry, FieldCodeChallenge, FieldCodeChallengeMethod, FieldAuthTime, } // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { if column == Columns[i] { return true } } return false } var ( // ClientIDValidator is a validator for the "client_id" field. It is called by the builders before save. ClientIDValidator func(string) error // NonceValidator is a validator for the "nonce" field. It is called by the builders before save. NonceValidator func(string) error // RedirectURIValidator is a validator for the "redirect_uri" field. It is called by the builders before save. RedirectURIValidator func(string) error // ClaimsUserIDValidator is a validator for the "claims_user_id" field. It is called by the builders before save. ClaimsUserIDValidator func(string) error // ClaimsUsernameValidator is a validator for the "claims_username" field. It is called by the builders before save. ClaimsUsernameValidator func(string) error // ClaimsEmailValidator is a validator for the "claims_email" field. It is called by the builders before save. ClaimsEmailValidator func(string) error // DefaultClaimsPreferredUsername holds the default value on creation for the "claims_preferred_username" field. DefaultClaimsPreferredUsername string // ConnectorIDValidator is a validator for the "connector_id" field. It is called by the builders before save. ConnectorIDValidator func(string) error // DefaultCodeChallenge holds the default value on creation for the "code_challenge" field. DefaultCodeChallenge string // DefaultCodeChallengeMethod holds the default value on creation for the "code_challenge_method" field. DefaultCodeChallengeMethod string // IDValidator is a validator for the "id" field. It is called by the builders before save. IDValidator func(string) error ) // OrderOption defines the ordering options for the AuthCode queries. type OrderOption func(*sql.Selector) // ByID orders the results by the id field. func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } // ByClientID orders the results by the client_id field. func ByClientID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClientID, opts...).ToFunc() } // ByNonce orders the results by the nonce field. func ByNonce(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldNonce, opts...).ToFunc() } // ByRedirectURI orders the results by the redirect_uri field. func ByRedirectURI(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldRedirectURI, opts...).ToFunc() } // ByClaimsUserID orders the results by the claims_user_id field. func ByClaimsUserID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsUserID, opts...).ToFunc() } // ByClaimsUsername orders the results by the claims_username field. func ByClaimsUsername(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsUsername, opts...).ToFunc() } // ByClaimsEmail orders the results by the claims_email field. func ByClaimsEmail(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsEmail, opts...).ToFunc() } // ByClaimsEmailVerified orders the results by the claims_email_verified field. func ByClaimsEmailVerified(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsEmailVerified, opts...).ToFunc() } // ByClaimsPreferredUsername orders the results by the claims_preferred_username field. func ByClaimsPreferredUsername(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsPreferredUsername, opts...).ToFunc() } // ByConnectorID orders the results by the connector_id field. func ByConnectorID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldConnectorID, opts...).ToFunc() } // ByExpiry orders the results by the expiry field. func ByExpiry(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldExpiry, opts...).ToFunc() } // ByCodeChallenge orders the results by the code_challenge field. func ByCodeChallenge(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCodeChallenge, opts...).ToFunc() } // ByCodeChallengeMethod orders the results by the code_challenge_method field. func ByCodeChallengeMethod(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCodeChallengeMethod, opts...).ToFunc() } // ByAuthTime orders the results by the auth_time field. func ByAuthTime(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldAuthTime, opts...).ToFunc() } ================================================ FILE: storage/ent/db/authcode/where.go ================================================ // Code generated by ent, DO NOT EDIT. package authcode import ( "time" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ID filters vertices based on their ID field. func ID(id string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. func IDEQ(id string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. func IDNEQ(id string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. func IDIn(ids ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. func IDNotIn(ids ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. func IDGT(id string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. func IDGTE(id string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. func IDLT(id string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. func IDLTE(id string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldID, id)) } // IDEqualFold applies the EqualFold predicate on the ID field. func IDEqualFold(id string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEqualFold(FieldID, id)) } // IDContainsFold applies the ContainsFold predicate on the ID field. func IDContainsFold(id string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContainsFold(FieldID, id)) } // ClientID applies equality check predicate on the "client_id" field. It's identical to ClientIDEQ. func ClientID(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldClientID, v)) } // Nonce applies equality check predicate on the "nonce" field. It's identical to NonceEQ. func Nonce(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldNonce, v)) } // RedirectURI applies equality check predicate on the "redirect_uri" field. It's identical to RedirectURIEQ. func RedirectURI(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldRedirectURI, v)) } // ClaimsUserID applies equality check predicate on the "claims_user_id" field. It's identical to ClaimsUserIDEQ. func ClaimsUserID(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldClaimsUserID, v)) } // ClaimsUsername applies equality check predicate on the "claims_username" field. It's identical to ClaimsUsernameEQ. func ClaimsUsername(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldClaimsUsername, v)) } // ClaimsEmail applies equality check predicate on the "claims_email" field. It's identical to ClaimsEmailEQ. func ClaimsEmail(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldClaimsEmail, v)) } // ClaimsEmailVerified applies equality check predicate on the "claims_email_verified" field. It's identical to ClaimsEmailVerifiedEQ. func ClaimsEmailVerified(v bool) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldClaimsEmailVerified, v)) } // ClaimsPreferredUsername applies equality check predicate on the "claims_preferred_username" field. It's identical to ClaimsPreferredUsernameEQ. func ClaimsPreferredUsername(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldClaimsPreferredUsername, v)) } // ConnectorID applies equality check predicate on the "connector_id" field. It's identical to ConnectorIDEQ. func ConnectorID(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldConnectorID, v)) } // ConnectorData applies equality check predicate on the "connector_data" field. It's identical to ConnectorDataEQ. func ConnectorData(v []byte) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldConnectorData, v)) } // Expiry applies equality check predicate on the "expiry" field. It's identical to ExpiryEQ. func Expiry(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldExpiry, v)) } // CodeChallenge applies equality check predicate on the "code_challenge" field. It's identical to CodeChallengeEQ. func CodeChallenge(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldCodeChallenge, v)) } // CodeChallengeMethod applies equality check predicate on the "code_challenge_method" field. It's identical to CodeChallengeMethodEQ. func CodeChallengeMethod(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldCodeChallengeMethod, v)) } // AuthTime applies equality check predicate on the "auth_time" field. It's identical to AuthTimeEQ. func AuthTime(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldAuthTime, v)) } // ClientIDEQ applies the EQ predicate on the "client_id" field. func ClientIDEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldClientID, v)) } // ClientIDNEQ applies the NEQ predicate on the "client_id" field. func ClientIDNEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldClientID, v)) } // ClientIDIn applies the In predicate on the "client_id" field. func ClientIDIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldClientID, vs...)) } // ClientIDNotIn applies the NotIn predicate on the "client_id" field. func ClientIDNotIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldClientID, vs...)) } // ClientIDGT applies the GT predicate on the "client_id" field. func ClientIDGT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldClientID, v)) } // ClientIDGTE applies the GTE predicate on the "client_id" field. func ClientIDGTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldClientID, v)) } // ClientIDLT applies the LT predicate on the "client_id" field. func ClientIDLT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldClientID, v)) } // ClientIDLTE applies the LTE predicate on the "client_id" field. func ClientIDLTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldClientID, v)) } // ClientIDContains applies the Contains predicate on the "client_id" field. func ClientIDContains(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContains(FieldClientID, v)) } // ClientIDHasPrefix applies the HasPrefix predicate on the "client_id" field. func ClientIDHasPrefix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasPrefix(FieldClientID, v)) } // ClientIDHasSuffix applies the HasSuffix predicate on the "client_id" field. func ClientIDHasSuffix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasSuffix(FieldClientID, v)) } // ClientIDEqualFold applies the EqualFold predicate on the "client_id" field. func ClientIDEqualFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEqualFold(FieldClientID, v)) } // ClientIDContainsFold applies the ContainsFold predicate on the "client_id" field. func ClientIDContainsFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContainsFold(FieldClientID, v)) } // ScopesIsNil applies the IsNil predicate on the "scopes" field. func ScopesIsNil() predicate.AuthCode { return predicate.AuthCode(sql.FieldIsNull(FieldScopes)) } // ScopesNotNil applies the NotNil predicate on the "scopes" field. func ScopesNotNil() predicate.AuthCode { return predicate.AuthCode(sql.FieldNotNull(FieldScopes)) } // NonceEQ applies the EQ predicate on the "nonce" field. func NonceEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldNonce, v)) } // NonceNEQ applies the NEQ predicate on the "nonce" field. func NonceNEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldNonce, v)) } // NonceIn applies the In predicate on the "nonce" field. func NonceIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldNonce, vs...)) } // NonceNotIn applies the NotIn predicate on the "nonce" field. func NonceNotIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldNonce, vs...)) } // NonceGT applies the GT predicate on the "nonce" field. func NonceGT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldNonce, v)) } // NonceGTE applies the GTE predicate on the "nonce" field. func NonceGTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldNonce, v)) } // NonceLT applies the LT predicate on the "nonce" field. func NonceLT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldNonce, v)) } // NonceLTE applies the LTE predicate on the "nonce" field. func NonceLTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldNonce, v)) } // NonceContains applies the Contains predicate on the "nonce" field. func NonceContains(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContains(FieldNonce, v)) } // NonceHasPrefix applies the HasPrefix predicate on the "nonce" field. func NonceHasPrefix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasPrefix(FieldNonce, v)) } // NonceHasSuffix applies the HasSuffix predicate on the "nonce" field. func NonceHasSuffix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasSuffix(FieldNonce, v)) } // NonceEqualFold applies the EqualFold predicate on the "nonce" field. func NonceEqualFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEqualFold(FieldNonce, v)) } // NonceContainsFold applies the ContainsFold predicate on the "nonce" field. func NonceContainsFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContainsFold(FieldNonce, v)) } // RedirectURIEQ applies the EQ predicate on the "redirect_uri" field. func RedirectURIEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldRedirectURI, v)) } // RedirectURINEQ applies the NEQ predicate on the "redirect_uri" field. func RedirectURINEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldRedirectURI, v)) } // RedirectURIIn applies the In predicate on the "redirect_uri" field. func RedirectURIIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldRedirectURI, vs...)) } // RedirectURINotIn applies the NotIn predicate on the "redirect_uri" field. func RedirectURINotIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldRedirectURI, vs...)) } // RedirectURIGT applies the GT predicate on the "redirect_uri" field. func RedirectURIGT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldRedirectURI, v)) } // RedirectURIGTE applies the GTE predicate on the "redirect_uri" field. func RedirectURIGTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldRedirectURI, v)) } // RedirectURILT applies the LT predicate on the "redirect_uri" field. func RedirectURILT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldRedirectURI, v)) } // RedirectURILTE applies the LTE predicate on the "redirect_uri" field. func RedirectURILTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldRedirectURI, v)) } // RedirectURIContains applies the Contains predicate on the "redirect_uri" field. func RedirectURIContains(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContains(FieldRedirectURI, v)) } // RedirectURIHasPrefix applies the HasPrefix predicate on the "redirect_uri" field. func RedirectURIHasPrefix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasPrefix(FieldRedirectURI, v)) } // RedirectURIHasSuffix applies the HasSuffix predicate on the "redirect_uri" field. func RedirectURIHasSuffix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasSuffix(FieldRedirectURI, v)) } // RedirectURIEqualFold applies the EqualFold predicate on the "redirect_uri" field. func RedirectURIEqualFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEqualFold(FieldRedirectURI, v)) } // RedirectURIContainsFold applies the ContainsFold predicate on the "redirect_uri" field. func RedirectURIContainsFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContainsFold(FieldRedirectURI, v)) } // ClaimsUserIDEQ applies the EQ predicate on the "claims_user_id" field. func ClaimsUserIDEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldClaimsUserID, v)) } // ClaimsUserIDNEQ applies the NEQ predicate on the "claims_user_id" field. func ClaimsUserIDNEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldClaimsUserID, v)) } // ClaimsUserIDIn applies the In predicate on the "claims_user_id" field. func ClaimsUserIDIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldClaimsUserID, vs...)) } // ClaimsUserIDNotIn applies the NotIn predicate on the "claims_user_id" field. func ClaimsUserIDNotIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldClaimsUserID, vs...)) } // ClaimsUserIDGT applies the GT predicate on the "claims_user_id" field. func ClaimsUserIDGT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldClaimsUserID, v)) } // ClaimsUserIDGTE applies the GTE predicate on the "claims_user_id" field. func ClaimsUserIDGTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldClaimsUserID, v)) } // ClaimsUserIDLT applies the LT predicate on the "claims_user_id" field. func ClaimsUserIDLT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldClaimsUserID, v)) } // ClaimsUserIDLTE applies the LTE predicate on the "claims_user_id" field. func ClaimsUserIDLTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldClaimsUserID, v)) } // ClaimsUserIDContains applies the Contains predicate on the "claims_user_id" field. func ClaimsUserIDContains(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContains(FieldClaimsUserID, v)) } // ClaimsUserIDHasPrefix applies the HasPrefix predicate on the "claims_user_id" field. func ClaimsUserIDHasPrefix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasPrefix(FieldClaimsUserID, v)) } // ClaimsUserIDHasSuffix applies the HasSuffix predicate on the "claims_user_id" field. func ClaimsUserIDHasSuffix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasSuffix(FieldClaimsUserID, v)) } // ClaimsUserIDEqualFold applies the EqualFold predicate on the "claims_user_id" field. func ClaimsUserIDEqualFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEqualFold(FieldClaimsUserID, v)) } // ClaimsUserIDContainsFold applies the ContainsFold predicate on the "claims_user_id" field. func ClaimsUserIDContainsFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContainsFold(FieldClaimsUserID, v)) } // ClaimsUsernameEQ applies the EQ predicate on the "claims_username" field. func ClaimsUsernameEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldClaimsUsername, v)) } // ClaimsUsernameNEQ applies the NEQ predicate on the "claims_username" field. func ClaimsUsernameNEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldClaimsUsername, v)) } // ClaimsUsernameIn applies the In predicate on the "claims_username" field. func ClaimsUsernameIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldClaimsUsername, vs...)) } // ClaimsUsernameNotIn applies the NotIn predicate on the "claims_username" field. func ClaimsUsernameNotIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldClaimsUsername, vs...)) } // ClaimsUsernameGT applies the GT predicate on the "claims_username" field. func ClaimsUsernameGT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldClaimsUsername, v)) } // ClaimsUsernameGTE applies the GTE predicate on the "claims_username" field. func ClaimsUsernameGTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldClaimsUsername, v)) } // ClaimsUsernameLT applies the LT predicate on the "claims_username" field. func ClaimsUsernameLT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldClaimsUsername, v)) } // ClaimsUsernameLTE applies the LTE predicate on the "claims_username" field. func ClaimsUsernameLTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldClaimsUsername, v)) } // ClaimsUsernameContains applies the Contains predicate on the "claims_username" field. func ClaimsUsernameContains(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContains(FieldClaimsUsername, v)) } // ClaimsUsernameHasPrefix applies the HasPrefix predicate on the "claims_username" field. func ClaimsUsernameHasPrefix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasPrefix(FieldClaimsUsername, v)) } // ClaimsUsernameHasSuffix applies the HasSuffix predicate on the "claims_username" field. func ClaimsUsernameHasSuffix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasSuffix(FieldClaimsUsername, v)) } // ClaimsUsernameEqualFold applies the EqualFold predicate on the "claims_username" field. func ClaimsUsernameEqualFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEqualFold(FieldClaimsUsername, v)) } // ClaimsUsernameContainsFold applies the ContainsFold predicate on the "claims_username" field. func ClaimsUsernameContainsFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContainsFold(FieldClaimsUsername, v)) } // ClaimsEmailEQ applies the EQ predicate on the "claims_email" field. func ClaimsEmailEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldClaimsEmail, v)) } // ClaimsEmailNEQ applies the NEQ predicate on the "claims_email" field. func ClaimsEmailNEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldClaimsEmail, v)) } // ClaimsEmailIn applies the In predicate on the "claims_email" field. func ClaimsEmailIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldClaimsEmail, vs...)) } // ClaimsEmailNotIn applies the NotIn predicate on the "claims_email" field. func ClaimsEmailNotIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldClaimsEmail, vs...)) } // ClaimsEmailGT applies the GT predicate on the "claims_email" field. func ClaimsEmailGT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldClaimsEmail, v)) } // ClaimsEmailGTE applies the GTE predicate on the "claims_email" field. func ClaimsEmailGTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldClaimsEmail, v)) } // ClaimsEmailLT applies the LT predicate on the "claims_email" field. func ClaimsEmailLT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldClaimsEmail, v)) } // ClaimsEmailLTE applies the LTE predicate on the "claims_email" field. func ClaimsEmailLTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldClaimsEmail, v)) } // ClaimsEmailContains applies the Contains predicate on the "claims_email" field. func ClaimsEmailContains(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContains(FieldClaimsEmail, v)) } // ClaimsEmailHasPrefix applies the HasPrefix predicate on the "claims_email" field. func ClaimsEmailHasPrefix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasPrefix(FieldClaimsEmail, v)) } // ClaimsEmailHasSuffix applies the HasSuffix predicate on the "claims_email" field. func ClaimsEmailHasSuffix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasSuffix(FieldClaimsEmail, v)) } // ClaimsEmailEqualFold applies the EqualFold predicate on the "claims_email" field. func ClaimsEmailEqualFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEqualFold(FieldClaimsEmail, v)) } // ClaimsEmailContainsFold applies the ContainsFold predicate on the "claims_email" field. func ClaimsEmailContainsFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContainsFold(FieldClaimsEmail, v)) } // ClaimsEmailVerifiedEQ applies the EQ predicate on the "claims_email_verified" field. func ClaimsEmailVerifiedEQ(v bool) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldClaimsEmailVerified, v)) } // ClaimsEmailVerifiedNEQ applies the NEQ predicate on the "claims_email_verified" field. func ClaimsEmailVerifiedNEQ(v bool) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldClaimsEmailVerified, v)) } // ClaimsGroupsIsNil applies the IsNil predicate on the "claims_groups" field. func ClaimsGroupsIsNil() predicate.AuthCode { return predicate.AuthCode(sql.FieldIsNull(FieldClaimsGroups)) } // ClaimsGroupsNotNil applies the NotNil predicate on the "claims_groups" field. func ClaimsGroupsNotNil() predicate.AuthCode { return predicate.AuthCode(sql.FieldNotNull(FieldClaimsGroups)) } // ClaimsPreferredUsernameEQ applies the EQ predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameNEQ applies the NEQ predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameNEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameIn applies the In predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldClaimsPreferredUsername, vs...)) } // ClaimsPreferredUsernameNotIn applies the NotIn predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameNotIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldClaimsPreferredUsername, vs...)) } // ClaimsPreferredUsernameGT applies the GT predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameGT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameGTE applies the GTE predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameGTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameLT applies the LT predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameLT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameLTE applies the LTE predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameLTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameContains applies the Contains predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameContains(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContains(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameHasPrefix applies the HasPrefix predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameHasPrefix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasPrefix(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameHasSuffix applies the HasSuffix predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameHasSuffix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasSuffix(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameEqualFold applies the EqualFold predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameEqualFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEqualFold(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameContainsFold applies the ContainsFold predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameContainsFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContainsFold(FieldClaimsPreferredUsername, v)) } // ConnectorIDEQ applies the EQ predicate on the "connector_id" field. func ConnectorIDEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldConnectorID, v)) } // ConnectorIDNEQ applies the NEQ predicate on the "connector_id" field. func ConnectorIDNEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldConnectorID, v)) } // ConnectorIDIn applies the In predicate on the "connector_id" field. func ConnectorIDIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldConnectorID, vs...)) } // ConnectorIDNotIn applies the NotIn predicate on the "connector_id" field. func ConnectorIDNotIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldConnectorID, vs...)) } // ConnectorIDGT applies the GT predicate on the "connector_id" field. func ConnectorIDGT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldConnectorID, v)) } // ConnectorIDGTE applies the GTE predicate on the "connector_id" field. func ConnectorIDGTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldConnectorID, v)) } // ConnectorIDLT applies the LT predicate on the "connector_id" field. func ConnectorIDLT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldConnectorID, v)) } // ConnectorIDLTE applies the LTE predicate on the "connector_id" field. func ConnectorIDLTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldConnectorID, v)) } // ConnectorIDContains applies the Contains predicate on the "connector_id" field. func ConnectorIDContains(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContains(FieldConnectorID, v)) } // ConnectorIDHasPrefix applies the HasPrefix predicate on the "connector_id" field. func ConnectorIDHasPrefix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasPrefix(FieldConnectorID, v)) } // ConnectorIDHasSuffix applies the HasSuffix predicate on the "connector_id" field. func ConnectorIDHasSuffix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasSuffix(FieldConnectorID, v)) } // ConnectorIDEqualFold applies the EqualFold predicate on the "connector_id" field. func ConnectorIDEqualFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEqualFold(FieldConnectorID, v)) } // ConnectorIDContainsFold applies the ContainsFold predicate on the "connector_id" field. func ConnectorIDContainsFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContainsFold(FieldConnectorID, v)) } // ConnectorDataEQ applies the EQ predicate on the "connector_data" field. func ConnectorDataEQ(v []byte) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldConnectorData, v)) } // ConnectorDataNEQ applies the NEQ predicate on the "connector_data" field. func ConnectorDataNEQ(v []byte) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldConnectorData, v)) } // ConnectorDataIn applies the In predicate on the "connector_data" field. func ConnectorDataIn(vs ...[]byte) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldConnectorData, vs...)) } // ConnectorDataNotIn applies the NotIn predicate on the "connector_data" field. func ConnectorDataNotIn(vs ...[]byte) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldConnectorData, vs...)) } // ConnectorDataGT applies the GT predicate on the "connector_data" field. func ConnectorDataGT(v []byte) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldConnectorData, v)) } // ConnectorDataGTE applies the GTE predicate on the "connector_data" field. func ConnectorDataGTE(v []byte) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldConnectorData, v)) } // ConnectorDataLT applies the LT predicate on the "connector_data" field. func ConnectorDataLT(v []byte) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldConnectorData, v)) } // ConnectorDataLTE applies the LTE predicate on the "connector_data" field. func ConnectorDataLTE(v []byte) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldConnectorData, v)) } // ConnectorDataIsNil applies the IsNil predicate on the "connector_data" field. func ConnectorDataIsNil() predicate.AuthCode { return predicate.AuthCode(sql.FieldIsNull(FieldConnectorData)) } // ConnectorDataNotNil applies the NotNil predicate on the "connector_data" field. func ConnectorDataNotNil() predicate.AuthCode { return predicate.AuthCode(sql.FieldNotNull(FieldConnectorData)) } // ExpiryEQ applies the EQ predicate on the "expiry" field. func ExpiryEQ(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldExpiry, v)) } // ExpiryNEQ applies the NEQ predicate on the "expiry" field. func ExpiryNEQ(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldExpiry, v)) } // ExpiryIn applies the In predicate on the "expiry" field. func ExpiryIn(vs ...time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldExpiry, vs...)) } // ExpiryNotIn applies the NotIn predicate on the "expiry" field. func ExpiryNotIn(vs ...time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldExpiry, vs...)) } // ExpiryGT applies the GT predicate on the "expiry" field. func ExpiryGT(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldExpiry, v)) } // ExpiryGTE applies the GTE predicate on the "expiry" field. func ExpiryGTE(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldExpiry, v)) } // ExpiryLT applies the LT predicate on the "expiry" field. func ExpiryLT(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldExpiry, v)) } // ExpiryLTE applies the LTE predicate on the "expiry" field. func ExpiryLTE(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldExpiry, v)) } // CodeChallengeEQ applies the EQ predicate on the "code_challenge" field. func CodeChallengeEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldCodeChallenge, v)) } // CodeChallengeNEQ applies the NEQ predicate on the "code_challenge" field. func CodeChallengeNEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldCodeChallenge, v)) } // CodeChallengeIn applies the In predicate on the "code_challenge" field. func CodeChallengeIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldCodeChallenge, vs...)) } // CodeChallengeNotIn applies the NotIn predicate on the "code_challenge" field. func CodeChallengeNotIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldCodeChallenge, vs...)) } // CodeChallengeGT applies the GT predicate on the "code_challenge" field. func CodeChallengeGT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldCodeChallenge, v)) } // CodeChallengeGTE applies the GTE predicate on the "code_challenge" field. func CodeChallengeGTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldCodeChallenge, v)) } // CodeChallengeLT applies the LT predicate on the "code_challenge" field. func CodeChallengeLT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldCodeChallenge, v)) } // CodeChallengeLTE applies the LTE predicate on the "code_challenge" field. func CodeChallengeLTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldCodeChallenge, v)) } // CodeChallengeContains applies the Contains predicate on the "code_challenge" field. func CodeChallengeContains(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContains(FieldCodeChallenge, v)) } // CodeChallengeHasPrefix applies the HasPrefix predicate on the "code_challenge" field. func CodeChallengeHasPrefix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasPrefix(FieldCodeChallenge, v)) } // CodeChallengeHasSuffix applies the HasSuffix predicate on the "code_challenge" field. func CodeChallengeHasSuffix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasSuffix(FieldCodeChallenge, v)) } // CodeChallengeEqualFold applies the EqualFold predicate on the "code_challenge" field. func CodeChallengeEqualFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEqualFold(FieldCodeChallenge, v)) } // CodeChallengeContainsFold applies the ContainsFold predicate on the "code_challenge" field. func CodeChallengeContainsFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContainsFold(FieldCodeChallenge, v)) } // CodeChallengeMethodEQ applies the EQ predicate on the "code_challenge_method" field. func CodeChallengeMethodEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodNEQ applies the NEQ predicate on the "code_challenge_method" field. func CodeChallengeMethodNEQ(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodIn applies the In predicate on the "code_challenge_method" field. func CodeChallengeMethodIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldCodeChallengeMethod, vs...)) } // CodeChallengeMethodNotIn applies the NotIn predicate on the "code_challenge_method" field. func CodeChallengeMethodNotIn(vs ...string) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldCodeChallengeMethod, vs...)) } // CodeChallengeMethodGT applies the GT predicate on the "code_challenge_method" field. func CodeChallengeMethodGT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodGTE applies the GTE predicate on the "code_challenge_method" field. func CodeChallengeMethodGTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodLT applies the LT predicate on the "code_challenge_method" field. func CodeChallengeMethodLT(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodLTE applies the LTE predicate on the "code_challenge_method" field. func CodeChallengeMethodLTE(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodContains applies the Contains predicate on the "code_challenge_method" field. func CodeChallengeMethodContains(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContains(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodHasPrefix applies the HasPrefix predicate on the "code_challenge_method" field. func CodeChallengeMethodHasPrefix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasPrefix(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodHasSuffix applies the HasSuffix predicate on the "code_challenge_method" field. func CodeChallengeMethodHasSuffix(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldHasSuffix(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodEqualFold applies the EqualFold predicate on the "code_challenge_method" field. func CodeChallengeMethodEqualFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldEqualFold(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodContainsFold applies the ContainsFold predicate on the "code_challenge_method" field. func CodeChallengeMethodContainsFold(v string) predicate.AuthCode { return predicate.AuthCode(sql.FieldContainsFold(FieldCodeChallengeMethod, v)) } // AuthTimeEQ applies the EQ predicate on the "auth_time" field. func AuthTimeEQ(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldEQ(FieldAuthTime, v)) } // AuthTimeNEQ applies the NEQ predicate on the "auth_time" field. func AuthTimeNEQ(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldNEQ(FieldAuthTime, v)) } // AuthTimeIn applies the In predicate on the "auth_time" field. func AuthTimeIn(vs ...time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldIn(FieldAuthTime, vs...)) } // AuthTimeNotIn applies the NotIn predicate on the "auth_time" field. func AuthTimeNotIn(vs ...time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldNotIn(FieldAuthTime, vs...)) } // AuthTimeGT applies the GT predicate on the "auth_time" field. func AuthTimeGT(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldGT(FieldAuthTime, v)) } // AuthTimeGTE applies the GTE predicate on the "auth_time" field. func AuthTimeGTE(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldGTE(FieldAuthTime, v)) } // AuthTimeLT applies the LT predicate on the "auth_time" field. func AuthTimeLT(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldLT(FieldAuthTime, v)) } // AuthTimeLTE applies the LTE predicate on the "auth_time" field. func AuthTimeLTE(v time.Time) predicate.AuthCode { return predicate.AuthCode(sql.FieldLTE(FieldAuthTime, v)) } // AuthTimeIsNil applies the IsNil predicate on the "auth_time" field. func AuthTimeIsNil() predicate.AuthCode { return predicate.AuthCode(sql.FieldIsNull(FieldAuthTime)) } // AuthTimeNotNil applies the NotNil predicate on the "auth_time" field. func AuthTimeNotNil() predicate.AuthCode { return predicate.AuthCode(sql.FieldNotNull(FieldAuthTime)) } // And groups predicates with the AND operator between them. func And(predicates ...predicate.AuthCode) predicate.AuthCode { return predicate.AuthCode(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.AuthCode) predicate.AuthCode { return predicate.AuthCode(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.AuthCode) predicate.AuthCode { return predicate.AuthCode(sql.NotPredicates(p)) } ================================================ FILE: storage/ent/db/authcode.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "encoding/json" "fmt" "strings" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/authcode" ) // AuthCode is the model entity for the AuthCode schema. type AuthCode struct { config `json:"-"` // ID of the ent. ID string `json:"id,omitempty"` // ClientID holds the value of the "client_id" field. ClientID string `json:"client_id,omitempty"` // Scopes holds the value of the "scopes" field. Scopes []string `json:"scopes,omitempty"` // Nonce holds the value of the "nonce" field. Nonce string `json:"nonce,omitempty"` // RedirectURI holds the value of the "redirect_uri" field. RedirectURI string `json:"redirect_uri,omitempty"` // ClaimsUserID holds the value of the "claims_user_id" field. ClaimsUserID string `json:"claims_user_id,omitempty"` // ClaimsUsername holds the value of the "claims_username" field. ClaimsUsername string `json:"claims_username,omitempty"` // ClaimsEmail holds the value of the "claims_email" field. ClaimsEmail string `json:"claims_email,omitempty"` // ClaimsEmailVerified holds the value of the "claims_email_verified" field. ClaimsEmailVerified bool `json:"claims_email_verified,omitempty"` // ClaimsGroups holds the value of the "claims_groups" field. ClaimsGroups []string `json:"claims_groups,omitempty"` // ClaimsPreferredUsername holds the value of the "claims_preferred_username" field. ClaimsPreferredUsername string `json:"claims_preferred_username,omitempty"` // ConnectorID holds the value of the "connector_id" field. ConnectorID string `json:"connector_id,omitempty"` // ConnectorData holds the value of the "connector_data" field. ConnectorData *[]byte `json:"connector_data,omitempty"` // Expiry holds the value of the "expiry" field. Expiry time.Time `json:"expiry,omitempty"` // CodeChallenge holds the value of the "code_challenge" field. CodeChallenge string `json:"code_challenge,omitempty"` // CodeChallengeMethod holds the value of the "code_challenge_method" field. CodeChallengeMethod string `json:"code_challenge_method,omitempty"` // AuthTime holds the value of the "auth_time" field. AuthTime time.Time `json:"auth_time,omitempty"` selectValues sql.SelectValues } // scanValues returns the types for scanning values from sql.Rows. func (*AuthCode) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { case authcode.FieldScopes, authcode.FieldClaimsGroups, authcode.FieldConnectorData: values[i] = new([]byte) case authcode.FieldClaimsEmailVerified: values[i] = new(sql.NullBool) case authcode.FieldID, authcode.FieldClientID, authcode.FieldNonce, authcode.FieldRedirectURI, authcode.FieldClaimsUserID, authcode.FieldClaimsUsername, authcode.FieldClaimsEmail, authcode.FieldClaimsPreferredUsername, authcode.FieldConnectorID, authcode.FieldCodeChallenge, authcode.FieldCodeChallengeMethod: values[i] = new(sql.NullString) case authcode.FieldExpiry, authcode.FieldAuthTime: values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } } return values, nil } // assignValues assigns the values that were returned from sql.Rows (after scanning) // to the AuthCode fields. func (_m *AuthCode) assignValues(columns []string, values []any) error { if m, n := len(values), len(columns); m < n { return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) } for i := range columns { switch columns[i] { case authcode.FieldID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field id", values[i]) } else if value.Valid { _m.ID = value.String } case authcode.FieldClientID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field client_id", values[i]) } else if value.Valid { _m.ClientID = value.String } case authcode.FieldScopes: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field scopes", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.Scopes); err != nil { return fmt.Errorf("unmarshal field scopes: %w", err) } } case authcode.FieldNonce: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field nonce", values[i]) } else if value.Valid { _m.Nonce = value.String } case authcode.FieldRedirectURI: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field redirect_uri", values[i]) } else if value.Valid { _m.RedirectURI = value.String } case authcode.FieldClaimsUserID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_user_id", values[i]) } else if value.Valid { _m.ClaimsUserID = value.String } case authcode.FieldClaimsUsername: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_username", values[i]) } else if value.Valid { _m.ClaimsUsername = value.String } case authcode.FieldClaimsEmail: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_email", values[i]) } else if value.Valid { _m.ClaimsEmail = value.String } case authcode.FieldClaimsEmailVerified: if value, ok := values[i].(*sql.NullBool); !ok { return fmt.Errorf("unexpected type %T for field claims_email_verified", values[i]) } else if value.Valid { _m.ClaimsEmailVerified = value.Bool } case authcode.FieldClaimsGroups: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field claims_groups", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.ClaimsGroups); err != nil { return fmt.Errorf("unmarshal field claims_groups: %w", err) } } case authcode.FieldClaimsPreferredUsername: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_preferred_username", values[i]) } else if value.Valid { _m.ClaimsPreferredUsername = value.String } case authcode.FieldConnectorID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field connector_id", values[i]) } else if value.Valid { _m.ConnectorID = value.String } case authcode.FieldConnectorData: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field connector_data", values[i]) } else if value != nil { _m.ConnectorData = value } case authcode.FieldExpiry: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field expiry", values[i]) } else if value.Valid { _m.Expiry = value.Time } case authcode.FieldCodeChallenge: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field code_challenge", values[i]) } else if value.Valid { _m.CodeChallenge = value.String } case authcode.FieldCodeChallengeMethod: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field code_challenge_method", values[i]) } else if value.Valid { _m.CodeChallengeMethod = value.String } case authcode.FieldAuthTime: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field auth_time", values[i]) } else if value.Valid { _m.AuthTime = value.Time } default: _m.selectValues.Set(columns[i], values[i]) } } return nil } // Value returns the ent.Value that was dynamically selected and assigned to the AuthCode. // This includes values selected through modifiers, order, etc. func (_m *AuthCode) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } // Update returns a builder for updating this AuthCode. // Note that you need to call AuthCode.Unwrap() before calling this method if this AuthCode // was returned from a transaction, and the transaction was committed or rolled back. func (_m *AuthCode) Update() *AuthCodeUpdateOne { return NewAuthCodeClient(_m.config).UpdateOne(_m) } // Unwrap unwraps the AuthCode entity that was returned from a transaction after it was closed, // so that all future queries will be executed through the driver which created the transaction. func (_m *AuthCode) Unwrap() *AuthCode { _tx, ok := _m.config.driver.(*txDriver) if !ok { panic("db: AuthCode is not a transactional entity") } _m.config.driver = _tx.drv return _m } // String implements the fmt.Stringer. func (_m *AuthCode) String() string { var builder strings.Builder builder.WriteString("AuthCode(") builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) builder.WriteString("client_id=") builder.WriteString(_m.ClientID) builder.WriteString(", ") builder.WriteString("scopes=") builder.WriteString(fmt.Sprintf("%v", _m.Scopes)) builder.WriteString(", ") builder.WriteString("nonce=") builder.WriteString(_m.Nonce) builder.WriteString(", ") builder.WriteString("redirect_uri=") builder.WriteString(_m.RedirectURI) builder.WriteString(", ") builder.WriteString("claims_user_id=") builder.WriteString(_m.ClaimsUserID) builder.WriteString(", ") builder.WriteString("claims_username=") builder.WriteString(_m.ClaimsUsername) builder.WriteString(", ") builder.WriteString("claims_email=") builder.WriteString(_m.ClaimsEmail) builder.WriteString(", ") builder.WriteString("claims_email_verified=") builder.WriteString(fmt.Sprintf("%v", _m.ClaimsEmailVerified)) builder.WriteString(", ") builder.WriteString("claims_groups=") builder.WriteString(fmt.Sprintf("%v", _m.ClaimsGroups)) builder.WriteString(", ") builder.WriteString("claims_preferred_username=") builder.WriteString(_m.ClaimsPreferredUsername) builder.WriteString(", ") builder.WriteString("connector_id=") builder.WriteString(_m.ConnectorID) builder.WriteString(", ") if v := _m.ConnectorData; v != nil { builder.WriteString("connector_data=") builder.WriteString(fmt.Sprintf("%v", *v)) } builder.WriteString(", ") builder.WriteString("expiry=") builder.WriteString(_m.Expiry.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("code_challenge=") builder.WriteString(_m.CodeChallenge) builder.WriteString(", ") builder.WriteString("code_challenge_method=") builder.WriteString(_m.CodeChallengeMethod) builder.WriteString(", ") builder.WriteString("auth_time=") builder.WriteString(_m.AuthTime.Format(time.ANSIC)) builder.WriteByte(')') return builder.String() } // AuthCodes is a parsable slice of AuthCode. type AuthCodes []*AuthCode ================================================ FILE: storage/ent/db/authcode_create.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/authcode" ) // AuthCodeCreate is the builder for creating a AuthCode entity. type AuthCodeCreate struct { config mutation *AuthCodeMutation hooks []Hook } // SetClientID sets the "client_id" field. func (_c *AuthCodeCreate) SetClientID(v string) *AuthCodeCreate { _c.mutation.SetClientID(v) return _c } // SetScopes sets the "scopes" field. func (_c *AuthCodeCreate) SetScopes(v []string) *AuthCodeCreate { _c.mutation.SetScopes(v) return _c } // SetNonce sets the "nonce" field. func (_c *AuthCodeCreate) SetNonce(v string) *AuthCodeCreate { _c.mutation.SetNonce(v) return _c } // SetRedirectURI sets the "redirect_uri" field. func (_c *AuthCodeCreate) SetRedirectURI(v string) *AuthCodeCreate { _c.mutation.SetRedirectURI(v) return _c } // SetClaimsUserID sets the "claims_user_id" field. func (_c *AuthCodeCreate) SetClaimsUserID(v string) *AuthCodeCreate { _c.mutation.SetClaimsUserID(v) return _c } // SetClaimsUsername sets the "claims_username" field. func (_c *AuthCodeCreate) SetClaimsUsername(v string) *AuthCodeCreate { _c.mutation.SetClaimsUsername(v) return _c } // SetClaimsEmail sets the "claims_email" field. func (_c *AuthCodeCreate) SetClaimsEmail(v string) *AuthCodeCreate { _c.mutation.SetClaimsEmail(v) return _c } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (_c *AuthCodeCreate) SetClaimsEmailVerified(v bool) *AuthCodeCreate { _c.mutation.SetClaimsEmailVerified(v) return _c } // SetClaimsGroups sets the "claims_groups" field. func (_c *AuthCodeCreate) SetClaimsGroups(v []string) *AuthCodeCreate { _c.mutation.SetClaimsGroups(v) return _c } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (_c *AuthCodeCreate) SetClaimsPreferredUsername(v string) *AuthCodeCreate { _c.mutation.SetClaimsPreferredUsername(v) return _c } // SetNillableClaimsPreferredUsername sets the "claims_preferred_username" field if the given value is not nil. func (_c *AuthCodeCreate) SetNillableClaimsPreferredUsername(v *string) *AuthCodeCreate { if v != nil { _c.SetClaimsPreferredUsername(*v) } return _c } // SetConnectorID sets the "connector_id" field. func (_c *AuthCodeCreate) SetConnectorID(v string) *AuthCodeCreate { _c.mutation.SetConnectorID(v) return _c } // SetConnectorData sets the "connector_data" field. func (_c *AuthCodeCreate) SetConnectorData(v []byte) *AuthCodeCreate { _c.mutation.SetConnectorData(v) return _c } // SetExpiry sets the "expiry" field. func (_c *AuthCodeCreate) SetExpiry(v time.Time) *AuthCodeCreate { _c.mutation.SetExpiry(v) return _c } // SetCodeChallenge sets the "code_challenge" field. func (_c *AuthCodeCreate) SetCodeChallenge(v string) *AuthCodeCreate { _c.mutation.SetCodeChallenge(v) return _c } // SetNillableCodeChallenge sets the "code_challenge" field if the given value is not nil. func (_c *AuthCodeCreate) SetNillableCodeChallenge(v *string) *AuthCodeCreate { if v != nil { _c.SetCodeChallenge(*v) } return _c } // SetCodeChallengeMethod sets the "code_challenge_method" field. func (_c *AuthCodeCreate) SetCodeChallengeMethod(v string) *AuthCodeCreate { _c.mutation.SetCodeChallengeMethod(v) return _c } // SetNillableCodeChallengeMethod sets the "code_challenge_method" field if the given value is not nil. func (_c *AuthCodeCreate) SetNillableCodeChallengeMethod(v *string) *AuthCodeCreate { if v != nil { _c.SetCodeChallengeMethod(*v) } return _c } // SetAuthTime sets the "auth_time" field. func (_c *AuthCodeCreate) SetAuthTime(v time.Time) *AuthCodeCreate { _c.mutation.SetAuthTime(v) return _c } // SetNillableAuthTime sets the "auth_time" field if the given value is not nil. func (_c *AuthCodeCreate) SetNillableAuthTime(v *time.Time) *AuthCodeCreate { if v != nil { _c.SetAuthTime(*v) } return _c } // SetID sets the "id" field. func (_c *AuthCodeCreate) SetID(v string) *AuthCodeCreate { _c.mutation.SetID(v) return _c } // Mutation returns the AuthCodeMutation object of the builder. func (_c *AuthCodeCreate) Mutation() *AuthCodeMutation { return _c.mutation } // Save creates the AuthCode in the database. func (_c *AuthCodeCreate) Save(ctx context.Context) (*AuthCode, error) { _c.defaults() return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) } // SaveX calls Save and panics if Save returns an error. func (_c *AuthCodeCreate) SaveX(ctx context.Context) *AuthCode { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *AuthCodeCreate) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *AuthCodeCreate) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } // defaults sets the default values of the builder before save. func (_c *AuthCodeCreate) defaults() { if _, ok := _c.mutation.ClaimsPreferredUsername(); !ok { v := authcode.DefaultClaimsPreferredUsername _c.mutation.SetClaimsPreferredUsername(v) } if _, ok := _c.mutation.CodeChallenge(); !ok { v := authcode.DefaultCodeChallenge _c.mutation.SetCodeChallenge(v) } if _, ok := _c.mutation.CodeChallengeMethod(); !ok { v := authcode.DefaultCodeChallengeMethod _c.mutation.SetCodeChallengeMethod(v) } } // check runs all checks and user-defined validators on the builder. func (_c *AuthCodeCreate) check() error { if _, ok := _c.mutation.ClientID(); !ok { return &ValidationError{Name: "client_id", err: errors.New(`db: missing required field "AuthCode.client_id"`)} } if v, ok := _c.mutation.ClientID(); ok { if err := authcode.ClientIDValidator(v); err != nil { return &ValidationError{Name: "client_id", err: fmt.Errorf(`db: validator failed for field "AuthCode.client_id": %w`, err)} } } if _, ok := _c.mutation.Nonce(); !ok { return &ValidationError{Name: "nonce", err: errors.New(`db: missing required field "AuthCode.nonce"`)} } if v, ok := _c.mutation.Nonce(); ok { if err := authcode.NonceValidator(v); err != nil { return &ValidationError{Name: "nonce", err: fmt.Errorf(`db: validator failed for field "AuthCode.nonce": %w`, err)} } } if _, ok := _c.mutation.RedirectURI(); !ok { return &ValidationError{Name: "redirect_uri", err: errors.New(`db: missing required field "AuthCode.redirect_uri"`)} } if v, ok := _c.mutation.RedirectURI(); ok { if err := authcode.RedirectURIValidator(v); err != nil { return &ValidationError{Name: "redirect_uri", err: fmt.Errorf(`db: validator failed for field "AuthCode.redirect_uri": %w`, err)} } } if _, ok := _c.mutation.ClaimsUserID(); !ok { return &ValidationError{Name: "claims_user_id", err: errors.New(`db: missing required field "AuthCode.claims_user_id"`)} } if v, ok := _c.mutation.ClaimsUserID(); ok { if err := authcode.ClaimsUserIDValidator(v); err != nil { return &ValidationError{Name: "claims_user_id", err: fmt.Errorf(`db: validator failed for field "AuthCode.claims_user_id": %w`, err)} } } if _, ok := _c.mutation.ClaimsUsername(); !ok { return &ValidationError{Name: "claims_username", err: errors.New(`db: missing required field "AuthCode.claims_username"`)} } if v, ok := _c.mutation.ClaimsUsername(); ok { if err := authcode.ClaimsUsernameValidator(v); err != nil { return &ValidationError{Name: "claims_username", err: fmt.Errorf(`db: validator failed for field "AuthCode.claims_username": %w`, err)} } } if _, ok := _c.mutation.ClaimsEmail(); !ok { return &ValidationError{Name: "claims_email", err: errors.New(`db: missing required field "AuthCode.claims_email"`)} } if v, ok := _c.mutation.ClaimsEmail(); ok { if err := authcode.ClaimsEmailValidator(v); err != nil { return &ValidationError{Name: "claims_email", err: fmt.Errorf(`db: validator failed for field "AuthCode.claims_email": %w`, err)} } } if _, ok := _c.mutation.ClaimsEmailVerified(); !ok { return &ValidationError{Name: "claims_email_verified", err: errors.New(`db: missing required field "AuthCode.claims_email_verified"`)} } if _, ok := _c.mutation.ClaimsPreferredUsername(); !ok { return &ValidationError{Name: "claims_preferred_username", err: errors.New(`db: missing required field "AuthCode.claims_preferred_username"`)} } if _, ok := _c.mutation.ConnectorID(); !ok { return &ValidationError{Name: "connector_id", err: errors.New(`db: missing required field "AuthCode.connector_id"`)} } if v, ok := _c.mutation.ConnectorID(); ok { if err := authcode.ConnectorIDValidator(v); err != nil { return &ValidationError{Name: "connector_id", err: fmt.Errorf(`db: validator failed for field "AuthCode.connector_id": %w`, err)} } } if _, ok := _c.mutation.Expiry(); !ok { return &ValidationError{Name: "expiry", err: errors.New(`db: missing required field "AuthCode.expiry"`)} } if _, ok := _c.mutation.CodeChallenge(); !ok { return &ValidationError{Name: "code_challenge", err: errors.New(`db: missing required field "AuthCode.code_challenge"`)} } if _, ok := _c.mutation.CodeChallengeMethod(); !ok { return &ValidationError{Name: "code_challenge_method", err: errors.New(`db: missing required field "AuthCode.code_challenge_method"`)} } if v, ok := _c.mutation.ID(); ok { if err := authcode.IDValidator(v); err != nil { return &ValidationError{Name: "id", err: fmt.Errorf(`db: validator failed for field "AuthCode.id": %w`, err)} } } return nil } func (_c *AuthCodeCreate) sqlSave(ctx context.Context) (*AuthCode, error) { if err := _c.check(); err != nil { return nil, err } _node, _spec := _c.createSpec() if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } if _spec.ID.Value != nil { if id, ok := _spec.ID.Value.(string); ok { _node.ID = id } else { return nil, fmt.Errorf("unexpected AuthCode.ID type: %T", _spec.ID.Value) } } _c.mutation.id = &_node.ID _c.mutation.done = true return _node, nil } func (_c *AuthCodeCreate) createSpec() (*AuthCode, *sqlgraph.CreateSpec) { var ( _node = &AuthCode{config: _c.config} _spec = sqlgraph.NewCreateSpec(authcode.Table, sqlgraph.NewFieldSpec(authcode.FieldID, field.TypeString)) ) if id, ok := _c.mutation.ID(); ok { _node.ID = id _spec.ID.Value = id } if value, ok := _c.mutation.ClientID(); ok { _spec.SetField(authcode.FieldClientID, field.TypeString, value) _node.ClientID = value } if value, ok := _c.mutation.Scopes(); ok { _spec.SetField(authcode.FieldScopes, field.TypeJSON, value) _node.Scopes = value } if value, ok := _c.mutation.Nonce(); ok { _spec.SetField(authcode.FieldNonce, field.TypeString, value) _node.Nonce = value } if value, ok := _c.mutation.RedirectURI(); ok { _spec.SetField(authcode.FieldRedirectURI, field.TypeString, value) _node.RedirectURI = value } if value, ok := _c.mutation.ClaimsUserID(); ok { _spec.SetField(authcode.FieldClaimsUserID, field.TypeString, value) _node.ClaimsUserID = value } if value, ok := _c.mutation.ClaimsUsername(); ok { _spec.SetField(authcode.FieldClaimsUsername, field.TypeString, value) _node.ClaimsUsername = value } if value, ok := _c.mutation.ClaimsEmail(); ok { _spec.SetField(authcode.FieldClaimsEmail, field.TypeString, value) _node.ClaimsEmail = value } if value, ok := _c.mutation.ClaimsEmailVerified(); ok { _spec.SetField(authcode.FieldClaimsEmailVerified, field.TypeBool, value) _node.ClaimsEmailVerified = value } if value, ok := _c.mutation.ClaimsGroups(); ok { _spec.SetField(authcode.FieldClaimsGroups, field.TypeJSON, value) _node.ClaimsGroups = value } if value, ok := _c.mutation.ClaimsPreferredUsername(); ok { _spec.SetField(authcode.FieldClaimsPreferredUsername, field.TypeString, value) _node.ClaimsPreferredUsername = value } if value, ok := _c.mutation.ConnectorID(); ok { _spec.SetField(authcode.FieldConnectorID, field.TypeString, value) _node.ConnectorID = value } if value, ok := _c.mutation.ConnectorData(); ok { _spec.SetField(authcode.FieldConnectorData, field.TypeBytes, value) _node.ConnectorData = &value } if value, ok := _c.mutation.Expiry(); ok { _spec.SetField(authcode.FieldExpiry, field.TypeTime, value) _node.Expiry = value } if value, ok := _c.mutation.CodeChallenge(); ok { _spec.SetField(authcode.FieldCodeChallenge, field.TypeString, value) _node.CodeChallenge = value } if value, ok := _c.mutation.CodeChallengeMethod(); ok { _spec.SetField(authcode.FieldCodeChallengeMethod, field.TypeString, value) _node.CodeChallengeMethod = value } if value, ok := _c.mutation.AuthTime(); ok { _spec.SetField(authcode.FieldAuthTime, field.TypeTime, value) _node.AuthTime = value } return _node, _spec } // AuthCodeCreateBulk is the builder for creating many AuthCode entities in bulk. type AuthCodeCreateBulk struct { config err error builders []*AuthCodeCreate } // Save creates the AuthCode entities in the database. func (_c *AuthCodeCreateBulk) Save(ctx context.Context) ([]*AuthCode, error) { if _c.err != nil { return nil, _c.err } specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) nodes := make([]*AuthCode, len(_c.builders)) mutators := make([]Mutator, len(_c.builders)) for i := range _c.builders { func(i int, root context.Context) { builder := _c.builders[i] builder.defaults() var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*AuthCodeMutation) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } if err := builder.check(); err != nil { return nil, err } builder.mutation = mutation var err error nodes[i], specs[i] = builder.createSpec() if i < len(mutators)-1 { _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) } else { spec := &sqlgraph.BatchCreateSpec{Nodes: specs} // Invoke the actual operation on the latest mutation in the chain. if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } } } if err != nil { return nil, err } mutation.id = &nodes[i].ID mutation.done = true return nodes[i], nil }) for i := len(builder.hooks) - 1; i >= 0; i-- { mut = builder.hooks[i](mut) } mutators[i] = mut }(i, ctx) } if len(mutators) > 0 { if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { return nil, err } } return nodes, nil } // SaveX is like Save, but panics if an error occurs. func (_c *AuthCodeCreateBulk) SaveX(ctx context.Context) []*AuthCode { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *AuthCodeCreateBulk) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *AuthCodeCreateBulk) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/authcode_delete.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/authcode" "github.com/dexidp/dex/storage/ent/db/predicate" ) // AuthCodeDelete is the builder for deleting a AuthCode entity. type AuthCodeDelete struct { config hooks []Hook mutation *AuthCodeMutation } // Where appends a list predicates to the AuthCodeDelete builder. func (_d *AuthCodeDelete) Where(ps ...predicate.AuthCode) *AuthCodeDelete { _d.mutation.Where(ps...) return _d } // Exec executes the deletion query and returns how many vertices were deleted. func (_d *AuthCodeDelete) Exec(ctx context.Context) (int, error) { return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) } // ExecX is like Exec, but panics if an error occurs. func (_d *AuthCodeDelete) ExecX(ctx context.Context) int { n, err := _d.Exec(ctx) if err != nil { panic(err) } return n } func (_d *AuthCodeDelete) sqlExec(ctx context.Context) (int, error) { _spec := sqlgraph.NewDeleteSpec(authcode.Table, sqlgraph.NewFieldSpec(authcode.FieldID, field.TypeString)) if ps := _d.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) if err != nil && sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } _d.mutation.done = true return affected, err } // AuthCodeDeleteOne is the builder for deleting a single AuthCode entity. type AuthCodeDeleteOne struct { _d *AuthCodeDelete } // Where appends a list predicates to the AuthCodeDelete builder. func (_d *AuthCodeDeleteOne) Where(ps ...predicate.AuthCode) *AuthCodeDeleteOne { _d._d.mutation.Where(ps...) return _d } // Exec executes the deletion query. func (_d *AuthCodeDeleteOne) Exec(ctx context.Context) error { n, err := _d._d.Exec(ctx) switch { case err != nil: return err case n == 0: return &NotFoundError{authcode.Label} default: return nil } } // ExecX is like Exec, but panics if an error occurs. func (_d *AuthCodeDeleteOne) ExecX(ctx context.Context) { if err := _d.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/authcode_query.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "fmt" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/authcode" "github.com/dexidp/dex/storage/ent/db/predicate" ) // AuthCodeQuery is the builder for querying AuthCode entities. type AuthCodeQuery struct { config ctx *QueryContext order []authcode.OrderOption inters []Interceptor predicates []predicate.AuthCode // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) } // Where adds a new predicate for the AuthCodeQuery builder. func (_q *AuthCodeQuery) Where(ps ...predicate.AuthCode) *AuthCodeQuery { _q.predicates = append(_q.predicates, ps...) return _q } // Limit the number of records to be returned by this query. func (_q *AuthCodeQuery) Limit(limit int) *AuthCodeQuery { _q.ctx.Limit = &limit return _q } // Offset to start from. func (_q *AuthCodeQuery) Offset(offset int) *AuthCodeQuery { _q.ctx.Offset = &offset return _q } // Unique configures the query builder to filter duplicate records on query. // By default, unique is set to true, and can be disabled using this method. func (_q *AuthCodeQuery) Unique(unique bool) *AuthCodeQuery { _q.ctx.Unique = &unique return _q } // Order specifies how the records should be ordered. func (_q *AuthCodeQuery) Order(o ...authcode.OrderOption) *AuthCodeQuery { _q.order = append(_q.order, o...) return _q } // First returns the first AuthCode entity from the query. // Returns a *NotFoundError when no AuthCode was found. func (_q *AuthCodeQuery) First(ctx context.Context) (*AuthCode, error) { nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) if err != nil { return nil, err } if len(nodes) == 0 { return nil, &NotFoundError{authcode.Label} } return nodes[0], nil } // FirstX is like First, but panics if an error occurs. func (_q *AuthCodeQuery) FirstX(ctx context.Context) *AuthCode { node, err := _q.First(ctx) if err != nil && !IsNotFound(err) { panic(err) } return node } // FirstID returns the first AuthCode ID from the query. // Returns a *NotFoundError when no AuthCode ID was found. func (_q *AuthCodeQuery) FirstID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { return } if len(ids) == 0 { err = &NotFoundError{authcode.Label} return } return ids[0], nil } // FirstIDX is like FirstID, but panics if an error occurs. func (_q *AuthCodeQuery) FirstIDX(ctx context.Context) string { id, err := _q.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) } return id } // Only returns a single AuthCode entity found by the query, ensuring it only returns one. // Returns a *NotSingularError when more than one AuthCode entity is found. // Returns a *NotFoundError when no AuthCode entities are found. func (_q *AuthCodeQuery) Only(ctx context.Context) (*AuthCode, error) { nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) if err != nil { return nil, err } switch len(nodes) { case 1: return nodes[0], nil case 0: return nil, &NotFoundError{authcode.Label} default: return nil, &NotSingularError{authcode.Label} } } // OnlyX is like Only, but panics if an error occurs. func (_q *AuthCodeQuery) OnlyX(ctx context.Context) *AuthCode { node, err := _q.Only(ctx) if err != nil { panic(err) } return node } // OnlyID is like Only, but returns the only AuthCode ID in the query. // Returns a *NotSingularError when more than one AuthCode ID is found. // Returns a *NotFoundError when no entities are found. func (_q *AuthCodeQuery) OnlyID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { return } switch len(ids) { case 1: id = ids[0] case 0: err = &NotFoundError{authcode.Label} default: err = &NotSingularError{authcode.Label} } return } // OnlyIDX is like OnlyID, but panics if an error occurs. func (_q *AuthCodeQuery) OnlyIDX(ctx context.Context) string { id, err := _q.OnlyID(ctx) if err != nil { panic(err) } return id } // All executes the query and returns a list of AuthCodes. func (_q *AuthCodeQuery) All(ctx context.Context) ([]*AuthCode, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) if err := _q.prepareQuery(ctx); err != nil { return nil, err } qr := querierAll[[]*AuthCode, *AuthCodeQuery]() return withInterceptors[[]*AuthCode](ctx, _q, qr, _q.inters) } // AllX is like All, but panics if an error occurs. func (_q *AuthCodeQuery) AllX(ctx context.Context) []*AuthCode { nodes, err := _q.All(ctx) if err != nil { panic(err) } return nodes } // IDs executes the query and returns a list of AuthCode IDs. func (_q *AuthCodeQuery) IDs(ctx context.Context) (ids []string, err error) { if _q.ctx.Unique == nil && _q.path != nil { _q.Unique(true) } ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) if err = _q.Select(authcode.FieldID).Scan(ctx, &ids); err != nil { return nil, err } return ids, nil } // IDsX is like IDs, but panics if an error occurs. func (_q *AuthCodeQuery) IDsX(ctx context.Context) []string { ids, err := _q.IDs(ctx) if err != nil { panic(err) } return ids } // Count returns the count of the given query. func (_q *AuthCodeQuery) Count(ctx context.Context) (int, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) if err := _q.prepareQuery(ctx); err != nil { return 0, err } return withInterceptors[int](ctx, _q, querierCount[*AuthCodeQuery](), _q.inters) } // CountX is like Count, but panics if an error occurs. func (_q *AuthCodeQuery) CountX(ctx context.Context) int { count, err := _q.Count(ctx) if err != nil { panic(err) } return count } // Exist returns true if the query has elements in the graph. func (_q *AuthCodeQuery) Exist(ctx context.Context) (bool, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) switch _, err := _q.FirstID(ctx); { case IsNotFound(err): return false, nil case err != nil: return false, fmt.Errorf("db: check existence: %w", err) default: return true, nil } } // ExistX is like Exist, but panics if an error occurs. func (_q *AuthCodeQuery) ExistX(ctx context.Context) bool { exist, err := _q.Exist(ctx) if err != nil { panic(err) } return exist } // Clone returns a duplicate of the AuthCodeQuery builder, including all associated steps. It can be // used to prepare common query builders and use them differently after the clone is made. func (_q *AuthCodeQuery) Clone() *AuthCodeQuery { if _q == nil { return nil } return &AuthCodeQuery{ config: _q.config, ctx: _q.ctx.Clone(), order: append([]authcode.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.AuthCode{}, _q.predicates...), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // // Example: // // var v []struct { // ClientID string `json:"client_id,omitempty"` // Count int `json:"count,omitempty"` // } // // client.AuthCode.Query(). // GroupBy(authcode.FieldClientID). // Aggregate(db.Count()). // Scan(ctx, &v) func (_q *AuthCodeQuery) GroupBy(field string, fields ...string) *AuthCodeGroupBy { _q.ctx.Fields = append([]string{field}, fields...) grbuild := &AuthCodeGroupBy{build: _q} grbuild.flds = &_q.ctx.Fields grbuild.label = authcode.Label grbuild.scan = grbuild.Scan return grbuild } // Select allows the selection one or more fields/columns for the given query, // instead of selecting all fields in the entity. // // Example: // // var v []struct { // ClientID string `json:"client_id,omitempty"` // } // // client.AuthCode.Query(). // Select(authcode.FieldClientID). // Scan(ctx, &v) func (_q *AuthCodeQuery) Select(fields ...string) *AuthCodeSelect { _q.ctx.Fields = append(_q.ctx.Fields, fields...) sbuild := &AuthCodeSelect{AuthCodeQuery: _q} sbuild.label = authcode.Label sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan return sbuild } // Aggregate returns a AuthCodeSelect configured with the given aggregations. func (_q *AuthCodeQuery) Aggregate(fns ...AggregateFunc) *AuthCodeSelect { return _q.Select().Aggregate(fns...) } func (_q *AuthCodeQuery) prepareQuery(ctx context.Context) error { for _, inter := range _q.inters { if inter == nil { return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") } if trv, ok := inter.(Traverser); ok { if err := trv.Traverse(ctx, _q); err != nil { return err } } } for _, f := range _q.ctx.Fields { if !authcode.ValidColumn(f) { return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } } if _q.path != nil { prev, err := _q.path(ctx) if err != nil { return err } _q.sql = prev } return nil } func (_q *AuthCodeQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AuthCode, error) { var ( nodes = []*AuthCode{} _spec = _q.querySpec() ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*AuthCode).scanValues(nil, columns) } _spec.Assign = func(columns []string, values []any) error { node := &AuthCode{config: _q.config} nodes = append(nodes, node) return node.assignValues(columns, values) } for i := range hooks { hooks[i](ctx, _spec) } if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { return nil, err } if len(nodes) == 0 { return nodes, nil } return nodes, nil } func (_q *AuthCodeQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields if len(_q.ctx.Fields) > 0 { _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique } return sqlgraph.CountNodes(ctx, _q.driver, _spec) } func (_q *AuthCodeQuery) querySpec() *sqlgraph.QuerySpec { _spec := sqlgraph.NewQuerySpec(authcode.Table, authcode.Columns, sqlgraph.NewFieldSpec(authcode.FieldID, field.TypeString)) _spec.From = _q.sql if unique := _q.ctx.Unique; unique != nil { _spec.Unique = *unique } else if _q.path != nil { _spec.Unique = true } if fields := _q.ctx.Fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, authcode.FieldID) for i := range fields { if fields[i] != authcode.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } } if ps := _q.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if limit := _q.ctx.Limit; limit != nil { _spec.Limit = *limit } if offset := _q.ctx.Offset; offset != nil { _spec.Offset = *offset } if ps := _q.order; len(ps) > 0 { _spec.Order = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } return _spec } func (_q *AuthCodeQuery) sqlQuery(ctx context.Context) *sql.Selector { builder := sql.Dialect(_q.driver.Dialect()) t1 := builder.Table(authcode.Table) columns := _q.ctx.Fields if len(columns) == 0 { columns = authcode.Columns } selector := builder.Select(t1.Columns(columns...)...).From(t1) if _q.sql != nil { selector = _q.sql selector.Select(selector.Columns(columns...)...) } if _q.ctx.Unique != nil && *_q.ctx.Unique { selector.Distinct() } for _, p := range _q.predicates { p(selector) } for _, p := range _q.order { p(selector) } if offset := _q.ctx.Offset; offset != nil { // limit is mandatory for offset clause. We start // with default value, and override it below if needed. selector.Offset(*offset).Limit(math.MaxInt32) } if limit := _q.ctx.Limit; limit != nil { selector.Limit(*limit) } return selector } // AuthCodeGroupBy is the group-by builder for AuthCode entities. type AuthCodeGroupBy struct { selector build *AuthCodeQuery } // Aggregate adds the given aggregation functions to the group-by query. func (_g *AuthCodeGroupBy) Aggregate(fns ...AggregateFunc) *AuthCodeGroupBy { _g.fns = append(_g.fns, fns...) return _g } // Scan applies the selector query and scans the result into the given value. func (_g *AuthCodeGroupBy) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) if err := _g.build.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*AuthCodeQuery, *AuthCodeGroupBy](ctx, _g.build, _g, _g.build.inters, v) } func (_g *AuthCodeGroupBy) sqlScan(ctx context.Context, root *AuthCodeQuery, v any) error { selector := root.sqlQuery(ctx).Select() aggregation := make([]string, 0, len(_g.fns)) for _, fn := range _g.fns { aggregation = append(aggregation, fn(selector)) } if len(selector.SelectedColumns()) == 0 { columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) for _, f := range *_g.flds { columns = append(columns, selector.C(f)) } columns = append(columns, aggregation...) selector.Select(columns...) } selector.GroupBy(selector.Columns(*_g.flds...)...) if err := selector.Err(); err != nil { return err } rows := &sql.Rows{} query, args := selector.Query() if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } // AuthCodeSelect is the builder for selecting fields of AuthCode entities. type AuthCodeSelect struct { *AuthCodeQuery selector } // Aggregate adds the given aggregation functions to the selector query. func (_s *AuthCodeSelect) Aggregate(fns ...AggregateFunc) *AuthCodeSelect { _s.fns = append(_s.fns, fns...) return _s } // Scan applies the selector query and scans the result into the given value. func (_s *AuthCodeSelect) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) if err := _s.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*AuthCodeQuery, *AuthCodeSelect](ctx, _s.AuthCodeQuery, _s, _s.inters, v) } func (_s *AuthCodeSelect) sqlScan(ctx context.Context, root *AuthCodeQuery, v any) error { selector := root.sqlQuery(ctx) aggregation := make([]string, 0, len(_s.fns)) for _, fn := range _s.fns { aggregation = append(aggregation, fn(selector)) } switch n := len(*_s.selector.flds); { case n == 0 && len(aggregation) > 0: selector.Select(aggregation...) case n != 0 && len(aggregation) > 0: selector.AppendSelect(aggregation...) } rows := &sql.Rows{} query, args := selector.Query() if err := _s.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } ================================================ FILE: storage/ent/db/authcode_update.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/authcode" "github.com/dexidp/dex/storage/ent/db/predicate" ) // AuthCodeUpdate is the builder for updating AuthCode entities. type AuthCodeUpdate struct { config hooks []Hook mutation *AuthCodeMutation } // Where appends a list predicates to the AuthCodeUpdate builder. func (_u *AuthCodeUpdate) Where(ps ...predicate.AuthCode) *AuthCodeUpdate { _u.mutation.Where(ps...) return _u } // SetClientID sets the "client_id" field. func (_u *AuthCodeUpdate) SetClientID(v string) *AuthCodeUpdate { _u.mutation.SetClientID(v) return _u } // SetNillableClientID sets the "client_id" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableClientID(v *string) *AuthCodeUpdate { if v != nil { _u.SetClientID(*v) } return _u } // SetScopes sets the "scopes" field. func (_u *AuthCodeUpdate) SetScopes(v []string) *AuthCodeUpdate { _u.mutation.SetScopes(v) return _u } // AppendScopes appends value to the "scopes" field. func (_u *AuthCodeUpdate) AppendScopes(v []string) *AuthCodeUpdate { _u.mutation.AppendScopes(v) return _u } // ClearScopes clears the value of the "scopes" field. func (_u *AuthCodeUpdate) ClearScopes() *AuthCodeUpdate { _u.mutation.ClearScopes() return _u } // SetNonce sets the "nonce" field. func (_u *AuthCodeUpdate) SetNonce(v string) *AuthCodeUpdate { _u.mutation.SetNonce(v) return _u } // SetNillableNonce sets the "nonce" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableNonce(v *string) *AuthCodeUpdate { if v != nil { _u.SetNonce(*v) } return _u } // SetRedirectURI sets the "redirect_uri" field. func (_u *AuthCodeUpdate) SetRedirectURI(v string) *AuthCodeUpdate { _u.mutation.SetRedirectURI(v) return _u } // SetNillableRedirectURI sets the "redirect_uri" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableRedirectURI(v *string) *AuthCodeUpdate { if v != nil { _u.SetRedirectURI(*v) } return _u } // SetClaimsUserID sets the "claims_user_id" field. func (_u *AuthCodeUpdate) SetClaimsUserID(v string) *AuthCodeUpdate { _u.mutation.SetClaimsUserID(v) return _u } // SetNillableClaimsUserID sets the "claims_user_id" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableClaimsUserID(v *string) *AuthCodeUpdate { if v != nil { _u.SetClaimsUserID(*v) } return _u } // SetClaimsUsername sets the "claims_username" field. func (_u *AuthCodeUpdate) SetClaimsUsername(v string) *AuthCodeUpdate { _u.mutation.SetClaimsUsername(v) return _u } // SetNillableClaimsUsername sets the "claims_username" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableClaimsUsername(v *string) *AuthCodeUpdate { if v != nil { _u.SetClaimsUsername(*v) } return _u } // SetClaimsEmail sets the "claims_email" field. func (_u *AuthCodeUpdate) SetClaimsEmail(v string) *AuthCodeUpdate { _u.mutation.SetClaimsEmail(v) return _u } // SetNillableClaimsEmail sets the "claims_email" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableClaimsEmail(v *string) *AuthCodeUpdate { if v != nil { _u.SetClaimsEmail(*v) } return _u } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (_u *AuthCodeUpdate) SetClaimsEmailVerified(v bool) *AuthCodeUpdate { _u.mutation.SetClaimsEmailVerified(v) return _u } // SetNillableClaimsEmailVerified sets the "claims_email_verified" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableClaimsEmailVerified(v *bool) *AuthCodeUpdate { if v != nil { _u.SetClaimsEmailVerified(*v) } return _u } // SetClaimsGroups sets the "claims_groups" field. func (_u *AuthCodeUpdate) SetClaimsGroups(v []string) *AuthCodeUpdate { _u.mutation.SetClaimsGroups(v) return _u } // AppendClaimsGroups appends value to the "claims_groups" field. func (_u *AuthCodeUpdate) AppendClaimsGroups(v []string) *AuthCodeUpdate { _u.mutation.AppendClaimsGroups(v) return _u } // ClearClaimsGroups clears the value of the "claims_groups" field. func (_u *AuthCodeUpdate) ClearClaimsGroups() *AuthCodeUpdate { _u.mutation.ClearClaimsGroups() return _u } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (_u *AuthCodeUpdate) SetClaimsPreferredUsername(v string) *AuthCodeUpdate { _u.mutation.SetClaimsPreferredUsername(v) return _u } // SetNillableClaimsPreferredUsername sets the "claims_preferred_username" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableClaimsPreferredUsername(v *string) *AuthCodeUpdate { if v != nil { _u.SetClaimsPreferredUsername(*v) } return _u } // SetConnectorID sets the "connector_id" field. func (_u *AuthCodeUpdate) SetConnectorID(v string) *AuthCodeUpdate { _u.mutation.SetConnectorID(v) return _u } // SetNillableConnectorID sets the "connector_id" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableConnectorID(v *string) *AuthCodeUpdate { if v != nil { _u.SetConnectorID(*v) } return _u } // SetConnectorData sets the "connector_data" field. func (_u *AuthCodeUpdate) SetConnectorData(v []byte) *AuthCodeUpdate { _u.mutation.SetConnectorData(v) return _u } // ClearConnectorData clears the value of the "connector_data" field. func (_u *AuthCodeUpdate) ClearConnectorData() *AuthCodeUpdate { _u.mutation.ClearConnectorData() return _u } // SetExpiry sets the "expiry" field. func (_u *AuthCodeUpdate) SetExpiry(v time.Time) *AuthCodeUpdate { _u.mutation.SetExpiry(v) return _u } // SetNillableExpiry sets the "expiry" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableExpiry(v *time.Time) *AuthCodeUpdate { if v != nil { _u.SetExpiry(*v) } return _u } // SetCodeChallenge sets the "code_challenge" field. func (_u *AuthCodeUpdate) SetCodeChallenge(v string) *AuthCodeUpdate { _u.mutation.SetCodeChallenge(v) return _u } // SetNillableCodeChallenge sets the "code_challenge" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableCodeChallenge(v *string) *AuthCodeUpdate { if v != nil { _u.SetCodeChallenge(*v) } return _u } // SetCodeChallengeMethod sets the "code_challenge_method" field. func (_u *AuthCodeUpdate) SetCodeChallengeMethod(v string) *AuthCodeUpdate { _u.mutation.SetCodeChallengeMethod(v) return _u } // SetNillableCodeChallengeMethod sets the "code_challenge_method" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableCodeChallengeMethod(v *string) *AuthCodeUpdate { if v != nil { _u.SetCodeChallengeMethod(*v) } return _u } // SetAuthTime sets the "auth_time" field. func (_u *AuthCodeUpdate) SetAuthTime(v time.Time) *AuthCodeUpdate { _u.mutation.SetAuthTime(v) return _u } // SetNillableAuthTime sets the "auth_time" field if the given value is not nil. func (_u *AuthCodeUpdate) SetNillableAuthTime(v *time.Time) *AuthCodeUpdate { if v != nil { _u.SetAuthTime(*v) } return _u } // ClearAuthTime clears the value of the "auth_time" field. func (_u *AuthCodeUpdate) ClearAuthTime() *AuthCodeUpdate { _u.mutation.ClearAuthTime() return _u } // Mutation returns the AuthCodeMutation object of the builder. func (_u *AuthCodeUpdate) Mutation() *AuthCodeMutation { return _u.mutation } // Save executes the query and returns the number of nodes affected by the update operation. func (_u *AuthCodeUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *AuthCodeUpdate) SaveX(ctx context.Context) int { affected, err := _u.Save(ctx) if err != nil { panic(err) } return affected } // Exec executes the query. func (_u *AuthCodeUpdate) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *AuthCodeUpdate) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *AuthCodeUpdate) check() error { if v, ok := _u.mutation.ClientID(); ok { if err := authcode.ClientIDValidator(v); err != nil { return &ValidationError{Name: "client_id", err: fmt.Errorf(`db: validator failed for field "AuthCode.client_id": %w`, err)} } } if v, ok := _u.mutation.Nonce(); ok { if err := authcode.NonceValidator(v); err != nil { return &ValidationError{Name: "nonce", err: fmt.Errorf(`db: validator failed for field "AuthCode.nonce": %w`, err)} } } if v, ok := _u.mutation.RedirectURI(); ok { if err := authcode.RedirectURIValidator(v); err != nil { return &ValidationError{Name: "redirect_uri", err: fmt.Errorf(`db: validator failed for field "AuthCode.redirect_uri": %w`, err)} } } if v, ok := _u.mutation.ClaimsUserID(); ok { if err := authcode.ClaimsUserIDValidator(v); err != nil { return &ValidationError{Name: "claims_user_id", err: fmt.Errorf(`db: validator failed for field "AuthCode.claims_user_id": %w`, err)} } } if v, ok := _u.mutation.ClaimsUsername(); ok { if err := authcode.ClaimsUsernameValidator(v); err != nil { return &ValidationError{Name: "claims_username", err: fmt.Errorf(`db: validator failed for field "AuthCode.claims_username": %w`, err)} } } if v, ok := _u.mutation.ClaimsEmail(); ok { if err := authcode.ClaimsEmailValidator(v); err != nil { return &ValidationError{Name: "claims_email", err: fmt.Errorf(`db: validator failed for field "AuthCode.claims_email": %w`, err)} } } if v, ok := _u.mutation.ConnectorID(); ok { if err := authcode.ConnectorIDValidator(v); err != nil { return &ValidationError{Name: "connector_id", err: fmt.Errorf(`db: validator failed for field "AuthCode.connector_id": %w`, err)} } } return nil } func (_u *AuthCodeUpdate) sqlSave(ctx context.Context) (_node int, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(authcode.Table, authcode.Columns, sqlgraph.NewFieldSpec(authcode.FieldID, field.TypeString)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.ClientID(); ok { _spec.SetField(authcode.FieldClientID, field.TypeString, value) } if value, ok := _u.mutation.Scopes(); ok { _spec.SetField(authcode.FieldScopes, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedScopes(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, authcode.FieldScopes, value) }) } if _u.mutation.ScopesCleared() { _spec.ClearField(authcode.FieldScopes, field.TypeJSON) } if value, ok := _u.mutation.Nonce(); ok { _spec.SetField(authcode.FieldNonce, field.TypeString, value) } if value, ok := _u.mutation.RedirectURI(); ok { _spec.SetField(authcode.FieldRedirectURI, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUserID(); ok { _spec.SetField(authcode.FieldClaimsUserID, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUsername(); ok { _spec.SetField(authcode.FieldClaimsUsername, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmail(); ok { _spec.SetField(authcode.FieldClaimsEmail, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmailVerified(); ok { _spec.SetField(authcode.FieldClaimsEmailVerified, field.TypeBool, value) } if value, ok := _u.mutation.ClaimsGroups(); ok { _spec.SetField(authcode.FieldClaimsGroups, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedClaimsGroups(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, authcode.FieldClaimsGroups, value) }) } if _u.mutation.ClaimsGroupsCleared() { _spec.ClearField(authcode.FieldClaimsGroups, field.TypeJSON) } if value, ok := _u.mutation.ClaimsPreferredUsername(); ok { _spec.SetField(authcode.FieldClaimsPreferredUsername, field.TypeString, value) } if value, ok := _u.mutation.ConnectorID(); ok { _spec.SetField(authcode.FieldConnectorID, field.TypeString, value) } if value, ok := _u.mutation.ConnectorData(); ok { _spec.SetField(authcode.FieldConnectorData, field.TypeBytes, value) } if _u.mutation.ConnectorDataCleared() { _spec.ClearField(authcode.FieldConnectorData, field.TypeBytes) } if value, ok := _u.mutation.Expiry(); ok { _spec.SetField(authcode.FieldExpiry, field.TypeTime, value) } if value, ok := _u.mutation.CodeChallenge(); ok { _spec.SetField(authcode.FieldCodeChallenge, field.TypeString, value) } if value, ok := _u.mutation.CodeChallengeMethod(); ok { _spec.SetField(authcode.FieldCodeChallengeMethod, field.TypeString, value) } if value, ok := _u.mutation.AuthTime(); ok { _spec.SetField(authcode.FieldAuthTime, field.TypeTime, value) } if _u.mutation.AuthTimeCleared() { _spec.ClearField(authcode.FieldAuthTime, field.TypeTime) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{authcode.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return 0, err } _u.mutation.done = true return _node, nil } // AuthCodeUpdateOne is the builder for updating a single AuthCode entity. type AuthCodeUpdateOne struct { config fields []string hooks []Hook mutation *AuthCodeMutation } // SetClientID sets the "client_id" field. func (_u *AuthCodeUpdateOne) SetClientID(v string) *AuthCodeUpdateOne { _u.mutation.SetClientID(v) return _u } // SetNillableClientID sets the "client_id" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableClientID(v *string) *AuthCodeUpdateOne { if v != nil { _u.SetClientID(*v) } return _u } // SetScopes sets the "scopes" field. func (_u *AuthCodeUpdateOne) SetScopes(v []string) *AuthCodeUpdateOne { _u.mutation.SetScopes(v) return _u } // AppendScopes appends value to the "scopes" field. func (_u *AuthCodeUpdateOne) AppendScopes(v []string) *AuthCodeUpdateOne { _u.mutation.AppendScopes(v) return _u } // ClearScopes clears the value of the "scopes" field. func (_u *AuthCodeUpdateOne) ClearScopes() *AuthCodeUpdateOne { _u.mutation.ClearScopes() return _u } // SetNonce sets the "nonce" field. func (_u *AuthCodeUpdateOne) SetNonce(v string) *AuthCodeUpdateOne { _u.mutation.SetNonce(v) return _u } // SetNillableNonce sets the "nonce" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableNonce(v *string) *AuthCodeUpdateOne { if v != nil { _u.SetNonce(*v) } return _u } // SetRedirectURI sets the "redirect_uri" field. func (_u *AuthCodeUpdateOne) SetRedirectURI(v string) *AuthCodeUpdateOne { _u.mutation.SetRedirectURI(v) return _u } // SetNillableRedirectURI sets the "redirect_uri" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableRedirectURI(v *string) *AuthCodeUpdateOne { if v != nil { _u.SetRedirectURI(*v) } return _u } // SetClaimsUserID sets the "claims_user_id" field. func (_u *AuthCodeUpdateOne) SetClaimsUserID(v string) *AuthCodeUpdateOne { _u.mutation.SetClaimsUserID(v) return _u } // SetNillableClaimsUserID sets the "claims_user_id" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableClaimsUserID(v *string) *AuthCodeUpdateOne { if v != nil { _u.SetClaimsUserID(*v) } return _u } // SetClaimsUsername sets the "claims_username" field. func (_u *AuthCodeUpdateOne) SetClaimsUsername(v string) *AuthCodeUpdateOne { _u.mutation.SetClaimsUsername(v) return _u } // SetNillableClaimsUsername sets the "claims_username" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableClaimsUsername(v *string) *AuthCodeUpdateOne { if v != nil { _u.SetClaimsUsername(*v) } return _u } // SetClaimsEmail sets the "claims_email" field. func (_u *AuthCodeUpdateOne) SetClaimsEmail(v string) *AuthCodeUpdateOne { _u.mutation.SetClaimsEmail(v) return _u } // SetNillableClaimsEmail sets the "claims_email" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableClaimsEmail(v *string) *AuthCodeUpdateOne { if v != nil { _u.SetClaimsEmail(*v) } return _u } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (_u *AuthCodeUpdateOne) SetClaimsEmailVerified(v bool) *AuthCodeUpdateOne { _u.mutation.SetClaimsEmailVerified(v) return _u } // SetNillableClaimsEmailVerified sets the "claims_email_verified" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableClaimsEmailVerified(v *bool) *AuthCodeUpdateOne { if v != nil { _u.SetClaimsEmailVerified(*v) } return _u } // SetClaimsGroups sets the "claims_groups" field. func (_u *AuthCodeUpdateOne) SetClaimsGroups(v []string) *AuthCodeUpdateOne { _u.mutation.SetClaimsGroups(v) return _u } // AppendClaimsGroups appends value to the "claims_groups" field. func (_u *AuthCodeUpdateOne) AppendClaimsGroups(v []string) *AuthCodeUpdateOne { _u.mutation.AppendClaimsGroups(v) return _u } // ClearClaimsGroups clears the value of the "claims_groups" field. func (_u *AuthCodeUpdateOne) ClearClaimsGroups() *AuthCodeUpdateOne { _u.mutation.ClearClaimsGroups() return _u } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (_u *AuthCodeUpdateOne) SetClaimsPreferredUsername(v string) *AuthCodeUpdateOne { _u.mutation.SetClaimsPreferredUsername(v) return _u } // SetNillableClaimsPreferredUsername sets the "claims_preferred_username" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableClaimsPreferredUsername(v *string) *AuthCodeUpdateOne { if v != nil { _u.SetClaimsPreferredUsername(*v) } return _u } // SetConnectorID sets the "connector_id" field. func (_u *AuthCodeUpdateOne) SetConnectorID(v string) *AuthCodeUpdateOne { _u.mutation.SetConnectorID(v) return _u } // SetNillableConnectorID sets the "connector_id" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableConnectorID(v *string) *AuthCodeUpdateOne { if v != nil { _u.SetConnectorID(*v) } return _u } // SetConnectorData sets the "connector_data" field. func (_u *AuthCodeUpdateOne) SetConnectorData(v []byte) *AuthCodeUpdateOne { _u.mutation.SetConnectorData(v) return _u } // ClearConnectorData clears the value of the "connector_data" field. func (_u *AuthCodeUpdateOne) ClearConnectorData() *AuthCodeUpdateOne { _u.mutation.ClearConnectorData() return _u } // SetExpiry sets the "expiry" field. func (_u *AuthCodeUpdateOne) SetExpiry(v time.Time) *AuthCodeUpdateOne { _u.mutation.SetExpiry(v) return _u } // SetNillableExpiry sets the "expiry" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableExpiry(v *time.Time) *AuthCodeUpdateOne { if v != nil { _u.SetExpiry(*v) } return _u } // SetCodeChallenge sets the "code_challenge" field. func (_u *AuthCodeUpdateOne) SetCodeChallenge(v string) *AuthCodeUpdateOne { _u.mutation.SetCodeChallenge(v) return _u } // SetNillableCodeChallenge sets the "code_challenge" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableCodeChallenge(v *string) *AuthCodeUpdateOne { if v != nil { _u.SetCodeChallenge(*v) } return _u } // SetCodeChallengeMethod sets the "code_challenge_method" field. func (_u *AuthCodeUpdateOne) SetCodeChallengeMethod(v string) *AuthCodeUpdateOne { _u.mutation.SetCodeChallengeMethod(v) return _u } // SetNillableCodeChallengeMethod sets the "code_challenge_method" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableCodeChallengeMethod(v *string) *AuthCodeUpdateOne { if v != nil { _u.SetCodeChallengeMethod(*v) } return _u } // SetAuthTime sets the "auth_time" field. func (_u *AuthCodeUpdateOne) SetAuthTime(v time.Time) *AuthCodeUpdateOne { _u.mutation.SetAuthTime(v) return _u } // SetNillableAuthTime sets the "auth_time" field if the given value is not nil. func (_u *AuthCodeUpdateOne) SetNillableAuthTime(v *time.Time) *AuthCodeUpdateOne { if v != nil { _u.SetAuthTime(*v) } return _u } // ClearAuthTime clears the value of the "auth_time" field. func (_u *AuthCodeUpdateOne) ClearAuthTime() *AuthCodeUpdateOne { _u.mutation.ClearAuthTime() return _u } // Mutation returns the AuthCodeMutation object of the builder. func (_u *AuthCodeUpdateOne) Mutation() *AuthCodeMutation { return _u.mutation } // Where appends a list predicates to the AuthCodeUpdate builder. func (_u *AuthCodeUpdateOne) Where(ps ...predicate.AuthCode) *AuthCodeUpdateOne { _u.mutation.Where(ps...) return _u } // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (_u *AuthCodeUpdateOne) Select(field string, fields ...string) *AuthCodeUpdateOne { _u.fields = append([]string{field}, fields...) return _u } // Save executes the query and returns the updated AuthCode entity. func (_u *AuthCodeUpdateOne) Save(ctx context.Context) (*AuthCode, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *AuthCodeUpdateOne) SaveX(ctx context.Context) *AuthCode { node, err := _u.Save(ctx) if err != nil { panic(err) } return node } // Exec executes the query on the entity. func (_u *AuthCodeUpdateOne) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *AuthCodeUpdateOne) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *AuthCodeUpdateOne) check() error { if v, ok := _u.mutation.ClientID(); ok { if err := authcode.ClientIDValidator(v); err != nil { return &ValidationError{Name: "client_id", err: fmt.Errorf(`db: validator failed for field "AuthCode.client_id": %w`, err)} } } if v, ok := _u.mutation.Nonce(); ok { if err := authcode.NonceValidator(v); err != nil { return &ValidationError{Name: "nonce", err: fmt.Errorf(`db: validator failed for field "AuthCode.nonce": %w`, err)} } } if v, ok := _u.mutation.RedirectURI(); ok { if err := authcode.RedirectURIValidator(v); err != nil { return &ValidationError{Name: "redirect_uri", err: fmt.Errorf(`db: validator failed for field "AuthCode.redirect_uri": %w`, err)} } } if v, ok := _u.mutation.ClaimsUserID(); ok { if err := authcode.ClaimsUserIDValidator(v); err != nil { return &ValidationError{Name: "claims_user_id", err: fmt.Errorf(`db: validator failed for field "AuthCode.claims_user_id": %w`, err)} } } if v, ok := _u.mutation.ClaimsUsername(); ok { if err := authcode.ClaimsUsernameValidator(v); err != nil { return &ValidationError{Name: "claims_username", err: fmt.Errorf(`db: validator failed for field "AuthCode.claims_username": %w`, err)} } } if v, ok := _u.mutation.ClaimsEmail(); ok { if err := authcode.ClaimsEmailValidator(v); err != nil { return &ValidationError{Name: "claims_email", err: fmt.Errorf(`db: validator failed for field "AuthCode.claims_email": %w`, err)} } } if v, ok := _u.mutation.ConnectorID(); ok { if err := authcode.ConnectorIDValidator(v); err != nil { return &ValidationError{Name: "connector_id", err: fmt.Errorf(`db: validator failed for field "AuthCode.connector_id": %w`, err)} } } return nil } func (_u *AuthCodeUpdateOne) sqlSave(ctx context.Context) (_node *AuthCode, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(authcode.Table, authcode.Columns, sqlgraph.NewFieldSpec(authcode.FieldID, field.TypeString)) id, ok := _u.mutation.ID() if !ok { return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "AuthCode.id" for update`)} } _spec.Node.ID.Value = id if fields := _u.fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, authcode.FieldID) for _, f := range fields { if !authcode.ValidColumn(f) { return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } if f != authcode.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, f) } } } if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.ClientID(); ok { _spec.SetField(authcode.FieldClientID, field.TypeString, value) } if value, ok := _u.mutation.Scopes(); ok { _spec.SetField(authcode.FieldScopes, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedScopes(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, authcode.FieldScopes, value) }) } if _u.mutation.ScopesCleared() { _spec.ClearField(authcode.FieldScopes, field.TypeJSON) } if value, ok := _u.mutation.Nonce(); ok { _spec.SetField(authcode.FieldNonce, field.TypeString, value) } if value, ok := _u.mutation.RedirectURI(); ok { _spec.SetField(authcode.FieldRedirectURI, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUserID(); ok { _spec.SetField(authcode.FieldClaimsUserID, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUsername(); ok { _spec.SetField(authcode.FieldClaimsUsername, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmail(); ok { _spec.SetField(authcode.FieldClaimsEmail, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmailVerified(); ok { _spec.SetField(authcode.FieldClaimsEmailVerified, field.TypeBool, value) } if value, ok := _u.mutation.ClaimsGroups(); ok { _spec.SetField(authcode.FieldClaimsGroups, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedClaimsGroups(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, authcode.FieldClaimsGroups, value) }) } if _u.mutation.ClaimsGroupsCleared() { _spec.ClearField(authcode.FieldClaimsGroups, field.TypeJSON) } if value, ok := _u.mutation.ClaimsPreferredUsername(); ok { _spec.SetField(authcode.FieldClaimsPreferredUsername, field.TypeString, value) } if value, ok := _u.mutation.ConnectorID(); ok { _spec.SetField(authcode.FieldConnectorID, field.TypeString, value) } if value, ok := _u.mutation.ConnectorData(); ok { _spec.SetField(authcode.FieldConnectorData, field.TypeBytes, value) } if _u.mutation.ConnectorDataCleared() { _spec.ClearField(authcode.FieldConnectorData, field.TypeBytes) } if value, ok := _u.mutation.Expiry(); ok { _spec.SetField(authcode.FieldExpiry, field.TypeTime, value) } if value, ok := _u.mutation.CodeChallenge(); ok { _spec.SetField(authcode.FieldCodeChallenge, field.TypeString, value) } if value, ok := _u.mutation.CodeChallengeMethod(); ok { _spec.SetField(authcode.FieldCodeChallengeMethod, field.TypeString, value) } if value, ok := _u.mutation.AuthTime(); ok { _spec.SetField(authcode.FieldAuthTime, field.TypeTime, value) } if _u.mutation.AuthTimeCleared() { _spec.ClearField(authcode.FieldAuthTime, field.TypeTime) } _node = &AuthCode{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{authcode.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } _u.mutation.done = true return _node, nil } ================================================ FILE: storage/ent/db/authrequest/authrequest.go ================================================ // Code generated by ent, DO NOT EDIT. package authrequest import ( "entgo.io/ent/dialect/sql" ) const ( // Label holds the string label denoting the authrequest type in the database. Label = "auth_request" // FieldID holds the string denoting the id field in the database. FieldID = "id" // FieldClientID holds the string denoting the client_id field in the database. FieldClientID = "client_id" // FieldScopes holds the string denoting the scopes field in the database. FieldScopes = "scopes" // FieldResponseTypes holds the string denoting the response_types field in the database. FieldResponseTypes = "response_types" // FieldRedirectURI holds the string denoting the redirect_uri field in the database. FieldRedirectURI = "redirect_uri" // FieldNonce holds the string denoting the nonce field in the database. FieldNonce = "nonce" // FieldState holds the string denoting the state field in the database. FieldState = "state" // FieldForceApprovalPrompt holds the string denoting the force_approval_prompt field in the database. FieldForceApprovalPrompt = "force_approval_prompt" // FieldLoggedIn holds the string denoting the logged_in field in the database. FieldLoggedIn = "logged_in" // FieldClaimsUserID holds the string denoting the claims_user_id field in the database. FieldClaimsUserID = "claims_user_id" // FieldClaimsUsername holds the string denoting the claims_username field in the database. FieldClaimsUsername = "claims_username" // FieldClaimsEmail holds the string denoting the claims_email field in the database. FieldClaimsEmail = "claims_email" // FieldClaimsEmailVerified holds the string denoting the claims_email_verified field in the database. FieldClaimsEmailVerified = "claims_email_verified" // FieldClaimsGroups holds the string denoting the claims_groups field in the database. FieldClaimsGroups = "claims_groups" // FieldClaimsPreferredUsername holds the string denoting the claims_preferred_username field in the database. FieldClaimsPreferredUsername = "claims_preferred_username" // FieldConnectorID holds the string denoting the connector_id field in the database. FieldConnectorID = "connector_id" // FieldConnectorData holds the string denoting the connector_data field in the database. FieldConnectorData = "connector_data" // FieldExpiry holds the string denoting the expiry field in the database. FieldExpiry = "expiry" // FieldCodeChallenge holds the string denoting the code_challenge field in the database. FieldCodeChallenge = "code_challenge" // FieldCodeChallengeMethod holds the string denoting the code_challenge_method field in the database. FieldCodeChallengeMethod = "code_challenge_method" // FieldHmacKey holds the string denoting the hmac_key field in the database. FieldHmacKey = "hmac_key" // FieldMfaValidated holds the string denoting the mfa_validated field in the database. FieldMfaValidated = "mfa_validated" // FieldPrompt holds the string denoting the prompt field in the database. FieldPrompt = "prompt" // FieldMaxAge holds the string denoting the max_age field in the database. FieldMaxAge = "max_age" // FieldAuthTime holds the string denoting the auth_time field in the database. FieldAuthTime = "auth_time" // Table holds the table name of the authrequest in the database. Table = "auth_requests" ) // Columns holds all SQL columns for authrequest fields. var Columns = []string{ FieldID, FieldClientID, FieldScopes, FieldResponseTypes, FieldRedirectURI, FieldNonce, FieldState, FieldForceApprovalPrompt, FieldLoggedIn, FieldClaimsUserID, FieldClaimsUsername, FieldClaimsEmail, FieldClaimsEmailVerified, FieldClaimsGroups, FieldClaimsPreferredUsername, FieldConnectorID, FieldConnectorData, FieldExpiry, FieldCodeChallenge, FieldCodeChallengeMethod, FieldHmacKey, FieldMfaValidated, FieldPrompt, FieldMaxAge, FieldAuthTime, } // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { if column == Columns[i] { return true } } return false } var ( // DefaultClaimsPreferredUsername holds the default value on creation for the "claims_preferred_username" field. DefaultClaimsPreferredUsername string // DefaultCodeChallenge holds the default value on creation for the "code_challenge" field. DefaultCodeChallenge string // DefaultCodeChallengeMethod holds the default value on creation for the "code_challenge_method" field. DefaultCodeChallengeMethod string // DefaultMfaValidated holds the default value on creation for the "mfa_validated" field. DefaultMfaValidated bool // DefaultPrompt holds the default value on creation for the "prompt" field. DefaultPrompt string // DefaultMaxAge holds the default value on creation for the "max_age" field. DefaultMaxAge int // IDValidator is a validator for the "id" field. It is called by the builders before save. IDValidator func(string) error ) // OrderOption defines the ordering options for the AuthRequest queries. type OrderOption func(*sql.Selector) // ByID orders the results by the id field. func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } // ByClientID orders the results by the client_id field. func ByClientID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClientID, opts...).ToFunc() } // ByRedirectURI orders the results by the redirect_uri field. func ByRedirectURI(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldRedirectURI, opts...).ToFunc() } // ByNonce orders the results by the nonce field. func ByNonce(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldNonce, opts...).ToFunc() } // ByState orders the results by the state field. func ByState(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldState, opts...).ToFunc() } // ByForceApprovalPrompt orders the results by the force_approval_prompt field. func ByForceApprovalPrompt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldForceApprovalPrompt, opts...).ToFunc() } // ByLoggedIn orders the results by the logged_in field. func ByLoggedIn(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldLoggedIn, opts...).ToFunc() } // ByClaimsUserID orders the results by the claims_user_id field. func ByClaimsUserID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsUserID, opts...).ToFunc() } // ByClaimsUsername orders the results by the claims_username field. func ByClaimsUsername(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsUsername, opts...).ToFunc() } // ByClaimsEmail orders the results by the claims_email field. func ByClaimsEmail(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsEmail, opts...).ToFunc() } // ByClaimsEmailVerified orders the results by the claims_email_verified field. func ByClaimsEmailVerified(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsEmailVerified, opts...).ToFunc() } // ByClaimsPreferredUsername orders the results by the claims_preferred_username field. func ByClaimsPreferredUsername(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsPreferredUsername, opts...).ToFunc() } // ByConnectorID orders the results by the connector_id field. func ByConnectorID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldConnectorID, opts...).ToFunc() } // ByExpiry orders the results by the expiry field. func ByExpiry(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldExpiry, opts...).ToFunc() } // ByCodeChallenge orders the results by the code_challenge field. func ByCodeChallenge(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCodeChallenge, opts...).ToFunc() } // ByCodeChallengeMethod orders the results by the code_challenge_method field. func ByCodeChallengeMethod(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCodeChallengeMethod, opts...).ToFunc() } // ByMfaValidated orders the results by the mfa_validated field. func ByMfaValidated(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldMfaValidated, opts...).ToFunc() } // ByPrompt orders the results by the prompt field. func ByPrompt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldPrompt, opts...).ToFunc() } // ByMaxAge orders the results by the max_age field. func ByMaxAge(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldMaxAge, opts...).ToFunc() } // ByAuthTime orders the results by the auth_time field. func ByAuthTime(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldAuthTime, opts...).ToFunc() } ================================================ FILE: storage/ent/db/authrequest/where.go ================================================ // Code generated by ent, DO NOT EDIT. package authrequest import ( "time" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ID filters vertices based on their ID field. func ID(id string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. func IDEQ(id string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. func IDNEQ(id string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. func IDIn(ids ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. func IDNotIn(ids ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. func IDGT(id string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. func IDGTE(id string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. func IDLT(id string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. func IDLTE(id string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldID, id)) } // IDEqualFold applies the EqualFold predicate on the ID field. func IDEqualFold(id string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldID, id)) } // IDContainsFold applies the ContainsFold predicate on the ID field. func IDContainsFold(id string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldID, id)) } // ClientID applies equality check predicate on the "client_id" field. It's identical to ClientIDEQ. func ClientID(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldClientID, v)) } // RedirectURI applies equality check predicate on the "redirect_uri" field. It's identical to RedirectURIEQ. func RedirectURI(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldRedirectURI, v)) } // Nonce applies equality check predicate on the "nonce" field. It's identical to NonceEQ. func Nonce(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldNonce, v)) } // State applies equality check predicate on the "state" field. It's identical to StateEQ. func State(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldState, v)) } // ForceApprovalPrompt applies equality check predicate on the "force_approval_prompt" field. It's identical to ForceApprovalPromptEQ. func ForceApprovalPrompt(v bool) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldForceApprovalPrompt, v)) } // LoggedIn applies equality check predicate on the "logged_in" field. It's identical to LoggedInEQ. func LoggedIn(v bool) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldLoggedIn, v)) } // ClaimsUserID applies equality check predicate on the "claims_user_id" field. It's identical to ClaimsUserIDEQ. func ClaimsUserID(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldClaimsUserID, v)) } // ClaimsUsername applies equality check predicate on the "claims_username" field. It's identical to ClaimsUsernameEQ. func ClaimsUsername(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldClaimsUsername, v)) } // ClaimsEmail applies equality check predicate on the "claims_email" field. It's identical to ClaimsEmailEQ. func ClaimsEmail(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldClaimsEmail, v)) } // ClaimsEmailVerified applies equality check predicate on the "claims_email_verified" field. It's identical to ClaimsEmailVerifiedEQ. func ClaimsEmailVerified(v bool) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldClaimsEmailVerified, v)) } // ClaimsPreferredUsername applies equality check predicate on the "claims_preferred_username" field. It's identical to ClaimsPreferredUsernameEQ. func ClaimsPreferredUsername(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldClaimsPreferredUsername, v)) } // ConnectorID applies equality check predicate on the "connector_id" field. It's identical to ConnectorIDEQ. func ConnectorID(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldConnectorID, v)) } // ConnectorData applies equality check predicate on the "connector_data" field. It's identical to ConnectorDataEQ. func ConnectorData(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldConnectorData, v)) } // Expiry applies equality check predicate on the "expiry" field. It's identical to ExpiryEQ. func Expiry(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldExpiry, v)) } // CodeChallenge applies equality check predicate on the "code_challenge" field. It's identical to CodeChallengeEQ. func CodeChallenge(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldCodeChallenge, v)) } // CodeChallengeMethod applies equality check predicate on the "code_challenge_method" field. It's identical to CodeChallengeMethodEQ. func CodeChallengeMethod(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldCodeChallengeMethod, v)) } // HmacKey applies equality check predicate on the "hmac_key" field. It's identical to HmacKeyEQ. func HmacKey(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldHmacKey, v)) } // MfaValidated applies equality check predicate on the "mfa_validated" field. It's identical to MfaValidatedEQ. func MfaValidated(v bool) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldMfaValidated, v)) } // Prompt applies equality check predicate on the "prompt" field. It's identical to PromptEQ. func Prompt(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldPrompt, v)) } // MaxAge applies equality check predicate on the "max_age" field. It's identical to MaxAgeEQ. func MaxAge(v int) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldMaxAge, v)) } // AuthTime applies equality check predicate on the "auth_time" field. It's identical to AuthTimeEQ. func AuthTime(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldAuthTime, v)) } // ClientIDEQ applies the EQ predicate on the "client_id" field. func ClientIDEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldClientID, v)) } // ClientIDNEQ applies the NEQ predicate on the "client_id" field. func ClientIDNEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldClientID, v)) } // ClientIDIn applies the In predicate on the "client_id" field. func ClientIDIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldClientID, vs...)) } // ClientIDNotIn applies the NotIn predicate on the "client_id" field. func ClientIDNotIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldClientID, vs...)) } // ClientIDGT applies the GT predicate on the "client_id" field. func ClientIDGT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldClientID, v)) } // ClientIDGTE applies the GTE predicate on the "client_id" field. func ClientIDGTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldClientID, v)) } // ClientIDLT applies the LT predicate on the "client_id" field. func ClientIDLT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldClientID, v)) } // ClientIDLTE applies the LTE predicate on the "client_id" field. func ClientIDLTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldClientID, v)) } // ClientIDContains applies the Contains predicate on the "client_id" field. func ClientIDContains(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContains(FieldClientID, v)) } // ClientIDHasPrefix applies the HasPrefix predicate on the "client_id" field. func ClientIDHasPrefix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasPrefix(FieldClientID, v)) } // ClientIDHasSuffix applies the HasSuffix predicate on the "client_id" field. func ClientIDHasSuffix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasSuffix(FieldClientID, v)) } // ClientIDEqualFold applies the EqualFold predicate on the "client_id" field. func ClientIDEqualFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldClientID, v)) } // ClientIDContainsFold applies the ContainsFold predicate on the "client_id" field. func ClientIDContainsFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldClientID, v)) } // ScopesIsNil applies the IsNil predicate on the "scopes" field. func ScopesIsNil() predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIsNull(FieldScopes)) } // ScopesNotNil applies the NotNil predicate on the "scopes" field. func ScopesNotNil() predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotNull(FieldScopes)) } // ResponseTypesIsNil applies the IsNil predicate on the "response_types" field. func ResponseTypesIsNil() predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIsNull(FieldResponseTypes)) } // ResponseTypesNotNil applies the NotNil predicate on the "response_types" field. func ResponseTypesNotNil() predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotNull(FieldResponseTypes)) } // RedirectURIEQ applies the EQ predicate on the "redirect_uri" field. func RedirectURIEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldRedirectURI, v)) } // RedirectURINEQ applies the NEQ predicate on the "redirect_uri" field. func RedirectURINEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldRedirectURI, v)) } // RedirectURIIn applies the In predicate on the "redirect_uri" field. func RedirectURIIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldRedirectURI, vs...)) } // RedirectURINotIn applies the NotIn predicate on the "redirect_uri" field. func RedirectURINotIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldRedirectURI, vs...)) } // RedirectURIGT applies the GT predicate on the "redirect_uri" field. func RedirectURIGT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldRedirectURI, v)) } // RedirectURIGTE applies the GTE predicate on the "redirect_uri" field. func RedirectURIGTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldRedirectURI, v)) } // RedirectURILT applies the LT predicate on the "redirect_uri" field. func RedirectURILT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldRedirectURI, v)) } // RedirectURILTE applies the LTE predicate on the "redirect_uri" field. func RedirectURILTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldRedirectURI, v)) } // RedirectURIContains applies the Contains predicate on the "redirect_uri" field. func RedirectURIContains(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContains(FieldRedirectURI, v)) } // RedirectURIHasPrefix applies the HasPrefix predicate on the "redirect_uri" field. func RedirectURIHasPrefix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasPrefix(FieldRedirectURI, v)) } // RedirectURIHasSuffix applies the HasSuffix predicate on the "redirect_uri" field. func RedirectURIHasSuffix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasSuffix(FieldRedirectURI, v)) } // RedirectURIEqualFold applies the EqualFold predicate on the "redirect_uri" field. func RedirectURIEqualFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldRedirectURI, v)) } // RedirectURIContainsFold applies the ContainsFold predicate on the "redirect_uri" field. func RedirectURIContainsFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldRedirectURI, v)) } // NonceEQ applies the EQ predicate on the "nonce" field. func NonceEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldNonce, v)) } // NonceNEQ applies the NEQ predicate on the "nonce" field. func NonceNEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldNonce, v)) } // NonceIn applies the In predicate on the "nonce" field. func NonceIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldNonce, vs...)) } // NonceNotIn applies the NotIn predicate on the "nonce" field. func NonceNotIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldNonce, vs...)) } // NonceGT applies the GT predicate on the "nonce" field. func NonceGT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldNonce, v)) } // NonceGTE applies the GTE predicate on the "nonce" field. func NonceGTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldNonce, v)) } // NonceLT applies the LT predicate on the "nonce" field. func NonceLT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldNonce, v)) } // NonceLTE applies the LTE predicate on the "nonce" field. func NonceLTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldNonce, v)) } // NonceContains applies the Contains predicate on the "nonce" field. func NonceContains(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContains(FieldNonce, v)) } // NonceHasPrefix applies the HasPrefix predicate on the "nonce" field. func NonceHasPrefix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasPrefix(FieldNonce, v)) } // NonceHasSuffix applies the HasSuffix predicate on the "nonce" field. func NonceHasSuffix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasSuffix(FieldNonce, v)) } // NonceEqualFold applies the EqualFold predicate on the "nonce" field. func NonceEqualFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldNonce, v)) } // NonceContainsFold applies the ContainsFold predicate on the "nonce" field. func NonceContainsFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldNonce, v)) } // StateEQ applies the EQ predicate on the "state" field. func StateEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldState, v)) } // StateNEQ applies the NEQ predicate on the "state" field. func StateNEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldState, v)) } // StateIn applies the In predicate on the "state" field. func StateIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldState, vs...)) } // StateNotIn applies the NotIn predicate on the "state" field. func StateNotIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldState, vs...)) } // StateGT applies the GT predicate on the "state" field. func StateGT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldState, v)) } // StateGTE applies the GTE predicate on the "state" field. func StateGTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldState, v)) } // StateLT applies the LT predicate on the "state" field. func StateLT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldState, v)) } // StateLTE applies the LTE predicate on the "state" field. func StateLTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldState, v)) } // StateContains applies the Contains predicate on the "state" field. func StateContains(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContains(FieldState, v)) } // StateHasPrefix applies the HasPrefix predicate on the "state" field. func StateHasPrefix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasPrefix(FieldState, v)) } // StateHasSuffix applies the HasSuffix predicate on the "state" field. func StateHasSuffix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasSuffix(FieldState, v)) } // StateEqualFold applies the EqualFold predicate on the "state" field. func StateEqualFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldState, v)) } // StateContainsFold applies the ContainsFold predicate on the "state" field. func StateContainsFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldState, v)) } // ForceApprovalPromptEQ applies the EQ predicate on the "force_approval_prompt" field. func ForceApprovalPromptEQ(v bool) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldForceApprovalPrompt, v)) } // ForceApprovalPromptNEQ applies the NEQ predicate on the "force_approval_prompt" field. func ForceApprovalPromptNEQ(v bool) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldForceApprovalPrompt, v)) } // LoggedInEQ applies the EQ predicate on the "logged_in" field. func LoggedInEQ(v bool) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldLoggedIn, v)) } // LoggedInNEQ applies the NEQ predicate on the "logged_in" field. func LoggedInNEQ(v bool) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldLoggedIn, v)) } // ClaimsUserIDEQ applies the EQ predicate on the "claims_user_id" field. func ClaimsUserIDEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldClaimsUserID, v)) } // ClaimsUserIDNEQ applies the NEQ predicate on the "claims_user_id" field. func ClaimsUserIDNEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldClaimsUserID, v)) } // ClaimsUserIDIn applies the In predicate on the "claims_user_id" field. func ClaimsUserIDIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldClaimsUserID, vs...)) } // ClaimsUserIDNotIn applies the NotIn predicate on the "claims_user_id" field. func ClaimsUserIDNotIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldClaimsUserID, vs...)) } // ClaimsUserIDGT applies the GT predicate on the "claims_user_id" field. func ClaimsUserIDGT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldClaimsUserID, v)) } // ClaimsUserIDGTE applies the GTE predicate on the "claims_user_id" field. func ClaimsUserIDGTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldClaimsUserID, v)) } // ClaimsUserIDLT applies the LT predicate on the "claims_user_id" field. func ClaimsUserIDLT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldClaimsUserID, v)) } // ClaimsUserIDLTE applies the LTE predicate on the "claims_user_id" field. func ClaimsUserIDLTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldClaimsUserID, v)) } // ClaimsUserIDContains applies the Contains predicate on the "claims_user_id" field. func ClaimsUserIDContains(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContains(FieldClaimsUserID, v)) } // ClaimsUserIDHasPrefix applies the HasPrefix predicate on the "claims_user_id" field. func ClaimsUserIDHasPrefix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasPrefix(FieldClaimsUserID, v)) } // ClaimsUserIDHasSuffix applies the HasSuffix predicate on the "claims_user_id" field. func ClaimsUserIDHasSuffix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasSuffix(FieldClaimsUserID, v)) } // ClaimsUserIDEqualFold applies the EqualFold predicate on the "claims_user_id" field. func ClaimsUserIDEqualFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldClaimsUserID, v)) } // ClaimsUserIDContainsFold applies the ContainsFold predicate on the "claims_user_id" field. func ClaimsUserIDContainsFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldClaimsUserID, v)) } // ClaimsUsernameEQ applies the EQ predicate on the "claims_username" field. func ClaimsUsernameEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldClaimsUsername, v)) } // ClaimsUsernameNEQ applies the NEQ predicate on the "claims_username" field. func ClaimsUsernameNEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldClaimsUsername, v)) } // ClaimsUsernameIn applies the In predicate on the "claims_username" field. func ClaimsUsernameIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldClaimsUsername, vs...)) } // ClaimsUsernameNotIn applies the NotIn predicate on the "claims_username" field. func ClaimsUsernameNotIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldClaimsUsername, vs...)) } // ClaimsUsernameGT applies the GT predicate on the "claims_username" field. func ClaimsUsernameGT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldClaimsUsername, v)) } // ClaimsUsernameGTE applies the GTE predicate on the "claims_username" field. func ClaimsUsernameGTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldClaimsUsername, v)) } // ClaimsUsernameLT applies the LT predicate on the "claims_username" field. func ClaimsUsernameLT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldClaimsUsername, v)) } // ClaimsUsernameLTE applies the LTE predicate on the "claims_username" field. func ClaimsUsernameLTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldClaimsUsername, v)) } // ClaimsUsernameContains applies the Contains predicate on the "claims_username" field. func ClaimsUsernameContains(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContains(FieldClaimsUsername, v)) } // ClaimsUsernameHasPrefix applies the HasPrefix predicate on the "claims_username" field. func ClaimsUsernameHasPrefix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasPrefix(FieldClaimsUsername, v)) } // ClaimsUsernameHasSuffix applies the HasSuffix predicate on the "claims_username" field. func ClaimsUsernameHasSuffix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasSuffix(FieldClaimsUsername, v)) } // ClaimsUsernameEqualFold applies the EqualFold predicate on the "claims_username" field. func ClaimsUsernameEqualFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldClaimsUsername, v)) } // ClaimsUsernameContainsFold applies the ContainsFold predicate on the "claims_username" field. func ClaimsUsernameContainsFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldClaimsUsername, v)) } // ClaimsEmailEQ applies the EQ predicate on the "claims_email" field. func ClaimsEmailEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldClaimsEmail, v)) } // ClaimsEmailNEQ applies the NEQ predicate on the "claims_email" field. func ClaimsEmailNEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldClaimsEmail, v)) } // ClaimsEmailIn applies the In predicate on the "claims_email" field. func ClaimsEmailIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldClaimsEmail, vs...)) } // ClaimsEmailNotIn applies the NotIn predicate on the "claims_email" field. func ClaimsEmailNotIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldClaimsEmail, vs...)) } // ClaimsEmailGT applies the GT predicate on the "claims_email" field. func ClaimsEmailGT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldClaimsEmail, v)) } // ClaimsEmailGTE applies the GTE predicate on the "claims_email" field. func ClaimsEmailGTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldClaimsEmail, v)) } // ClaimsEmailLT applies the LT predicate on the "claims_email" field. func ClaimsEmailLT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldClaimsEmail, v)) } // ClaimsEmailLTE applies the LTE predicate on the "claims_email" field. func ClaimsEmailLTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldClaimsEmail, v)) } // ClaimsEmailContains applies the Contains predicate on the "claims_email" field. func ClaimsEmailContains(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContains(FieldClaimsEmail, v)) } // ClaimsEmailHasPrefix applies the HasPrefix predicate on the "claims_email" field. func ClaimsEmailHasPrefix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasPrefix(FieldClaimsEmail, v)) } // ClaimsEmailHasSuffix applies the HasSuffix predicate on the "claims_email" field. func ClaimsEmailHasSuffix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasSuffix(FieldClaimsEmail, v)) } // ClaimsEmailEqualFold applies the EqualFold predicate on the "claims_email" field. func ClaimsEmailEqualFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldClaimsEmail, v)) } // ClaimsEmailContainsFold applies the ContainsFold predicate on the "claims_email" field. func ClaimsEmailContainsFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldClaimsEmail, v)) } // ClaimsEmailVerifiedEQ applies the EQ predicate on the "claims_email_verified" field. func ClaimsEmailVerifiedEQ(v bool) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldClaimsEmailVerified, v)) } // ClaimsEmailVerifiedNEQ applies the NEQ predicate on the "claims_email_verified" field. func ClaimsEmailVerifiedNEQ(v bool) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldClaimsEmailVerified, v)) } // ClaimsGroupsIsNil applies the IsNil predicate on the "claims_groups" field. func ClaimsGroupsIsNil() predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIsNull(FieldClaimsGroups)) } // ClaimsGroupsNotNil applies the NotNil predicate on the "claims_groups" field. func ClaimsGroupsNotNil() predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotNull(FieldClaimsGroups)) } // ClaimsPreferredUsernameEQ applies the EQ predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameNEQ applies the NEQ predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameNEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameIn applies the In predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldClaimsPreferredUsername, vs...)) } // ClaimsPreferredUsernameNotIn applies the NotIn predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameNotIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldClaimsPreferredUsername, vs...)) } // ClaimsPreferredUsernameGT applies the GT predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameGT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameGTE applies the GTE predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameGTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameLT applies the LT predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameLT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameLTE applies the LTE predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameLTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameContains applies the Contains predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameContains(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContains(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameHasPrefix applies the HasPrefix predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameHasPrefix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasPrefix(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameHasSuffix applies the HasSuffix predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameHasSuffix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasSuffix(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameEqualFold applies the EqualFold predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameEqualFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameContainsFold applies the ContainsFold predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameContainsFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldClaimsPreferredUsername, v)) } // ConnectorIDEQ applies the EQ predicate on the "connector_id" field. func ConnectorIDEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldConnectorID, v)) } // ConnectorIDNEQ applies the NEQ predicate on the "connector_id" field. func ConnectorIDNEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldConnectorID, v)) } // ConnectorIDIn applies the In predicate on the "connector_id" field. func ConnectorIDIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldConnectorID, vs...)) } // ConnectorIDNotIn applies the NotIn predicate on the "connector_id" field. func ConnectorIDNotIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldConnectorID, vs...)) } // ConnectorIDGT applies the GT predicate on the "connector_id" field. func ConnectorIDGT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldConnectorID, v)) } // ConnectorIDGTE applies the GTE predicate on the "connector_id" field. func ConnectorIDGTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldConnectorID, v)) } // ConnectorIDLT applies the LT predicate on the "connector_id" field. func ConnectorIDLT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldConnectorID, v)) } // ConnectorIDLTE applies the LTE predicate on the "connector_id" field. func ConnectorIDLTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldConnectorID, v)) } // ConnectorIDContains applies the Contains predicate on the "connector_id" field. func ConnectorIDContains(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContains(FieldConnectorID, v)) } // ConnectorIDHasPrefix applies the HasPrefix predicate on the "connector_id" field. func ConnectorIDHasPrefix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasPrefix(FieldConnectorID, v)) } // ConnectorIDHasSuffix applies the HasSuffix predicate on the "connector_id" field. func ConnectorIDHasSuffix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasSuffix(FieldConnectorID, v)) } // ConnectorIDEqualFold applies the EqualFold predicate on the "connector_id" field. func ConnectorIDEqualFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldConnectorID, v)) } // ConnectorIDContainsFold applies the ContainsFold predicate on the "connector_id" field. func ConnectorIDContainsFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldConnectorID, v)) } // ConnectorDataEQ applies the EQ predicate on the "connector_data" field. func ConnectorDataEQ(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldConnectorData, v)) } // ConnectorDataNEQ applies the NEQ predicate on the "connector_data" field. func ConnectorDataNEQ(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldConnectorData, v)) } // ConnectorDataIn applies the In predicate on the "connector_data" field. func ConnectorDataIn(vs ...[]byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldConnectorData, vs...)) } // ConnectorDataNotIn applies the NotIn predicate on the "connector_data" field. func ConnectorDataNotIn(vs ...[]byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldConnectorData, vs...)) } // ConnectorDataGT applies the GT predicate on the "connector_data" field. func ConnectorDataGT(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldConnectorData, v)) } // ConnectorDataGTE applies the GTE predicate on the "connector_data" field. func ConnectorDataGTE(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldConnectorData, v)) } // ConnectorDataLT applies the LT predicate on the "connector_data" field. func ConnectorDataLT(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldConnectorData, v)) } // ConnectorDataLTE applies the LTE predicate on the "connector_data" field. func ConnectorDataLTE(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldConnectorData, v)) } // ConnectorDataIsNil applies the IsNil predicate on the "connector_data" field. func ConnectorDataIsNil() predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIsNull(FieldConnectorData)) } // ConnectorDataNotNil applies the NotNil predicate on the "connector_data" field. func ConnectorDataNotNil() predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotNull(FieldConnectorData)) } // ExpiryEQ applies the EQ predicate on the "expiry" field. func ExpiryEQ(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldExpiry, v)) } // ExpiryNEQ applies the NEQ predicate on the "expiry" field. func ExpiryNEQ(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldExpiry, v)) } // ExpiryIn applies the In predicate on the "expiry" field. func ExpiryIn(vs ...time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldExpiry, vs...)) } // ExpiryNotIn applies the NotIn predicate on the "expiry" field. func ExpiryNotIn(vs ...time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldExpiry, vs...)) } // ExpiryGT applies the GT predicate on the "expiry" field. func ExpiryGT(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldExpiry, v)) } // ExpiryGTE applies the GTE predicate on the "expiry" field. func ExpiryGTE(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldExpiry, v)) } // ExpiryLT applies the LT predicate on the "expiry" field. func ExpiryLT(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldExpiry, v)) } // ExpiryLTE applies the LTE predicate on the "expiry" field. func ExpiryLTE(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldExpiry, v)) } // CodeChallengeEQ applies the EQ predicate on the "code_challenge" field. func CodeChallengeEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldCodeChallenge, v)) } // CodeChallengeNEQ applies the NEQ predicate on the "code_challenge" field. func CodeChallengeNEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldCodeChallenge, v)) } // CodeChallengeIn applies the In predicate on the "code_challenge" field. func CodeChallengeIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldCodeChallenge, vs...)) } // CodeChallengeNotIn applies the NotIn predicate on the "code_challenge" field. func CodeChallengeNotIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldCodeChallenge, vs...)) } // CodeChallengeGT applies the GT predicate on the "code_challenge" field. func CodeChallengeGT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldCodeChallenge, v)) } // CodeChallengeGTE applies the GTE predicate on the "code_challenge" field. func CodeChallengeGTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldCodeChallenge, v)) } // CodeChallengeLT applies the LT predicate on the "code_challenge" field. func CodeChallengeLT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldCodeChallenge, v)) } // CodeChallengeLTE applies the LTE predicate on the "code_challenge" field. func CodeChallengeLTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldCodeChallenge, v)) } // CodeChallengeContains applies the Contains predicate on the "code_challenge" field. func CodeChallengeContains(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContains(FieldCodeChallenge, v)) } // CodeChallengeHasPrefix applies the HasPrefix predicate on the "code_challenge" field. func CodeChallengeHasPrefix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasPrefix(FieldCodeChallenge, v)) } // CodeChallengeHasSuffix applies the HasSuffix predicate on the "code_challenge" field. func CodeChallengeHasSuffix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasSuffix(FieldCodeChallenge, v)) } // CodeChallengeEqualFold applies the EqualFold predicate on the "code_challenge" field. func CodeChallengeEqualFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldCodeChallenge, v)) } // CodeChallengeContainsFold applies the ContainsFold predicate on the "code_challenge" field. func CodeChallengeContainsFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldCodeChallenge, v)) } // CodeChallengeMethodEQ applies the EQ predicate on the "code_challenge_method" field. func CodeChallengeMethodEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodNEQ applies the NEQ predicate on the "code_challenge_method" field. func CodeChallengeMethodNEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodIn applies the In predicate on the "code_challenge_method" field. func CodeChallengeMethodIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldCodeChallengeMethod, vs...)) } // CodeChallengeMethodNotIn applies the NotIn predicate on the "code_challenge_method" field. func CodeChallengeMethodNotIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldCodeChallengeMethod, vs...)) } // CodeChallengeMethodGT applies the GT predicate on the "code_challenge_method" field. func CodeChallengeMethodGT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodGTE applies the GTE predicate on the "code_challenge_method" field. func CodeChallengeMethodGTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodLT applies the LT predicate on the "code_challenge_method" field. func CodeChallengeMethodLT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodLTE applies the LTE predicate on the "code_challenge_method" field. func CodeChallengeMethodLTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodContains applies the Contains predicate on the "code_challenge_method" field. func CodeChallengeMethodContains(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContains(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodHasPrefix applies the HasPrefix predicate on the "code_challenge_method" field. func CodeChallengeMethodHasPrefix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasPrefix(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodHasSuffix applies the HasSuffix predicate on the "code_challenge_method" field. func CodeChallengeMethodHasSuffix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasSuffix(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodEqualFold applies the EqualFold predicate on the "code_challenge_method" field. func CodeChallengeMethodEqualFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodContainsFold applies the ContainsFold predicate on the "code_challenge_method" field. func CodeChallengeMethodContainsFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldCodeChallengeMethod, v)) } // HmacKeyEQ applies the EQ predicate on the "hmac_key" field. func HmacKeyEQ(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldHmacKey, v)) } // HmacKeyNEQ applies the NEQ predicate on the "hmac_key" field. func HmacKeyNEQ(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldHmacKey, v)) } // HmacKeyIn applies the In predicate on the "hmac_key" field. func HmacKeyIn(vs ...[]byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldHmacKey, vs...)) } // HmacKeyNotIn applies the NotIn predicate on the "hmac_key" field. func HmacKeyNotIn(vs ...[]byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldHmacKey, vs...)) } // HmacKeyGT applies the GT predicate on the "hmac_key" field. func HmacKeyGT(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldHmacKey, v)) } // HmacKeyGTE applies the GTE predicate on the "hmac_key" field. func HmacKeyGTE(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldHmacKey, v)) } // HmacKeyLT applies the LT predicate on the "hmac_key" field. func HmacKeyLT(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldHmacKey, v)) } // HmacKeyLTE applies the LTE predicate on the "hmac_key" field. func HmacKeyLTE(v []byte) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldHmacKey, v)) } // MfaValidatedEQ applies the EQ predicate on the "mfa_validated" field. func MfaValidatedEQ(v bool) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldMfaValidated, v)) } // MfaValidatedNEQ applies the NEQ predicate on the "mfa_validated" field. func MfaValidatedNEQ(v bool) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldMfaValidated, v)) } // PromptEQ applies the EQ predicate on the "prompt" field. func PromptEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldPrompt, v)) } // PromptNEQ applies the NEQ predicate on the "prompt" field. func PromptNEQ(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldPrompt, v)) } // PromptIn applies the In predicate on the "prompt" field. func PromptIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldPrompt, vs...)) } // PromptNotIn applies the NotIn predicate on the "prompt" field. func PromptNotIn(vs ...string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldPrompt, vs...)) } // PromptGT applies the GT predicate on the "prompt" field. func PromptGT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldPrompt, v)) } // PromptGTE applies the GTE predicate on the "prompt" field. func PromptGTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldPrompt, v)) } // PromptLT applies the LT predicate on the "prompt" field. func PromptLT(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldPrompt, v)) } // PromptLTE applies the LTE predicate on the "prompt" field. func PromptLTE(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldPrompt, v)) } // PromptContains applies the Contains predicate on the "prompt" field. func PromptContains(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContains(FieldPrompt, v)) } // PromptHasPrefix applies the HasPrefix predicate on the "prompt" field. func PromptHasPrefix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasPrefix(FieldPrompt, v)) } // PromptHasSuffix applies the HasSuffix predicate on the "prompt" field. func PromptHasSuffix(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldHasSuffix(FieldPrompt, v)) } // PromptEqualFold applies the EqualFold predicate on the "prompt" field. func PromptEqualFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEqualFold(FieldPrompt, v)) } // PromptContainsFold applies the ContainsFold predicate on the "prompt" field. func PromptContainsFold(v string) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldContainsFold(FieldPrompt, v)) } // MaxAgeEQ applies the EQ predicate on the "max_age" field. func MaxAgeEQ(v int) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldMaxAge, v)) } // MaxAgeNEQ applies the NEQ predicate on the "max_age" field. func MaxAgeNEQ(v int) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldMaxAge, v)) } // MaxAgeIn applies the In predicate on the "max_age" field. func MaxAgeIn(vs ...int) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldMaxAge, vs...)) } // MaxAgeNotIn applies the NotIn predicate on the "max_age" field. func MaxAgeNotIn(vs ...int) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldMaxAge, vs...)) } // MaxAgeGT applies the GT predicate on the "max_age" field. func MaxAgeGT(v int) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldMaxAge, v)) } // MaxAgeGTE applies the GTE predicate on the "max_age" field. func MaxAgeGTE(v int) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldMaxAge, v)) } // MaxAgeLT applies the LT predicate on the "max_age" field. func MaxAgeLT(v int) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldMaxAge, v)) } // MaxAgeLTE applies the LTE predicate on the "max_age" field. func MaxAgeLTE(v int) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldMaxAge, v)) } // AuthTimeEQ applies the EQ predicate on the "auth_time" field. func AuthTimeEQ(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldEQ(FieldAuthTime, v)) } // AuthTimeNEQ applies the NEQ predicate on the "auth_time" field. func AuthTimeNEQ(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNEQ(FieldAuthTime, v)) } // AuthTimeIn applies the In predicate on the "auth_time" field. func AuthTimeIn(vs ...time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIn(FieldAuthTime, vs...)) } // AuthTimeNotIn applies the NotIn predicate on the "auth_time" field. func AuthTimeNotIn(vs ...time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotIn(FieldAuthTime, vs...)) } // AuthTimeGT applies the GT predicate on the "auth_time" field. func AuthTimeGT(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGT(FieldAuthTime, v)) } // AuthTimeGTE applies the GTE predicate on the "auth_time" field. func AuthTimeGTE(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldGTE(FieldAuthTime, v)) } // AuthTimeLT applies the LT predicate on the "auth_time" field. func AuthTimeLT(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLT(FieldAuthTime, v)) } // AuthTimeLTE applies the LTE predicate on the "auth_time" field. func AuthTimeLTE(v time.Time) predicate.AuthRequest { return predicate.AuthRequest(sql.FieldLTE(FieldAuthTime, v)) } // AuthTimeIsNil applies the IsNil predicate on the "auth_time" field. func AuthTimeIsNil() predicate.AuthRequest { return predicate.AuthRequest(sql.FieldIsNull(FieldAuthTime)) } // AuthTimeNotNil applies the NotNil predicate on the "auth_time" field. func AuthTimeNotNil() predicate.AuthRequest { return predicate.AuthRequest(sql.FieldNotNull(FieldAuthTime)) } // And groups predicates with the AND operator between them. func And(predicates ...predicate.AuthRequest) predicate.AuthRequest { return predicate.AuthRequest(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.AuthRequest) predicate.AuthRequest { return predicate.AuthRequest(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.AuthRequest) predicate.AuthRequest { return predicate.AuthRequest(sql.NotPredicates(p)) } ================================================ FILE: storage/ent/db/authrequest.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "encoding/json" "fmt" "strings" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/authrequest" ) // AuthRequest is the model entity for the AuthRequest schema. type AuthRequest struct { config `json:"-"` // ID of the ent. ID string `json:"id,omitempty"` // ClientID holds the value of the "client_id" field. ClientID string `json:"client_id,omitempty"` // Scopes holds the value of the "scopes" field. Scopes []string `json:"scopes,omitempty"` // ResponseTypes holds the value of the "response_types" field. ResponseTypes []string `json:"response_types,omitempty"` // RedirectURI holds the value of the "redirect_uri" field. RedirectURI string `json:"redirect_uri,omitempty"` // Nonce holds the value of the "nonce" field. Nonce string `json:"nonce,omitempty"` // State holds the value of the "state" field. State string `json:"state,omitempty"` // ForceApprovalPrompt holds the value of the "force_approval_prompt" field. ForceApprovalPrompt bool `json:"force_approval_prompt,omitempty"` // LoggedIn holds the value of the "logged_in" field. LoggedIn bool `json:"logged_in,omitempty"` // ClaimsUserID holds the value of the "claims_user_id" field. ClaimsUserID string `json:"claims_user_id,omitempty"` // ClaimsUsername holds the value of the "claims_username" field. ClaimsUsername string `json:"claims_username,omitempty"` // ClaimsEmail holds the value of the "claims_email" field. ClaimsEmail string `json:"claims_email,omitempty"` // ClaimsEmailVerified holds the value of the "claims_email_verified" field. ClaimsEmailVerified bool `json:"claims_email_verified,omitempty"` // ClaimsGroups holds the value of the "claims_groups" field. ClaimsGroups []string `json:"claims_groups,omitempty"` // ClaimsPreferredUsername holds the value of the "claims_preferred_username" field. ClaimsPreferredUsername string `json:"claims_preferred_username,omitempty"` // ConnectorID holds the value of the "connector_id" field. ConnectorID string `json:"connector_id,omitempty"` // ConnectorData holds the value of the "connector_data" field. ConnectorData *[]byte `json:"connector_data,omitempty"` // Expiry holds the value of the "expiry" field. Expiry time.Time `json:"expiry,omitempty"` // CodeChallenge holds the value of the "code_challenge" field. CodeChallenge string `json:"code_challenge,omitempty"` // CodeChallengeMethod holds the value of the "code_challenge_method" field. CodeChallengeMethod string `json:"code_challenge_method,omitempty"` // HmacKey holds the value of the "hmac_key" field. HmacKey []byte `json:"hmac_key,omitempty"` // MfaValidated holds the value of the "mfa_validated" field. MfaValidated bool `json:"mfa_validated,omitempty"` // Prompt holds the value of the "prompt" field. Prompt string `json:"prompt,omitempty"` // MaxAge holds the value of the "max_age" field. MaxAge int `json:"max_age,omitempty"` // AuthTime holds the value of the "auth_time" field. AuthTime time.Time `json:"auth_time,omitempty"` selectValues sql.SelectValues } // scanValues returns the types for scanning values from sql.Rows. func (*AuthRequest) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { case authrequest.FieldScopes, authrequest.FieldResponseTypes, authrequest.FieldClaimsGroups, authrequest.FieldConnectorData, authrequest.FieldHmacKey: values[i] = new([]byte) case authrequest.FieldForceApprovalPrompt, authrequest.FieldLoggedIn, authrequest.FieldClaimsEmailVerified, authrequest.FieldMfaValidated: values[i] = new(sql.NullBool) case authrequest.FieldMaxAge: values[i] = new(sql.NullInt64) case authrequest.FieldID, authrequest.FieldClientID, authrequest.FieldRedirectURI, authrequest.FieldNonce, authrequest.FieldState, authrequest.FieldClaimsUserID, authrequest.FieldClaimsUsername, authrequest.FieldClaimsEmail, authrequest.FieldClaimsPreferredUsername, authrequest.FieldConnectorID, authrequest.FieldCodeChallenge, authrequest.FieldCodeChallengeMethod, authrequest.FieldPrompt: values[i] = new(sql.NullString) case authrequest.FieldExpiry, authrequest.FieldAuthTime: values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } } return values, nil } // assignValues assigns the values that were returned from sql.Rows (after scanning) // to the AuthRequest fields. func (_m *AuthRequest) assignValues(columns []string, values []any) error { if m, n := len(values), len(columns); m < n { return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) } for i := range columns { switch columns[i] { case authrequest.FieldID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field id", values[i]) } else if value.Valid { _m.ID = value.String } case authrequest.FieldClientID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field client_id", values[i]) } else if value.Valid { _m.ClientID = value.String } case authrequest.FieldScopes: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field scopes", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.Scopes); err != nil { return fmt.Errorf("unmarshal field scopes: %w", err) } } case authrequest.FieldResponseTypes: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field response_types", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.ResponseTypes); err != nil { return fmt.Errorf("unmarshal field response_types: %w", err) } } case authrequest.FieldRedirectURI: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field redirect_uri", values[i]) } else if value.Valid { _m.RedirectURI = value.String } case authrequest.FieldNonce: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field nonce", values[i]) } else if value.Valid { _m.Nonce = value.String } case authrequest.FieldState: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field state", values[i]) } else if value.Valid { _m.State = value.String } case authrequest.FieldForceApprovalPrompt: if value, ok := values[i].(*sql.NullBool); !ok { return fmt.Errorf("unexpected type %T for field force_approval_prompt", values[i]) } else if value.Valid { _m.ForceApprovalPrompt = value.Bool } case authrequest.FieldLoggedIn: if value, ok := values[i].(*sql.NullBool); !ok { return fmt.Errorf("unexpected type %T for field logged_in", values[i]) } else if value.Valid { _m.LoggedIn = value.Bool } case authrequest.FieldClaimsUserID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_user_id", values[i]) } else if value.Valid { _m.ClaimsUserID = value.String } case authrequest.FieldClaimsUsername: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_username", values[i]) } else if value.Valid { _m.ClaimsUsername = value.String } case authrequest.FieldClaimsEmail: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_email", values[i]) } else if value.Valid { _m.ClaimsEmail = value.String } case authrequest.FieldClaimsEmailVerified: if value, ok := values[i].(*sql.NullBool); !ok { return fmt.Errorf("unexpected type %T for field claims_email_verified", values[i]) } else if value.Valid { _m.ClaimsEmailVerified = value.Bool } case authrequest.FieldClaimsGroups: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field claims_groups", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.ClaimsGroups); err != nil { return fmt.Errorf("unmarshal field claims_groups: %w", err) } } case authrequest.FieldClaimsPreferredUsername: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_preferred_username", values[i]) } else if value.Valid { _m.ClaimsPreferredUsername = value.String } case authrequest.FieldConnectorID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field connector_id", values[i]) } else if value.Valid { _m.ConnectorID = value.String } case authrequest.FieldConnectorData: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field connector_data", values[i]) } else if value != nil { _m.ConnectorData = value } case authrequest.FieldExpiry: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field expiry", values[i]) } else if value.Valid { _m.Expiry = value.Time } case authrequest.FieldCodeChallenge: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field code_challenge", values[i]) } else if value.Valid { _m.CodeChallenge = value.String } case authrequest.FieldCodeChallengeMethod: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field code_challenge_method", values[i]) } else if value.Valid { _m.CodeChallengeMethod = value.String } case authrequest.FieldHmacKey: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field hmac_key", values[i]) } else if value != nil { _m.HmacKey = *value } case authrequest.FieldMfaValidated: if value, ok := values[i].(*sql.NullBool); !ok { return fmt.Errorf("unexpected type %T for field mfa_validated", values[i]) } else if value.Valid { _m.MfaValidated = value.Bool } case authrequest.FieldPrompt: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field prompt", values[i]) } else if value.Valid { _m.Prompt = value.String } case authrequest.FieldMaxAge: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field max_age", values[i]) } else if value.Valid { _m.MaxAge = int(value.Int64) } case authrequest.FieldAuthTime: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field auth_time", values[i]) } else if value.Valid { _m.AuthTime = value.Time } default: _m.selectValues.Set(columns[i], values[i]) } } return nil } // Value returns the ent.Value that was dynamically selected and assigned to the AuthRequest. // This includes values selected through modifiers, order, etc. func (_m *AuthRequest) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } // Update returns a builder for updating this AuthRequest. // Note that you need to call AuthRequest.Unwrap() before calling this method if this AuthRequest // was returned from a transaction, and the transaction was committed or rolled back. func (_m *AuthRequest) Update() *AuthRequestUpdateOne { return NewAuthRequestClient(_m.config).UpdateOne(_m) } // Unwrap unwraps the AuthRequest entity that was returned from a transaction after it was closed, // so that all future queries will be executed through the driver which created the transaction. func (_m *AuthRequest) Unwrap() *AuthRequest { _tx, ok := _m.config.driver.(*txDriver) if !ok { panic("db: AuthRequest is not a transactional entity") } _m.config.driver = _tx.drv return _m } // String implements the fmt.Stringer. func (_m *AuthRequest) String() string { var builder strings.Builder builder.WriteString("AuthRequest(") builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) builder.WriteString("client_id=") builder.WriteString(_m.ClientID) builder.WriteString(", ") builder.WriteString("scopes=") builder.WriteString(fmt.Sprintf("%v", _m.Scopes)) builder.WriteString(", ") builder.WriteString("response_types=") builder.WriteString(fmt.Sprintf("%v", _m.ResponseTypes)) builder.WriteString(", ") builder.WriteString("redirect_uri=") builder.WriteString(_m.RedirectURI) builder.WriteString(", ") builder.WriteString("nonce=") builder.WriteString(_m.Nonce) builder.WriteString(", ") builder.WriteString("state=") builder.WriteString(_m.State) builder.WriteString(", ") builder.WriteString("force_approval_prompt=") builder.WriteString(fmt.Sprintf("%v", _m.ForceApprovalPrompt)) builder.WriteString(", ") builder.WriteString("logged_in=") builder.WriteString(fmt.Sprintf("%v", _m.LoggedIn)) builder.WriteString(", ") builder.WriteString("claims_user_id=") builder.WriteString(_m.ClaimsUserID) builder.WriteString(", ") builder.WriteString("claims_username=") builder.WriteString(_m.ClaimsUsername) builder.WriteString(", ") builder.WriteString("claims_email=") builder.WriteString(_m.ClaimsEmail) builder.WriteString(", ") builder.WriteString("claims_email_verified=") builder.WriteString(fmt.Sprintf("%v", _m.ClaimsEmailVerified)) builder.WriteString(", ") builder.WriteString("claims_groups=") builder.WriteString(fmt.Sprintf("%v", _m.ClaimsGroups)) builder.WriteString(", ") builder.WriteString("claims_preferred_username=") builder.WriteString(_m.ClaimsPreferredUsername) builder.WriteString(", ") builder.WriteString("connector_id=") builder.WriteString(_m.ConnectorID) builder.WriteString(", ") if v := _m.ConnectorData; v != nil { builder.WriteString("connector_data=") builder.WriteString(fmt.Sprintf("%v", *v)) } builder.WriteString(", ") builder.WriteString("expiry=") builder.WriteString(_m.Expiry.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("code_challenge=") builder.WriteString(_m.CodeChallenge) builder.WriteString(", ") builder.WriteString("code_challenge_method=") builder.WriteString(_m.CodeChallengeMethod) builder.WriteString(", ") builder.WriteString("hmac_key=") builder.WriteString(fmt.Sprintf("%v", _m.HmacKey)) builder.WriteString(", ") builder.WriteString("mfa_validated=") builder.WriteString(fmt.Sprintf("%v", _m.MfaValidated)) builder.WriteString(", ") builder.WriteString("prompt=") builder.WriteString(_m.Prompt) builder.WriteString(", ") builder.WriteString("max_age=") builder.WriteString(fmt.Sprintf("%v", _m.MaxAge)) builder.WriteString(", ") builder.WriteString("auth_time=") builder.WriteString(_m.AuthTime.Format(time.ANSIC)) builder.WriteByte(')') return builder.String() } // AuthRequests is a parsable slice of AuthRequest. type AuthRequests []*AuthRequest ================================================ FILE: storage/ent/db/authrequest_create.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/authrequest" ) // AuthRequestCreate is the builder for creating a AuthRequest entity. type AuthRequestCreate struct { config mutation *AuthRequestMutation hooks []Hook } // SetClientID sets the "client_id" field. func (_c *AuthRequestCreate) SetClientID(v string) *AuthRequestCreate { _c.mutation.SetClientID(v) return _c } // SetScopes sets the "scopes" field. func (_c *AuthRequestCreate) SetScopes(v []string) *AuthRequestCreate { _c.mutation.SetScopes(v) return _c } // SetResponseTypes sets the "response_types" field. func (_c *AuthRequestCreate) SetResponseTypes(v []string) *AuthRequestCreate { _c.mutation.SetResponseTypes(v) return _c } // SetRedirectURI sets the "redirect_uri" field. func (_c *AuthRequestCreate) SetRedirectURI(v string) *AuthRequestCreate { _c.mutation.SetRedirectURI(v) return _c } // SetNonce sets the "nonce" field. func (_c *AuthRequestCreate) SetNonce(v string) *AuthRequestCreate { _c.mutation.SetNonce(v) return _c } // SetState sets the "state" field. func (_c *AuthRequestCreate) SetState(v string) *AuthRequestCreate { _c.mutation.SetState(v) return _c } // SetForceApprovalPrompt sets the "force_approval_prompt" field. func (_c *AuthRequestCreate) SetForceApprovalPrompt(v bool) *AuthRequestCreate { _c.mutation.SetForceApprovalPrompt(v) return _c } // SetLoggedIn sets the "logged_in" field. func (_c *AuthRequestCreate) SetLoggedIn(v bool) *AuthRequestCreate { _c.mutation.SetLoggedIn(v) return _c } // SetClaimsUserID sets the "claims_user_id" field. func (_c *AuthRequestCreate) SetClaimsUserID(v string) *AuthRequestCreate { _c.mutation.SetClaimsUserID(v) return _c } // SetClaimsUsername sets the "claims_username" field. func (_c *AuthRequestCreate) SetClaimsUsername(v string) *AuthRequestCreate { _c.mutation.SetClaimsUsername(v) return _c } // SetClaimsEmail sets the "claims_email" field. func (_c *AuthRequestCreate) SetClaimsEmail(v string) *AuthRequestCreate { _c.mutation.SetClaimsEmail(v) return _c } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (_c *AuthRequestCreate) SetClaimsEmailVerified(v bool) *AuthRequestCreate { _c.mutation.SetClaimsEmailVerified(v) return _c } // SetClaimsGroups sets the "claims_groups" field. func (_c *AuthRequestCreate) SetClaimsGroups(v []string) *AuthRequestCreate { _c.mutation.SetClaimsGroups(v) return _c } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (_c *AuthRequestCreate) SetClaimsPreferredUsername(v string) *AuthRequestCreate { _c.mutation.SetClaimsPreferredUsername(v) return _c } // SetNillableClaimsPreferredUsername sets the "claims_preferred_username" field if the given value is not nil. func (_c *AuthRequestCreate) SetNillableClaimsPreferredUsername(v *string) *AuthRequestCreate { if v != nil { _c.SetClaimsPreferredUsername(*v) } return _c } // SetConnectorID sets the "connector_id" field. func (_c *AuthRequestCreate) SetConnectorID(v string) *AuthRequestCreate { _c.mutation.SetConnectorID(v) return _c } // SetConnectorData sets the "connector_data" field. func (_c *AuthRequestCreate) SetConnectorData(v []byte) *AuthRequestCreate { _c.mutation.SetConnectorData(v) return _c } // SetExpiry sets the "expiry" field. func (_c *AuthRequestCreate) SetExpiry(v time.Time) *AuthRequestCreate { _c.mutation.SetExpiry(v) return _c } // SetCodeChallenge sets the "code_challenge" field. func (_c *AuthRequestCreate) SetCodeChallenge(v string) *AuthRequestCreate { _c.mutation.SetCodeChallenge(v) return _c } // SetNillableCodeChallenge sets the "code_challenge" field if the given value is not nil. func (_c *AuthRequestCreate) SetNillableCodeChallenge(v *string) *AuthRequestCreate { if v != nil { _c.SetCodeChallenge(*v) } return _c } // SetCodeChallengeMethod sets the "code_challenge_method" field. func (_c *AuthRequestCreate) SetCodeChallengeMethod(v string) *AuthRequestCreate { _c.mutation.SetCodeChallengeMethod(v) return _c } // SetNillableCodeChallengeMethod sets the "code_challenge_method" field if the given value is not nil. func (_c *AuthRequestCreate) SetNillableCodeChallengeMethod(v *string) *AuthRequestCreate { if v != nil { _c.SetCodeChallengeMethod(*v) } return _c } // SetHmacKey sets the "hmac_key" field. func (_c *AuthRequestCreate) SetHmacKey(v []byte) *AuthRequestCreate { _c.mutation.SetHmacKey(v) return _c } // SetMfaValidated sets the "mfa_validated" field. func (_c *AuthRequestCreate) SetMfaValidated(v bool) *AuthRequestCreate { _c.mutation.SetMfaValidated(v) return _c } // SetNillableMfaValidated sets the "mfa_validated" field if the given value is not nil. func (_c *AuthRequestCreate) SetNillableMfaValidated(v *bool) *AuthRequestCreate { if v != nil { _c.SetMfaValidated(*v) } return _c } // SetPrompt sets the "prompt" field. func (_c *AuthRequestCreate) SetPrompt(v string) *AuthRequestCreate { _c.mutation.SetPrompt(v) return _c } // SetNillablePrompt sets the "prompt" field if the given value is not nil. func (_c *AuthRequestCreate) SetNillablePrompt(v *string) *AuthRequestCreate { if v != nil { _c.SetPrompt(*v) } return _c } // SetMaxAge sets the "max_age" field. func (_c *AuthRequestCreate) SetMaxAge(v int) *AuthRequestCreate { _c.mutation.SetMaxAge(v) return _c } // SetNillableMaxAge sets the "max_age" field if the given value is not nil. func (_c *AuthRequestCreate) SetNillableMaxAge(v *int) *AuthRequestCreate { if v != nil { _c.SetMaxAge(*v) } return _c } // SetAuthTime sets the "auth_time" field. func (_c *AuthRequestCreate) SetAuthTime(v time.Time) *AuthRequestCreate { _c.mutation.SetAuthTime(v) return _c } // SetNillableAuthTime sets the "auth_time" field if the given value is not nil. func (_c *AuthRequestCreate) SetNillableAuthTime(v *time.Time) *AuthRequestCreate { if v != nil { _c.SetAuthTime(*v) } return _c } // SetID sets the "id" field. func (_c *AuthRequestCreate) SetID(v string) *AuthRequestCreate { _c.mutation.SetID(v) return _c } // Mutation returns the AuthRequestMutation object of the builder. func (_c *AuthRequestCreate) Mutation() *AuthRequestMutation { return _c.mutation } // Save creates the AuthRequest in the database. func (_c *AuthRequestCreate) Save(ctx context.Context) (*AuthRequest, error) { _c.defaults() return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) } // SaveX calls Save and panics if Save returns an error. func (_c *AuthRequestCreate) SaveX(ctx context.Context) *AuthRequest { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *AuthRequestCreate) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *AuthRequestCreate) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } // defaults sets the default values of the builder before save. func (_c *AuthRequestCreate) defaults() { if _, ok := _c.mutation.ClaimsPreferredUsername(); !ok { v := authrequest.DefaultClaimsPreferredUsername _c.mutation.SetClaimsPreferredUsername(v) } if _, ok := _c.mutation.CodeChallenge(); !ok { v := authrequest.DefaultCodeChallenge _c.mutation.SetCodeChallenge(v) } if _, ok := _c.mutation.CodeChallengeMethod(); !ok { v := authrequest.DefaultCodeChallengeMethod _c.mutation.SetCodeChallengeMethod(v) } if _, ok := _c.mutation.MfaValidated(); !ok { v := authrequest.DefaultMfaValidated _c.mutation.SetMfaValidated(v) } if _, ok := _c.mutation.Prompt(); !ok { v := authrequest.DefaultPrompt _c.mutation.SetPrompt(v) } if _, ok := _c.mutation.MaxAge(); !ok { v := authrequest.DefaultMaxAge _c.mutation.SetMaxAge(v) } } // check runs all checks and user-defined validators on the builder. func (_c *AuthRequestCreate) check() error { if _, ok := _c.mutation.ClientID(); !ok { return &ValidationError{Name: "client_id", err: errors.New(`db: missing required field "AuthRequest.client_id"`)} } if _, ok := _c.mutation.RedirectURI(); !ok { return &ValidationError{Name: "redirect_uri", err: errors.New(`db: missing required field "AuthRequest.redirect_uri"`)} } if _, ok := _c.mutation.Nonce(); !ok { return &ValidationError{Name: "nonce", err: errors.New(`db: missing required field "AuthRequest.nonce"`)} } if _, ok := _c.mutation.State(); !ok { return &ValidationError{Name: "state", err: errors.New(`db: missing required field "AuthRequest.state"`)} } if _, ok := _c.mutation.ForceApprovalPrompt(); !ok { return &ValidationError{Name: "force_approval_prompt", err: errors.New(`db: missing required field "AuthRequest.force_approval_prompt"`)} } if _, ok := _c.mutation.LoggedIn(); !ok { return &ValidationError{Name: "logged_in", err: errors.New(`db: missing required field "AuthRequest.logged_in"`)} } if _, ok := _c.mutation.ClaimsUserID(); !ok { return &ValidationError{Name: "claims_user_id", err: errors.New(`db: missing required field "AuthRequest.claims_user_id"`)} } if _, ok := _c.mutation.ClaimsUsername(); !ok { return &ValidationError{Name: "claims_username", err: errors.New(`db: missing required field "AuthRequest.claims_username"`)} } if _, ok := _c.mutation.ClaimsEmail(); !ok { return &ValidationError{Name: "claims_email", err: errors.New(`db: missing required field "AuthRequest.claims_email"`)} } if _, ok := _c.mutation.ClaimsEmailVerified(); !ok { return &ValidationError{Name: "claims_email_verified", err: errors.New(`db: missing required field "AuthRequest.claims_email_verified"`)} } if _, ok := _c.mutation.ClaimsPreferredUsername(); !ok { return &ValidationError{Name: "claims_preferred_username", err: errors.New(`db: missing required field "AuthRequest.claims_preferred_username"`)} } if _, ok := _c.mutation.ConnectorID(); !ok { return &ValidationError{Name: "connector_id", err: errors.New(`db: missing required field "AuthRequest.connector_id"`)} } if _, ok := _c.mutation.Expiry(); !ok { return &ValidationError{Name: "expiry", err: errors.New(`db: missing required field "AuthRequest.expiry"`)} } if _, ok := _c.mutation.CodeChallenge(); !ok { return &ValidationError{Name: "code_challenge", err: errors.New(`db: missing required field "AuthRequest.code_challenge"`)} } if _, ok := _c.mutation.CodeChallengeMethod(); !ok { return &ValidationError{Name: "code_challenge_method", err: errors.New(`db: missing required field "AuthRequest.code_challenge_method"`)} } if _, ok := _c.mutation.HmacKey(); !ok { return &ValidationError{Name: "hmac_key", err: errors.New(`db: missing required field "AuthRequest.hmac_key"`)} } if _, ok := _c.mutation.MfaValidated(); !ok { return &ValidationError{Name: "mfa_validated", err: errors.New(`db: missing required field "AuthRequest.mfa_validated"`)} } if _, ok := _c.mutation.Prompt(); !ok { return &ValidationError{Name: "prompt", err: errors.New(`db: missing required field "AuthRequest.prompt"`)} } if _, ok := _c.mutation.MaxAge(); !ok { return &ValidationError{Name: "max_age", err: errors.New(`db: missing required field "AuthRequest.max_age"`)} } if v, ok := _c.mutation.ID(); ok { if err := authrequest.IDValidator(v); err != nil { return &ValidationError{Name: "id", err: fmt.Errorf(`db: validator failed for field "AuthRequest.id": %w`, err)} } } return nil } func (_c *AuthRequestCreate) sqlSave(ctx context.Context) (*AuthRequest, error) { if err := _c.check(); err != nil { return nil, err } _node, _spec := _c.createSpec() if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } if _spec.ID.Value != nil { if id, ok := _spec.ID.Value.(string); ok { _node.ID = id } else { return nil, fmt.Errorf("unexpected AuthRequest.ID type: %T", _spec.ID.Value) } } _c.mutation.id = &_node.ID _c.mutation.done = true return _node, nil } func (_c *AuthRequestCreate) createSpec() (*AuthRequest, *sqlgraph.CreateSpec) { var ( _node = &AuthRequest{config: _c.config} _spec = sqlgraph.NewCreateSpec(authrequest.Table, sqlgraph.NewFieldSpec(authrequest.FieldID, field.TypeString)) ) if id, ok := _c.mutation.ID(); ok { _node.ID = id _spec.ID.Value = id } if value, ok := _c.mutation.ClientID(); ok { _spec.SetField(authrequest.FieldClientID, field.TypeString, value) _node.ClientID = value } if value, ok := _c.mutation.Scopes(); ok { _spec.SetField(authrequest.FieldScopes, field.TypeJSON, value) _node.Scopes = value } if value, ok := _c.mutation.ResponseTypes(); ok { _spec.SetField(authrequest.FieldResponseTypes, field.TypeJSON, value) _node.ResponseTypes = value } if value, ok := _c.mutation.RedirectURI(); ok { _spec.SetField(authrequest.FieldRedirectURI, field.TypeString, value) _node.RedirectURI = value } if value, ok := _c.mutation.Nonce(); ok { _spec.SetField(authrequest.FieldNonce, field.TypeString, value) _node.Nonce = value } if value, ok := _c.mutation.State(); ok { _spec.SetField(authrequest.FieldState, field.TypeString, value) _node.State = value } if value, ok := _c.mutation.ForceApprovalPrompt(); ok { _spec.SetField(authrequest.FieldForceApprovalPrompt, field.TypeBool, value) _node.ForceApprovalPrompt = value } if value, ok := _c.mutation.LoggedIn(); ok { _spec.SetField(authrequest.FieldLoggedIn, field.TypeBool, value) _node.LoggedIn = value } if value, ok := _c.mutation.ClaimsUserID(); ok { _spec.SetField(authrequest.FieldClaimsUserID, field.TypeString, value) _node.ClaimsUserID = value } if value, ok := _c.mutation.ClaimsUsername(); ok { _spec.SetField(authrequest.FieldClaimsUsername, field.TypeString, value) _node.ClaimsUsername = value } if value, ok := _c.mutation.ClaimsEmail(); ok { _spec.SetField(authrequest.FieldClaimsEmail, field.TypeString, value) _node.ClaimsEmail = value } if value, ok := _c.mutation.ClaimsEmailVerified(); ok { _spec.SetField(authrequest.FieldClaimsEmailVerified, field.TypeBool, value) _node.ClaimsEmailVerified = value } if value, ok := _c.mutation.ClaimsGroups(); ok { _spec.SetField(authrequest.FieldClaimsGroups, field.TypeJSON, value) _node.ClaimsGroups = value } if value, ok := _c.mutation.ClaimsPreferredUsername(); ok { _spec.SetField(authrequest.FieldClaimsPreferredUsername, field.TypeString, value) _node.ClaimsPreferredUsername = value } if value, ok := _c.mutation.ConnectorID(); ok { _spec.SetField(authrequest.FieldConnectorID, field.TypeString, value) _node.ConnectorID = value } if value, ok := _c.mutation.ConnectorData(); ok { _spec.SetField(authrequest.FieldConnectorData, field.TypeBytes, value) _node.ConnectorData = &value } if value, ok := _c.mutation.Expiry(); ok { _spec.SetField(authrequest.FieldExpiry, field.TypeTime, value) _node.Expiry = value } if value, ok := _c.mutation.CodeChallenge(); ok { _spec.SetField(authrequest.FieldCodeChallenge, field.TypeString, value) _node.CodeChallenge = value } if value, ok := _c.mutation.CodeChallengeMethod(); ok { _spec.SetField(authrequest.FieldCodeChallengeMethod, field.TypeString, value) _node.CodeChallengeMethod = value } if value, ok := _c.mutation.HmacKey(); ok { _spec.SetField(authrequest.FieldHmacKey, field.TypeBytes, value) _node.HmacKey = value } if value, ok := _c.mutation.MfaValidated(); ok { _spec.SetField(authrequest.FieldMfaValidated, field.TypeBool, value) _node.MfaValidated = value } if value, ok := _c.mutation.Prompt(); ok { _spec.SetField(authrequest.FieldPrompt, field.TypeString, value) _node.Prompt = value } if value, ok := _c.mutation.MaxAge(); ok { _spec.SetField(authrequest.FieldMaxAge, field.TypeInt, value) _node.MaxAge = value } if value, ok := _c.mutation.AuthTime(); ok { _spec.SetField(authrequest.FieldAuthTime, field.TypeTime, value) _node.AuthTime = value } return _node, _spec } // AuthRequestCreateBulk is the builder for creating many AuthRequest entities in bulk. type AuthRequestCreateBulk struct { config err error builders []*AuthRequestCreate } // Save creates the AuthRequest entities in the database. func (_c *AuthRequestCreateBulk) Save(ctx context.Context) ([]*AuthRequest, error) { if _c.err != nil { return nil, _c.err } specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) nodes := make([]*AuthRequest, len(_c.builders)) mutators := make([]Mutator, len(_c.builders)) for i := range _c.builders { func(i int, root context.Context) { builder := _c.builders[i] builder.defaults() var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*AuthRequestMutation) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } if err := builder.check(); err != nil { return nil, err } builder.mutation = mutation var err error nodes[i], specs[i] = builder.createSpec() if i < len(mutators)-1 { _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) } else { spec := &sqlgraph.BatchCreateSpec{Nodes: specs} // Invoke the actual operation on the latest mutation in the chain. if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } } } if err != nil { return nil, err } mutation.id = &nodes[i].ID mutation.done = true return nodes[i], nil }) for i := len(builder.hooks) - 1; i >= 0; i-- { mut = builder.hooks[i](mut) } mutators[i] = mut }(i, ctx) } if len(mutators) > 0 { if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { return nil, err } } return nodes, nil } // SaveX is like Save, but panics if an error occurs. func (_c *AuthRequestCreateBulk) SaveX(ctx context.Context) []*AuthRequest { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *AuthRequestCreateBulk) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *AuthRequestCreateBulk) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/authrequest_delete.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/authrequest" "github.com/dexidp/dex/storage/ent/db/predicate" ) // AuthRequestDelete is the builder for deleting a AuthRequest entity. type AuthRequestDelete struct { config hooks []Hook mutation *AuthRequestMutation } // Where appends a list predicates to the AuthRequestDelete builder. func (_d *AuthRequestDelete) Where(ps ...predicate.AuthRequest) *AuthRequestDelete { _d.mutation.Where(ps...) return _d } // Exec executes the deletion query and returns how many vertices were deleted. func (_d *AuthRequestDelete) Exec(ctx context.Context) (int, error) { return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) } // ExecX is like Exec, but panics if an error occurs. func (_d *AuthRequestDelete) ExecX(ctx context.Context) int { n, err := _d.Exec(ctx) if err != nil { panic(err) } return n } func (_d *AuthRequestDelete) sqlExec(ctx context.Context) (int, error) { _spec := sqlgraph.NewDeleteSpec(authrequest.Table, sqlgraph.NewFieldSpec(authrequest.FieldID, field.TypeString)) if ps := _d.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) if err != nil && sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } _d.mutation.done = true return affected, err } // AuthRequestDeleteOne is the builder for deleting a single AuthRequest entity. type AuthRequestDeleteOne struct { _d *AuthRequestDelete } // Where appends a list predicates to the AuthRequestDelete builder. func (_d *AuthRequestDeleteOne) Where(ps ...predicate.AuthRequest) *AuthRequestDeleteOne { _d._d.mutation.Where(ps...) return _d } // Exec executes the deletion query. func (_d *AuthRequestDeleteOne) Exec(ctx context.Context) error { n, err := _d._d.Exec(ctx) switch { case err != nil: return err case n == 0: return &NotFoundError{authrequest.Label} default: return nil } } // ExecX is like Exec, but panics if an error occurs. func (_d *AuthRequestDeleteOne) ExecX(ctx context.Context) { if err := _d.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/authrequest_query.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "fmt" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/authrequest" "github.com/dexidp/dex/storage/ent/db/predicate" ) // AuthRequestQuery is the builder for querying AuthRequest entities. type AuthRequestQuery struct { config ctx *QueryContext order []authrequest.OrderOption inters []Interceptor predicates []predicate.AuthRequest // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) } // Where adds a new predicate for the AuthRequestQuery builder. func (_q *AuthRequestQuery) Where(ps ...predicate.AuthRequest) *AuthRequestQuery { _q.predicates = append(_q.predicates, ps...) return _q } // Limit the number of records to be returned by this query. func (_q *AuthRequestQuery) Limit(limit int) *AuthRequestQuery { _q.ctx.Limit = &limit return _q } // Offset to start from. func (_q *AuthRequestQuery) Offset(offset int) *AuthRequestQuery { _q.ctx.Offset = &offset return _q } // Unique configures the query builder to filter duplicate records on query. // By default, unique is set to true, and can be disabled using this method. func (_q *AuthRequestQuery) Unique(unique bool) *AuthRequestQuery { _q.ctx.Unique = &unique return _q } // Order specifies how the records should be ordered. func (_q *AuthRequestQuery) Order(o ...authrequest.OrderOption) *AuthRequestQuery { _q.order = append(_q.order, o...) return _q } // First returns the first AuthRequest entity from the query. // Returns a *NotFoundError when no AuthRequest was found. func (_q *AuthRequestQuery) First(ctx context.Context) (*AuthRequest, error) { nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) if err != nil { return nil, err } if len(nodes) == 0 { return nil, &NotFoundError{authrequest.Label} } return nodes[0], nil } // FirstX is like First, but panics if an error occurs. func (_q *AuthRequestQuery) FirstX(ctx context.Context) *AuthRequest { node, err := _q.First(ctx) if err != nil && !IsNotFound(err) { panic(err) } return node } // FirstID returns the first AuthRequest ID from the query. // Returns a *NotFoundError when no AuthRequest ID was found. func (_q *AuthRequestQuery) FirstID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { return } if len(ids) == 0 { err = &NotFoundError{authrequest.Label} return } return ids[0], nil } // FirstIDX is like FirstID, but panics if an error occurs. func (_q *AuthRequestQuery) FirstIDX(ctx context.Context) string { id, err := _q.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) } return id } // Only returns a single AuthRequest entity found by the query, ensuring it only returns one. // Returns a *NotSingularError when more than one AuthRequest entity is found. // Returns a *NotFoundError when no AuthRequest entities are found. func (_q *AuthRequestQuery) Only(ctx context.Context) (*AuthRequest, error) { nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) if err != nil { return nil, err } switch len(nodes) { case 1: return nodes[0], nil case 0: return nil, &NotFoundError{authrequest.Label} default: return nil, &NotSingularError{authrequest.Label} } } // OnlyX is like Only, but panics if an error occurs. func (_q *AuthRequestQuery) OnlyX(ctx context.Context) *AuthRequest { node, err := _q.Only(ctx) if err != nil { panic(err) } return node } // OnlyID is like Only, but returns the only AuthRequest ID in the query. // Returns a *NotSingularError when more than one AuthRequest ID is found. // Returns a *NotFoundError when no entities are found. func (_q *AuthRequestQuery) OnlyID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { return } switch len(ids) { case 1: id = ids[0] case 0: err = &NotFoundError{authrequest.Label} default: err = &NotSingularError{authrequest.Label} } return } // OnlyIDX is like OnlyID, but panics if an error occurs. func (_q *AuthRequestQuery) OnlyIDX(ctx context.Context) string { id, err := _q.OnlyID(ctx) if err != nil { panic(err) } return id } // All executes the query and returns a list of AuthRequests. func (_q *AuthRequestQuery) All(ctx context.Context) ([]*AuthRequest, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) if err := _q.prepareQuery(ctx); err != nil { return nil, err } qr := querierAll[[]*AuthRequest, *AuthRequestQuery]() return withInterceptors[[]*AuthRequest](ctx, _q, qr, _q.inters) } // AllX is like All, but panics if an error occurs. func (_q *AuthRequestQuery) AllX(ctx context.Context) []*AuthRequest { nodes, err := _q.All(ctx) if err != nil { panic(err) } return nodes } // IDs executes the query and returns a list of AuthRequest IDs. func (_q *AuthRequestQuery) IDs(ctx context.Context) (ids []string, err error) { if _q.ctx.Unique == nil && _q.path != nil { _q.Unique(true) } ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) if err = _q.Select(authrequest.FieldID).Scan(ctx, &ids); err != nil { return nil, err } return ids, nil } // IDsX is like IDs, but panics if an error occurs. func (_q *AuthRequestQuery) IDsX(ctx context.Context) []string { ids, err := _q.IDs(ctx) if err != nil { panic(err) } return ids } // Count returns the count of the given query. func (_q *AuthRequestQuery) Count(ctx context.Context) (int, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) if err := _q.prepareQuery(ctx); err != nil { return 0, err } return withInterceptors[int](ctx, _q, querierCount[*AuthRequestQuery](), _q.inters) } // CountX is like Count, but panics if an error occurs. func (_q *AuthRequestQuery) CountX(ctx context.Context) int { count, err := _q.Count(ctx) if err != nil { panic(err) } return count } // Exist returns true if the query has elements in the graph. func (_q *AuthRequestQuery) Exist(ctx context.Context) (bool, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) switch _, err := _q.FirstID(ctx); { case IsNotFound(err): return false, nil case err != nil: return false, fmt.Errorf("db: check existence: %w", err) default: return true, nil } } // ExistX is like Exist, but panics if an error occurs. func (_q *AuthRequestQuery) ExistX(ctx context.Context) bool { exist, err := _q.Exist(ctx) if err != nil { panic(err) } return exist } // Clone returns a duplicate of the AuthRequestQuery builder, including all associated steps. It can be // used to prepare common query builders and use them differently after the clone is made. func (_q *AuthRequestQuery) Clone() *AuthRequestQuery { if _q == nil { return nil } return &AuthRequestQuery{ config: _q.config, ctx: _q.ctx.Clone(), order: append([]authrequest.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.AuthRequest{}, _q.predicates...), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // // Example: // // var v []struct { // ClientID string `json:"client_id,omitempty"` // Count int `json:"count,omitempty"` // } // // client.AuthRequest.Query(). // GroupBy(authrequest.FieldClientID). // Aggregate(db.Count()). // Scan(ctx, &v) func (_q *AuthRequestQuery) GroupBy(field string, fields ...string) *AuthRequestGroupBy { _q.ctx.Fields = append([]string{field}, fields...) grbuild := &AuthRequestGroupBy{build: _q} grbuild.flds = &_q.ctx.Fields grbuild.label = authrequest.Label grbuild.scan = grbuild.Scan return grbuild } // Select allows the selection one or more fields/columns for the given query, // instead of selecting all fields in the entity. // // Example: // // var v []struct { // ClientID string `json:"client_id,omitempty"` // } // // client.AuthRequest.Query(). // Select(authrequest.FieldClientID). // Scan(ctx, &v) func (_q *AuthRequestQuery) Select(fields ...string) *AuthRequestSelect { _q.ctx.Fields = append(_q.ctx.Fields, fields...) sbuild := &AuthRequestSelect{AuthRequestQuery: _q} sbuild.label = authrequest.Label sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan return sbuild } // Aggregate returns a AuthRequestSelect configured with the given aggregations. func (_q *AuthRequestQuery) Aggregate(fns ...AggregateFunc) *AuthRequestSelect { return _q.Select().Aggregate(fns...) } func (_q *AuthRequestQuery) prepareQuery(ctx context.Context) error { for _, inter := range _q.inters { if inter == nil { return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") } if trv, ok := inter.(Traverser); ok { if err := trv.Traverse(ctx, _q); err != nil { return err } } } for _, f := range _q.ctx.Fields { if !authrequest.ValidColumn(f) { return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } } if _q.path != nil { prev, err := _q.path(ctx) if err != nil { return err } _q.sql = prev } return nil } func (_q *AuthRequestQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AuthRequest, error) { var ( nodes = []*AuthRequest{} _spec = _q.querySpec() ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*AuthRequest).scanValues(nil, columns) } _spec.Assign = func(columns []string, values []any) error { node := &AuthRequest{config: _q.config} nodes = append(nodes, node) return node.assignValues(columns, values) } for i := range hooks { hooks[i](ctx, _spec) } if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { return nil, err } if len(nodes) == 0 { return nodes, nil } return nodes, nil } func (_q *AuthRequestQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields if len(_q.ctx.Fields) > 0 { _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique } return sqlgraph.CountNodes(ctx, _q.driver, _spec) } func (_q *AuthRequestQuery) querySpec() *sqlgraph.QuerySpec { _spec := sqlgraph.NewQuerySpec(authrequest.Table, authrequest.Columns, sqlgraph.NewFieldSpec(authrequest.FieldID, field.TypeString)) _spec.From = _q.sql if unique := _q.ctx.Unique; unique != nil { _spec.Unique = *unique } else if _q.path != nil { _spec.Unique = true } if fields := _q.ctx.Fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, authrequest.FieldID) for i := range fields { if fields[i] != authrequest.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } } if ps := _q.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if limit := _q.ctx.Limit; limit != nil { _spec.Limit = *limit } if offset := _q.ctx.Offset; offset != nil { _spec.Offset = *offset } if ps := _q.order; len(ps) > 0 { _spec.Order = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } return _spec } func (_q *AuthRequestQuery) sqlQuery(ctx context.Context) *sql.Selector { builder := sql.Dialect(_q.driver.Dialect()) t1 := builder.Table(authrequest.Table) columns := _q.ctx.Fields if len(columns) == 0 { columns = authrequest.Columns } selector := builder.Select(t1.Columns(columns...)...).From(t1) if _q.sql != nil { selector = _q.sql selector.Select(selector.Columns(columns...)...) } if _q.ctx.Unique != nil && *_q.ctx.Unique { selector.Distinct() } for _, p := range _q.predicates { p(selector) } for _, p := range _q.order { p(selector) } if offset := _q.ctx.Offset; offset != nil { // limit is mandatory for offset clause. We start // with default value, and override it below if needed. selector.Offset(*offset).Limit(math.MaxInt32) } if limit := _q.ctx.Limit; limit != nil { selector.Limit(*limit) } return selector } // AuthRequestGroupBy is the group-by builder for AuthRequest entities. type AuthRequestGroupBy struct { selector build *AuthRequestQuery } // Aggregate adds the given aggregation functions to the group-by query. func (_g *AuthRequestGroupBy) Aggregate(fns ...AggregateFunc) *AuthRequestGroupBy { _g.fns = append(_g.fns, fns...) return _g } // Scan applies the selector query and scans the result into the given value. func (_g *AuthRequestGroupBy) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) if err := _g.build.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*AuthRequestQuery, *AuthRequestGroupBy](ctx, _g.build, _g, _g.build.inters, v) } func (_g *AuthRequestGroupBy) sqlScan(ctx context.Context, root *AuthRequestQuery, v any) error { selector := root.sqlQuery(ctx).Select() aggregation := make([]string, 0, len(_g.fns)) for _, fn := range _g.fns { aggregation = append(aggregation, fn(selector)) } if len(selector.SelectedColumns()) == 0 { columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) for _, f := range *_g.flds { columns = append(columns, selector.C(f)) } columns = append(columns, aggregation...) selector.Select(columns...) } selector.GroupBy(selector.Columns(*_g.flds...)...) if err := selector.Err(); err != nil { return err } rows := &sql.Rows{} query, args := selector.Query() if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } // AuthRequestSelect is the builder for selecting fields of AuthRequest entities. type AuthRequestSelect struct { *AuthRequestQuery selector } // Aggregate adds the given aggregation functions to the selector query. func (_s *AuthRequestSelect) Aggregate(fns ...AggregateFunc) *AuthRequestSelect { _s.fns = append(_s.fns, fns...) return _s } // Scan applies the selector query and scans the result into the given value. func (_s *AuthRequestSelect) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) if err := _s.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*AuthRequestQuery, *AuthRequestSelect](ctx, _s.AuthRequestQuery, _s, _s.inters, v) } func (_s *AuthRequestSelect) sqlScan(ctx context.Context, root *AuthRequestQuery, v any) error { selector := root.sqlQuery(ctx) aggregation := make([]string, 0, len(_s.fns)) for _, fn := range _s.fns { aggregation = append(aggregation, fn(selector)) } switch n := len(*_s.selector.flds); { case n == 0 && len(aggregation) > 0: selector.Select(aggregation...) case n != 0 && len(aggregation) > 0: selector.AppendSelect(aggregation...) } rows := &sql.Rows{} query, args := selector.Query() if err := _s.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } ================================================ FILE: storage/ent/db/authrequest_update.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/authrequest" "github.com/dexidp/dex/storage/ent/db/predicate" ) // AuthRequestUpdate is the builder for updating AuthRequest entities. type AuthRequestUpdate struct { config hooks []Hook mutation *AuthRequestMutation } // Where appends a list predicates to the AuthRequestUpdate builder. func (_u *AuthRequestUpdate) Where(ps ...predicate.AuthRequest) *AuthRequestUpdate { _u.mutation.Where(ps...) return _u } // SetClientID sets the "client_id" field. func (_u *AuthRequestUpdate) SetClientID(v string) *AuthRequestUpdate { _u.mutation.SetClientID(v) return _u } // SetNillableClientID sets the "client_id" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableClientID(v *string) *AuthRequestUpdate { if v != nil { _u.SetClientID(*v) } return _u } // SetScopes sets the "scopes" field. func (_u *AuthRequestUpdate) SetScopes(v []string) *AuthRequestUpdate { _u.mutation.SetScopes(v) return _u } // AppendScopes appends value to the "scopes" field. func (_u *AuthRequestUpdate) AppendScopes(v []string) *AuthRequestUpdate { _u.mutation.AppendScopes(v) return _u } // ClearScopes clears the value of the "scopes" field. func (_u *AuthRequestUpdate) ClearScopes() *AuthRequestUpdate { _u.mutation.ClearScopes() return _u } // SetResponseTypes sets the "response_types" field. func (_u *AuthRequestUpdate) SetResponseTypes(v []string) *AuthRequestUpdate { _u.mutation.SetResponseTypes(v) return _u } // AppendResponseTypes appends value to the "response_types" field. func (_u *AuthRequestUpdate) AppendResponseTypes(v []string) *AuthRequestUpdate { _u.mutation.AppendResponseTypes(v) return _u } // ClearResponseTypes clears the value of the "response_types" field. func (_u *AuthRequestUpdate) ClearResponseTypes() *AuthRequestUpdate { _u.mutation.ClearResponseTypes() return _u } // SetRedirectURI sets the "redirect_uri" field. func (_u *AuthRequestUpdate) SetRedirectURI(v string) *AuthRequestUpdate { _u.mutation.SetRedirectURI(v) return _u } // SetNillableRedirectURI sets the "redirect_uri" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableRedirectURI(v *string) *AuthRequestUpdate { if v != nil { _u.SetRedirectURI(*v) } return _u } // SetNonce sets the "nonce" field. func (_u *AuthRequestUpdate) SetNonce(v string) *AuthRequestUpdate { _u.mutation.SetNonce(v) return _u } // SetNillableNonce sets the "nonce" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableNonce(v *string) *AuthRequestUpdate { if v != nil { _u.SetNonce(*v) } return _u } // SetState sets the "state" field. func (_u *AuthRequestUpdate) SetState(v string) *AuthRequestUpdate { _u.mutation.SetState(v) return _u } // SetNillableState sets the "state" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableState(v *string) *AuthRequestUpdate { if v != nil { _u.SetState(*v) } return _u } // SetForceApprovalPrompt sets the "force_approval_prompt" field. func (_u *AuthRequestUpdate) SetForceApprovalPrompt(v bool) *AuthRequestUpdate { _u.mutation.SetForceApprovalPrompt(v) return _u } // SetNillableForceApprovalPrompt sets the "force_approval_prompt" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableForceApprovalPrompt(v *bool) *AuthRequestUpdate { if v != nil { _u.SetForceApprovalPrompt(*v) } return _u } // SetLoggedIn sets the "logged_in" field. func (_u *AuthRequestUpdate) SetLoggedIn(v bool) *AuthRequestUpdate { _u.mutation.SetLoggedIn(v) return _u } // SetNillableLoggedIn sets the "logged_in" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableLoggedIn(v *bool) *AuthRequestUpdate { if v != nil { _u.SetLoggedIn(*v) } return _u } // SetClaimsUserID sets the "claims_user_id" field. func (_u *AuthRequestUpdate) SetClaimsUserID(v string) *AuthRequestUpdate { _u.mutation.SetClaimsUserID(v) return _u } // SetNillableClaimsUserID sets the "claims_user_id" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableClaimsUserID(v *string) *AuthRequestUpdate { if v != nil { _u.SetClaimsUserID(*v) } return _u } // SetClaimsUsername sets the "claims_username" field. func (_u *AuthRequestUpdate) SetClaimsUsername(v string) *AuthRequestUpdate { _u.mutation.SetClaimsUsername(v) return _u } // SetNillableClaimsUsername sets the "claims_username" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableClaimsUsername(v *string) *AuthRequestUpdate { if v != nil { _u.SetClaimsUsername(*v) } return _u } // SetClaimsEmail sets the "claims_email" field. func (_u *AuthRequestUpdate) SetClaimsEmail(v string) *AuthRequestUpdate { _u.mutation.SetClaimsEmail(v) return _u } // SetNillableClaimsEmail sets the "claims_email" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableClaimsEmail(v *string) *AuthRequestUpdate { if v != nil { _u.SetClaimsEmail(*v) } return _u } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (_u *AuthRequestUpdate) SetClaimsEmailVerified(v bool) *AuthRequestUpdate { _u.mutation.SetClaimsEmailVerified(v) return _u } // SetNillableClaimsEmailVerified sets the "claims_email_verified" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableClaimsEmailVerified(v *bool) *AuthRequestUpdate { if v != nil { _u.SetClaimsEmailVerified(*v) } return _u } // SetClaimsGroups sets the "claims_groups" field. func (_u *AuthRequestUpdate) SetClaimsGroups(v []string) *AuthRequestUpdate { _u.mutation.SetClaimsGroups(v) return _u } // AppendClaimsGroups appends value to the "claims_groups" field. func (_u *AuthRequestUpdate) AppendClaimsGroups(v []string) *AuthRequestUpdate { _u.mutation.AppendClaimsGroups(v) return _u } // ClearClaimsGroups clears the value of the "claims_groups" field. func (_u *AuthRequestUpdate) ClearClaimsGroups() *AuthRequestUpdate { _u.mutation.ClearClaimsGroups() return _u } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (_u *AuthRequestUpdate) SetClaimsPreferredUsername(v string) *AuthRequestUpdate { _u.mutation.SetClaimsPreferredUsername(v) return _u } // SetNillableClaimsPreferredUsername sets the "claims_preferred_username" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableClaimsPreferredUsername(v *string) *AuthRequestUpdate { if v != nil { _u.SetClaimsPreferredUsername(*v) } return _u } // SetConnectorID sets the "connector_id" field. func (_u *AuthRequestUpdate) SetConnectorID(v string) *AuthRequestUpdate { _u.mutation.SetConnectorID(v) return _u } // SetNillableConnectorID sets the "connector_id" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableConnectorID(v *string) *AuthRequestUpdate { if v != nil { _u.SetConnectorID(*v) } return _u } // SetConnectorData sets the "connector_data" field. func (_u *AuthRequestUpdate) SetConnectorData(v []byte) *AuthRequestUpdate { _u.mutation.SetConnectorData(v) return _u } // ClearConnectorData clears the value of the "connector_data" field. func (_u *AuthRequestUpdate) ClearConnectorData() *AuthRequestUpdate { _u.mutation.ClearConnectorData() return _u } // SetExpiry sets the "expiry" field. func (_u *AuthRequestUpdate) SetExpiry(v time.Time) *AuthRequestUpdate { _u.mutation.SetExpiry(v) return _u } // SetNillableExpiry sets the "expiry" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableExpiry(v *time.Time) *AuthRequestUpdate { if v != nil { _u.SetExpiry(*v) } return _u } // SetCodeChallenge sets the "code_challenge" field. func (_u *AuthRequestUpdate) SetCodeChallenge(v string) *AuthRequestUpdate { _u.mutation.SetCodeChallenge(v) return _u } // SetNillableCodeChallenge sets the "code_challenge" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableCodeChallenge(v *string) *AuthRequestUpdate { if v != nil { _u.SetCodeChallenge(*v) } return _u } // SetCodeChallengeMethod sets the "code_challenge_method" field. func (_u *AuthRequestUpdate) SetCodeChallengeMethod(v string) *AuthRequestUpdate { _u.mutation.SetCodeChallengeMethod(v) return _u } // SetNillableCodeChallengeMethod sets the "code_challenge_method" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableCodeChallengeMethod(v *string) *AuthRequestUpdate { if v != nil { _u.SetCodeChallengeMethod(*v) } return _u } // SetHmacKey sets the "hmac_key" field. func (_u *AuthRequestUpdate) SetHmacKey(v []byte) *AuthRequestUpdate { _u.mutation.SetHmacKey(v) return _u } // SetMfaValidated sets the "mfa_validated" field. func (_u *AuthRequestUpdate) SetMfaValidated(v bool) *AuthRequestUpdate { _u.mutation.SetMfaValidated(v) return _u } // SetNillableMfaValidated sets the "mfa_validated" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableMfaValidated(v *bool) *AuthRequestUpdate { if v != nil { _u.SetMfaValidated(*v) } return _u } // SetPrompt sets the "prompt" field. func (_u *AuthRequestUpdate) SetPrompt(v string) *AuthRequestUpdate { _u.mutation.SetPrompt(v) return _u } // SetNillablePrompt sets the "prompt" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillablePrompt(v *string) *AuthRequestUpdate { if v != nil { _u.SetPrompt(*v) } return _u } // SetMaxAge sets the "max_age" field. func (_u *AuthRequestUpdate) SetMaxAge(v int) *AuthRequestUpdate { _u.mutation.ResetMaxAge() _u.mutation.SetMaxAge(v) return _u } // SetNillableMaxAge sets the "max_age" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableMaxAge(v *int) *AuthRequestUpdate { if v != nil { _u.SetMaxAge(*v) } return _u } // AddMaxAge adds value to the "max_age" field. func (_u *AuthRequestUpdate) AddMaxAge(v int) *AuthRequestUpdate { _u.mutation.AddMaxAge(v) return _u } // SetAuthTime sets the "auth_time" field. func (_u *AuthRequestUpdate) SetAuthTime(v time.Time) *AuthRequestUpdate { _u.mutation.SetAuthTime(v) return _u } // SetNillableAuthTime sets the "auth_time" field if the given value is not nil. func (_u *AuthRequestUpdate) SetNillableAuthTime(v *time.Time) *AuthRequestUpdate { if v != nil { _u.SetAuthTime(*v) } return _u } // ClearAuthTime clears the value of the "auth_time" field. func (_u *AuthRequestUpdate) ClearAuthTime() *AuthRequestUpdate { _u.mutation.ClearAuthTime() return _u } // Mutation returns the AuthRequestMutation object of the builder. func (_u *AuthRequestUpdate) Mutation() *AuthRequestMutation { return _u.mutation } // Save executes the query and returns the number of nodes affected by the update operation. func (_u *AuthRequestUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *AuthRequestUpdate) SaveX(ctx context.Context) int { affected, err := _u.Save(ctx) if err != nil { panic(err) } return affected } // Exec executes the query. func (_u *AuthRequestUpdate) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *AuthRequestUpdate) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } func (_u *AuthRequestUpdate) sqlSave(ctx context.Context) (_node int, err error) { _spec := sqlgraph.NewUpdateSpec(authrequest.Table, authrequest.Columns, sqlgraph.NewFieldSpec(authrequest.FieldID, field.TypeString)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.ClientID(); ok { _spec.SetField(authrequest.FieldClientID, field.TypeString, value) } if value, ok := _u.mutation.Scopes(); ok { _spec.SetField(authrequest.FieldScopes, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedScopes(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, authrequest.FieldScopes, value) }) } if _u.mutation.ScopesCleared() { _spec.ClearField(authrequest.FieldScopes, field.TypeJSON) } if value, ok := _u.mutation.ResponseTypes(); ok { _spec.SetField(authrequest.FieldResponseTypes, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedResponseTypes(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, authrequest.FieldResponseTypes, value) }) } if _u.mutation.ResponseTypesCleared() { _spec.ClearField(authrequest.FieldResponseTypes, field.TypeJSON) } if value, ok := _u.mutation.RedirectURI(); ok { _spec.SetField(authrequest.FieldRedirectURI, field.TypeString, value) } if value, ok := _u.mutation.Nonce(); ok { _spec.SetField(authrequest.FieldNonce, field.TypeString, value) } if value, ok := _u.mutation.State(); ok { _spec.SetField(authrequest.FieldState, field.TypeString, value) } if value, ok := _u.mutation.ForceApprovalPrompt(); ok { _spec.SetField(authrequest.FieldForceApprovalPrompt, field.TypeBool, value) } if value, ok := _u.mutation.LoggedIn(); ok { _spec.SetField(authrequest.FieldLoggedIn, field.TypeBool, value) } if value, ok := _u.mutation.ClaimsUserID(); ok { _spec.SetField(authrequest.FieldClaimsUserID, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUsername(); ok { _spec.SetField(authrequest.FieldClaimsUsername, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmail(); ok { _spec.SetField(authrequest.FieldClaimsEmail, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmailVerified(); ok { _spec.SetField(authrequest.FieldClaimsEmailVerified, field.TypeBool, value) } if value, ok := _u.mutation.ClaimsGroups(); ok { _spec.SetField(authrequest.FieldClaimsGroups, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedClaimsGroups(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, authrequest.FieldClaimsGroups, value) }) } if _u.mutation.ClaimsGroupsCleared() { _spec.ClearField(authrequest.FieldClaimsGroups, field.TypeJSON) } if value, ok := _u.mutation.ClaimsPreferredUsername(); ok { _spec.SetField(authrequest.FieldClaimsPreferredUsername, field.TypeString, value) } if value, ok := _u.mutation.ConnectorID(); ok { _spec.SetField(authrequest.FieldConnectorID, field.TypeString, value) } if value, ok := _u.mutation.ConnectorData(); ok { _spec.SetField(authrequest.FieldConnectorData, field.TypeBytes, value) } if _u.mutation.ConnectorDataCleared() { _spec.ClearField(authrequest.FieldConnectorData, field.TypeBytes) } if value, ok := _u.mutation.Expiry(); ok { _spec.SetField(authrequest.FieldExpiry, field.TypeTime, value) } if value, ok := _u.mutation.CodeChallenge(); ok { _spec.SetField(authrequest.FieldCodeChallenge, field.TypeString, value) } if value, ok := _u.mutation.CodeChallengeMethod(); ok { _spec.SetField(authrequest.FieldCodeChallengeMethod, field.TypeString, value) } if value, ok := _u.mutation.HmacKey(); ok { _spec.SetField(authrequest.FieldHmacKey, field.TypeBytes, value) } if value, ok := _u.mutation.MfaValidated(); ok { _spec.SetField(authrequest.FieldMfaValidated, field.TypeBool, value) } if value, ok := _u.mutation.Prompt(); ok { _spec.SetField(authrequest.FieldPrompt, field.TypeString, value) } if value, ok := _u.mutation.MaxAge(); ok { _spec.SetField(authrequest.FieldMaxAge, field.TypeInt, value) } if value, ok := _u.mutation.AddedMaxAge(); ok { _spec.AddField(authrequest.FieldMaxAge, field.TypeInt, value) } if value, ok := _u.mutation.AuthTime(); ok { _spec.SetField(authrequest.FieldAuthTime, field.TypeTime, value) } if _u.mutation.AuthTimeCleared() { _spec.ClearField(authrequest.FieldAuthTime, field.TypeTime) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{authrequest.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return 0, err } _u.mutation.done = true return _node, nil } // AuthRequestUpdateOne is the builder for updating a single AuthRequest entity. type AuthRequestUpdateOne struct { config fields []string hooks []Hook mutation *AuthRequestMutation } // SetClientID sets the "client_id" field. func (_u *AuthRequestUpdateOne) SetClientID(v string) *AuthRequestUpdateOne { _u.mutation.SetClientID(v) return _u } // SetNillableClientID sets the "client_id" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableClientID(v *string) *AuthRequestUpdateOne { if v != nil { _u.SetClientID(*v) } return _u } // SetScopes sets the "scopes" field. func (_u *AuthRequestUpdateOne) SetScopes(v []string) *AuthRequestUpdateOne { _u.mutation.SetScopes(v) return _u } // AppendScopes appends value to the "scopes" field. func (_u *AuthRequestUpdateOne) AppendScopes(v []string) *AuthRequestUpdateOne { _u.mutation.AppendScopes(v) return _u } // ClearScopes clears the value of the "scopes" field. func (_u *AuthRequestUpdateOne) ClearScopes() *AuthRequestUpdateOne { _u.mutation.ClearScopes() return _u } // SetResponseTypes sets the "response_types" field. func (_u *AuthRequestUpdateOne) SetResponseTypes(v []string) *AuthRequestUpdateOne { _u.mutation.SetResponseTypes(v) return _u } // AppendResponseTypes appends value to the "response_types" field. func (_u *AuthRequestUpdateOne) AppendResponseTypes(v []string) *AuthRequestUpdateOne { _u.mutation.AppendResponseTypes(v) return _u } // ClearResponseTypes clears the value of the "response_types" field. func (_u *AuthRequestUpdateOne) ClearResponseTypes() *AuthRequestUpdateOne { _u.mutation.ClearResponseTypes() return _u } // SetRedirectURI sets the "redirect_uri" field. func (_u *AuthRequestUpdateOne) SetRedirectURI(v string) *AuthRequestUpdateOne { _u.mutation.SetRedirectURI(v) return _u } // SetNillableRedirectURI sets the "redirect_uri" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableRedirectURI(v *string) *AuthRequestUpdateOne { if v != nil { _u.SetRedirectURI(*v) } return _u } // SetNonce sets the "nonce" field. func (_u *AuthRequestUpdateOne) SetNonce(v string) *AuthRequestUpdateOne { _u.mutation.SetNonce(v) return _u } // SetNillableNonce sets the "nonce" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableNonce(v *string) *AuthRequestUpdateOne { if v != nil { _u.SetNonce(*v) } return _u } // SetState sets the "state" field. func (_u *AuthRequestUpdateOne) SetState(v string) *AuthRequestUpdateOne { _u.mutation.SetState(v) return _u } // SetNillableState sets the "state" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableState(v *string) *AuthRequestUpdateOne { if v != nil { _u.SetState(*v) } return _u } // SetForceApprovalPrompt sets the "force_approval_prompt" field. func (_u *AuthRequestUpdateOne) SetForceApprovalPrompt(v bool) *AuthRequestUpdateOne { _u.mutation.SetForceApprovalPrompt(v) return _u } // SetNillableForceApprovalPrompt sets the "force_approval_prompt" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableForceApprovalPrompt(v *bool) *AuthRequestUpdateOne { if v != nil { _u.SetForceApprovalPrompt(*v) } return _u } // SetLoggedIn sets the "logged_in" field. func (_u *AuthRequestUpdateOne) SetLoggedIn(v bool) *AuthRequestUpdateOne { _u.mutation.SetLoggedIn(v) return _u } // SetNillableLoggedIn sets the "logged_in" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableLoggedIn(v *bool) *AuthRequestUpdateOne { if v != nil { _u.SetLoggedIn(*v) } return _u } // SetClaimsUserID sets the "claims_user_id" field. func (_u *AuthRequestUpdateOne) SetClaimsUserID(v string) *AuthRequestUpdateOne { _u.mutation.SetClaimsUserID(v) return _u } // SetNillableClaimsUserID sets the "claims_user_id" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableClaimsUserID(v *string) *AuthRequestUpdateOne { if v != nil { _u.SetClaimsUserID(*v) } return _u } // SetClaimsUsername sets the "claims_username" field. func (_u *AuthRequestUpdateOne) SetClaimsUsername(v string) *AuthRequestUpdateOne { _u.mutation.SetClaimsUsername(v) return _u } // SetNillableClaimsUsername sets the "claims_username" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableClaimsUsername(v *string) *AuthRequestUpdateOne { if v != nil { _u.SetClaimsUsername(*v) } return _u } // SetClaimsEmail sets the "claims_email" field. func (_u *AuthRequestUpdateOne) SetClaimsEmail(v string) *AuthRequestUpdateOne { _u.mutation.SetClaimsEmail(v) return _u } // SetNillableClaimsEmail sets the "claims_email" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableClaimsEmail(v *string) *AuthRequestUpdateOne { if v != nil { _u.SetClaimsEmail(*v) } return _u } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (_u *AuthRequestUpdateOne) SetClaimsEmailVerified(v bool) *AuthRequestUpdateOne { _u.mutation.SetClaimsEmailVerified(v) return _u } // SetNillableClaimsEmailVerified sets the "claims_email_verified" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableClaimsEmailVerified(v *bool) *AuthRequestUpdateOne { if v != nil { _u.SetClaimsEmailVerified(*v) } return _u } // SetClaimsGroups sets the "claims_groups" field. func (_u *AuthRequestUpdateOne) SetClaimsGroups(v []string) *AuthRequestUpdateOne { _u.mutation.SetClaimsGroups(v) return _u } // AppendClaimsGroups appends value to the "claims_groups" field. func (_u *AuthRequestUpdateOne) AppendClaimsGroups(v []string) *AuthRequestUpdateOne { _u.mutation.AppendClaimsGroups(v) return _u } // ClearClaimsGroups clears the value of the "claims_groups" field. func (_u *AuthRequestUpdateOne) ClearClaimsGroups() *AuthRequestUpdateOne { _u.mutation.ClearClaimsGroups() return _u } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (_u *AuthRequestUpdateOne) SetClaimsPreferredUsername(v string) *AuthRequestUpdateOne { _u.mutation.SetClaimsPreferredUsername(v) return _u } // SetNillableClaimsPreferredUsername sets the "claims_preferred_username" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableClaimsPreferredUsername(v *string) *AuthRequestUpdateOne { if v != nil { _u.SetClaimsPreferredUsername(*v) } return _u } // SetConnectorID sets the "connector_id" field. func (_u *AuthRequestUpdateOne) SetConnectorID(v string) *AuthRequestUpdateOne { _u.mutation.SetConnectorID(v) return _u } // SetNillableConnectorID sets the "connector_id" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableConnectorID(v *string) *AuthRequestUpdateOne { if v != nil { _u.SetConnectorID(*v) } return _u } // SetConnectorData sets the "connector_data" field. func (_u *AuthRequestUpdateOne) SetConnectorData(v []byte) *AuthRequestUpdateOne { _u.mutation.SetConnectorData(v) return _u } // ClearConnectorData clears the value of the "connector_data" field. func (_u *AuthRequestUpdateOne) ClearConnectorData() *AuthRequestUpdateOne { _u.mutation.ClearConnectorData() return _u } // SetExpiry sets the "expiry" field. func (_u *AuthRequestUpdateOne) SetExpiry(v time.Time) *AuthRequestUpdateOne { _u.mutation.SetExpiry(v) return _u } // SetNillableExpiry sets the "expiry" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableExpiry(v *time.Time) *AuthRequestUpdateOne { if v != nil { _u.SetExpiry(*v) } return _u } // SetCodeChallenge sets the "code_challenge" field. func (_u *AuthRequestUpdateOne) SetCodeChallenge(v string) *AuthRequestUpdateOne { _u.mutation.SetCodeChallenge(v) return _u } // SetNillableCodeChallenge sets the "code_challenge" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableCodeChallenge(v *string) *AuthRequestUpdateOne { if v != nil { _u.SetCodeChallenge(*v) } return _u } // SetCodeChallengeMethod sets the "code_challenge_method" field. func (_u *AuthRequestUpdateOne) SetCodeChallengeMethod(v string) *AuthRequestUpdateOne { _u.mutation.SetCodeChallengeMethod(v) return _u } // SetNillableCodeChallengeMethod sets the "code_challenge_method" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableCodeChallengeMethod(v *string) *AuthRequestUpdateOne { if v != nil { _u.SetCodeChallengeMethod(*v) } return _u } // SetHmacKey sets the "hmac_key" field. func (_u *AuthRequestUpdateOne) SetHmacKey(v []byte) *AuthRequestUpdateOne { _u.mutation.SetHmacKey(v) return _u } // SetMfaValidated sets the "mfa_validated" field. func (_u *AuthRequestUpdateOne) SetMfaValidated(v bool) *AuthRequestUpdateOne { _u.mutation.SetMfaValidated(v) return _u } // SetNillableMfaValidated sets the "mfa_validated" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableMfaValidated(v *bool) *AuthRequestUpdateOne { if v != nil { _u.SetMfaValidated(*v) } return _u } // SetPrompt sets the "prompt" field. func (_u *AuthRequestUpdateOne) SetPrompt(v string) *AuthRequestUpdateOne { _u.mutation.SetPrompt(v) return _u } // SetNillablePrompt sets the "prompt" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillablePrompt(v *string) *AuthRequestUpdateOne { if v != nil { _u.SetPrompt(*v) } return _u } // SetMaxAge sets the "max_age" field. func (_u *AuthRequestUpdateOne) SetMaxAge(v int) *AuthRequestUpdateOne { _u.mutation.ResetMaxAge() _u.mutation.SetMaxAge(v) return _u } // SetNillableMaxAge sets the "max_age" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableMaxAge(v *int) *AuthRequestUpdateOne { if v != nil { _u.SetMaxAge(*v) } return _u } // AddMaxAge adds value to the "max_age" field. func (_u *AuthRequestUpdateOne) AddMaxAge(v int) *AuthRequestUpdateOne { _u.mutation.AddMaxAge(v) return _u } // SetAuthTime sets the "auth_time" field. func (_u *AuthRequestUpdateOne) SetAuthTime(v time.Time) *AuthRequestUpdateOne { _u.mutation.SetAuthTime(v) return _u } // SetNillableAuthTime sets the "auth_time" field if the given value is not nil. func (_u *AuthRequestUpdateOne) SetNillableAuthTime(v *time.Time) *AuthRequestUpdateOne { if v != nil { _u.SetAuthTime(*v) } return _u } // ClearAuthTime clears the value of the "auth_time" field. func (_u *AuthRequestUpdateOne) ClearAuthTime() *AuthRequestUpdateOne { _u.mutation.ClearAuthTime() return _u } // Mutation returns the AuthRequestMutation object of the builder. func (_u *AuthRequestUpdateOne) Mutation() *AuthRequestMutation { return _u.mutation } // Where appends a list predicates to the AuthRequestUpdate builder. func (_u *AuthRequestUpdateOne) Where(ps ...predicate.AuthRequest) *AuthRequestUpdateOne { _u.mutation.Where(ps...) return _u } // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (_u *AuthRequestUpdateOne) Select(field string, fields ...string) *AuthRequestUpdateOne { _u.fields = append([]string{field}, fields...) return _u } // Save executes the query and returns the updated AuthRequest entity. func (_u *AuthRequestUpdateOne) Save(ctx context.Context) (*AuthRequest, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *AuthRequestUpdateOne) SaveX(ctx context.Context) *AuthRequest { node, err := _u.Save(ctx) if err != nil { panic(err) } return node } // Exec executes the query on the entity. func (_u *AuthRequestUpdateOne) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *AuthRequestUpdateOne) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } func (_u *AuthRequestUpdateOne) sqlSave(ctx context.Context) (_node *AuthRequest, err error) { _spec := sqlgraph.NewUpdateSpec(authrequest.Table, authrequest.Columns, sqlgraph.NewFieldSpec(authrequest.FieldID, field.TypeString)) id, ok := _u.mutation.ID() if !ok { return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "AuthRequest.id" for update`)} } _spec.Node.ID.Value = id if fields := _u.fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, authrequest.FieldID) for _, f := range fields { if !authrequest.ValidColumn(f) { return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } if f != authrequest.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, f) } } } if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.ClientID(); ok { _spec.SetField(authrequest.FieldClientID, field.TypeString, value) } if value, ok := _u.mutation.Scopes(); ok { _spec.SetField(authrequest.FieldScopes, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedScopes(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, authrequest.FieldScopes, value) }) } if _u.mutation.ScopesCleared() { _spec.ClearField(authrequest.FieldScopes, field.TypeJSON) } if value, ok := _u.mutation.ResponseTypes(); ok { _spec.SetField(authrequest.FieldResponseTypes, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedResponseTypes(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, authrequest.FieldResponseTypes, value) }) } if _u.mutation.ResponseTypesCleared() { _spec.ClearField(authrequest.FieldResponseTypes, field.TypeJSON) } if value, ok := _u.mutation.RedirectURI(); ok { _spec.SetField(authrequest.FieldRedirectURI, field.TypeString, value) } if value, ok := _u.mutation.Nonce(); ok { _spec.SetField(authrequest.FieldNonce, field.TypeString, value) } if value, ok := _u.mutation.State(); ok { _spec.SetField(authrequest.FieldState, field.TypeString, value) } if value, ok := _u.mutation.ForceApprovalPrompt(); ok { _spec.SetField(authrequest.FieldForceApprovalPrompt, field.TypeBool, value) } if value, ok := _u.mutation.LoggedIn(); ok { _spec.SetField(authrequest.FieldLoggedIn, field.TypeBool, value) } if value, ok := _u.mutation.ClaimsUserID(); ok { _spec.SetField(authrequest.FieldClaimsUserID, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUsername(); ok { _spec.SetField(authrequest.FieldClaimsUsername, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmail(); ok { _spec.SetField(authrequest.FieldClaimsEmail, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmailVerified(); ok { _spec.SetField(authrequest.FieldClaimsEmailVerified, field.TypeBool, value) } if value, ok := _u.mutation.ClaimsGroups(); ok { _spec.SetField(authrequest.FieldClaimsGroups, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedClaimsGroups(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, authrequest.FieldClaimsGroups, value) }) } if _u.mutation.ClaimsGroupsCleared() { _spec.ClearField(authrequest.FieldClaimsGroups, field.TypeJSON) } if value, ok := _u.mutation.ClaimsPreferredUsername(); ok { _spec.SetField(authrequest.FieldClaimsPreferredUsername, field.TypeString, value) } if value, ok := _u.mutation.ConnectorID(); ok { _spec.SetField(authrequest.FieldConnectorID, field.TypeString, value) } if value, ok := _u.mutation.ConnectorData(); ok { _spec.SetField(authrequest.FieldConnectorData, field.TypeBytes, value) } if _u.mutation.ConnectorDataCleared() { _spec.ClearField(authrequest.FieldConnectorData, field.TypeBytes) } if value, ok := _u.mutation.Expiry(); ok { _spec.SetField(authrequest.FieldExpiry, field.TypeTime, value) } if value, ok := _u.mutation.CodeChallenge(); ok { _spec.SetField(authrequest.FieldCodeChallenge, field.TypeString, value) } if value, ok := _u.mutation.CodeChallengeMethod(); ok { _spec.SetField(authrequest.FieldCodeChallengeMethod, field.TypeString, value) } if value, ok := _u.mutation.HmacKey(); ok { _spec.SetField(authrequest.FieldHmacKey, field.TypeBytes, value) } if value, ok := _u.mutation.MfaValidated(); ok { _spec.SetField(authrequest.FieldMfaValidated, field.TypeBool, value) } if value, ok := _u.mutation.Prompt(); ok { _spec.SetField(authrequest.FieldPrompt, field.TypeString, value) } if value, ok := _u.mutation.MaxAge(); ok { _spec.SetField(authrequest.FieldMaxAge, field.TypeInt, value) } if value, ok := _u.mutation.AddedMaxAge(); ok { _spec.AddField(authrequest.FieldMaxAge, field.TypeInt, value) } if value, ok := _u.mutation.AuthTime(); ok { _spec.SetField(authrequest.FieldAuthTime, field.TypeTime, value) } if _u.mutation.AuthTimeCleared() { _spec.ClearField(authrequest.FieldAuthTime, field.TypeTime) } _node = &AuthRequest{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{authrequest.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } _u.mutation.done = true return _node, nil } ================================================ FILE: storage/ent/db/authsession/authsession.go ================================================ // Code generated by ent, DO NOT EDIT. package authsession import ( "entgo.io/ent/dialect/sql" ) const ( // Label holds the string label denoting the authsession type in the database. Label = "auth_session" // FieldID holds the string denoting the id field in the database. FieldID = "id" // FieldUserID holds the string denoting the user_id field in the database. FieldUserID = "user_id" // FieldConnectorID holds the string denoting the connector_id field in the database. FieldConnectorID = "connector_id" // FieldNonce holds the string denoting the nonce field in the database. FieldNonce = "nonce" // FieldClientStates holds the string denoting the client_states field in the database. FieldClientStates = "client_states" // FieldCreatedAt holds the string denoting the created_at field in the database. FieldCreatedAt = "created_at" // FieldLastActivity holds the string denoting the last_activity field in the database. FieldLastActivity = "last_activity" // FieldIPAddress holds the string denoting the ip_address field in the database. FieldIPAddress = "ip_address" // FieldUserAgent holds the string denoting the user_agent field in the database. FieldUserAgent = "user_agent" // FieldAbsoluteExpiry holds the string denoting the absolute_expiry field in the database. FieldAbsoluteExpiry = "absolute_expiry" // FieldIdleExpiry holds the string denoting the idle_expiry field in the database. FieldIdleExpiry = "idle_expiry" // Table holds the table name of the authsession in the database. Table = "auth_sessions" ) // Columns holds all SQL columns for authsession fields. var Columns = []string{ FieldID, FieldUserID, FieldConnectorID, FieldNonce, FieldClientStates, FieldCreatedAt, FieldLastActivity, FieldIPAddress, FieldUserAgent, FieldAbsoluteExpiry, FieldIdleExpiry, } // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { if column == Columns[i] { return true } } return false } var ( // UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. UserIDValidator func(string) error // ConnectorIDValidator is a validator for the "connector_id" field. It is called by the builders before save. ConnectorIDValidator func(string) error // NonceValidator is a validator for the "nonce" field. It is called by the builders before save. NonceValidator func(string) error // DefaultIPAddress holds the default value on creation for the "ip_address" field. DefaultIPAddress string // DefaultUserAgent holds the default value on creation for the "user_agent" field. DefaultUserAgent string // IDValidator is a validator for the "id" field. It is called by the builders before save. IDValidator func(string) error ) // OrderOption defines the ordering options for the AuthSession queries. type OrderOption func(*sql.Selector) // ByID orders the results by the id field. func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } // ByUserID orders the results by the user_id field. func ByUserID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldUserID, opts...).ToFunc() } // ByConnectorID orders the results by the connector_id field. func ByConnectorID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldConnectorID, opts...).ToFunc() } // ByNonce orders the results by the nonce field. func ByNonce(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldNonce, opts...).ToFunc() } // ByCreatedAt orders the results by the created_at field. func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() } // ByLastActivity orders the results by the last_activity field. func ByLastActivity(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldLastActivity, opts...).ToFunc() } // ByIPAddress orders the results by the ip_address field. func ByIPAddress(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldIPAddress, opts...).ToFunc() } // ByUserAgent orders the results by the user_agent field. func ByUserAgent(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldUserAgent, opts...).ToFunc() } // ByAbsoluteExpiry orders the results by the absolute_expiry field. func ByAbsoluteExpiry(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldAbsoluteExpiry, opts...).ToFunc() } // ByIdleExpiry orders the results by the idle_expiry field. func ByIdleExpiry(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldIdleExpiry, opts...).ToFunc() } ================================================ FILE: storage/ent/db/authsession/where.go ================================================ // Code generated by ent, DO NOT EDIT. package authsession import ( "time" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ID filters vertices based on their ID field. func ID(id string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. func IDEQ(id string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. func IDNEQ(id string) predicate.AuthSession { return predicate.AuthSession(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. func IDIn(ids ...string) predicate.AuthSession { return predicate.AuthSession(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. func IDNotIn(ids ...string) predicate.AuthSession { return predicate.AuthSession(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. func IDGT(id string) predicate.AuthSession { return predicate.AuthSession(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. func IDGTE(id string) predicate.AuthSession { return predicate.AuthSession(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. func IDLT(id string) predicate.AuthSession { return predicate.AuthSession(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. func IDLTE(id string) predicate.AuthSession { return predicate.AuthSession(sql.FieldLTE(FieldID, id)) } // IDEqualFold applies the EqualFold predicate on the ID field. func IDEqualFold(id string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEqualFold(FieldID, id)) } // IDContainsFold applies the ContainsFold predicate on the ID field. func IDContainsFold(id string) predicate.AuthSession { return predicate.AuthSession(sql.FieldContainsFold(FieldID, id)) } // UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. func UserID(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldUserID, v)) } // ConnectorID applies equality check predicate on the "connector_id" field. It's identical to ConnectorIDEQ. func ConnectorID(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldConnectorID, v)) } // Nonce applies equality check predicate on the "nonce" field. It's identical to NonceEQ. func Nonce(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldNonce, v)) } // ClientStates applies equality check predicate on the "client_states" field. It's identical to ClientStatesEQ. func ClientStates(v []byte) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldClientStates, v)) } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. func CreatedAt(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldCreatedAt, v)) } // LastActivity applies equality check predicate on the "last_activity" field. It's identical to LastActivityEQ. func LastActivity(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldLastActivity, v)) } // IPAddress applies equality check predicate on the "ip_address" field. It's identical to IPAddressEQ. func IPAddress(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldIPAddress, v)) } // UserAgent applies equality check predicate on the "user_agent" field. It's identical to UserAgentEQ. func UserAgent(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldUserAgent, v)) } // AbsoluteExpiry applies equality check predicate on the "absolute_expiry" field. It's identical to AbsoluteExpiryEQ. func AbsoluteExpiry(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldAbsoluteExpiry, v)) } // IdleExpiry applies equality check predicate on the "idle_expiry" field. It's identical to IdleExpiryEQ. func IdleExpiry(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldIdleExpiry, v)) } // UserIDEQ applies the EQ predicate on the "user_id" field. func UserIDEQ(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldUserID, v)) } // UserIDNEQ applies the NEQ predicate on the "user_id" field. func UserIDNEQ(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldNEQ(FieldUserID, v)) } // UserIDIn applies the In predicate on the "user_id" field. func UserIDIn(vs ...string) predicate.AuthSession { return predicate.AuthSession(sql.FieldIn(FieldUserID, vs...)) } // UserIDNotIn applies the NotIn predicate on the "user_id" field. func UserIDNotIn(vs ...string) predicate.AuthSession { return predicate.AuthSession(sql.FieldNotIn(FieldUserID, vs...)) } // UserIDGT applies the GT predicate on the "user_id" field. func UserIDGT(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldGT(FieldUserID, v)) } // UserIDGTE applies the GTE predicate on the "user_id" field. func UserIDGTE(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldGTE(FieldUserID, v)) } // UserIDLT applies the LT predicate on the "user_id" field. func UserIDLT(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldLT(FieldUserID, v)) } // UserIDLTE applies the LTE predicate on the "user_id" field. func UserIDLTE(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldLTE(FieldUserID, v)) } // UserIDContains applies the Contains predicate on the "user_id" field. func UserIDContains(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldContains(FieldUserID, v)) } // UserIDHasPrefix applies the HasPrefix predicate on the "user_id" field. func UserIDHasPrefix(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldHasPrefix(FieldUserID, v)) } // UserIDHasSuffix applies the HasSuffix predicate on the "user_id" field. func UserIDHasSuffix(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldHasSuffix(FieldUserID, v)) } // UserIDEqualFold applies the EqualFold predicate on the "user_id" field. func UserIDEqualFold(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEqualFold(FieldUserID, v)) } // UserIDContainsFold applies the ContainsFold predicate on the "user_id" field. func UserIDContainsFold(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldContainsFold(FieldUserID, v)) } // ConnectorIDEQ applies the EQ predicate on the "connector_id" field. func ConnectorIDEQ(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldConnectorID, v)) } // ConnectorIDNEQ applies the NEQ predicate on the "connector_id" field. func ConnectorIDNEQ(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldNEQ(FieldConnectorID, v)) } // ConnectorIDIn applies the In predicate on the "connector_id" field. func ConnectorIDIn(vs ...string) predicate.AuthSession { return predicate.AuthSession(sql.FieldIn(FieldConnectorID, vs...)) } // ConnectorIDNotIn applies the NotIn predicate on the "connector_id" field. func ConnectorIDNotIn(vs ...string) predicate.AuthSession { return predicate.AuthSession(sql.FieldNotIn(FieldConnectorID, vs...)) } // ConnectorIDGT applies the GT predicate on the "connector_id" field. func ConnectorIDGT(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldGT(FieldConnectorID, v)) } // ConnectorIDGTE applies the GTE predicate on the "connector_id" field. func ConnectorIDGTE(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldGTE(FieldConnectorID, v)) } // ConnectorIDLT applies the LT predicate on the "connector_id" field. func ConnectorIDLT(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldLT(FieldConnectorID, v)) } // ConnectorIDLTE applies the LTE predicate on the "connector_id" field. func ConnectorIDLTE(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldLTE(FieldConnectorID, v)) } // ConnectorIDContains applies the Contains predicate on the "connector_id" field. func ConnectorIDContains(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldContains(FieldConnectorID, v)) } // ConnectorIDHasPrefix applies the HasPrefix predicate on the "connector_id" field. func ConnectorIDHasPrefix(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldHasPrefix(FieldConnectorID, v)) } // ConnectorIDHasSuffix applies the HasSuffix predicate on the "connector_id" field. func ConnectorIDHasSuffix(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldHasSuffix(FieldConnectorID, v)) } // ConnectorIDEqualFold applies the EqualFold predicate on the "connector_id" field. func ConnectorIDEqualFold(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEqualFold(FieldConnectorID, v)) } // ConnectorIDContainsFold applies the ContainsFold predicate on the "connector_id" field. func ConnectorIDContainsFold(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldContainsFold(FieldConnectorID, v)) } // NonceEQ applies the EQ predicate on the "nonce" field. func NonceEQ(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldNonce, v)) } // NonceNEQ applies the NEQ predicate on the "nonce" field. func NonceNEQ(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldNEQ(FieldNonce, v)) } // NonceIn applies the In predicate on the "nonce" field. func NonceIn(vs ...string) predicate.AuthSession { return predicate.AuthSession(sql.FieldIn(FieldNonce, vs...)) } // NonceNotIn applies the NotIn predicate on the "nonce" field. func NonceNotIn(vs ...string) predicate.AuthSession { return predicate.AuthSession(sql.FieldNotIn(FieldNonce, vs...)) } // NonceGT applies the GT predicate on the "nonce" field. func NonceGT(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldGT(FieldNonce, v)) } // NonceGTE applies the GTE predicate on the "nonce" field. func NonceGTE(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldGTE(FieldNonce, v)) } // NonceLT applies the LT predicate on the "nonce" field. func NonceLT(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldLT(FieldNonce, v)) } // NonceLTE applies the LTE predicate on the "nonce" field. func NonceLTE(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldLTE(FieldNonce, v)) } // NonceContains applies the Contains predicate on the "nonce" field. func NonceContains(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldContains(FieldNonce, v)) } // NonceHasPrefix applies the HasPrefix predicate on the "nonce" field. func NonceHasPrefix(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldHasPrefix(FieldNonce, v)) } // NonceHasSuffix applies the HasSuffix predicate on the "nonce" field. func NonceHasSuffix(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldHasSuffix(FieldNonce, v)) } // NonceEqualFold applies the EqualFold predicate on the "nonce" field. func NonceEqualFold(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEqualFold(FieldNonce, v)) } // NonceContainsFold applies the ContainsFold predicate on the "nonce" field. func NonceContainsFold(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldContainsFold(FieldNonce, v)) } // ClientStatesEQ applies the EQ predicate on the "client_states" field. func ClientStatesEQ(v []byte) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldClientStates, v)) } // ClientStatesNEQ applies the NEQ predicate on the "client_states" field. func ClientStatesNEQ(v []byte) predicate.AuthSession { return predicate.AuthSession(sql.FieldNEQ(FieldClientStates, v)) } // ClientStatesIn applies the In predicate on the "client_states" field. func ClientStatesIn(vs ...[]byte) predicate.AuthSession { return predicate.AuthSession(sql.FieldIn(FieldClientStates, vs...)) } // ClientStatesNotIn applies the NotIn predicate on the "client_states" field. func ClientStatesNotIn(vs ...[]byte) predicate.AuthSession { return predicate.AuthSession(sql.FieldNotIn(FieldClientStates, vs...)) } // ClientStatesGT applies the GT predicate on the "client_states" field. func ClientStatesGT(v []byte) predicate.AuthSession { return predicate.AuthSession(sql.FieldGT(FieldClientStates, v)) } // ClientStatesGTE applies the GTE predicate on the "client_states" field. func ClientStatesGTE(v []byte) predicate.AuthSession { return predicate.AuthSession(sql.FieldGTE(FieldClientStates, v)) } // ClientStatesLT applies the LT predicate on the "client_states" field. func ClientStatesLT(v []byte) predicate.AuthSession { return predicate.AuthSession(sql.FieldLT(FieldClientStates, v)) } // ClientStatesLTE applies the LTE predicate on the "client_states" field. func ClientStatesLTE(v []byte) predicate.AuthSession { return predicate.AuthSession(sql.FieldLTE(FieldClientStates, v)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. func CreatedAtNEQ(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. func CreatedAtIn(vs ...time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. func CreatedAtNotIn(vs ...time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. func CreatedAtGT(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. func CreatedAtGTE(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. func CreatedAtLT(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. func CreatedAtLTE(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldLTE(FieldCreatedAt, v)) } // LastActivityEQ applies the EQ predicate on the "last_activity" field. func LastActivityEQ(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldLastActivity, v)) } // LastActivityNEQ applies the NEQ predicate on the "last_activity" field. func LastActivityNEQ(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldNEQ(FieldLastActivity, v)) } // LastActivityIn applies the In predicate on the "last_activity" field. func LastActivityIn(vs ...time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldIn(FieldLastActivity, vs...)) } // LastActivityNotIn applies the NotIn predicate on the "last_activity" field. func LastActivityNotIn(vs ...time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldNotIn(FieldLastActivity, vs...)) } // LastActivityGT applies the GT predicate on the "last_activity" field. func LastActivityGT(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldGT(FieldLastActivity, v)) } // LastActivityGTE applies the GTE predicate on the "last_activity" field. func LastActivityGTE(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldGTE(FieldLastActivity, v)) } // LastActivityLT applies the LT predicate on the "last_activity" field. func LastActivityLT(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldLT(FieldLastActivity, v)) } // LastActivityLTE applies the LTE predicate on the "last_activity" field. func LastActivityLTE(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldLTE(FieldLastActivity, v)) } // IPAddressEQ applies the EQ predicate on the "ip_address" field. func IPAddressEQ(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldIPAddress, v)) } // IPAddressNEQ applies the NEQ predicate on the "ip_address" field. func IPAddressNEQ(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldNEQ(FieldIPAddress, v)) } // IPAddressIn applies the In predicate on the "ip_address" field. func IPAddressIn(vs ...string) predicate.AuthSession { return predicate.AuthSession(sql.FieldIn(FieldIPAddress, vs...)) } // IPAddressNotIn applies the NotIn predicate on the "ip_address" field. func IPAddressNotIn(vs ...string) predicate.AuthSession { return predicate.AuthSession(sql.FieldNotIn(FieldIPAddress, vs...)) } // IPAddressGT applies the GT predicate on the "ip_address" field. func IPAddressGT(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldGT(FieldIPAddress, v)) } // IPAddressGTE applies the GTE predicate on the "ip_address" field. func IPAddressGTE(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldGTE(FieldIPAddress, v)) } // IPAddressLT applies the LT predicate on the "ip_address" field. func IPAddressLT(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldLT(FieldIPAddress, v)) } // IPAddressLTE applies the LTE predicate on the "ip_address" field. func IPAddressLTE(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldLTE(FieldIPAddress, v)) } // IPAddressContains applies the Contains predicate on the "ip_address" field. func IPAddressContains(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldContains(FieldIPAddress, v)) } // IPAddressHasPrefix applies the HasPrefix predicate on the "ip_address" field. func IPAddressHasPrefix(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldHasPrefix(FieldIPAddress, v)) } // IPAddressHasSuffix applies the HasSuffix predicate on the "ip_address" field. func IPAddressHasSuffix(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldHasSuffix(FieldIPAddress, v)) } // IPAddressEqualFold applies the EqualFold predicate on the "ip_address" field. func IPAddressEqualFold(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEqualFold(FieldIPAddress, v)) } // IPAddressContainsFold applies the ContainsFold predicate on the "ip_address" field. func IPAddressContainsFold(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldContainsFold(FieldIPAddress, v)) } // UserAgentEQ applies the EQ predicate on the "user_agent" field. func UserAgentEQ(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldUserAgent, v)) } // UserAgentNEQ applies the NEQ predicate on the "user_agent" field. func UserAgentNEQ(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldNEQ(FieldUserAgent, v)) } // UserAgentIn applies the In predicate on the "user_agent" field. func UserAgentIn(vs ...string) predicate.AuthSession { return predicate.AuthSession(sql.FieldIn(FieldUserAgent, vs...)) } // UserAgentNotIn applies the NotIn predicate on the "user_agent" field. func UserAgentNotIn(vs ...string) predicate.AuthSession { return predicate.AuthSession(sql.FieldNotIn(FieldUserAgent, vs...)) } // UserAgentGT applies the GT predicate on the "user_agent" field. func UserAgentGT(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldGT(FieldUserAgent, v)) } // UserAgentGTE applies the GTE predicate on the "user_agent" field. func UserAgentGTE(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldGTE(FieldUserAgent, v)) } // UserAgentLT applies the LT predicate on the "user_agent" field. func UserAgentLT(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldLT(FieldUserAgent, v)) } // UserAgentLTE applies the LTE predicate on the "user_agent" field. func UserAgentLTE(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldLTE(FieldUserAgent, v)) } // UserAgentContains applies the Contains predicate on the "user_agent" field. func UserAgentContains(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldContains(FieldUserAgent, v)) } // UserAgentHasPrefix applies the HasPrefix predicate on the "user_agent" field. func UserAgentHasPrefix(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldHasPrefix(FieldUserAgent, v)) } // UserAgentHasSuffix applies the HasSuffix predicate on the "user_agent" field. func UserAgentHasSuffix(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldHasSuffix(FieldUserAgent, v)) } // UserAgentEqualFold applies the EqualFold predicate on the "user_agent" field. func UserAgentEqualFold(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldEqualFold(FieldUserAgent, v)) } // UserAgentContainsFold applies the ContainsFold predicate on the "user_agent" field. func UserAgentContainsFold(v string) predicate.AuthSession { return predicate.AuthSession(sql.FieldContainsFold(FieldUserAgent, v)) } // AbsoluteExpiryEQ applies the EQ predicate on the "absolute_expiry" field. func AbsoluteExpiryEQ(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldAbsoluteExpiry, v)) } // AbsoluteExpiryNEQ applies the NEQ predicate on the "absolute_expiry" field. func AbsoluteExpiryNEQ(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldNEQ(FieldAbsoluteExpiry, v)) } // AbsoluteExpiryIn applies the In predicate on the "absolute_expiry" field. func AbsoluteExpiryIn(vs ...time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldIn(FieldAbsoluteExpiry, vs...)) } // AbsoluteExpiryNotIn applies the NotIn predicate on the "absolute_expiry" field. func AbsoluteExpiryNotIn(vs ...time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldNotIn(FieldAbsoluteExpiry, vs...)) } // AbsoluteExpiryGT applies the GT predicate on the "absolute_expiry" field. func AbsoluteExpiryGT(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldGT(FieldAbsoluteExpiry, v)) } // AbsoluteExpiryGTE applies the GTE predicate on the "absolute_expiry" field. func AbsoluteExpiryGTE(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldGTE(FieldAbsoluteExpiry, v)) } // AbsoluteExpiryLT applies the LT predicate on the "absolute_expiry" field. func AbsoluteExpiryLT(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldLT(FieldAbsoluteExpiry, v)) } // AbsoluteExpiryLTE applies the LTE predicate on the "absolute_expiry" field. func AbsoluteExpiryLTE(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldLTE(FieldAbsoluteExpiry, v)) } // IdleExpiryEQ applies the EQ predicate on the "idle_expiry" field. func IdleExpiryEQ(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldEQ(FieldIdleExpiry, v)) } // IdleExpiryNEQ applies the NEQ predicate on the "idle_expiry" field. func IdleExpiryNEQ(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldNEQ(FieldIdleExpiry, v)) } // IdleExpiryIn applies the In predicate on the "idle_expiry" field. func IdleExpiryIn(vs ...time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldIn(FieldIdleExpiry, vs...)) } // IdleExpiryNotIn applies the NotIn predicate on the "idle_expiry" field. func IdleExpiryNotIn(vs ...time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldNotIn(FieldIdleExpiry, vs...)) } // IdleExpiryGT applies the GT predicate on the "idle_expiry" field. func IdleExpiryGT(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldGT(FieldIdleExpiry, v)) } // IdleExpiryGTE applies the GTE predicate on the "idle_expiry" field. func IdleExpiryGTE(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldGTE(FieldIdleExpiry, v)) } // IdleExpiryLT applies the LT predicate on the "idle_expiry" field. func IdleExpiryLT(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldLT(FieldIdleExpiry, v)) } // IdleExpiryLTE applies the LTE predicate on the "idle_expiry" field. func IdleExpiryLTE(v time.Time) predicate.AuthSession { return predicate.AuthSession(sql.FieldLTE(FieldIdleExpiry, v)) } // And groups predicates with the AND operator between them. func And(predicates ...predicate.AuthSession) predicate.AuthSession { return predicate.AuthSession(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.AuthSession) predicate.AuthSession { return predicate.AuthSession(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.AuthSession) predicate.AuthSession { return predicate.AuthSession(sql.NotPredicates(p)) } ================================================ FILE: storage/ent/db/authsession.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "fmt" "strings" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/authsession" ) // AuthSession is the model entity for the AuthSession schema. type AuthSession struct { config `json:"-"` // ID of the ent. ID string `json:"id,omitempty"` // UserID holds the value of the "user_id" field. UserID string `json:"user_id,omitempty"` // ConnectorID holds the value of the "connector_id" field. ConnectorID string `json:"connector_id,omitempty"` // Nonce holds the value of the "nonce" field. Nonce string `json:"nonce,omitempty"` // ClientStates holds the value of the "client_states" field. ClientStates []byte `json:"client_states,omitempty"` // CreatedAt holds the value of the "created_at" field. CreatedAt time.Time `json:"created_at,omitempty"` // LastActivity holds the value of the "last_activity" field. LastActivity time.Time `json:"last_activity,omitempty"` // IPAddress holds the value of the "ip_address" field. IPAddress string `json:"ip_address,omitempty"` // UserAgent holds the value of the "user_agent" field. UserAgent string `json:"user_agent,omitempty"` // AbsoluteExpiry holds the value of the "absolute_expiry" field. AbsoluteExpiry time.Time `json:"absolute_expiry,omitempty"` // IdleExpiry holds the value of the "idle_expiry" field. IdleExpiry time.Time `json:"idle_expiry,omitempty"` selectValues sql.SelectValues } // scanValues returns the types for scanning values from sql.Rows. func (*AuthSession) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { case authsession.FieldClientStates: values[i] = new([]byte) case authsession.FieldID, authsession.FieldUserID, authsession.FieldConnectorID, authsession.FieldNonce, authsession.FieldIPAddress, authsession.FieldUserAgent: values[i] = new(sql.NullString) case authsession.FieldCreatedAt, authsession.FieldLastActivity, authsession.FieldAbsoluteExpiry, authsession.FieldIdleExpiry: values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } } return values, nil } // assignValues assigns the values that were returned from sql.Rows (after scanning) // to the AuthSession fields. func (_m *AuthSession) assignValues(columns []string, values []any) error { if m, n := len(values), len(columns); m < n { return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) } for i := range columns { switch columns[i] { case authsession.FieldID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field id", values[i]) } else if value.Valid { _m.ID = value.String } case authsession.FieldUserID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field user_id", values[i]) } else if value.Valid { _m.UserID = value.String } case authsession.FieldConnectorID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field connector_id", values[i]) } else if value.Valid { _m.ConnectorID = value.String } case authsession.FieldNonce: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field nonce", values[i]) } else if value.Valid { _m.Nonce = value.String } case authsession.FieldClientStates: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field client_states", values[i]) } else if value != nil { _m.ClientStates = *value } case authsession.FieldCreatedAt: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { _m.CreatedAt = value.Time } case authsession.FieldLastActivity: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field last_activity", values[i]) } else if value.Valid { _m.LastActivity = value.Time } case authsession.FieldIPAddress: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field ip_address", values[i]) } else if value.Valid { _m.IPAddress = value.String } case authsession.FieldUserAgent: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field user_agent", values[i]) } else if value.Valid { _m.UserAgent = value.String } case authsession.FieldAbsoluteExpiry: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field absolute_expiry", values[i]) } else if value.Valid { _m.AbsoluteExpiry = value.Time } case authsession.FieldIdleExpiry: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field idle_expiry", values[i]) } else if value.Valid { _m.IdleExpiry = value.Time } default: _m.selectValues.Set(columns[i], values[i]) } } return nil } // Value returns the ent.Value that was dynamically selected and assigned to the AuthSession. // This includes values selected through modifiers, order, etc. func (_m *AuthSession) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } // Update returns a builder for updating this AuthSession. // Note that you need to call AuthSession.Unwrap() before calling this method if this AuthSession // was returned from a transaction, and the transaction was committed or rolled back. func (_m *AuthSession) Update() *AuthSessionUpdateOne { return NewAuthSessionClient(_m.config).UpdateOne(_m) } // Unwrap unwraps the AuthSession entity that was returned from a transaction after it was closed, // so that all future queries will be executed through the driver which created the transaction. func (_m *AuthSession) Unwrap() *AuthSession { _tx, ok := _m.config.driver.(*txDriver) if !ok { panic("db: AuthSession is not a transactional entity") } _m.config.driver = _tx.drv return _m } // String implements the fmt.Stringer. func (_m *AuthSession) String() string { var builder strings.Builder builder.WriteString("AuthSession(") builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) builder.WriteString("user_id=") builder.WriteString(_m.UserID) builder.WriteString(", ") builder.WriteString("connector_id=") builder.WriteString(_m.ConnectorID) builder.WriteString(", ") builder.WriteString("nonce=") builder.WriteString(_m.Nonce) builder.WriteString(", ") builder.WriteString("client_states=") builder.WriteString(fmt.Sprintf("%v", _m.ClientStates)) builder.WriteString(", ") builder.WriteString("created_at=") builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("last_activity=") builder.WriteString(_m.LastActivity.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("ip_address=") builder.WriteString(_m.IPAddress) builder.WriteString(", ") builder.WriteString("user_agent=") builder.WriteString(_m.UserAgent) builder.WriteString(", ") builder.WriteString("absolute_expiry=") builder.WriteString(_m.AbsoluteExpiry.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("idle_expiry=") builder.WriteString(_m.IdleExpiry.Format(time.ANSIC)) builder.WriteByte(')') return builder.String() } // AuthSessions is a parsable slice of AuthSession. type AuthSessions []*AuthSession ================================================ FILE: storage/ent/db/authsession_create.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/authsession" ) // AuthSessionCreate is the builder for creating a AuthSession entity. type AuthSessionCreate struct { config mutation *AuthSessionMutation hooks []Hook } // SetUserID sets the "user_id" field. func (_c *AuthSessionCreate) SetUserID(v string) *AuthSessionCreate { _c.mutation.SetUserID(v) return _c } // SetConnectorID sets the "connector_id" field. func (_c *AuthSessionCreate) SetConnectorID(v string) *AuthSessionCreate { _c.mutation.SetConnectorID(v) return _c } // SetNonce sets the "nonce" field. func (_c *AuthSessionCreate) SetNonce(v string) *AuthSessionCreate { _c.mutation.SetNonce(v) return _c } // SetClientStates sets the "client_states" field. func (_c *AuthSessionCreate) SetClientStates(v []byte) *AuthSessionCreate { _c.mutation.SetClientStates(v) return _c } // SetCreatedAt sets the "created_at" field. func (_c *AuthSessionCreate) SetCreatedAt(v time.Time) *AuthSessionCreate { _c.mutation.SetCreatedAt(v) return _c } // SetLastActivity sets the "last_activity" field. func (_c *AuthSessionCreate) SetLastActivity(v time.Time) *AuthSessionCreate { _c.mutation.SetLastActivity(v) return _c } // SetIPAddress sets the "ip_address" field. func (_c *AuthSessionCreate) SetIPAddress(v string) *AuthSessionCreate { _c.mutation.SetIPAddress(v) return _c } // SetNillableIPAddress sets the "ip_address" field if the given value is not nil. func (_c *AuthSessionCreate) SetNillableIPAddress(v *string) *AuthSessionCreate { if v != nil { _c.SetIPAddress(*v) } return _c } // SetUserAgent sets the "user_agent" field. func (_c *AuthSessionCreate) SetUserAgent(v string) *AuthSessionCreate { _c.mutation.SetUserAgent(v) return _c } // SetNillableUserAgent sets the "user_agent" field if the given value is not nil. func (_c *AuthSessionCreate) SetNillableUserAgent(v *string) *AuthSessionCreate { if v != nil { _c.SetUserAgent(*v) } return _c } // SetAbsoluteExpiry sets the "absolute_expiry" field. func (_c *AuthSessionCreate) SetAbsoluteExpiry(v time.Time) *AuthSessionCreate { _c.mutation.SetAbsoluteExpiry(v) return _c } // SetIdleExpiry sets the "idle_expiry" field. func (_c *AuthSessionCreate) SetIdleExpiry(v time.Time) *AuthSessionCreate { _c.mutation.SetIdleExpiry(v) return _c } // SetID sets the "id" field. func (_c *AuthSessionCreate) SetID(v string) *AuthSessionCreate { _c.mutation.SetID(v) return _c } // Mutation returns the AuthSessionMutation object of the builder. func (_c *AuthSessionCreate) Mutation() *AuthSessionMutation { return _c.mutation } // Save creates the AuthSession in the database. func (_c *AuthSessionCreate) Save(ctx context.Context) (*AuthSession, error) { _c.defaults() return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) } // SaveX calls Save and panics if Save returns an error. func (_c *AuthSessionCreate) SaveX(ctx context.Context) *AuthSession { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *AuthSessionCreate) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *AuthSessionCreate) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } // defaults sets the default values of the builder before save. func (_c *AuthSessionCreate) defaults() { if _, ok := _c.mutation.IPAddress(); !ok { v := authsession.DefaultIPAddress _c.mutation.SetIPAddress(v) } if _, ok := _c.mutation.UserAgent(); !ok { v := authsession.DefaultUserAgent _c.mutation.SetUserAgent(v) } } // check runs all checks and user-defined validators on the builder. func (_c *AuthSessionCreate) check() error { if _, ok := _c.mutation.UserID(); !ok { return &ValidationError{Name: "user_id", err: errors.New(`db: missing required field "AuthSession.user_id"`)} } if v, ok := _c.mutation.UserID(); ok { if err := authsession.UserIDValidator(v); err != nil { return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "AuthSession.user_id": %w`, err)} } } if _, ok := _c.mutation.ConnectorID(); !ok { return &ValidationError{Name: "connector_id", err: errors.New(`db: missing required field "AuthSession.connector_id"`)} } if v, ok := _c.mutation.ConnectorID(); ok { if err := authsession.ConnectorIDValidator(v); err != nil { return &ValidationError{Name: "connector_id", err: fmt.Errorf(`db: validator failed for field "AuthSession.connector_id": %w`, err)} } } if _, ok := _c.mutation.Nonce(); !ok { return &ValidationError{Name: "nonce", err: errors.New(`db: missing required field "AuthSession.nonce"`)} } if v, ok := _c.mutation.Nonce(); ok { if err := authsession.NonceValidator(v); err != nil { return &ValidationError{Name: "nonce", err: fmt.Errorf(`db: validator failed for field "AuthSession.nonce": %w`, err)} } } if _, ok := _c.mutation.ClientStates(); !ok { return &ValidationError{Name: "client_states", err: errors.New(`db: missing required field "AuthSession.client_states"`)} } if _, ok := _c.mutation.CreatedAt(); !ok { return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "AuthSession.created_at"`)} } if _, ok := _c.mutation.LastActivity(); !ok { return &ValidationError{Name: "last_activity", err: errors.New(`db: missing required field "AuthSession.last_activity"`)} } if _, ok := _c.mutation.IPAddress(); !ok { return &ValidationError{Name: "ip_address", err: errors.New(`db: missing required field "AuthSession.ip_address"`)} } if _, ok := _c.mutation.UserAgent(); !ok { return &ValidationError{Name: "user_agent", err: errors.New(`db: missing required field "AuthSession.user_agent"`)} } if _, ok := _c.mutation.AbsoluteExpiry(); !ok { return &ValidationError{Name: "absolute_expiry", err: errors.New(`db: missing required field "AuthSession.absolute_expiry"`)} } if _, ok := _c.mutation.IdleExpiry(); !ok { return &ValidationError{Name: "idle_expiry", err: errors.New(`db: missing required field "AuthSession.idle_expiry"`)} } if v, ok := _c.mutation.ID(); ok { if err := authsession.IDValidator(v); err != nil { return &ValidationError{Name: "id", err: fmt.Errorf(`db: validator failed for field "AuthSession.id": %w`, err)} } } return nil } func (_c *AuthSessionCreate) sqlSave(ctx context.Context) (*AuthSession, error) { if err := _c.check(); err != nil { return nil, err } _node, _spec := _c.createSpec() if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } if _spec.ID.Value != nil { if id, ok := _spec.ID.Value.(string); ok { _node.ID = id } else { return nil, fmt.Errorf("unexpected AuthSession.ID type: %T", _spec.ID.Value) } } _c.mutation.id = &_node.ID _c.mutation.done = true return _node, nil } func (_c *AuthSessionCreate) createSpec() (*AuthSession, *sqlgraph.CreateSpec) { var ( _node = &AuthSession{config: _c.config} _spec = sqlgraph.NewCreateSpec(authsession.Table, sqlgraph.NewFieldSpec(authsession.FieldID, field.TypeString)) ) if id, ok := _c.mutation.ID(); ok { _node.ID = id _spec.ID.Value = id } if value, ok := _c.mutation.UserID(); ok { _spec.SetField(authsession.FieldUserID, field.TypeString, value) _node.UserID = value } if value, ok := _c.mutation.ConnectorID(); ok { _spec.SetField(authsession.FieldConnectorID, field.TypeString, value) _node.ConnectorID = value } if value, ok := _c.mutation.Nonce(); ok { _spec.SetField(authsession.FieldNonce, field.TypeString, value) _node.Nonce = value } if value, ok := _c.mutation.ClientStates(); ok { _spec.SetField(authsession.FieldClientStates, field.TypeBytes, value) _node.ClientStates = value } if value, ok := _c.mutation.CreatedAt(); ok { _spec.SetField(authsession.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := _c.mutation.LastActivity(); ok { _spec.SetField(authsession.FieldLastActivity, field.TypeTime, value) _node.LastActivity = value } if value, ok := _c.mutation.IPAddress(); ok { _spec.SetField(authsession.FieldIPAddress, field.TypeString, value) _node.IPAddress = value } if value, ok := _c.mutation.UserAgent(); ok { _spec.SetField(authsession.FieldUserAgent, field.TypeString, value) _node.UserAgent = value } if value, ok := _c.mutation.AbsoluteExpiry(); ok { _spec.SetField(authsession.FieldAbsoluteExpiry, field.TypeTime, value) _node.AbsoluteExpiry = value } if value, ok := _c.mutation.IdleExpiry(); ok { _spec.SetField(authsession.FieldIdleExpiry, field.TypeTime, value) _node.IdleExpiry = value } return _node, _spec } // AuthSessionCreateBulk is the builder for creating many AuthSession entities in bulk. type AuthSessionCreateBulk struct { config err error builders []*AuthSessionCreate } // Save creates the AuthSession entities in the database. func (_c *AuthSessionCreateBulk) Save(ctx context.Context) ([]*AuthSession, error) { if _c.err != nil { return nil, _c.err } specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) nodes := make([]*AuthSession, len(_c.builders)) mutators := make([]Mutator, len(_c.builders)) for i := range _c.builders { func(i int, root context.Context) { builder := _c.builders[i] builder.defaults() var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*AuthSessionMutation) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } if err := builder.check(); err != nil { return nil, err } builder.mutation = mutation var err error nodes[i], specs[i] = builder.createSpec() if i < len(mutators)-1 { _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) } else { spec := &sqlgraph.BatchCreateSpec{Nodes: specs} // Invoke the actual operation on the latest mutation in the chain. if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } } } if err != nil { return nil, err } mutation.id = &nodes[i].ID mutation.done = true return nodes[i], nil }) for i := len(builder.hooks) - 1; i >= 0; i-- { mut = builder.hooks[i](mut) } mutators[i] = mut }(i, ctx) } if len(mutators) > 0 { if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { return nil, err } } return nodes, nil } // SaveX is like Save, but panics if an error occurs. func (_c *AuthSessionCreateBulk) SaveX(ctx context.Context) []*AuthSession { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *AuthSessionCreateBulk) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *AuthSessionCreateBulk) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/authsession_delete.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/authsession" "github.com/dexidp/dex/storage/ent/db/predicate" ) // AuthSessionDelete is the builder for deleting a AuthSession entity. type AuthSessionDelete struct { config hooks []Hook mutation *AuthSessionMutation } // Where appends a list predicates to the AuthSessionDelete builder. func (_d *AuthSessionDelete) Where(ps ...predicate.AuthSession) *AuthSessionDelete { _d.mutation.Where(ps...) return _d } // Exec executes the deletion query and returns how many vertices were deleted. func (_d *AuthSessionDelete) Exec(ctx context.Context) (int, error) { return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) } // ExecX is like Exec, but panics if an error occurs. func (_d *AuthSessionDelete) ExecX(ctx context.Context) int { n, err := _d.Exec(ctx) if err != nil { panic(err) } return n } func (_d *AuthSessionDelete) sqlExec(ctx context.Context) (int, error) { _spec := sqlgraph.NewDeleteSpec(authsession.Table, sqlgraph.NewFieldSpec(authsession.FieldID, field.TypeString)) if ps := _d.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) if err != nil && sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } _d.mutation.done = true return affected, err } // AuthSessionDeleteOne is the builder for deleting a single AuthSession entity. type AuthSessionDeleteOne struct { _d *AuthSessionDelete } // Where appends a list predicates to the AuthSessionDelete builder. func (_d *AuthSessionDeleteOne) Where(ps ...predicate.AuthSession) *AuthSessionDeleteOne { _d._d.mutation.Where(ps...) return _d } // Exec executes the deletion query. func (_d *AuthSessionDeleteOne) Exec(ctx context.Context) error { n, err := _d._d.Exec(ctx) switch { case err != nil: return err case n == 0: return &NotFoundError{authsession.Label} default: return nil } } // ExecX is like Exec, but panics if an error occurs. func (_d *AuthSessionDeleteOne) ExecX(ctx context.Context) { if err := _d.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/authsession_query.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "fmt" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/authsession" "github.com/dexidp/dex/storage/ent/db/predicate" ) // AuthSessionQuery is the builder for querying AuthSession entities. type AuthSessionQuery struct { config ctx *QueryContext order []authsession.OrderOption inters []Interceptor predicates []predicate.AuthSession // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) } // Where adds a new predicate for the AuthSessionQuery builder. func (_q *AuthSessionQuery) Where(ps ...predicate.AuthSession) *AuthSessionQuery { _q.predicates = append(_q.predicates, ps...) return _q } // Limit the number of records to be returned by this query. func (_q *AuthSessionQuery) Limit(limit int) *AuthSessionQuery { _q.ctx.Limit = &limit return _q } // Offset to start from. func (_q *AuthSessionQuery) Offset(offset int) *AuthSessionQuery { _q.ctx.Offset = &offset return _q } // Unique configures the query builder to filter duplicate records on query. // By default, unique is set to true, and can be disabled using this method. func (_q *AuthSessionQuery) Unique(unique bool) *AuthSessionQuery { _q.ctx.Unique = &unique return _q } // Order specifies how the records should be ordered. func (_q *AuthSessionQuery) Order(o ...authsession.OrderOption) *AuthSessionQuery { _q.order = append(_q.order, o...) return _q } // First returns the first AuthSession entity from the query. // Returns a *NotFoundError when no AuthSession was found. func (_q *AuthSessionQuery) First(ctx context.Context) (*AuthSession, error) { nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) if err != nil { return nil, err } if len(nodes) == 0 { return nil, &NotFoundError{authsession.Label} } return nodes[0], nil } // FirstX is like First, but panics if an error occurs. func (_q *AuthSessionQuery) FirstX(ctx context.Context) *AuthSession { node, err := _q.First(ctx) if err != nil && !IsNotFound(err) { panic(err) } return node } // FirstID returns the first AuthSession ID from the query. // Returns a *NotFoundError when no AuthSession ID was found. func (_q *AuthSessionQuery) FirstID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { return } if len(ids) == 0 { err = &NotFoundError{authsession.Label} return } return ids[0], nil } // FirstIDX is like FirstID, but panics if an error occurs. func (_q *AuthSessionQuery) FirstIDX(ctx context.Context) string { id, err := _q.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) } return id } // Only returns a single AuthSession entity found by the query, ensuring it only returns one. // Returns a *NotSingularError when more than one AuthSession entity is found. // Returns a *NotFoundError when no AuthSession entities are found. func (_q *AuthSessionQuery) Only(ctx context.Context) (*AuthSession, error) { nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) if err != nil { return nil, err } switch len(nodes) { case 1: return nodes[0], nil case 0: return nil, &NotFoundError{authsession.Label} default: return nil, &NotSingularError{authsession.Label} } } // OnlyX is like Only, but panics if an error occurs. func (_q *AuthSessionQuery) OnlyX(ctx context.Context) *AuthSession { node, err := _q.Only(ctx) if err != nil { panic(err) } return node } // OnlyID is like Only, but returns the only AuthSession ID in the query. // Returns a *NotSingularError when more than one AuthSession ID is found. // Returns a *NotFoundError when no entities are found. func (_q *AuthSessionQuery) OnlyID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { return } switch len(ids) { case 1: id = ids[0] case 0: err = &NotFoundError{authsession.Label} default: err = &NotSingularError{authsession.Label} } return } // OnlyIDX is like OnlyID, but panics if an error occurs. func (_q *AuthSessionQuery) OnlyIDX(ctx context.Context) string { id, err := _q.OnlyID(ctx) if err != nil { panic(err) } return id } // All executes the query and returns a list of AuthSessions. func (_q *AuthSessionQuery) All(ctx context.Context) ([]*AuthSession, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) if err := _q.prepareQuery(ctx); err != nil { return nil, err } qr := querierAll[[]*AuthSession, *AuthSessionQuery]() return withInterceptors[[]*AuthSession](ctx, _q, qr, _q.inters) } // AllX is like All, but panics if an error occurs. func (_q *AuthSessionQuery) AllX(ctx context.Context) []*AuthSession { nodes, err := _q.All(ctx) if err != nil { panic(err) } return nodes } // IDs executes the query and returns a list of AuthSession IDs. func (_q *AuthSessionQuery) IDs(ctx context.Context) (ids []string, err error) { if _q.ctx.Unique == nil && _q.path != nil { _q.Unique(true) } ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) if err = _q.Select(authsession.FieldID).Scan(ctx, &ids); err != nil { return nil, err } return ids, nil } // IDsX is like IDs, but panics if an error occurs. func (_q *AuthSessionQuery) IDsX(ctx context.Context) []string { ids, err := _q.IDs(ctx) if err != nil { panic(err) } return ids } // Count returns the count of the given query. func (_q *AuthSessionQuery) Count(ctx context.Context) (int, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) if err := _q.prepareQuery(ctx); err != nil { return 0, err } return withInterceptors[int](ctx, _q, querierCount[*AuthSessionQuery](), _q.inters) } // CountX is like Count, but panics if an error occurs. func (_q *AuthSessionQuery) CountX(ctx context.Context) int { count, err := _q.Count(ctx) if err != nil { panic(err) } return count } // Exist returns true if the query has elements in the graph. func (_q *AuthSessionQuery) Exist(ctx context.Context) (bool, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) switch _, err := _q.FirstID(ctx); { case IsNotFound(err): return false, nil case err != nil: return false, fmt.Errorf("db: check existence: %w", err) default: return true, nil } } // ExistX is like Exist, but panics if an error occurs. func (_q *AuthSessionQuery) ExistX(ctx context.Context) bool { exist, err := _q.Exist(ctx) if err != nil { panic(err) } return exist } // Clone returns a duplicate of the AuthSessionQuery builder, including all associated steps. It can be // used to prepare common query builders and use them differently after the clone is made. func (_q *AuthSessionQuery) Clone() *AuthSessionQuery { if _q == nil { return nil } return &AuthSessionQuery{ config: _q.config, ctx: _q.ctx.Clone(), order: append([]authsession.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.AuthSession{}, _q.predicates...), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // // Example: // // var v []struct { // UserID string `json:"user_id,omitempty"` // Count int `json:"count,omitempty"` // } // // client.AuthSession.Query(). // GroupBy(authsession.FieldUserID). // Aggregate(db.Count()). // Scan(ctx, &v) func (_q *AuthSessionQuery) GroupBy(field string, fields ...string) *AuthSessionGroupBy { _q.ctx.Fields = append([]string{field}, fields...) grbuild := &AuthSessionGroupBy{build: _q} grbuild.flds = &_q.ctx.Fields grbuild.label = authsession.Label grbuild.scan = grbuild.Scan return grbuild } // Select allows the selection one or more fields/columns for the given query, // instead of selecting all fields in the entity. // // Example: // // var v []struct { // UserID string `json:"user_id,omitempty"` // } // // client.AuthSession.Query(). // Select(authsession.FieldUserID). // Scan(ctx, &v) func (_q *AuthSessionQuery) Select(fields ...string) *AuthSessionSelect { _q.ctx.Fields = append(_q.ctx.Fields, fields...) sbuild := &AuthSessionSelect{AuthSessionQuery: _q} sbuild.label = authsession.Label sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan return sbuild } // Aggregate returns a AuthSessionSelect configured with the given aggregations. func (_q *AuthSessionQuery) Aggregate(fns ...AggregateFunc) *AuthSessionSelect { return _q.Select().Aggregate(fns...) } func (_q *AuthSessionQuery) prepareQuery(ctx context.Context) error { for _, inter := range _q.inters { if inter == nil { return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") } if trv, ok := inter.(Traverser); ok { if err := trv.Traverse(ctx, _q); err != nil { return err } } } for _, f := range _q.ctx.Fields { if !authsession.ValidColumn(f) { return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } } if _q.path != nil { prev, err := _q.path(ctx) if err != nil { return err } _q.sql = prev } return nil } func (_q *AuthSessionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AuthSession, error) { var ( nodes = []*AuthSession{} _spec = _q.querySpec() ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*AuthSession).scanValues(nil, columns) } _spec.Assign = func(columns []string, values []any) error { node := &AuthSession{config: _q.config} nodes = append(nodes, node) return node.assignValues(columns, values) } for i := range hooks { hooks[i](ctx, _spec) } if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { return nil, err } if len(nodes) == 0 { return nodes, nil } return nodes, nil } func (_q *AuthSessionQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields if len(_q.ctx.Fields) > 0 { _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique } return sqlgraph.CountNodes(ctx, _q.driver, _spec) } func (_q *AuthSessionQuery) querySpec() *sqlgraph.QuerySpec { _spec := sqlgraph.NewQuerySpec(authsession.Table, authsession.Columns, sqlgraph.NewFieldSpec(authsession.FieldID, field.TypeString)) _spec.From = _q.sql if unique := _q.ctx.Unique; unique != nil { _spec.Unique = *unique } else if _q.path != nil { _spec.Unique = true } if fields := _q.ctx.Fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, authsession.FieldID) for i := range fields { if fields[i] != authsession.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } } if ps := _q.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if limit := _q.ctx.Limit; limit != nil { _spec.Limit = *limit } if offset := _q.ctx.Offset; offset != nil { _spec.Offset = *offset } if ps := _q.order; len(ps) > 0 { _spec.Order = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } return _spec } func (_q *AuthSessionQuery) sqlQuery(ctx context.Context) *sql.Selector { builder := sql.Dialect(_q.driver.Dialect()) t1 := builder.Table(authsession.Table) columns := _q.ctx.Fields if len(columns) == 0 { columns = authsession.Columns } selector := builder.Select(t1.Columns(columns...)...).From(t1) if _q.sql != nil { selector = _q.sql selector.Select(selector.Columns(columns...)...) } if _q.ctx.Unique != nil && *_q.ctx.Unique { selector.Distinct() } for _, p := range _q.predicates { p(selector) } for _, p := range _q.order { p(selector) } if offset := _q.ctx.Offset; offset != nil { // limit is mandatory for offset clause. We start // with default value, and override it below if needed. selector.Offset(*offset).Limit(math.MaxInt32) } if limit := _q.ctx.Limit; limit != nil { selector.Limit(*limit) } return selector } // AuthSessionGroupBy is the group-by builder for AuthSession entities. type AuthSessionGroupBy struct { selector build *AuthSessionQuery } // Aggregate adds the given aggregation functions to the group-by query. func (_g *AuthSessionGroupBy) Aggregate(fns ...AggregateFunc) *AuthSessionGroupBy { _g.fns = append(_g.fns, fns...) return _g } // Scan applies the selector query and scans the result into the given value. func (_g *AuthSessionGroupBy) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) if err := _g.build.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*AuthSessionQuery, *AuthSessionGroupBy](ctx, _g.build, _g, _g.build.inters, v) } func (_g *AuthSessionGroupBy) sqlScan(ctx context.Context, root *AuthSessionQuery, v any) error { selector := root.sqlQuery(ctx).Select() aggregation := make([]string, 0, len(_g.fns)) for _, fn := range _g.fns { aggregation = append(aggregation, fn(selector)) } if len(selector.SelectedColumns()) == 0 { columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) for _, f := range *_g.flds { columns = append(columns, selector.C(f)) } columns = append(columns, aggregation...) selector.Select(columns...) } selector.GroupBy(selector.Columns(*_g.flds...)...) if err := selector.Err(); err != nil { return err } rows := &sql.Rows{} query, args := selector.Query() if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } // AuthSessionSelect is the builder for selecting fields of AuthSession entities. type AuthSessionSelect struct { *AuthSessionQuery selector } // Aggregate adds the given aggregation functions to the selector query. func (_s *AuthSessionSelect) Aggregate(fns ...AggregateFunc) *AuthSessionSelect { _s.fns = append(_s.fns, fns...) return _s } // Scan applies the selector query and scans the result into the given value. func (_s *AuthSessionSelect) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) if err := _s.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*AuthSessionQuery, *AuthSessionSelect](ctx, _s.AuthSessionQuery, _s, _s.inters, v) } func (_s *AuthSessionSelect) sqlScan(ctx context.Context, root *AuthSessionQuery, v any) error { selector := root.sqlQuery(ctx) aggregation := make([]string, 0, len(_s.fns)) for _, fn := range _s.fns { aggregation = append(aggregation, fn(selector)) } switch n := len(*_s.selector.flds); { case n == 0 && len(aggregation) > 0: selector.Select(aggregation...) case n != 0 && len(aggregation) > 0: selector.AppendSelect(aggregation...) } rows := &sql.Rows{} query, args := selector.Query() if err := _s.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } ================================================ FILE: storage/ent/db/authsession_update.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/authsession" "github.com/dexidp/dex/storage/ent/db/predicate" ) // AuthSessionUpdate is the builder for updating AuthSession entities. type AuthSessionUpdate struct { config hooks []Hook mutation *AuthSessionMutation } // Where appends a list predicates to the AuthSessionUpdate builder. func (_u *AuthSessionUpdate) Where(ps ...predicate.AuthSession) *AuthSessionUpdate { _u.mutation.Where(ps...) return _u } // SetUserID sets the "user_id" field. func (_u *AuthSessionUpdate) SetUserID(v string) *AuthSessionUpdate { _u.mutation.SetUserID(v) return _u } // SetNillableUserID sets the "user_id" field if the given value is not nil. func (_u *AuthSessionUpdate) SetNillableUserID(v *string) *AuthSessionUpdate { if v != nil { _u.SetUserID(*v) } return _u } // SetConnectorID sets the "connector_id" field. func (_u *AuthSessionUpdate) SetConnectorID(v string) *AuthSessionUpdate { _u.mutation.SetConnectorID(v) return _u } // SetNillableConnectorID sets the "connector_id" field if the given value is not nil. func (_u *AuthSessionUpdate) SetNillableConnectorID(v *string) *AuthSessionUpdate { if v != nil { _u.SetConnectorID(*v) } return _u } // SetNonce sets the "nonce" field. func (_u *AuthSessionUpdate) SetNonce(v string) *AuthSessionUpdate { _u.mutation.SetNonce(v) return _u } // SetNillableNonce sets the "nonce" field if the given value is not nil. func (_u *AuthSessionUpdate) SetNillableNonce(v *string) *AuthSessionUpdate { if v != nil { _u.SetNonce(*v) } return _u } // SetClientStates sets the "client_states" field. func (_u *AuthSessionUpdate) SetClientStates(v []byte) *AuthSessionUpdate { _u.mutation.SetClientStates(v) return _u } // SetCreatedAt sets the "created_at" field. func (_u *AuthSessionUpdate) SetCreatedAt(v time.Time) *AuthSessionUpdate { _u.mutation.SetCreatedAt(v) return _u } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. func (_u *AuthSessionUpdate) SetNillableCreatedAt(v *time.Time) *AuthSessionUpdate { if v != nil { _u.SetCreatedAt(*v) } return _u } // SetLastActivity sets the "last_activity" field. func (_u *AuthSessionUpdate) SetLastActivity(v time.Time) *AuthSessionUpdate { _u.mutation.SetLastActivity(v) return _u } // SetNillableLastActivity sets the "last_activity" field if the given value is not nil. func (_u *AuthSessionUpdate) SetNillableLastActivity(v *time.Time) *AuthSessionUpdate { if v != nil { _u.SetLastActivity(*v) } return _u } // SetIPAddress sets the "ip_address" field. func (_u *AuthSessionUpdate) SetIPAddress(v string) *AuthSessionUpdate { _u.mutation.SetIPAddress(v) return _u } // SetNillableIPAddress sets the "ip_address" field if the given value is not nil. func (_u *AuthSessionUpdate) SetNillableIPAddress(v *string) *AuthSessionUpdate { if v != nil { _u.SetIPAddress(*v) } return _u } // SetUserAgent sets the "user_agent" field. func (_u *AuthSessionUpdate) SetUserAgent(v string) *AuthSessionUpdate { _u.mutation.SetUserAgent(v) return _u } // SetNillableUserAgent sets the "user_agent" field if the given value is not nil. func (_u *AuthSessionUpdate) SetNillableUserAgent(v *string) *AuthSessionUpdate { if v != nil { _u.SetUserAgent(*v) } return _u } // SetAbsoluteExpiry sets the "absolute_expiry" field. func (_u *AuthSessionUpdate) SetAbsoluteExpiry(v time.Time) *AuthSessionUpdate { _u.mutation.SetAbsoluteExpiry(v) return _u } // SetNillableAbsoluteExpiry sets the "absolute_expiry" field if the given value is not nil. func (_u *AuthSessionUpdate) SetNillableAbsoluteExpiry(v *time.Time) *AuthSessionUpdate { if v != nil { _u.SetAbsoluteExpiry(*v) } return _u } // SetIdleExpiry sets the "idle_expiry" field. func (_u *AuthSessionUpdate) SetIdleExpiry(v time.Time) *AuthSessionUpdate { _u.mutation.SetIdleExpiry(v) return _u } // SetNillableIdleExpiry sets the "idle_expiry" field if the given value is not nil. func (_u *AuthSessionUpdate) SetNillableIdleExpiry(v *time.Time) *AuthSessionUpdate { if v != nil { _u.SetIdleExpiry(*v) } return _u } // Mutation returns the AuthSessionMutation object of the builder. func (_u *AuthSessionUpdate) Mutation() *AuthSessionMutation { return _u.mutation } // Save executes the query and returns the number of nodes affected by the update operation. func (_u *AuthSessionUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *AuthSessionUpdate) SaveX(ctx context.Context) int { affected, err := _u.Save(ctx) if err != nil { panic(err) } return affected } // Exec executes the query. func (_u *AuthSessionUpdate) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *AuthSessionUpdate) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *AuthSessionUpdate) check() error { if v, ok := _u.mutation.UserID(); ok { if err := authsession.UserIDValidator(v); err != nil { return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "AuthSession.user_id": %w`, err)} } } if v, ok := _u.mutation.ConnectorID(); ok { if err := authsession.ConnectorIDValidator(v); err != nil { return &ValidationError{Name: "connector_id", err: fmt.Errorf(`db: validator failed for field "AuthSession.connector_id": %w`, err)} } } if v, ok := _u.mutation.Nonce(); ok { if err := authsession.NonceValidator(v); err != nil { return &ValidationError{Name: "nonce", err: fmt.Errorf(`db: validator failed for field "AuthSession.nonce": %w`, err)} } } return nil } func (_u *AuthSessionUpdate) sqlSave(ctx context.Context) (_node int, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(authsession.Table, authsession.Columns, sqlgraph.NewFieldSpec(authsession.FieldID, field.TypeString)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.UserID(); ok { _spec.SetField(authsession.FieldUserID, field.TypeString, value) } if value, ok := _u.mutation.ConnectorID(); ok { _spec.SetField(authsession.FieldConnectorID, field.TypeString, value) } if value, ok := _u.mutation.Nonce(); ok { _spec.SetField(authsession.FieldNonce, field.TypeString, value) } if value, ok := _u.mutation.ClientStates(); ok { _spec.SetField(authsession.FieldClientStates, field.TypeBytes, value) } if value, ok := _u.mutation.CreatedAt(); ok { _spec.SetField(authsession.FieldCreatedAt, field.TypeTime, value) } if value, ok := _u.mutation.LastActivity(); ok { _spec.SetField(authsession.FieldLastActivity, field.TypeTime, value) } if value, ok := _u.mutation.IPAddress(); ok { _spec.SetField(authsession.FieldIPAddress, field.TypeString, value) } if value, ok := _u.mutation.UserAgent(); ok { _spec.SetField(authsession.FieldUserAgent, field.TypeString, value) } if value, ok := _u.mutation.AbsoluteExpiry(); ok { _spec.SetField(authsession.FieldAbsoluteExpiry, field.TypeTime, value) } if value, ok := _u.mutation.IdleExpiry(); ok { _spec.SetField(authsession.FieldIdleExpiry, field.TypeTime, value) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{authsession.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return 0, err } _u.mutation.done = true return _node, nil } // AuthSessionUpdateOne is the builder for updating a single AuthSession entity. type AuthSessionUpdateOne struct { config fields []string hooks []Hook mutation *AuthSessionMutation } // SetUserID sets the "user_id" field. func (_u *AuthSessionUpdateOne) SetUserID(v string) *AuthSessionUpdateOne { _u.mutation.SetUserID(v) return _u } // SetNillableUserID sets the "user_id" field if the given value is not nil. func (_u *AuthSessionUpdateOne) SetNillableUserID(v *string) *AuthSessionUpdateOne { if v != nil { _u.SetUserID(*v) } return _u } // SetConnectorID sets the "connector_id" field. func (_u *AuthSessionUpdateOne) SetConnectorID(v string) *AuthSessionUpdateOne { _u.mutation.SetConnectorID(v) return _u } // SetNillableConnectorID sets the "connector_id" field if the given value is not nil. func (_u *AuthSessionUpdateOne) SetNillableConnectorID(v *string) *AuthSessionUpdateOne { if v != nil { _u.SetConnectorID(*v) } return _u } // SetNonce sets the "nonce" field. func (_u *AuthSessionUpdateOne) SetNonce(v string) *AuthSessionUpdateOne { _u.mutation.SetNonce(v) return _u } // SetNillableNonce sets the "nonce" field if the given value is not nil. func (_u *AuthSessionUpdateOne) SetNillableNonce(v *string) *AuthSessionUpdateOne { if v != nil { _u.SetNonce(*v) } return _u } // SetClientStates sets the "client_states" field. func (_u *AuthSessionUpdateOne) SetClientStates(v []byte) *AuthSessionUpdateOne { _u.mutation.SetClientStates(v) return _u } // SetCreatedAt sets the "created_at" field. func (_u *AuthSessionUpdateOne) SetCreatedAt(v time.Time) *AuthSessionUpdateOne { _u.mutation.SetCreatedAt(v) return _u } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. func (_u *AuthSessionUpdateOne) SetNillableCreatedAt(v *time.Time) *AuthSessionUpdateOne { if v != nil { _u.SetCreatedAt(*v) } return _u } // SetLastActivity sets the "last_activity" field. func (_u *AuthSessionUpdateOne) SetLastActivity(v time.Time) *AuthSessionUpdateOne { _u.mutation.SetLastActivity(v) return _u } // SetNillableLastActivity sets the "last_activity" field if the given value is not nil. func (_u *AuthSessionUpdateOne) SetNillableLastActivity(v *time.Time) *AuthSessionUpdateOne { if v != nil { _u.SetLastActivity(*v) } return _u } // SetIPAddress sets the "ip_address" field. func (_u *AuthSessionUpdateOne) SetIPAddress(v string) *AuthSessionUpdateOne { _u.mutation.SetIPAddress(v) return _u } // SetNillableIPAddress sets the "ip_address" field if the given value is not nil. func (_u *AuthSessionUpdateOne) SetNillableIPAddress(v *string) *AuthSessionUpdateOne { if v != nil { _u.SetIPAddress(*v) } return _u } // SetUserAgent sets the "user_agent" field. func (_u *AuthSessionUpdateOne) SetUserAgent(v string) *AuthSessionUpdateOne { _u.mutation.SetUserAgent(v) return _u } // SetNillableUserAgent sets the "user_agent" field if the given value is not nil. func (_u *AuthSessionUpdateOne) SetNillableUserAgent(v *string) *AuthSessionUpdateOne { if v != nil { _u.SetUserAgent(*v) } return _u } // SetAbsoluteExpiry sets the "absolute_expiry" field. func (_u *AuthSessionUpdateOne) SetAbsoluteExpiry(v time.Time) *AuthSessionUpdateOne { _u.mutation.SetAbsoluteExpiry(v) return _u } // SetNillableAbsoluteExpiry sets the "absolute_expiry" field if the given value is not nil. func (_u *AuthSessionUpdateOne) SetNillableAbsoluteExpiry(v *time.Time) *AuthSessionUpdateOne { if v != nil { _u.SetAbsoluteExpiry(*v) } return _u } // SetIdleExpiry sets the "idle_expiry" field. func (_u *AuthSessionUpdateOne) SetIdleExpiry(v time.Time) *AuthSessionUpdateOne { _u.mutation.SetIdleExpiry(v) return _u } // SetNillableIdleExpiry sets the "idle_expiry" field if the given value is not nil. func (_u *AuthSessionUpdateOne) SetNillableIdleExpiry(v *time.Time) *AuthSessionUpdateOne { if v != nil { _u.SetIdleExpiry(*v) } return _u } // Mutation returns the AuthSessionMutation object of the builder. func (_u *AuthSessionUpdateOne) Mutation() *AuthSessionMutation { return _u.mutation } // Where appends a list predicates to the AuthSessionUpdate builder. func (_u *AuthSessionUpdateOne) Where(ps ...predicate.AuthSession) *AuthSessionUpdateOne { _u.mutation.Where(ps...) return _u } // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (_u *AuthSessionUpdateOne) Select(field string, fields ...string) *AuthSessionUpdateOne { _u.fields = append([]string{field}, fields...) return _u } // Save executes the query and returns the updated AuthSession entity. func (_u *AuthSessionUpdateOne) Save(ctx context.Context) (*AuthSession, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *AuthSessionUpdateOne) SaveX(ctx context.Context) *AuthSession { node, err := _u.Save(ctx) if err != nil { panic(err) } return node } // Exec executes the query on the entity. func (_u *AuthSessionUpdateOne) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *AuthSessionUpdateOne) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *AuthSessionUpdateOne) check() error { if v, ok := _u.mutation.UserID(); ok { if err := authsession.UserIDValidator(v); err != nil { return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "AuthSession.user_id": %w`, err)} } } if v, ok := _u.mutation.ConnectorID(); ok { if err := authsession.ConnectorIDValidator(v); err != nil { return &ValidationError{Name: "connector_id", err: fmt.Errorf(`db: validator failed for field "AuthSession.connector_id": %w`, err)} } } if v, ok := _u.mutation.Nonce(); ok { if err := authsession.NonceValidator(v); err != nil { return &ValidationError{Name: "nonce", err: fmt.Errorf(`db: validator failed for field "AuthSession.nonce": %w`, err)} } } return nil } func (_u *AuthSessionUpdateOne) sqlSave(ctx context.Context) (_node *AuthSession, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(authsession.Table, authsession.Columns, sqlgraph.NewFieldSpec(authsession.FieldID, field.TypeString)) id, ok := _u.mutation.ID() if !ok { return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "AuthSession.id" for update`)} } _spec.Node.ID.Value = id if fields := _u.fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, authsession.FieldID) for _, f := range fields { if !authsession.ValidColumn(f) { return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } if f != authsession.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, f) } } } if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.UserID(); ok { _spec.SetField(authsession.FieldUserID, field.TypeString, value) } if value, ok := _u.mutation.ConnectorID(); ok { _spec.SetField(authsession.FieldConnectorID, field.TypeString, value) } if value, ok := _u.mutation.Nonce(); ok { _spec.SetField(authsession.FieldNonce, field.TypeString, value) } if value, ok := _u.mutation.ClientStates(); ok { _spec.SetField(authsession.FieldClientStates, field.TypeBytes, value) } if value, ok := _u.mutation.CreatedAt(); ok { _spec.SetField(authsession.FieldCreatedAt, field.TypeTime, value) } if value, ok := _u.mutation.LastActivity(); ok { _spec.SetField(authsession.FieldLastActivity, field.TypeTime, value) } if value, ok := _u.mutation.IPAddress(); ok { _spec.SetField(authsession.FieldIPAddress, field.TypeString, value) } if value, ok := _u.mutation.UserAgent(); ok { _spec.SetField(authsession.FieldUserAgent, field.TypeString, value) } if value, ok := _u.mutation.AbsoluteExpiry(); ok { _spec.SetField(authsession.FieldAbsoluteExpiry, field.TypeTime, value) } if value, ok := _u.mutation.IdleExpiry(); ok { _spec.SetField(authsession.FieldIdleExpiry, field.TypeTime, value) } _node = &AuthSession{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{authsession.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } _u.mutation.done = true return _node, nil } ================================================ FILE: storage/ent/db/client.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "log" "reflect" "github.com/dexidp/dex/storage/ent/db/migrate" "entgo.io/ent" "entgo.io/ent/dialect" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/authcode" "github.com/dexidp/dex/storage/ent/db/authrequest" "github.com/dexidp/dex/storage/ent/db/authsession" "github.com/dexidp/dex/storage/ent/db/connector" "github.com/dexidp/dex/storage/ent/db/devicerequest" "github.com/dexidp/dex/storage/ent/db/devicetoken" "github.com/dexidp/dex/storage/ent/db/keys" "github.com/dexidp/dex/storage/ent/db/oauth2client" "github.com/dexidp/dex/storage/ent/db/offlinesession" "github.com/dexidp/dex/storage/ent/db/password" "github.com/dexidp/dex/storage/ent/db/refreshtoken" "github.com/dexidp/dex/storage/ent/db/useridentity" ) // Client is the client that holds all ent builders. type Client struct { config // Schema is the client for creating, migrating and dropping schema. Schema *migrate.Schema // AuthCode is the client for interacting with the AuthCode builders. AuthCode *AuthCodeClient // AuthRequest is the client for interacting with the AuthRequest builders. AuthRequest *AuthRequestClient // AuthSession is the client for interacting with the AuthSession builders. AuthSession *AuthSessionClient // Connector is the client for interacting with the Connector builders. Connector *ConnectorClient // DeviceRequest is the client for interacting with the DeviceRequest builders. DeviceRequest *DeviceRequestClient // DeviceToken is the client for interacting with the DeviceToken builders. DeviceToken *DeviceTokenClient // Keys is the client for interacting with the Keys builders. Keys *KeysClient // OAuth2Client is the client for interacting with the OAuth2Client builders. OAuth2Client *OAuth2ClientClient // OfflineSession is the client for interacting with the OfflineSession builders. OfflineSession *OfflineSessionClient // Password is the client for interacting with the Password builders. Password *PasswordClient // RefreshToken is the client for interacting with the RefreshToken builders. RefreshToken *RefreshTokenClient // UserIdentity is the client for interacting with the UserIdentity builders. UserIdentity *UserIdentityClient } // NewClient creates a new client configured with the given options. func NewClient(opts ...Option) *Client { client := &Client{config: newConfig(opts...)} client.init() return client } func (c *Client) init() { c.Schema = migrate.NewSchema(c.driver) c.AuthCode = NewAuthCodeClient(c.config) c.AuthRequest = NewAuthRequestClient(c.config) c.AuthSession = NewAuthSessionClient(c.config) c.Connector = NewConnectorClient(c.config) c.DeviceRequest = NewDeviceRequestClient(c.config) c.DeviceToken = NewDeviceTokenClient(c.config) c.Keys = NewKeysClient(c.config) c.OAuth2Client = NewOAuth2ClientClient(c.config) c.OfflineSession = NewOfflineSessionClient(c.config) c.Password = NewPasswordClient(c.config) c.RefreshToken = NewRefreshTokenClient(c.config) c.UserIdentity = NewUserIdentityClient(c.config) } type ( // config is the configuration for the client and its builder. config struct { // driver used for executing database requests. driver dialect.Driver // debug enable a debug logging. debug bool // log used for logging on debug mode. log func(...any) // hooks to execute on mutations. hooks *hooks // interceptors to execute on queries. inters *inters } // Option function to configure the client. Option func(*config) ) // newConfig creates a new config for the client. func newConfig(opts ...Option) config { cfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}} cfg.options(opts...) return cfg } // options applies the options on the config object. func (c *config) options(opts ...Option) { for _, opt := range opts { opt(c) } if c.debug { c.driver = dialect.Debug(c.driver, c.log) } } // Debug enables debug logging on the ent.Driver. func Debug() Option { return func(c *config) { c.debug = true } } // Log sets the logging function for debug mode. func Log(fn func(...any)) Option { return func(c *config) { c.log = fn } } // Driver configures the client driver. func Driver(driver dialect.Driver) Option { return func(c *config) { c.driver = driver } } // Open opens a database/sql.DB specified by the driver name and // the data source name, and returns a new client attached to it. // Optional parameters can be added for configuring the client. func Open(driverName, dataSourceName string, options ...Option) (*Client, error) { switch driverName { case dialect.MySQL, dialect.Postgres, dialect.SQLite: drv, err := sql.Open(driverName, dataSourceName) if err != nil { return nil, err } return NewClient(append(options, Driver(drv))...), nil default: return nil, fmt.Errorf("unsupported driver: %q", driverName) } } // ErrTxStarted is returned when trying to start a new transaction from a transactional client. var ErrTxStarted = errors.New("db: cannot start a transaction within a transaction") // Tx returns a new transactional client. The provided context // is used until the transaction is committed or rolled back. func (c *Client) Tx(ctx context.Context) (*Tx, error) { if _, ok := c.driver.(*txDriver); ok { return nil, ErrTxStarted } tx, err := newTx(ctx, c.driver) if err != nil { return nil, fmt.Errorf("db: starting a transaction: %w", err) } cfg := c.config cfg.driver = tx return &Tx{ ctx: ctx, config: cfg, AuthCode: NewAuthCodeClient(cfg), AuthRequest: NewAuthRequestClient(cfg), AuthSession: NewAuthSessionClient(cfg), Connector: NewConnectorClient(cfg), DeviceRequest: NewDeviceRequestClient(cfg), DeviceToken: NewDeviceTokenClient(cfg), Keys: NewKeysClient(cfg), OAuth2Client: NewOAuth2ClientClient(cfg), OfflineSession: NewOfflineSessionClient(cfg), Password: NewPasswordClient(cfg), RefreshToken: NewRefreshTokenClient(cfg), UserIdentity: NewUserIdentityClient(cfg), }, nil } // BeginTx returns a transactional client with specified options. func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { if _, ok := c.driver.(*txDriver); ok { return nil, errors.New("ent: cannot start a transaction within a transaction") } tx, err := c.driver.(interface { BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error) }).BeginTx(ctx, opts) if err != nil { return nil, fmt.Errorf("ent: starting a transaction: %w", err) } cfg := c.config cfg.driver = &txDriver{tx: tx, drv: c.driver} return &Tx{ ctx: ctx, config: cfg, AuthCode: NewAuthCodeClient(cfg), AuthRequest: NewAuthRequestClient(cfg), AuthSession: NewAuthSessionClient(cfg), Connector: NewConnectorClient(cfg), DeviceRequest: NewDeviceRequestClient(cfg), DeviceToken: NewDeviceTokenClient(cfg), Keys: NewKeysClient(cfg), OAuth2Client: NewOAuth2ClientClient(cfg), OfflineSession: NewOfflineSessionClient(cfg), Password: NewPasswordClient(cfg), RefreshToken: NewRefreshTokenClient(cfg), UserIdentity: NewUserIdentityClient(cfg), }, nil } // Debug returns a new debug-client. It's used to get verbose logging on specific operations. // // client.Debug(). // AuthCode. // Query(). // Count(ctx) func (c *Client) Debug() *Client { if c.debug { return c } cfg := c.config cfg.driver = dialect.Debug(c.driver, c.log) client := &Client{config: cfg} client.init() return client } // Close closes the database connection and prevents new queries from starting. func (c *Client) Close() error { return c.driver.Close() } // Use adds the mutation hooks to all the entity clients. // In order to add hooks to a specific client, call: `client.Node.Use(...)`. func (c *Client) Use(hooks ...Hook) { for _, n := range []interface{ Use(...Hook) }{ c.AuthCode, c.AuthRequest, c.AuthSession, c.Connector, c.DeviceRequest, c.DeviceToken, c.Keys, c.OAuth2Client, c.OfflineSession, c.Password, c.RefreshToken, c.UserIdentity, } { n.Use(hooks...) } } // Intercept adds the query interceptors to all the entity clients. // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. func (c *Client) Intercept(interceptors ...Interceptor) { for _, n := range []interface{ Intercept(...Interceptor) }{ c.AuthCode, c.AuthRequest, c.AuthSession, c.Connector, c.DeviceRequest, c.DeviceToken, c.Keys, c.OAuth2Client, c.OfflineSession, c.Password, c.RefreshToken, c.UserIdentity, } { n.Intercept(interceptors...) } } // Mutate implements the ent.Mutator interface. func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { switch m := m.(type) { case *AuthCodeMutation: return c.AuthCode.mutate(ctx, m) case *AuthRequestMutation: return c.AuthRequest.mutate(ctx, m) case *AuthSessionMutation: return c.AuthSession.mutate(ctx, m) case *ConnectorMutation: return c.Connector.mutate(ctx, m) case *DeviceRequestMutation: return c.DeviceRequest.mutate(ctx, m) case *DeviceTokenMutation: return c.DeviceToken.mutate(ctx, m) case *KeysMutation: return c.Keys.mutate(ctx, m) case *OAuth2ClientMutation: return c.OAuth2Client.mutate(ctx, m) case *OfflineSessionMutation: return c.OfflineSession.mutate(ctx, m) case *PasswordMutation: return c.Password.mutate(ctx, m) case *RefreshTokenMutation: return c.RefreshToken.mutate(ctx, m) case *UserIdentityMutation: return c.UserIdentity.mutate(ctx, m) default: return nil, fmt.Errorf("db: unknown mutation type %T", m) } } // AuthCodeClient is a client for the AuthCode schema. type AuthCodeClient struct { config } // NewAuthCodeClient returns a client for the AuthCode from the given config. func NewAuthCodeClient(c config) *AuthCodeClient { return &AuthCodeClient{config: c} } // Use adds a list of mutation hooks to the hooks stack. // A call to `Use(f, g, h)` equals to `authcode.Hooks(f(g(h())))`. func (c *AuthCodeClient) Use(hooks ...Hook) { c.hooks.AuthCode = append(c.hooks.AuthCode, hooks...) } // Intercept adds a list of query interceptors to the interceptors stack. // A call to `Intercept(f, g, h)` equals to `authcode.Intercept(f(g(h())))`. func (c *AuthCodeClient) Intercept(interceptors ...Interceptor) { c.inters.AuthCode = append(c.inters.AuthCode, interceptors...) } // Create returns a builder for creating a AuthCode entity. func (c *AuthCodeClient) Create() *AuthCodeCreate { mutation := newAuthCodeMutation(c.config, OpCreate) return &AuthCodeCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // CreateBulk returns a builder for creating a bulk of AuthCode entities. func (c *AuthCodeClient) CreateBulk(builders ...*AuthCodeCreate) *AuthCodeCreateBulk { return &AuthCodeCreateBulk{config: c.config, builders: builders} } // MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates // a builder and applies setFunc on it. func (c *AuthCodeClient) MapCreateBulk(slice any, setFunc func(*AuthCodeCreate, int)) *AuthCodeCreateBulk { rv := reflect.ValueOf(slice) if rv.Kind() != reflect.Slice { return &AuthCodeCreateBulk{err: fmt.Errorf("calling to AuthCodeClient.MapCreateBulk with wrong type %T, need slice", slice)} } builders := make([]*AuthCodeCreate, rv.Len()) for i := 0; i < rv.Len(); i++ { builders[i] = c.Create() setFunc(builders[i], i) } return &AuthCodeCreateBulk{config: c.config, builders: builders} } // Update returns an update builder for AuthCode. func (c *AuthCodeClient) Update() *AuthCodeUpdate { mutation := newAuthCodeMutation(c.config, OpUpdate) return &AuthCodeUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOne returns an update builder for the given entity. func (c *AuthCodeClient) UpdateOne(_m *AuthCode) *AuthCodeUpdateOne { mutation := newAuthCodeMutation(c.config, OpUpdateOne, withAuthCode(_m)) return &AuthCodeUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOneID returns an update builder for the given id. func (c *AuthCodeClient) UpdateOneID(id string) *AuthCodeUpdateOne { mutation := newAuthCodeMutation(c.config, OpUpdateOne, withAuthCodeID(id)) return &AuthCodeUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // Delete returns a delete builder for AuthCode. func (c *AuthCodeClient) Delete() *AuthCodeDelete { mutation := newAuthCodeMutation(c.config, OpDelete) return &AuthCodeDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} } // DeleteOne returns a builder for deleting the given entity. func (c *AuthCodeClient) DeleteOne(_m *AuthCode) *AuthCodeDeleteOne { return c.DeleteOneID(_m.ID) } // DeleteOneID returns a builder for deleting the given entity by its id. func (c *AuthCodeClient) DeleteOneID(id string) *AuthCodeDeleteOne { builder := c.Delete().Where(authcode.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne return &AuthCodeDeleteOne{builder} } // Query returns a query builder for AuthCode. func (c *AuthCodeClient) Query() *AuthCodeQuery { return &AuthCodeQuery{ config: c.config, ctx: &QueryContext{Type: TypeAuthCode}, inters: c.Interceptors(), } } // Get returns a AuthCode entity by its id. func (c *AuthCodeClient) Get(ctx context.Context, id string) (*AuthCode, error) { return c.Query().Where(authcode.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. func (c *AuthCodeClient) GetX(ctx context.Context, id string) *AuthCode { obj, err := c.Get(ctx, id) if err != nil { panic(err) } return obj } // Hooks returns the client hooks. func (c *AuthCodeClient) Hooks() []Hook { return c.hooks.AuthCode } // Interceptors returns the client interceptors. func (c *AuthCodeClient) Interceptors() []Interceptor { return c.inters.AuthCode } func (c *AuthCodeClient) mutate(ctx context.Context, m *AuthCodeMutation) (Value, error) { switch m.Op() { case OpCreate: return (&AuthCodeCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdate: return (&AuthCodeUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdateOne: return (&AuthCodeUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpDelete, OpDeleteOne: return (&AuthCodeDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) default: return nil, fmt.Errorf("db: unknown AuthCode mutation op: %q", m.Op()) } } // AuthRequestClient is a client for the AuthRequest schema. type AuthRequestClient struct { config } // NewAuthRequestClient returns a client for the AuthRequest from the given config. func NewAuthRequestClient(c config) *AuthRequestClient { return &AuthRequestClient{config: c} } // Use adds a list of mutation hooks to the hooks stack. // A call to `Use(f, g, h)` equals to `authrequest.Hooks(f(g(h())))`. func (c *AuthRequestClient) Use(hooks ...Hook) { c.hooks.AuthRequest = append(c.hooks.AuthRequest, hooks...) } // Intercept adds a list of query interceptors to the interceptors stack. // A call to `Intercept(f, g, h)` equals to `authrequest.Intercept(f(g(h())))`. func (c *AuthRequestClient) Intercept(interceptors ...Interceptor) { c.inters.AuthRequest = append(c.inters.AuthRequest, interceptors...) } // Create returns a builder for creating a AuthRequest entity. func (c *AuthRequestClient) Create() *AuthRequestCreate { mutation := newAuthRequestMutation(c.config, OpCreate) return &AuthRequestCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // CreateBulk returns a builder for creating a bulk of AuthRequest entities. func (c *AuthRequestClient) CreateBulk(builders ...*AuthRequestCreate) *AuthRequestCreateBulk { return &AuthRequestCreateBulk{config: c.config, builders: builders} } // MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates // a builder and applies setFunc on it. func (c *AuthRequestClient) MapCreateBulk(slice any, setFunc func(*AuthRequestCreate, int)) *AuthRequestCreateBulk { rv := reflect.ValueOf(slice) if rv.Kind() != reflect.Slice { return &AuthRequestCreateBulk{err: fmt.Errorf("calling to AuthRequestClient.MapCreateBulk with wrong type %T, need slice", slice)} } builders := make([]*AuthRequestCreate, rv.Len()) for i := 0; i < rv.Len(); i++ { builders[i] = c.Create() setFunc(builders[i], i) } return &AuthRequestCreateBulk{config: c.config, builders: builders} } // Update returns an update builder for AuthRequest. func (c *AuthRequestClient) Update() *AuthRequestUpdate { mutation := newAuthRequestMutation(c.config, OpUpdate) return &AuthRequestUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOne returns an update builder for the given entity. func (c *AuthRequestClient) UpdateOne(_m *AuthRequest) *AuthRequestUpdateOne { mutation := newAuthRequestMutation(c.config, OpUpdateOne, withAuthRequest(_m)) return &AuthRequestUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOneID returns an update builder for the given id. func (c *AuthRequestClient) UpdateOneID(id string) *AuthRequestUpdateOne { mutation := newAuthRequestMutation(c.config, OpUpdateOne, withAuthRequestID(id)) return &AuthRequestUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // Delete returns a delete builder for AuthRequest. func (c *AuthRequestClient) Delete() *AuthRequestDelete { mutation := newAuthRequestMutation(c.config, OpDelete) return &AuthRequestDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} } // DeleteOne returns a builder for deleting the given entity. func (c *AuthRequestClient) DeleteOne(_m *AuthRequest) *AuthRequestDeleteOne { return c.DeleteOneID(_m.ID) } // DeleteOneID returns a builder for deleting the given entity by its id. func (c *AuthRequestClient) DeleteOneID(id string) *AuthRequestDeleteOne { builder := c.Delete().Where(authrequest.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne return &AuthRequestDeleteOne{builder} } // Query returns a query builder for AuthRequest. func (c *AuthRequestClient) Query() *AuthRequestQuery { return &AuthRequestQuery{ config: c.config, ctx: &QueryContext{Type: TypeAuthRequest}, inters: c.Interceptors(), } } // Get returns a AuthRequest entity by its id. func (c *AuthRequestClient) Get(ctx context.Context, id string) (*AuthRequest, error) { return c.Query().Where(authrequest.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. func (c *AuthRequestClient) GetX(ctx context.Context, id string) *AuthRequest { obj, err := c.Get(ctx, id) if err != nil { panic(err) } return obj } // Hooks returns the client hooks. func (c *AuthRequestClient) Hooks() []Hook { return c.hooks.AuthRequest } // Interceptors returns the client interceptors. func (c *AuthRequestClient) Interceptors() []Interceptor { return c.inters.AuthRequest } func (c *AuthRequestClient) mutate(ctx context.Context, m *AuthRequestMutation) (Value, error) { switch m.Op() { case OpCreate: return (&AuthRequestCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdate: return (&AuthRequestUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdateOne: return (&AuthRequestUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpDelete, OpDeleteOne: return (&AuthRequestDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) default: return nil, fmt.Errorf("db: unknown AuthRequest mutation op: %q", m.Op()) } } // AuthSessionClient is a client for the AuthSession schema. type AuthSessionClient struct { config } // NewAuthSessionClient returns a client for the AuthSession from the given config. func NewAuthSessionClient(c config) *AuthSessionClient { return &AuthSessionClient{config: c} } // Use adds a list of mutation hooks to the hooks stack. // A call to `Use(f, g, h)` equals to `authsession.Hooks(f(g(h())))`. func (c *AuthSessionClient) Use(hooks ...Hook) { c.hooks.AuthSession = append(c.hooks.AuthSession, hooks...) } // Intercept adds a list of query interceptors to the interceptors stack. // A call to `Intercept(f, g, h)` equals to `authsession.Intercept(f(g(h())))`. func (c *AuthSessionClient) Intercept(interceptors ...Interceptor) { c.inters.AuthSession = append(c.inters.AuthSession, interceptors...) } // Create returns a builder for creating a AuthSession entity. func (c *AuthSessionClient) Create() *AuthSessionCreate { mutation := newAuthSessionMutation(c.config, OpCreate) return &AuthSessionCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // CreateBulk returns a builder for creating a bulk of AuthSession entities. func (c *AuthSessionClient) CreateBulk(builders ...*AuthSessionCreate) *AuthSessionCreateBulk { return &AuthSessionCreateBulk{config: c.config, builders: builders} } // MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates // a builder and applies setFunc on it. func (c *AuthSessionClient) MapCreateBulk(slice any, setFunc func(*AuthSessionCreate, int)) *AuthSessionCreateBulk { rv := reflect.ValueOf(slice) if rv.Kind() != reflect.Slice { return &AuthSessionCreateBulk{err: fmt.Errorf("calling to AuthSessionClient.MapCreateBulk with wrong type %T, need slice", slice)} } builders := make([]*AuthSessionCreate, rv.Len()) for i := 0; i < rv.Len(); i++ { builders[i] = c.Create() setFunc(builders[i], i) } return &AuthSessionCreateBulk{config: c.config, builders: builders} } // Update returns an update builder for AuthSession. func (c *AuthSessionClient) Update() *AuthSessionUpdate { mutation := newAuthSessionMutation(c.config, OpUpdate) return &AuthSessionUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOne returns an update builder for the given entity. func (c *AuthSessionClient) UpdateOne(_m *AuthSession) *AuthSessionUpdateOne { mutation := newAuthSessionMutation(c.config, OpUpdateOne, withAuthSession(_m)) return &AuthSessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOneID returns an update builder for the given id. func (c *AuthSessionClient) UpdateOneID(id string) *AuthSessionUpdateOne { mutation := newAuthSessionMutation(c.config, OpUpdateOne, withAuthSessionID(id)) return &AuthSessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // Delete returns a delete builder for AuthSession. func (c *AuthSessionClient) Delete() *AuthSessionDelete { mutation := newAuthSessionMutation(c.config, OpDelete) return &AuthSessionDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} } // DeleteOne returns a builder for deleting the given entity. func (c *AuthSessionClient) DeleteOne(_m *AuthSession) *AuthSessionDeleteOne { return c.DeleteOneID(_m.ID) } // DeleteOneID returns a builder for deleting the given entity by its id. func (c *AuthSessionClient) DeleteOneID(id string) *AuthSessionDeleteOne { builder := c.Delete().Where(authsession.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne return &AuthSessionDeleteOne{builder} } // Query returns a query builder for AuthSession. func (c *AuthSessionClient) Query() *AuthSessionQuery { return &AuthSessionQuery{ config: c.config, ctx: &QueryContext{Type: TypeAuthSession}, inters: c.Interceptors(), } } // Get returns a AuthSession entity by its id. func (c *AuthSessionClient) Get(ctx context.Context, id string) (*AuthSession, error) { return c.Query().Where(authsession.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. func (c *AuthSessionClient) GetX(ctx context.Context, id string) *AuthSession { obj, err := c.Get(ctx, id) if err != nil { panic(err) } return obj } // Hooks returns the client hooks. func (c *AuthSessionClient) Hooks() []Hook { return c.hooks.AuthSession } // Interceptors returns the client interceptors. func (c *AuthSessionClient) Interceptors() []Interceptor { return c.inters.AuthSession } func (c *AuthSessionClient) mutate(ctx context.Context, m *AuthSessionMutation) (Value, error) { switch m.Op() { case OpCreate: return (&AuthSessionCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdate: return (&AuthSessionUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdateOne: return (&AuthSessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpDelete, OpDeleteOne: return (&AuthSessionDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) default: return nil, fmt.Errorf("db: unknown AuthSession mutation op: %q", m.Op()) } } // ConnectorClient is a client for the Connector schema. type ConnectorClient struct { config } // NewConnectorClient returns a client for the Connector from the given config. func NewConnectorClient(c config) *ConnectorClient { return &ConnectorClient{config: c} } // Use adds a list of mutation hooks to the hooks stack. // A call to `Use(f, g, h)` equals to `connector.Hooks(f(g(h())))`. func (c *ConnectorClient) Use(hooks ...Hook) { c.hooks.Connector = append(c.hooks.Connector, hooks...) } // Intercept adds a list of query interceptors to the interceptors stack. // A call to `Intercept(f, g, h)` equals to `connector.Intercept(f(g(h())))`. func (c *ConnectorClient) Intercept(interceptors ...Interceptor) { c.inters.Connector = append(c.inters.Connector, interceptors...) } // Create returns a builder for creating a Connector entity. func (c *ConnectorClient) Create() *ConnectorCreate { mutation := newConnectorMutation(c.config, OpCreate) return &ConnectorCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // CreateBulk returns a builder for creating a bulk of Connector entities. func (c *ConnectorClient) CreateBulk(builders ...*ConnectorCreate) *ConnectorCreateBulk { return &ConnectorCreateBulk{config: c.config, builders: builders} } // MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates // a builder and applies setFunc on it. func (c *ConnectorClient) MapCreateBulk(slice any, setFunc func(*ConnectorCreate, int)) *ConnectorCreateBulk { rv := reflect.ValueOf(slice) if rv.Kind() != reflect.Slice { return &ConnectorCreateBulk{err: fmt.Errorf("calling to ConnectorClient.MapCreateBulk with wrong type %T, need slice", slice)} } builders := make([]*ConnectorCreate, rv.Len()) for i := 0; i < rv.Len(); i++ { builders[i] = c.Create() setFunc(builders[i], i) } return &ConnectorCreateBulk{config: c.config, builders: builders} } // Update returns an update builder for Connector. func (c *ConnectorClient) Update() *ConnectorUpdate { mutation := newConnectorMutation(c.config, OpUpdate) return &ConnectorUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOne returns an update builder for the given entity. func (c *ConnectorClient) UpdateOne(_m *Connector) *ConnectorUpdateOne { mutation := newConnectorMutation(c.config, OpUpdateOne, withConnector(_m)) return &ConnectorUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOneID returns an update builder for the given id. func (c *ConnectorClient) UpdateOneID(id string) *ConnectorUpdateOne { mutation := newConnectorMutation(c.config, OpUpdateOne, withConnectorID(id)) return &ConnectorUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // Delete returns a delete builder for Connector. func (c *ConnectorClient) Delete() *ConnectorDelete { mutation := newConnectorMutation(c.config, OpDelete) return &ConnectorDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} } // DeleteOne returns a builder for deleting the given entity. func (c *ConnectorClient) DeleteOne(_m *Connector) *ConnectorDeleteOne { return c.DeleteOneID(_m.ID) } // DeleteOneID returns a builder for deleting the given entity by its id. func (c *ConnectorClient) DeleteOneID(id string) *ConnectorDeleteOne { builder := c.Delete().Where(connector.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne return &ConnectorDeleteOne{builder} } // Query returns a query builder for Connector. func (c *ConnectorClient) Query() *ConnectorQuery { return &ConnectorQuery{ config: c.config, ctx: &QueryContext{Type: TypeConnector}, inters: c.Interceptors(), } } // Get returns a Connector entity by its id. func (c *ConnectorClient) Get(ctx context.Context, id string) (*Connector, error) { return c.Query().Where(connector.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. func (c *ConnectorClient) GetX(ctx context.Context, id string) *Connector { obj, err := c.Get(ctx, id) if err != nil { panic(err) } return obj } // Hooks returns the client hooks. func (c *ConnectorClient) Hooks() []Hook { return c.hooks.Connector } // Interceptors returns the client interceptors. func (c *ConnectorClient) Interceptors() []Interceptor { return c.inters.Connector } func (c *ConnectorClient) mutate(ctx context.Context, m *ConnectorMutation) (Value, error) { switch m.Op() { case OpCreate: return (&ConnectorCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdate: return (&ConnectorUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdateOne: return (&ConnectorUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpDelete, OpDeleteOne: return (&ConnectorDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) default: return nil, fmt.Errorf("db: unknown Connector mutation op: %q", m.Op()) } } // DeviceRequestClient is a client for the DeviceRequest schema. type DeviceRequestClient struct { config } // NewDeviceRequestClient returns a client for the DeviceRequest from the given config. func NewDeviceRequestClient(c config) *DeviceRequestClient { return &DeviceRequestClient{config: c} } // Use adds a list of mutation hooks to the hooks stack. // A call to `Use(f, g, h)` equals to `devicerequest.Hooks(f(g(h())))`. func (c *DeviceRequestClient) Use(hooks ...Hook) { c.hooks.DeviceRequest = append(c.hooks.DeviceRequest, hooks...) } // Intercept adds a list of query interceptors to the interceptors stack. // A call to `Intercept(f, g, h)` equals to `devicerequest.Intercept(f(g(h())))`. func (c *DeviceRequestClient) Intercept(interceptors ...Interceptor) { c.inters.DeviceRequest = append(c.inters.DeviceRequest, interceptors...) } // Create returns a builder for creating a DeviceRequest entity. func (c *DeviceRequestClient) Create() *DeviceRequestCreate { mutation := newDeviceRequestMutation(c.config, OpCreate) return &DeviceRequestCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // CreateBulk returns a builder for creating a bulk of DeviceRequest entities. func (c *DeviceRequestClient) CreateBulk(builders ...*DeviceRequestCreate) *DeviceRequestCreateBulk { return &DeviceRequestCreateBulk{config: c.config, builders: builders} } // MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates // a builder and applies setFunc on it. func (c *DeviceRequestClient) MapCreateBulk(slice any, setFunc func(*DeviceRequestCreate, int)) *DeviceRequestCreateBulk { rv := reflect.ValueOf(slice) if rv.Kind() != reflect.Slice { return &DeviceRequestCreateBulk{err: fmt.Errorf("calling to DeviceRequestClient.MapCreateBulk with wrong type %T, need slice", slice)} } builders := make([]*DeviceRequestCreate, rv.Len()) for i := 0; i < rv.Len(); i++ { builders[i] = c.Create() setFunc(builders[i], i) } return &DeviceRequestCreateBulk{config: c.config, builders: builders} } // Update returns an update builder for DeviceRequest. func (c *DeviceRequestClient) Update() *DeviceRequestUpdate { mutation := newDeviceRequestMutation(c.config, OpUpdate) return &DeviceRequestUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOne returns an update builder for the given entity. func (c *DeviceRequestClient) UpdateOne(_m *DeviceRequest) *DeviceRequestUpdateOne { mutation := newDeviceRequestMutation(c.config, OpUpdateOne, withDeviceRequest(_m)) return &DeviceRequestUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOneID returns an update builder for the given id. func (c *DeviceRequestClient) UpdateOneID(id int) *DeviceRequestUpdateOne { mutation := newDeviceRequestMutation(c.config, OpUpdateOne, withDeviceRequestID(id)) return &DeviceRequestUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // Delete returns a delete builder for DeviceRequest. func (c *DeviceRequestClient) Delete() *DeviceRequestDelete { mutation := newDeviceRequestMutation(c.config, OpDelete) return &DeviceRequestDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} } // DeleteOne returns a builder for deleting the given entity. func (c *DeviceRequestClient) DeleteOne(_m *DeviceRequest) *DeviceRequestDeleteOne { return c.DeleteOneID(_m.ID) } // DeleteOneID returns a builder for deleting the given entity by its id. func (c *DeviceRequestClient) DeleteOneID(id int) *DeviceRequestDeleteOne { builder := c.Delete().Where(devicerequest.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne return &DeviceRequestDeleteOne{builder} } // Query returns a query builder for DeviceRequest. func (c *DeviceRequestClient) Query() *DeviceRequestQuery { return &DeviceRequestQuery{ config: c.config, ctx: &QueryContext{Type: TypeDeviceRequest}, inters: c.Interceptors(), } } // Get returns a DeviceRequest entity by its id. func (c *DeviceRequestClient) Get(ctx context.Context, id int) (*DeviceRequest, error) { return c.Query().Where(devicerequest.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. func (c *DeviceRequestClient) GetX(ctx context.Context, id int) *DeviceRequest { obj, err := c.Get(ctx, id) if err != nil { panic(err) } return obj } // Hooks returns the client hooks. func (c *DeviceRequestClient) Hooks() []Hook { return c.hooks.DeviceRequest } // Interceptors returns the client interceptors. func (c *DeviceRequestClient) Interceptors() []Interceptor { return c.inters.DeviceRequest } func (c *DeviceRequestClient) mutate(ctx context.Context, m *DeviceRequestMutation) (Value, error) { switch m.Op() { case OpCreate: return (&DeviceRequestCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdate: return (&DeviceRequestUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdateOne: return (&DeviceRequestUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpDelete, OpDeleteOne: return (&DeviceRequestDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) default: return nil, fmt.Errorf("db: unknown DeviceRequest mutation op: %q", m.Op()) } } // DeviceTokenClient is a client for the DeviceToken schema. type DeviceTokenClient struct { config } // NewDeviceTokenClient returns a client for the DeviceToken from the given config. func NewDeviceTokenClient(c config) *DeviceTokenClient { return &DeviceTokenClient{config: c} } // Use adds a list of mutation hooks to the hooks stack. // A call to `Use(f, g, h)` equals to `devicetoken.Hooks(f(g(h())))`. func (c *DeviceTokenClient) Use(hooks ...Hook) { c.hooks.DeviceToken = append(c.hooks.DeviceToken, hooks...) } // Intercept adds a list of query interceptors to the interceptors stack. // A call to `Intercept(f, g, h)` equals to `devicetoken.Intercept(f(g(h())))`. func (c *DeviceTokenClient) Intercept(interceptors ...Interceptor) { c.inters.DeviceToken = append(c.inters.DeviceToken, interceptors...) } // Create returns a builder for creating a DeviceToken entity. func (c *DeviceTokenClient) Create() *DeviceTokenCreate { mutation := newDeviceTokenMutation(c.config, OpCreate) return &DeviceTokenCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // CreateBulk returns a builder for creating a bulk of DeviceToken entities. func (c *DeviceTokenClient) CreateBulk(builders ...*DeviceTokenCreate) *DeviceTokenCreateBulk { return &DeviceTokenCreateBulk{config: c.config, builders: builders} } // MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates // a builder and applies setFunc on it. func (c *DeviceTokenClient) MapCreateBulk(slice any, setFunc func(*DeviceTokenCreate, int)) *DeviceTokenCreateBulk { rv := reflect.ValueOf(slice) if rv.Kind() != reflect.Slice { return &DeviceTokenCreateBulk{err: fmt.Errorf("calling to DeviceTokenClient.MapCreateBulk with wrong type %T, need slice", slice)} } builders := make([]*DeviceTokenCreate, rv.Len()) for i := 0; i < rv.Len(); i++ { builders[i] = c.Create() setFunc(builders[i], i) } return &DeviceTokenCreateBulk{config: c.config, builders: builders} } // Update returns an update builder for DeviceToken. func (c *DeviceTokenClient) Update() *DeviceTokenUpdate { mutation := newDeviceTokenMutation(c.config, OpUpdate) return &DeviceTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOne returns an update builder for the given entity. func (c *DeviceTokenClient) UpdateOne(_m *DeviceToken) *DeviceTokenUpdateOne { mutation := newDeviceTokenMutation(c.config, OpUpdateOne, withDeviceToken(_m)) return &DeviceTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOneID returns an update builder for the given id. func (c *DeviceTokenClient) UpdateOneID(id int) *DeviceTokenUpdateOne { mutation := newDeviceTokenMutation(c.config, OpUpdateOne, withDeviceTokenID(id)) return &DeviceTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // Delete returns a delete builder for DeviceToken. func (c *DeviceTokenClient) Delete() *DeviceTokenDelete { mutation := newDeviceTokenMutation(c.config, OpDelete) return &DeviceTokenDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} } // DeleteOne returns a builder for deleting the given entity. func (c *DeviceTokenClient) DeleteOne(_m *DeviceToken) *DeviceTokenDeleteOne { return c.DeleteOneID(_m.ID) } // DeleteOneID returns a builder for deleting the given entity by its id. func (c *DeviceTokenClient) DeleteOneID(id int) *DeviceTokenDeleteOne { builder := c.Delete().Where(devicetoken.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne return &DeviceTokenDeleteOne{builder} } // Query returns a query builder for DeviceToken. func (c *DeviceTokenClient) Query() *DeviceTokenQuery { return &DeviceTokenQuery{ config: c.config, ctx: &QueryContext{Type: TypeDeviceToken}, inters: c.Interceptors(), } } // Get returns a DeviceToken entity by its id. func (c *DeviceTokenClient) Get(ctx context.Context, id int) (*DeviceToken, error) { return c.Query().Where(devicetoken.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. func (c *DeviceTokenClient) GetX(ctx context.Context, id int) *DeviceToken { obj, err := c.Get(ctx, id) if err != nil { panic(err) } return obj } // Hooks returns the client hooks. func (c *DeviceTokenClient) Hooks() []Hook { return c.hooks.DeviceToken } // Interceptors returns the client interceptors. func (c *DeviceTokenClient) Interceptors() []Interceptor { return c.inters.DeviceToken } func (c *DeviceTokenClient) mutate(ctx context.Context, m *DeviceTokenMutation) (Value, error) { switch m.Op() { case OpCreate: return (&DeviceTokenCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdate: return (&DeviceTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdateOne: return (&DeviceTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpDelete, OpDeleteOne: return (&DeviceTokenDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) default: return nil, fmt.Errorf("db: unknown DeviceToken mutation op: %q", m.Op()) } } // KeysClient is a client for the Keys schema. type KeysClient struct { config } // NewKeysClient returns a client for the Keys from the given config. func NewKeysClient(c config) *KeysClient { return &KeysClient{config: c} } // Use adds a list of mutation hooks to the hooks stack. // A call to `Use(f, g, h)` equals to `keys.Hooks(f(g(h())))`. func (c *KeysClient) Use(hooks ...Hook) { c.hooks.Keys = append(c.hooks.Keys, hooks...) } // Intercept adds a list of query interceptors to the interceptors stack. // A call to `Intercept(f, g, h)` equals to `keys.Intercept(f(g(h())))`. func (c *KeysClient) Intercept(interceptors ...Interceptor) { c.inters.Keys = append(c.inters.Keys, interceptors...) } // Create returns a builder for creating a Keys entity. func (c *KeysClient) Create() *KeysCreate { mutation := newKeysMutation(c.config, OpCreate) return &KeysCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // CreateBulk returns a builder for creating a bulk of Keys entities. func (c *KeysClient) CreateBulk(builders ...*KeysCreate) *KeysCreateBulk { return &KeysCreateBulk{config: c.config, builders: builders} } // MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates // a builder and applies setFunc on it. func (c *KeysClient) MapCreateBulk(slice any, setFunc func(*KeysCreate, int)) *KeysCreateBulk { rv := reflect.ValueOf(slice) if rv.Kind() != reflect.Slice { return &KeysCreateBulk{err: fmt.Errorf("calling to KeysClient.MapCreateBulk with wrong type %T, need slice", slice)} } builders := make([]*KeysCreate, rv.Len()) for i := 0; i < rv.Len(); i++ { builders[i] = c.Create() setFunc(builders[i], i) } return &KeysCreateBulk{config: c.config, builders: builders} } // Update returns an update builder for Keys. func (c *KeysClient) Update() *KeysUpdate { mutation := newKeysMutation(c.config, OpUpdate) return &KeysUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOne returns an update builder for the given entity. func (c *KeysClient) UpdateOne(_m *Keys) *KeysUpdateOne { mutation := newKeysMutation(c.config, OpUpdateOne, withKeys(_m)) return &KeysUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOneID returns an update builder for the given id. func (c *KeysClient) UpdateOneID(id string) *KeysUpdateOne { mutation := newKeysMutation(c.config, OpUpdateOne, withKeysID(id)) return &KeysUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // Delete returns a delete builder for Keys. func (c *KeysClient) Delete() *KeysDelete { mutation := newKeysMutation(c.config, OpDelete) return &KeysDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} } // DeleteOne returns a builder for deleting the given entity. func (c *KeysClient) DeleteOne(_m *Keys) *KeysDeleteOne { return c.DeleteOneID(_m.ID) } // DeleteOneID returns a builder for deleting the given entity by its id. func (c *KeysClient) DeleteOneID(id string) *KeysDeleteOne { builder := c.Delete().Where(keys.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne return &KeysDeleteOne{builder} } // Query returns a query builder for Keys. func (c *KeysClient) Query() *KeysQuery { return &KeysQuery{ config: c.config, ctx: &QueryContext{Type: TypeKeys}, inters: c.Interceptors(), } } // Get returns a Keys entity by its id. func (c *KeysClient) Get(ctx context.Context, id string) (*Keys, error) { return c.Query().Where(keys.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. func (c *KeysClient) GetX(ctx context.Context, id string) *Keys { obj, err := c.Get(ctx, id) if err != nil { panic(err) } return obj } // Hooks returns the client hooks. func (c *KeysClient) Hooks() []Hook { return c.hooks.Keys } // Interceptors returns the client interceptors. func (c *KeysClient) Interceptors() []Interceptor { return c.inters.Keys } func (c *KeysClient) mutate(ctx context.Context, m *KeysMutation) (Value, error) { switch m.Op() { case OpCreate: return (&KeysCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdate: return (&KeysUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdateOne: return (&KeysUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpDelete, OpDeleteOne: return (&KeysDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) default: return nil, fmt.Errorf("db: unknown Keys mutation op: %q", m.Op()) } } // OAuth2ClientClient is a client for the OAuth2Client schema. type OAuth2ClientClient struct { config } // NewOAuth2ClientClient returns a client for the OAuth2Client from the given config. func NewOAuth2ClientClient(c config) *OAuth2ClientClient { return &OAuth2ClientClient{config: c} } // Use adds a list of mutation hooks to the hooks stack. // A call to `Use(f, g, h)` equals to `oauth2client.Hooks(f(g(h())))`. func (c *OAuth2ClientClient) Use(hooks ...Hook) { c.hooks.OAuth2Client = append(c.hooks.OAuth2Client, hooks...) } // Intercept adds a list of query interceptors to the interceptors stack. // A call to `Intercept(f, g, h)` equals to `oauth2client.Intercept(f(g(h())))`. func (c *OAuth2ClientClient) Intercept(interceptors ...Interceptor) { c.inters.OAuth2Client = append(c.inters.OAuth2Client, interceptors...) } // Create returns a builder for creating a OAuth2Client entity. func (c *OAuth2ClientClient) Create() *OAuth2ClientCreate { mutation := newOAuth2ClientMutation(c.config, OpCreate) return &OAuth2ClientCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // CreateBulk returns a builder for creating a bulk of OAuth2Client entities. func (c *OAuth2ClientClient) CreateBulk(builders ...*OAuth2ClientCreate) *OAuth2ClientCreateBulk { return &OAuth2ClientCreateBulk{config: c.config, builders: builders} } // MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates // a builder and applies setFunc on it. func (c *OAuth2ClientClient) MapCreateBulk(slice any, setFunc func(*OAuth2ClientCreate, int)) *OAuth2ClientCreateBulk { rv := reflect.ValueOf(slice) if rv.Kind() != reflect.Slice { return &OAuth2ClientCreateBulk{err: fmt.Errorf("calling to OAuth2ClientClient.MapCreateBulk with wrong type %T, need slice", slice)} } builders := make([]*OAuth2ClientCreate, rv.Len()) for i := 0; i < rv.Len(); i++ { builders[i] = c.Create() setFunc(builders[i], i) } return &OAuth2ClientCreateBulk{config: c.config, builders: builders} } // Update returns an update builder for OAuth2Client. func (c *OAuth2ClientClient) Update() *OAuth2ClientUpdate { mutation := newOAuth2ClientMutation(c.config, OpUpdate) return &OAuth2ClientUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOne returns an update builder for the given entity. func (c *OAuth2ClientClient) UpdateOne(_m *OAuth2Client) *OAuth2ClientUpdateOne { mutation := newOAuth2ClientMutation(c.config, OpUpdateOne, withOAuth2Client(_m)) return &OAuth2ClientUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOneID returns an update builder for the given id. func (c *OAuth2ClientClient) UpdateOneID(id string) *OAuth2ClientUpdateOne { mutation := newOAuth2ClientMutation(c.config, OpUpdateOne, withOAuth2ClientID(id)) return &OAuth2ClientUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // Delete returns a delete builder for OAuth2Client. func (c *OAuth2ClientClient) Delete() *OAuth2ClientDelete { mutation := newOAuth2ClientMutation(c.config, OpDelete) return &OAuth2ClientDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} } // DeleteOne returns a builder for deleting the given entity. func (c *OAuth2ClientClient) DeleteOne(_m *OAuth2Client) *OAuth2ClientDeleteOne { return c.DeleteOneID(_m.ID) } // DeleteOneID returns a builder for deleting the given entity by its id. func (c *OAuth2ClientClient) DeleteOneID(id string) *OAuth2ClientDeleteOne { builder := c.Delete().Where(oauth2client.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne return &OAuth2ClientDeleteOne{builder} } // Query returns a query builder for OAuth2Client. func (c *OAuth2ClientClient) Query() *OAuth2ClientQuery { return &OAuth2ClientQuery{ config: c.config, ctx: &QueryContext{Type: TypeOAuth2Client}, inters: c.Interceptors(), } } // Get returns a OAuth2Client entity by its id. func (c *OAuth2ClientClient) Get(ctx context.Context, id string) (*OAuth2Client, error) { return c.Query().Where(oauth2client.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. func (c *OAuth2ClientClient) GetX(ctx context.Context, id string) *OAuth2Client { obj, err := c.Get(ctx, id) if err != nil { panic(err) } return obj } // Hooks returns the client hooks. func (c *OAuth2ClientClient) Hooks() []Hook { return c.hooks.OAuth2Client } // Interceptors returns the client interceptors. func (c *OAuth2ClientClient) Interceptors() []Interceptor { return c.inters.OAuth2Client } func (c *OAuth2ClientClient) mutate(ctx context.Context, m *OAuth2ClientMutation) (Value, error) { switch m.Op() { case OpCreate: return (&OAuth2ClientCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdate: return (&OAuth2ClientUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdateOne: return (&OAuth2ClientUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpDelete, OpDeleteOne: return (&OAuth2ClientDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) default: return nil, fmt.Errorf("db: unknown OAuth2Client mutation op: %q", m.Op()) } } // OfflineSessionClient is a client for the OfflineSession schema. type OfflineSessionClient struct { config } // NewOfflineSessionClient returns a client for the OfflineSession from the given config. func NewOfflineSessionClient(c config) *OfflineSessionClient { return &OfflineSessionClient{config: c} } // Use adds a list of mutation hooks to the hooks stack. // A call to `Use(f, g, h)` equals to `offlinesession.Hooks(f(g(h())))`. func (c *OfflineSessionClient) Use(hooks ...Hook) { c.hooks.OfflineSession = append(c.hooks.OfflineSession, hooks...) } // Intercept adds a list of query interceptors to the interceptors stack. // A call to `Intercept(f, g, h)` equals to `offlinesession.Intercept(f(g(h())))`. func (c *OfflineSessionClient) Intercept(interceptors ...Interceptor) { c.inters.OfflineSession = append(c.inters.OfflineSession, interceptors...) } // Create returns a builder for creating a OfflineSession entity. func (c *OfflineSessionClient) Create() *OfflineSessionCreate { mutation := newOfflineSessionMutation(c.config, OpCreate) return &OfflineSessionCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // CreateBulk returns a builder for creating a bulk of OfflineSession entities. func (c *OfflineSessionClient) CreateBulk(builders ...*OfflineSessionCreate) *OfflineSessionCreateBulk { return &OfflineSessionCreateBulk{config: c.config, builders: builders} } // MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates // a builder and applies setFunc on it. func (c *OfflineSessionClient) MapCreateBulk(slice any, setFunc func(*OfflineSessionCreate, int)) *OfflineSessionCreateBulk { rv := reflect.ValueOf(slice) if rv.Kind() != reflect.Slice { return &OfflineSessionCreateBulk{err: fmt.Errorf("calling to OfflineSessionClient.MapCreateBulk with wrong type %T, need slice", slice)} } builders := make([]*OfflineSessionCreate, rv.Len()) for i := 0; i < rv.Len(); i++ { builders[i] = c.Create() setFunc(builders[i], i) } return &OfflineSessionCreateBulk{config: c.config, builders: builders} } // Update returns an update builder for OfflineSession. func (c *OfflineSessionClient) Update() *OfflineSessionUpdate { mutation := newOfflineSessionMutation(c.config, OpUpdate) return &OfflineSessionUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOne returns an update builder for the given entity. func (c *OfflineSessionClient) UpdateOne(_m *OfflineSession) *OfflineSessionUpdateOne { mutation := newOfflineSessionMutation(c.config, OpUpdateOne, withOfflineSession(_m)) return &OfflineSessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOneID returns an update builder for the given id. func (c *OfflineSessionClient) UpdateOneID(id string) *OfflineSessionUpdateOne { mutation := newOfflineSessionMutation(c.config, OpUpdateOne, withOfflineSessionID(id)) return &OfflineSessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // Delete returns a delete builder for OfflineSession. func (c *OfflineSessionClient) Delete() *OfflineSessionDelete { mutation := newOfflineSessionMutation(c.config, OpDelete) return &OfflineSessionDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} } // DeleteOne returns a builder for deleting the given entity. func (c *OfflineSessionClient) DeleteOne(_m *OfflineSession) *OfflineSessionDeleteOne { return c.DeleteOneID(_m.ID) } // DeleteOneID returns a builder for deleting the given entity by its id. func (c *OfflineSessionClient) DeleteOneID(id string) *OfflineSessionDeleteOne { builder := c.Delete().Where(offlinesession.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne return &OfflineSessionDeleteOne{builder} } // Query returns a query builder for OfflineSession. func (c *OfflineSessionClient) Query() *OfflineSessionQuery { return &OfflineSessionQuery{ config: c.config, ctx: &QueryContext{Type: TypeOfflineSession}, inters: c.Interceptors(), } } // Get returns a OfflineSession entity by its id. func (c *OfflineSessionClient) Get(ctx context.Context, id string) (*OfflineSession, error) { return c.Query().Where(offlinesession.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. func (c *OfflineSessionClient) GetX(ctx context.Context, id string) *OfflineSession { obj, err := c.Get(ctx, id) if err != nil { panic(err) } return obj } // Hooks returns the client hooks. func (c *OfflineSessionClient) Hooks() []Hook { return c.hooks.OfflineSession } // Interceptors returns the client interceptors. func (c *OfflineSessionClient) Interceptors() []Interceptor { return c.inters.OfflineSession } func (c *OfflineSessionClient) mutate(ctx context.Context, m *OfflineSessionMutation) (Value, error) { switch m.Op() { case OpCreate: return (&OfflineSessionCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdate: return (&OfflineSessionUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdateOne: return (&OfflineSessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpDelete, OpDeleteOne: return (&OfflineSessionDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) default: return nil, fmt.Errorf("db: unknown OfflineSession mutation op: %q", m.Op()) } } // PasswordClient is a client for the Password schema. type PasswordClient struct { config } // NewPasswordClient returns a client for the Password from the given config. func NewPasswordClient(c config) *PasswordClient { return &PasswordClient{config: c} } // Use adds a list of mutation hooks to the hooks stack. // A call to `Use(f, g, h)` equals to `password.Hooks(f(g(h())))`. func (c *PasswordClient) Use(hooks ...Hook) { c.hooks.Password = append(c.hooks.Password, hooks...) } // Intercept adds a list of query interceptors to the interceptors stack. // A call to `Intercept(f, g, h)` equals to `password.Intercept(f(g(h())))`. func (c *PasswordClient) Intercept(interceptors ...Interceptor) { c.inters.Password = append(c.inters.Password, interceptors...) } // Create returns a builder for creating a Password entity. func (c *PasswordClient) Create() *PasswordCreate { mutation := newPasswordMutation(c.config, OpCreate) return &PasswordCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // CreateBulk returns a builder for creating a bulk of Password entities. func (c *PasswordClient) CreateBulk(builders ...*PasswordCreate) *PasswordCreateBulk { return &PasswordCreateBulk{config: c.config, builders: builders} } // MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates // a builder and applies setFunc on it. func (c *PasswordClient) MapCreateBulk(slice any, setFunc func(*PasswordCreate, int)) *PasswordCreateBulk { rv := reflect.ValueOf(slice) if rv.Kind() != reflect.Slice { return &PasswordCreateBulk{err: fmt.Errorf("calling to PasswordClient.MapCreateBulk with wrong type %T, need slice", slice)} } builders := make([]*PasswordCreate, rv.Len()) for i := 0; i < rv.Len(); i++ { builders[i] = c.Create() setFunc(builders[i], i) } return &PasswordCreateBulk{config: c.config, builders: builders} } // Update returns an update builder for Password. func (c *PasswordClient) Update() *PasswordUpdate { mutation := newPasswordMutation(c.config, OpUpdate) return &PasswordUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOne returns an update builder for the given entity. func (c *PasswordClient) UpdateOne(_m *Password) *PasswordUpdateOne { mutation := newPasswordMutation(c.config, OpUpdateOne, withPassword(_m)) return &PasswordUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOneID returns an update builder for the given id. func (c *PasswordClient) UpdateOneID(id int) *PasswordUpdateOne { mutation := newPasswordMutation(c.config, OpUpdateOne, withPasswordID(id)) return &PasswordUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // Delete returns a delete builder for Password. func (c *PasswordClient) Delete() *PasswordDelete { mutation := newPasswordMutation(c.config, OpDelete) return &PasswordDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} } // DeleteOne returns a builder for deleting the given entity. func (c *PasswordClient) DeleteOne(_m *Password) *PasswordDeleteOne { return c.DeleteOneID(_m.ID) } // DeleteOneID returns a builder for deleting the given entity by its id. func (c *PasswordClient) DeleteOneID(id int) *PasswordDeleteOne { builder := c.Delete().Where(password.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne return &PasswordDeleteOne{builder} } // Query returns a query builder for Password. func (c *PasswordClient) Query() *PasswordQuery { return &PasswordQuery{ config: c.config, ctx: &QueryContext{Type: TypePassword}, inters: c.Interceptors(), } } // Get returns a Password entity by its id. func (c *PasswordClient) Get(ctx context.Context, id int) (*Password, error) { return c.Query().Where(password.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. func (c *PasswordClient) GetX(ctx context.Context, id int) *Password { obj, err := c.Get(ctx, id) if err != nil { panic(err) } return obj } // Hooks returns the client hooks. func (c *PasswordClient) Hooks() []Hook { return c.hooks.Password } // Interceptors returns the client interceptors. func (c *PasswordClient) Interceptors() []Interceptor { return c.inters.Password } func (c *PasswordClient) mutate(ctx context.Context, m *PasswordMutation) (Value, error) { switch m.Op() { case OpCreate: return (&PasswordCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdate: return (&PasswordUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdateOne: return (&PasswordUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpDelete, OpDeleteOne: return (&PasswordDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) default: return nil, fmt.Errorf("db: unknown Password mutation op: %q", m.Op()) } } // RefreshTokenClient is a client for the RefreshToken schema. type RefreshTokenClient struct { config } // NewRefreshTokenClient returns a client for the RefreshToken from the given config. func NewRefreshTokenClient(c config) *RefreshTokenClient { return &RefreshTokenClient{config: c} } // Use adds a list of mutation hooks to the hooks stack. // A call to `Use(f, g, h)` equals to `refreshtoken.Hooks(f(g(h())))`. func (c *RefreshTokenClient) Use(hooks ...Hook) { c.hooks.RefreshToken = append(c.hooks.RefreshToken, hooks...) } // Intercept adds a list of query interceptors to the interceptors stack. // A call to `Intercept(f, g, h)` equals to `refreshtoken.Intercept(f(g(h())))`. func (c *RefreshTokenClient) Intercept(interceptors ...Interceptor) { c.inters.RefreshToken = append(c.inters.RefreshToken, interceptors...) } // Create returns a builder for creating a RefreshToken entity. func (c *RefreshTokenClient) Create() *RefreshTokenCreate { mutation := newRefreshTokenMutation(c.config, OpCreate) return &RefreshTokenCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // CreateBulk returns a builder for creating a bulk of RefreshToken entities. func (c *RefreshTokenClient) CreateBulk(builders ...*RefreshTokenCreate) *RefreshTokenCreateBulk { return &RefreshTokenCreateBulk{config: c.config, builders: builders} } // MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates // a builder and applies setFunc on it. func (c *RefreshTokenClient) MapCreateBulk(slice any, setFunc func(*RefreshTokenCreate, int)) *RefreshTokenCreateBulk { rv := reflect.ValueOf(slice) if rv.Kind() != reflect.Slice { return &RefreshTokenCreateBulk{err: fmt.Errorf("calling to RefreshTokenClient.MapCreateBulk with wrong type %T, need slice", slice)} } builders := make([]*RefreshTokenCreate, rv.Len()) for i := 0; i < rv.Len(); i++ { builders[i] = c.Create() setFunc(builders[i], i) } return &RefreshTokenCreateBulk{config: c.config, builders: builders} } // Update returns an update builder for RefreshToken. func (c *RefreshTokenClient) Update() *RefreshTokenUpdate { mutation := newRefreshTokenMutation(c.config, OpUpdate) return &RefreshTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOne returns an update builder for the given entity. func (c *RefreshTokenClient) UpdateOne(_m *RefreshToken) *RefreshTokenUpdateOne { mutation := newRefreshTokenMutation(c.config, OpUpdateOne, withRefreshToken(_m)) return &RefreshTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOneID returns an update builder for the given id. func (c *RefreshTokenClient) UpdateOneID(id string) *RefreshTokenUpdateOne { mutation := newRefreshTokenMutation(c.config, OpUpdateOne, withRefreshTokenID(id)) return &RefreshTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // Delete returns a delete builder for RefreshToken. func (c *RefreshTokenClient) Delete() *RefreshTokenDelete { mutation := newRefreshTokenMutation(c.config, OpDelete) return &RefreshTokenDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} } // DeleteOne returns a builder for deleting the given entity. func (c *RefreshTokenClient) DeleteOne(_m *RefreshToken) *RefreshTokenDeleteOne { return c.DeleteOneID(_m.ID) } // DeleteOneID returns a builder for deleting the given entity by its id. func (c *RefreshTokenClient) DeleteOneID(id string) *RefreshTokenDeleteOne { builder := c.Delete().Where(refreshtoken.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne return &RefreshTokenDeleteOne{builder} } // Query returns a query builder for RefreshToken. func (c *RefreshTokenClient) Query() *RefreshTokenQuery { return &RefreshTokenQuery{ config: c.config, ctx: &QueryContext{Type: TypeRefreshToken}, inters: c.Interceptors(), } } // Get returns a RefreshToken entity by its id. func (c *RefreshTokenClient) Get(ctx context.Context, id string) (*RefreshToken, error) { return c.Query().Where(refreshtoken.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. func (c *RefreshTokenClient) GetX(ctx context.Context, id string) *RefreshToken { obj, err := c.Get(ctx, id) if err != nil { panic(err) } return obj } // Hooks returns the client hooks. func (c *RefreshTokenClient) Hooks() []Hook { return c.hooks.RefreshToken } // Interceptors returns the client interceptors. func (c *RefreshTokenClient) Interceptors() []Interceptor { return c.inters.RefreshToken } func (c *RefreshTokenClient) mutate(ctx context.Context, m *RefreshTokenMutation) (Value, error) { switch m.Op() { case OpCreate: return (&RefreshTokenCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdate: return (&RefreshTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdateOne: return (&RefreshTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpDelete, OpDeleteOne: return (&RefreshTokenDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) default: return nil, fmt.Errorf("db: unknown RefreshToken mutation op: %q", m.Op()) } } // UserIdentityClient is a client for the UserIdentity schema. type UserIdentityClient struct { config } // NewUserIdentityClient returns a client for the UserIdentity from the given config. func NewUserIdentityClient(c config) *UserIdentityClient { return &UserIdentityClient{config: c} } // Use adds a list of mutation hooks to the hooks stack. // A call to `Use(f, g, h)` equals to `useridentity.Hooks(f(g(h())))`. func (c *UserIdentityClient) Use(hooks ...Hook) { c.hooks.UserIdentity = append(c.hooks.UserIdentity, hooks...) } // Intercept adds a list of query interceptors to the interceptors stack. // A call to `Intercept(f, g, h)` equals to `useridentity.Intercept(f(g(h())))`. func (c *UserIdentityClient) Intercept(interceptors ...Interceptor) { c.inters.UserIdentity = append(c.inters.UserIdentity, interceptors...) } // Create returns a builder for creating a UserIdentity entity. func (c *UserIdentityClient) Create() *UserIdentityCreate { mutation := newUserIdentityMutation(c.config, OpCreate) return &UserIdentityCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // CreateBulk returns a builder for creating a bulk of UserIdentity entities. func (c *UserIdentityClient) CreateBulk(builders ...*UserIdentityCreate) *UserIdentityCreateBulk { return &UserIdentityCreateBulk{config: c.config, builders: builders} } // MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates // a builder and applies setFunc on it. func (c *UserIdentityClient) MapCreateBulk(slice any, setFunc func(*UserIdentityCreate, int)) *UserIdentityCreateBulk { rv := reflect.ValueOf(slice) if rv.Kind() != reflect.Slice { return &UserIdentityCreateBulk{err: fmt.Errorf("calling to UserIdentityClient.MapCreateBulk with wrong type %T, need slice", slice)} } builders := make([]*UserIdentityCreate, rv.Len()) for i := 0; i < rv.Len(); i++ { builders[i] = c.Create() setFunc(builders[i], i) } return &UserIdentityCreateBulk{config: c.config, builders: builders} } // Update returns an update builder for UserIdentity. func (c *UserIdentityClient) Update() *UserIdentityUpdate { mutation := newUserIdentityMutation(c.config, OpUpdate) return &UserIdentityUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOne returns an update builder for the given entity. func (c *UserIdentityClient) UpdateOne(_m *UserIdentity) *UserIdentityUpdateOne { mutation := newUserIdentityMutation(c.config, OpUpdateOne, withUserIdentity(_m)) return &UserIdentityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // UpdateOneID returns an update builder for the given id. func (c *UserIdentityClient) UpdateOneID(id string) *UserIdentityUpdateOne { mutation := newUserIdentityMutation(c.config, OpUpdateOne, withUserIdentityID(id)) return &UserIdentityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} } // Delete returns a delete builder for UserIdentity. func (c *UserIdentityClient) Delete() *UserIdentityDelete { mutation := newUserIdentityMutation(c.config, OpDelete) return &UserIdentityDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} } // DeleteOne returns a builder for deleting the given entity. func (c *UserIdentityClient) DeleteOne(_m *UserIdentity) *UserIdentityDeleteOne { return c.DeleteOneID(_m.ID) } // DeleteOneID returns a builder for deleting the given entity by its id. func (c *UserIdentityClient) DeleteOneID(id string) *UserIdentityDeleteOne { builder := c.Delete().Where(useridentity.ID(id)) builder.mutation.id = &id builder.mutation.op = OpDeleteOne return &UserIdentityDeleteOne{builder} } // Query returns a query builder for UserIdentity. func (c *UserIdentityClient) Query() *UserIdentityQuery { return &UserIdentityQuery{ config: c.config, ctx: &QueryContext{Type: TypeUserIdentity}, inters: c.Interceptors(), } } // Get returns a UserIdentity entity by its id. func (c *UserIdentityClient) Get(ctx context.Context, id string) (*UserIdentity, error) { return c.Query().Where(useridentity.ID(id)).Only(ctx) } // GetX is like Get, but panics if an error occurs. func (c *UserIdentityClient) GetX(ctx context.Context, id string) *UserIdentity { obj, err := c.Get(ctx, id) if err != nil { panic(err) } return obj } // Hooks returns the client hooks. func (c *UserIdentityClient) Hooks() []Hook { return c.hooks.UserIdentity } // Interceptors returns the client interceptors. func (c *UserIdentityClient) Interceptors() []Interceptor { return c.inters.UserIdentity } func (c *UserIdentityClient) mutate(ctx context.Context, m *UserIdentityMutation) (Value, error) { switch m.Op() { case OpCreate: return (&UserIdentityCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdate: return (&UserIdentityUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpUpdateOne: return (&UserIdentityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) case OpDelete, OpDeleteOne: return (&UserIdentityDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) default: return nil, fmt.Errorf("db: unknown UserIdentity mutation op: %q", m.Op()) } } // hooks and interceptors per client, for fast access. type ( hooks struct { AuthCode, AuthRequest, AuthSession, Connector, DeviceRequest, DeviceToken, Keys, OAuth2Client, OfflineSession, Password, RefreshToken, UserIdentity []ent.Hook } inters struct { AuthCode, AuthRequest, AuthSession, Connector, DeviceRequest, DeviceToken, Keys, OAuth2Client, OfflineSession, Password, RefreshToken, UserIdentity []ent.Interceptor } ) ================================================ FILE: storage/ent/db/connector/connector.go ================================================ // Code generated by ent, DO NOT EDIT. package connector import ( "entgo.io/ent/dialect/sql" ) const ( // Label holds the string label denoting the connector type in the database. Label = "connector" // FieldID holds the string denoting the id field in the database. FieldID = "id" // FieldType holds the string denoting the type field in the database. FieldType = "type" // FieldName holds the string denoting the name field in the database. FieldName = "name" // FieldResourceVersion holds the string denoting the resource_version field in the database. FieldResourceVersion = "resource_version" // FieldConfig holds the string denoting the config field in the database. FieldConfig = "config" // FieldGrantTypes holds the string denoting the grant_types field in the database. FieldGrantTypes = "grant_types" // Table holds the table name of the connector in the database. Table = "connectors" ) // Columns holds all SQL columns for connector fields. var Columns = []string{ FieldID, FieldType, FieldName, FieldResourceVersion, FieldConfig, FieldGrantTypes, } // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { if column == Columns[i] { return true } } return false } var ( // TypeValidator is a validator for the "type" field. It is called by the builders before save. TypeValidator func(string) error // NameValidator is a validator for the "name" field. It is called by the builders before save. NameValidator func(string) error // IDValidator is a validator for the "id" field. It is called by the builders before save. IDValidator func(string) error ) // OrderOption defines the ordering options for the Connector queries. type OrderOption func(*sql.Selector) // ByID orders the results by the id field. func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } // ByType orders the results by the type field. func ByType(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldType, opts...).ToFunc() } // ByName orders the results by the name field. func ByName(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldName, opts...).ToFunc() } // ByResourceVersion orders the results by the resource_version field. func ByResourceVersion(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldResourceVersion, opts...).ToFunc() } ================================================ FILE: storage/ent/db/connector/where.go ================================================ // Code generated by ent, DO NOT EDIT. package connector import ( "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ID filters vertices based on their ID field. func ID(id string) predicate.Connector { return predicate.Connector(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. func IDEQ(id string) predicate.Connector { return predicate.Connector(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. func IDNEQ(id string) predicate.Connector { return predicate.Connector(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. func IDIn(ids ...string) predicate.Connector { return predicate.Connector(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. func IDNotIn(ids ...string) predicate.Connector { return predicate.Connector(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. func IDGT(id string) predicate.Connector { return predicate.Connector(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. func IDGTE(id string) predicate.Connector { return predicate.Connector(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. func IDLT(id string) predicate.Connector { return predicate.Connector(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. func IDLTE(id string) predicate.Connector { return predicate.Connector(sql.FieldLTE(FieldID, id)) } // IDEqualFold applies the EqualFold predicate on the ID field. func IDEqualFold(id string) predicate.Connector { return predicate.Connector(sql.FieldEqualFold(FieldID, id)) } // IDContainsFold applies the ContainsFold predicate on the ID field. func IDContainsFold(id string) predicate.Connector { return predicate.Connector(sql.FieldContainsFold(FieldID, id)) } // Type applies equality check predicate on the "type" field. It's identical to TypeEQ. func Type(v string) predicate.Connector { return predicate.Connector(sql.FieldEQ(FieldType, v)) } // Name applies equality check predicate on the "name" field. It's identical to NameEQ. func Name(v string) predicate.Connector { return predicate.Connector(sql.FieldEQ(FieldName, v)) } // ResourceVersion applies equality check predicate on the "resource_version" field. It's identical to ResourceVersionEQ. func ResourceVersion(v string) predicate.Connector { return predicate.Connector(sql.FieldEQ(FieldResourceVersion, v)) } // Config applies equality check predicate on the "config" field. It's identical to ConfigEQ. func Config(v []byte) predicate.Connector { return predicate.Connector(sql.FieldEQ(FieldConfig, v)) } // TypeEQ applies the EQ predicate on the "type" field. func TypeEQ(v string) predicate.Connector { return predicate.Connector(sql.FieldEQ(FieldType, v)) } // TypeNEQ applies the NEQ predicate on the "type" field. func TypeNEQ(v string) predicate.Connector { return predicate.Connector(sql.FieldNEQ(FieldType, v)) } // TypeIn applies the In predicate on the "type" field. func TypeIn(vs ...string) predicate.Connector { return predicate.Connector(sql.FieldIn(FieldType, vs...)) } // TypeNotIn applies the NotIn predicate on the "type" field. func TypeNotIn(vs ...string) predicate.Connector { return predicate.Connector(sql.FieldNotIn(FieldType, vs...)) } // TypeGT applies the GT predicate on the "type" field. func TypeGT(v string) predicate.Connector { return predicate.Connector(sql.FieldGT(FieldType, v)) } // TypeGTE applies the GTE predicate on the "type" field. func TypeGTE(v string) predicate.Connector { return predicate.Connector(sql.FieldGTE(FieldType, v)) } // TypeLT applies the LT predicate on the "type" field. func TypeLT(v string) predicate.Connector { return predicate.Connector(sql.FieldLT(FieldType, v)) } // TypeLTE applies the LTE predicate on the "type" field. func TypeLTE(v string) predicate.Connector { return predicate.Connector(sql.FieldLTE(FieldType, v)) } // TypeContains applies the Contains predicate on the "type" field. func TypeContains(v string) predicate.Connector { return predicate.Connector(sql.FieldContains(FieldType, v)) } // TypeHasPrefix applies the HasPrefix predicate on the "type" field. func TypeHasPrefix(v string) predicate.Connector { return predicate.Connector(sql.FieldHasPrefix(FieldType, v)) } // TypeHasSuffix applies the HasSuffix predicate on the "type" field. func TypeHasSuffix(v string) predicate.Connector { return predicate.Connector(sql.FieldHasSuffix(FieldType, v)) } // TypeEqualFold applies the EqualFold predicate on the "type" field. func TypeEqualFold(v string) predicate.Connector { return predicate.Connector(sql.FieldEqualFold(FieldType, v)) } // TypeContainsFold applies the ContainsFold predicate on the "type" field. func TypeContainsFold(v string) predicate.Connector { return predicate.Connector(sql.FieldContainsFold(FieldType, v)) } // NameEQ applies the EQ predicate on the "name" field. func NameEQ(v string) predicate.Connector { return predicate.Connector(sql.FieldEQ(FieldName, v)) } // NameNEQ applies the NEQ predicate on the "name" field. func NameNEQ(v string) predicate.Connector { return predicate.Connector(sql.FieldNEQ(FieldName, v)) } // NameIn applies the In predicate on the "name" field. func NameIn(vs ...string) predicate.Connector { return predicate.Connector(sql.FieldIn(FieldName, vs...)) } // NameNotIn applies the NotIn predicate on the "name" field. func NameNotIn(vs ...string) predicate.Connector { return predicate.Connector(sql.FieldNotIn(FieldName, vs...)) } // NameGT applies the GT predicate on the "name" field. func NameGT(v string) predicate.Connector { return predicate.Connector(sql.FieldGT(FieldName, v)) } // NameGTE applies the GTE predicate on the "name" field. func NameGTE(v string) predicate.Connector { return predicate.Connector(sql.FieldGTE(FieldName, v)) } // NameLT applies the LT predicate on the "name" field. func NameLT(v string) predicate.Connector { return predicate.Connector(sql.FieldLT(FieldName, v)) } // NameLTE applies the LTE predicate on the "name" field. func NameLTE(v string) predicate.Connector { return predicate.Connector(sql.FieldLTE(FieldName, v)) } // NameContains applies the Contains predicate on the "name" field. func NameContains(v string) predicate.Connector { return predicate.Connector(sql.FieldContains(FieldName, v)) } // NameHasPrefix applies the HasPrefix predicate on the "name" field. func NameHasPrefix(v string) predicate.Connector { return predicate.Connector(sql.FieldHasPrefix(FieldName, v)) } // NameHasSuffix applies the HasSuffix predicate on the "name" field. func NameHasSuffix(v string) predicate.Connector { return predicate.Connector(sql.FieldHasSuffix(FieldName, v)) } // NameEqualFold applies the EqualFold predicate on the "name" field. func NameEqualFold(v string) predicate.Connector { return predicate.Connector(sql.FieldEqualFold(FieldName, v)) } // NameContainsFold applies the ContainsFold predicate on the "name" field. func NameContainsFold(v string) predicate.Connector { return predicate.Connector(sql.FieldContainsFold(FieldName, v)) } // ResourceVersionEQ applies the EQ predicate on the "resource_version" field. func ResourceVersionEQ(v string) predicate.Connector { return predicate.Connector(sql.FieldEQ(FieldResourceVersion, v)) } // ResourceVersionNEQ applies the NEQ predicate on the "resource_version" field. func ResourceVersionNEQ(v string) predicate.Connector { return predicate.Connector(sql.FieldNEQ(FieldResourceVersion, v)) } // ResourceVersionIn applies the In predicate on the "resource_version" field. func ResourceVersionIn(vs ...string) predicate.Connector { return predicate.Connector(sql.FieldIn(FieldResourceVersion, vs...)) } // ResourceVersionNotIn applies the NotIn predicate on the "resource_version" field. func ResourceVersionNotIn(vs ...string) predicate.Connector { return predicate.Connector(sql.FieldNotIn(FieldResourceVersion, vs...)) } // ResourceVersionGT applies the GT predicate on the "resource_version" field. func ResourceVersionGT(v string) predicate.Connector { return predicate.Connector(sql.FieldGT(FieldResourceVersion, v)) } // ResourceVersionGTE applies the GTE predicate on the "resource_version" field. func ResourceVersionGTE(v string) predicate.Connector { return predicate.Connector(sql.FieldGTE(FieldResourceVersion, v)) } // ResourceVersionLT applies the LT predicate on the "resource_version" field. func ResourceVersionLT(v string) predicate.Connector { return predicate.Connector(sql.FieldLT(FieldResourceVersion, v)) } // ResourceVersionLTE applies the LTE predicate on the "resource_version" field. func ResourceVersionLTE(v string) predicate.Connector { return predicate.Connector(sql.FieldLTE(FieldResourceVersion, v)) } // ResourceVersionContains applies the Contains predicate on the "resource_version" field. func ResourceVersionContains(v string) predicate.Connector { return predicate.Connector(sql.FieldContains(FieldResourceVersion, v)) } // ResourceVersionHasPrefix applies the HasPrefix predicate on the "resource_version" field. func ResourceVersionHasPrefix(v string) predicate.Connector { return predicate.Connector(sql.FieldHasPrefix(FieldResourceVersion, v)) } // ResourceVersionHasSuffix applies the HasSuffix predicate on the "resource_version" field. func ResourceVersionHasSuffix(v string) predicate.Connector { return predicate.Connector(sql.FieldHasSuffix(FieldResourceVersion, v)) } // ResourceVersionEqualFold applies the EqualFold predicate on the "resource_version" field. func ResourceVersionEqualFold(v string) predicate.Connector { return predicate.Connector(sql.FieldEqualFold(FieldResourceVersion, v)) } // ResourceVersionContainsFold applies the ContainsFold predicate on the "resource_version" field. func ResourceVersionContainsFold(v string) predicate.Connector { return predicate.Connector(sql.FieldContainsFold(FieldResourceVersion, v)) } // ConfigEQ applies the EQ predicate on the "config" field. func ConfigEQ(v []byte) predicate.Connector { return predicate.Connector(sql.FieldEQ(FieldConfig, v)) } // ConfigNEQ applies the NEQ predicate on the "config" field. func ConfigNEQ(v []byte) predicate.Connector { return predicate.Connector(sql.FieldNEQ(FieldConfig, v)) } // ConfigIn applies the In predicate on the "config" field. func ConfigIn(vs ...[]byte) predicate.Connector { return predicate.Connector(sql.FieldIn(FieldConfig, vs...)) } // ConfigNotIn applies the NotIn predicate on the "config" field. func ConfigNotIn(vs ...[]byte) predicate.Connector { return predicate.Connector(sql.FieldNotIn(FieldConfig, vs...)) } // ConfigGT applies the GT predicate on the "config" field. func ConfigGT(v []byte) predicate.Connector { return predicate.Connector(sql.FieldGT(FieldConfig, v)) } // ConfigGTE applies the GTE predicate on the "config" field. func ConfigGTE(v []byte) predicate.Connector { return predicate.Connector(sql.FieldGTE(FieldConfig, v)) } // ConfigLT applies the LT predicate on the "config" field. func ConfigLT(v []byte) predicate.Connector { return predicate.Connector(sql.FieldLT(FieldConfig, v)) } // ConfigLTE applies the LTE predicate on the "config" field. func ConfigLTE(v []byte) predicate.Connector { return predicate.Connector(sql.FieldLTE(FieldConfig, v)) } // GrantTypesIsNil applies the IsNil predicate on the "grant_types" field. func GrantTypesIsNil() predicate.Connector { return predicate.Connector(sql.FieldIsNull(FieldGrantTypes)) } // GrantTypesNotNil applies the NotNil predicate on the "grant_types" field. func GrantTypesNotNil() predicate.Connector { return predicate.Connector(sql.FieldNotNull(FieldGrantTypes)) } // And groups predicates with the AND operator between them. func And(predicates ...predicate.Connector) predicate.Connector { return predicate.Connector(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.Connector) predicate.Connector { return predicate.Connector(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.Connector) predicate.Connector { return predicate.Connector(sql.NotPredicates(p)) } ================================================ FILE: storage/ent/db/connector.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "encoding/json" "fmt" "strings" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/connector" ) // Connector is the model entity for the Connector schema. type Connector struct { config `json:"-"` // ID of the ent. ID string `json:"id,omitempty"` // Type holds the value of the "type" field. Type string `json:"type,omitempty"` // Name holds the value of the "name" field. Name string `json:"name,omitempty"` // ResourceVersion holds the value of the "resource_version" field. ResourceVersion string `json:"resource_version,omitempty"` // Config holds the value of the "config" field. Config []byte `json:"config,omitempty"` // GrantTypes holds the value of the "grant_types" field. GrantTypes []string `json:"grant_types,omitempty"` selectValues sql.SelectValues } // scanValues returns the types for scanning values from sql.Rows. func (*Connector) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { case connector.FieldConfig, connector.FieldGrantTypes: values[i] = new([]byte) case connector.FieldID, connector.FieldType, connector.FieldName, connector.FieldResourceVersion: values[i] = new(sql.NullString) default: values[i] = new(sql.UnknownType) } } return values, nil } // assignValues assigns the values that were returned from sql.Rows (after scanning) // to the Connector fields. func (_m *Connector) assignValues(columns []string, values []any) error { if m, n := len(values), len(columns); m < n { return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) } for i := range columns { switch columns[i] { case connector.FieldID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field id", values[i]) } else if value.Valid { _m.ID = value.String } case connector.FieldType: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field type", values[i]) } else if value.Valid { _m.Type = value.String } case connector.FieldName: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field name", values[i]) } else if value.Valid { _m.Name = value.String } case connector.FieldResourceVersion: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field resource_version", values[i]) } else if value.Valid { _m.ResourceVersion = value.String } case connector.FieldConfig: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field config", values[i]) } else if value != nil { _m.Config = *value } case connector.FieldGrantTypes: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field grant_types", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.GrantTypes); err != nil { return fmt.Errorf("unmarshal field grant_types: %w", err) } } default: _m.selectValues.Set(columns[i], values[i]) } } return nil } // Value returns the ent.Value that was dynamically selected and assigned to the Connector. // This includes values selected through modifiers, order, etc. func (_m *Connector) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } // Update returns a builder for updating this Connector. // Note that you need to call Connector.Unwrap() before calling this method if this Connector // was returned from a transaction, and the transaction was committed or rolled back. func (_m *Connector) Update() *ConnectorUpdateOne { return NewConnectorClient(_m.config).UpdateOne(_m) } // Unwrap unwraps the Connector entity that was returned from a transaction after it was closed, // so that all future queries will be executed through the driver which created the transaction. func (_m *Connector) Unwrap() *Connector { _tx, ok := _m.config.driver.(*txDriver) if !ok { panic("db: Connector is not a transactional entity") } _m.config.driver = _tx.drv return _m } // String implements the fmt.Stringer. func (_m *Connector) String() string { var builder strings.Builder builder.WriteString("Connector(") builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) builder.WriteString("type=") builder.WriteString(_m.Type) builder.WriteString(", ") builder.WriteString("name=") builder.WriteString(_m.Name) builder.WriteString(", ") builder.WriteString("resource_version=") builder.WriteString(_m.ResourceVersion) builder.WriteString(", ") builder.WriteString("config=") builder.WriteString(fmt.Sprintf("%v", _m.Config)) builder.WriteString(", ") builder.WriteString("grant_types=") builder.WriteString(fmt.Sprintf("%v", _m.GrantTypes)) builder.WriteByte(')') return builder.String() } // Connectors is a parsable slice of Connector. type Connectors []*Connector ================================================ FILE: storage/ent/db/connector_create.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/connector" ) // ConnectorCreate is the builder for creating a Connector entity. type ConnectorCreate struct { config mutation *ConnectorMutation hooks []Hook } // SetType sets the "type" field. func (_c *ConnectorCreate) SetType(v string) *ConnectorCreate { _c.mutation.SetType(v) return _c } // SetName sets the "name" field. func (_c *ConnectorCreate) SetName(v string) *ConnectorCreate { _c.mutation.SetName(v) return _c } // SetResourceVersion sets the "resource_version" field. func (_c *ConnectorCreate) SetResourceVersion(v string) *ConnectorCreate { _c.mutation.SetResourceVersion(v) return _c } // SetConfig sets the "config" field. func (_c *ConnectorCreate) SetConfig(v []byte) *ConnectorCreate { _c.mutation.SetConfig(v) return _c } // SetGrantTypes sets the "grant_types" field. func (_c *ConnectorCreate) SetGrantTypes(v []string) *ConnectorCreate { _c.mutation.SetGrantTypes(v) return _c } // SetID sets the "id" field. func (_c *ConnectorCreate) SetID(v string) *ConnectorCreate { _c.mutation.SetID(v) return _c } // Mutation returns the ConnectorMutation object of the builder. func (_c *ConnectorCreate) Mutation() *ConnectorMutation { return _c.mutation } // Save creates the Connector in the database. func (_c *ConnectorCreate) Save(ctx context.Context) (*Connector, error) { return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) } // SaveX calls Save and panics if Save returns an error. func (_c *ConnectorCreate) SaveX(ctx context.Context) *Connector { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *ConnectorCreate) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *ConnectorCreate) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_c *ConnectorCreate) check() error { if _, ok := _c.mutation.GetType(); !ok { return &ValidationError{Name: "type", err: errors.New(`db: missing required field "Connector.type"`)} } if v, ok := _c.mutation.GetType(); ok { if err := connector.TypeValidator(v); err != nil { return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "Connector.type": %w`, err)} } } if _, ok := _c.mutation.Name(); !ok { return &ValidationError{Name: "name", err: errors.New(`db: missing required field "Connector.name"`)} } if v, ok := _c.mutation.Name(); ok { if err := connector.NameValidator(v); err != nil { return &ValidationError{Name: "name", err: fmt.Errorf(`db: validator failed for field "Connector.name": %w`, err)} } } if _, ok := _c.mutation.ResourceVersion(); !ok { return &ValidationError{Name: "resource_version", err: errors.New(`db: missing required field "Connector.resource_version"`)} } if _, ok := _c.mutation.Config(); !ok { return &ValidationError{Name: "config", err: errors.New(`db: missing required field "Connector.config"`)} } if v, ok := _c.mutation.ID(); ok { if err := connector.IDValidator(v); err != nil { return &ValidationError{Name: "id", err: fmt.Errorf(`db: validator failed for field "Connector.id": %w`, err)} } } return nil } func (_c *ConnectorCreate) sqlSave(ctx context.Context) (*Connector, error) { if err := _c.check(); err != nil { return nil, err } _node, _spec := _c.createSpec() if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } if _spec.ID.Value != nil { if id, ok := _spec.ID.Value.(string); ok { _node.ID = id } else { return nil, fmt.Errorf("unexpected Connector.ID type: %T", _spec.ID.Value) } } _c.mutation.id = &_node.ID _c.mutation.done = true return _node, nil } func (_c *ConnectorCreate) createSpec() (*Connector, *sqlgraph.CreateSpec) { var ( _node = &Connector{config: _c.config} _spec = sqlgraph.NewCreateSpec(connector.Table, sqlgraph.NewFieldSpec(connector.FieldID, field.TypeString)) ) if id, ok := _c.mutation.ID(); ok { _node.ID = id _spec.ID.Value = id } if value, ok := _c.mutation.GetType(); ok { _spec.SetField(connector.FieldType, field.TypeString, value) _node.Type = value } if value, ok := _c.mutation.Name(); ok { _spec.SetField(connector.FieldName, field.TypeString, value) _node.Name = value } if value, ok := _c.mutation.ResourceVersion(); ok { _spec.SetField(connector.FieldResourceVersion, field.TypeString, value) _node.ResourceVersion = value } if value, ok := _c.mutation.Config(); ok { _spec.SetField(connector.FieldConfig, field.TypeBytes, value) _node.Config = value } if value, ok := _c.mutation.GrantTypes(); ok { _spec.SetField(connector.FieldGrantTypes, field.TypeJSON, value) _node.GrantTypes = value } return _node, _spec } // ConnectorCreateBulk is the builder for creating many Connector entities in bulk. type ConnectorCreateBulk struct { config err error builders []*ConnectorCreate } // Save creates the Connector entities in the database. func (_c *ConnectorCreateBulk) Save(ctx context.Context) ([]*Connector, error) { if _c.err != nil { return nil, _c.err } specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) nodes := make([]*Connector, len(_c.builders)) mutators := make([]Mutator, len(_c.builders)) for i := range _c.builders { func(i int, root context.Context) { builder := _c.builders[i] var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*ConnectorMutation) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } if err := builder.check(); err != nil { return nil, err } builder.mutation = mutation var err error nodes[i], specs[i] = builder.createSpec() if i < len(mutators)-1 { _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) } else { spec := &sqlgraph.BatchCreateSpec{Nodes: specs} // Invoke the actual operation on the latest mutation in the chain. if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } } } if err != nil { return nil, err } mutation.id = &nodes[i].ID mutation.done = true return nodes[i], nil }) for i := len(builder.hooks) - 1; i >= 0; i-- { mut = builder.hooks[i](mut) } mutators[i] = mut }(i, ctx) } if len(mutators) > 0 { if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { return nil, err } } return nodes, nil } // SaveX is like Save, but panics if an error occurs. func (_c *ConnectorCreateBulk) SaveX(ctx context.Context) []*Connector { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *ConnectorCreateBulk) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *ConnectorCreateBulk) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/connector_delete.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/connector" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ConnectorDelete is the builder for deleting a Connector entity. type ConnectorDelete struct { config hooks []Hook mutation *ConnectorMutation } // Where appends a list predicates to the ConnectorDelete builder. func (_d *ConnectorDelete) Where(ps ...predicate.Connector) *ConnectorDelete { _d.mutation.Where(ps...) return _d } // Exec executes the deletion query and returns how many vertices were deleted. func (_d *ConnectorDelete) Exec(ctx context.Context) (int, error) { return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) } // ExecX is like Exec, but panics if an error occurs. func (_d *ConnectorDelete) ExecX(ctx context.Context) int { n, err := _d.Exec(ctx) if err != nil { panic(err) } return n } func (_d *ConnectorDelete) sqlExec(ctx context.Context) (int, error) { _spec := sqlgraph.NewDeleteSpec(connector.Table, sqlgraph.NewFieldSpec(connector.FieldID, field.TypeString)) if ps := _d.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) if err != nil && sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } _d.mutation.done = true return affected, err } // ConnectorDeleteOne is the builder for deleting a single Connector entity. type ConnectorDeleteOne struct { _d *ConnectorDelete } // Where appends a list predicates to the ConnectorDelete builder. func (_d *ConnectorDeleteOne) Where(ps ...predicate.Connector) *ConnectorDeleteOne { _d._d.mutation.Where(ps...) return _d } // Exec executes the deletion query. func (_d *ConnectorDeleteOne) Exec(ctx context.Context) error { n, err := _d._d.Exec(ctx) switch { case err != nil: return err case n == 0: return &NotFoundError{connector.Label} default: return nil } } // ExecX is like Exec, but panics if an error occurs. func (_d *ConnectorDeleteOne) ExecX(ctx context.Context) { if err := _d.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/connector_query.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "fmt" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/connector" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ConnectorQuery is the builder for querying Connector entities. type ConnectorQuery struct { config ctx *QueryContext order []connector.OrderOption inters []Interceptor predicates []predicate.Connector // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) } // Where adds a new predicate for the ConnectorQuery builder. func (_q *ConnectorQuery) Where(ps ...predicate.Connector) *ConnectorQuery { _q.predicates = append(_q.predicates, ps...) return _q } // Limit the number of records to be returned by this query. func (_q *ConnectorQuery) Limit(limit int) *ConnectorQuery { _q.ctx.Limit = &limit return _q } // Offset to start from. func (_q *ConnectorQuery) Offset(offset int) *ConnectorQuery { _q.ctx.Offset = &offset return _q } // Unique configures the query builder to filter duplicate records on query. // By default, unique is set to true, and can be disabled using this method. func (_q *ConnectorQuery) Unique(unique bool) *ConnectorQuery { _q.ctx.Unique = &unique return _q } // Order specifies how the records should be ordered. func (_q *ConnectorQuery) Order(o ...connector.OrderOption) *ConnectorQuery { _q.order = append(_q.order, o...) return _q } // First returns the first Connector entity from the query. // Returns a *NotFoundError when no Connector was found. func (_q *ConnectorQuery) First(ctx context.Context) (*Connector, error) { nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) if err != nil { return nil, err } if len(nodes) == 0 { return nil, &NotFoundError{connector.Label} } return nodes[0], nil } // FirstX is like First, but panics if an error occurs. func (_q *ConnectorQuery) FirstX(ctx context.Context) *Connector { node, err := _q.First(ctx) if err != nil && !IsNotFound(err) { panic(err) } return node } // FirstID returns the first Connector ID from the query. // Returns a *NotFoundError when no Connector ID was found. func (_q *ConnectorQuery) FirstID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { return } if len(ids) == 0 { err = &NotFoundError{connector.Label} return } return ids[0], nil } // FirstIDX is like FirstID, but panics if an error occurs. func (_q *ConnectorQuery) FirstIDX(ctx context.Context) string { id, err := _q.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) } return id } // Only returns a single Connector entity found by the query, ensuring it only returns one. // Returns a *NotSingularError when more than one Connector entity is found. // Returns a *NotFoundError when no Connector entities are found. func (_q *ConnectorQuery) Only(ctx context.Context) (*Connector, error) { nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) if err != nil { return nil, err } switch len(nodes) { case 1: return nodes[0], nil case 0: return nil, &NotFoundError{connector.Label} default: return nil, &NotSingularError{connector.Label} } } // OnlyX is like Only, but panics if an error occurs. func (_q *ConnectorQuery) OnlyX(ctx context.Context) *Connector { node, err := _q.Only(ctx) if err != nil { panic(err) } return node } // OnlyID is like Only, but returns the only Connector ID in the query. // Returns a *NotSingularError when more than one Connector ID is found. // Returns a *NotFoundError when no entities are found. func (_q *ConnectorQuery) OnlyID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { return } switch len(ids) { case 1: id = ids[0] case 0: err = &NotFoundError{connector.Label} default: err = &NotSingularError{connector.Label} } return } // OnlyIDX is like OnlyID, but panics if an error occurs. func (_q *ConnectorQuery) OnlyIDX(ctx context.Context) string { id, err := _q.OnlyID(ctx) if err != nil { panic(err) } return id } // All executes the query and returns a list of Connectors. func (_q *ConnectorQuery) All(ctx context.Context) ([]*Connector, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) if err := _q.prepareQuery(ctx); err != nil { return nil, err } qr := querierAll[[]*Connector, *ConnectorQuery]() return withInterceptors[[]*Connector](ctx, _q, qr, _q.inters) } // AllX is like All, but panics if an error occurs. func (_q *ConnectorQuery) AllX(ctx context.Context) []*Connector { nodes, err := _q.All(ctx) if err != nil { panic(err) } return nodes } // IDs executes the query and returns a list of Connector IDs. func (_q *ConnectorQuery) IDs(ctx context.Context) (ids []string, err error) { if _q.ctx.Unique == nil && _q.path != nil { _q.Unique(true) } ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) if err = _q.Select(connector.FieldID).Scan(ctx, &ids); err != nil { return nil, err } return ids, nil } // IDsX is like IDs, but panics if an error occurs. func (_q *ConnectorQuery) IDsX(ctx context.Context) []string { ids, err := _q.IDs(ctx) if err != nil { panic(err) } return ids } // Count returns the count of the given query. func (_q *ConnectorQuery) Count(ctx context.Context) (int, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) if err := _q.prepareQuery(ctx); err != nil { return 0, err } return withInterceptors[int](ctx, _q, querierCount[*ConnectorQuery](), _q.inters) } // CountX is like Count, but panics if an error occurs. func (_q *ConnectorQuery) CountX(ctx context.Context) int { count, err := _q.Count(ctx) if err != nil { panic(err) } return count } // Exist returns true if the query has elements in the graph. func (_q *ConnectorQuery) Exist(ctx context.Context) (bool, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) switch _, err := _q.FirstID(ctx); { case IsNotFound(err): return false, nil case err != nil: return false, fmt.Errorf("db: check existence: %w", err) default: return true, nil } } // ExistX is like Exist, but panics if an error occurs. func (_q *ConnectorQuery) ExistX(ctx context.Context) bool { exist, err := _q.Exist(ctx) if err != nil { panic(err) } return exist } // Clone returns a duplicate of the ConnectorQuery builder, including all associated steps. It can be // used to prepare common query builders and use them differently after the clone is made. func (_q *ConnectorQuery) Clone() *ConnectorQuery { if _q == nil { return nil } return &ConnectorQuery{ config: _q.config, ctx: _q.ctx.Clone(), order: append([]connector.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.Connector{}, _q.predicates...), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // // Example: // // var v []struct { // Type string `json:"type,omitempty"` // Count int `json:"count,omitempty"` // } // // client.Connector.Query(). // GroupBy(connector.FieldType). // Aggregate(db.Count()). // Scan(ctx, &v) func (_q *ConnectorQuery) GroupBy(field string, fields ...string) *ConnectorGroupBy { _q.ctx.Fields = append([]string{field}, fields...) grbuild := &ConnectorGroupBy{build: _q} grbuild.flds = &_q.ctx.Fields grbuild.label = connector.Label grbuild.scan = grbuild.Scan return grbuild } // Select allows the selection one or more fields/columns for the given query, // instead of selecting all fields in the entity. // // Example: // // var v []struct { // Type string `json:"type,omitempty"` // } // // client.Connector.Query(). // Select(connector.FieldType). // Scan(ctx, &v) func (_q *ConnectorQuery) Select(fields ...string) *ConnectorSelect { _q.ctx.Fields = append(_q.ctx.Fields, fields...) sbuild := &ConnectorSelect{ConnectorQuery: _q} sbuild.label = connector.Label sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan return sbuild } // Aggregate returns a ConnectorSelect configured with the given aggregations. func (_q *ConnectorQuery) Aggregate(fns ...AggregateFunc) *ConnectorSelect { return _q.Select().Aggregate(fns...) } func (_q *ConnectorQuery) prepareQuery(ctx context.Context) error { for _, inter := range _q.inters { if inter == nil { return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") } if trv, ok := inter.(Traverser); ok { if err := trv.Traverse(ctx, _q); err != nil { return err } } } for _, f := range _q.ctx.Fields { if !connector.ValidColumn(f) { return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } } if _q.path != nil { prev, err := _q.path(ctx) if err != nil { return err } _q.sql = prev } return nil } func (_q *ConnectorQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Connector, error) { var ( nodes = []*Connector{} _spec = _q.querySpec() ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*Connector).scanValues(nil, columns) } _spec.Assign = func(columns []string, values []any) error { node := &Connector{config: _q.config} nodes = append(nodes, node) return node.assignValues(columns, values) } for i := range hooks { hooks[i](ctx, _spec) } if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { return nil, err } if len(nodes) == 0 { return nodes, nil } return nodes, nil } func (_q *ConnectorQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields if len(_q.ctx.Fields) > 0 { _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique } return sqlgraph.CountNodes(ctx, _q.driver, _spec) } func (_q *ConnectorQuery) querySpec() *sqlgraph.QuerySpec { _spec := sqlgraph.NewQuerySpec(connector.Table, connector.Columns, sqlgraph.NewFieldSpec(connector.FieldID, field.TypeString)) _spec.From = _q.sql if unique := _q.ctx.Unique; unique != nil { _spec.Unique = *unique } else if _q.path != nil { _spec.Unique = true } if fields := _q.ctx.Fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, connector.FieldID) for i := range fields { if fields[i] != connector.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } } if ps := _q.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if limit := _q.ctx.Limit; limit != nil { _spec.Limit = *limit } if offset := _q.ctx.Offset; offset != nil { _spec.Offset = *offset } if ps := _q.order; len(ps) > 0 { _spec.Order = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } return _spec } func (_q *ConnectorQuery) sqlQuery(ctx context.Context) *sql.Selector { builder := sql.Dialect(_q.driver.Dialect()) t1 := builder.Table(connector.Table) columns := _q.ctx.Fields if len(columns) == 0 { columns = connector.Columns } selector := builder.Select(t1.Columns(columns...)...).From(t1) if _q.sql != nil { selector = _q.sql selector.Select(selector.Columns(columns...)...) } if _q.ctx.Unique != nil && *_q.ctx.Unique { selector.Distinct() } for _, p := range _q.predicates { p(selector) } for _, p := range _q.order { p(selector) } if offset := _q.ctx.Offset; offset != nil { // limit is mandatory for offset clause. We start // with default value, and override it below if needed. selector.Offset(*offset).Limit(math.MaxInt32) } if limit := _q.ctx.Limit; limit != nil { selector.Limit(*limit) } return selector } // ConnectorGroupBy is the group-by builder for Connector entities. type ConnectorGroupBy struct { selector build *ConnectorQuery } // Aggregate adds the given aggregation functions to the group-by query. func (_g *ConnectorGroupBy) Aggregate(fns ...AggregateFunc) *ConnectorGroupBy { _g.fns = append(_g.fns, fns...) return _g } // Scan applies the selector query and scans the result into the given value. func (_g *ConnectorGroupBy) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) if err := _g.build.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*ConnectorQuery, *ConnectorGroupBy](ctx, _g.build, _g, _g.build.inters, v) } func (_g *ConnectorGroupBy) sqlScan(ctx context.Context, root *ConnectorQuery, v any) error { selector := root.sqlQuery(ctx).Select() aggregation := make([]string, 0, len(_g.fns)) for _, fn := range _g.fns { aggregation = append(aggregation, fn(selector)) } if len(selector.SelectedColumns()) == 0 { columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) for _, f := range *_g.flds { columns = append(columns, selector.C(f)) } columns = append(columns, aggregation...) selector.Select(columns...) } selector.GroupBy(selector.Columns(*_g.flds...)...) if err := selector.Err(); err != nil { return err } rows := &sql.Rows{} query, args := selector.Query() if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } // ConnectorSelect is the builder for selecting fields of Connector entities. type ConnectorSelect struct { *ConnectorQuery selector } // Aggregate adds the given aggregation functions to the selector query. func (_s *ConnectorSelect) Aggregate(fns ...AggregateFunc) *ConnectorSelect { _s.fns = append(_s.fns, fns...) return _s } // Scan applies the selector query and scans the result into the given value. func (_s *ConnectorSelect) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) if err := _s.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*ConnectorQuery, *ConnectorSelect](ctx, _s.ConnectorQuery, _s, _s.inters, v) } func (_s *ConnectorSelect) sqlScan(ctx context.Context, root *ConnectorQuery, v any) error { selector := root.sqlQuery(ctx) aggregation := make([]string, 0, len(_s.fns)) for _, fn := range _s.fns { aggregation = append(aggregation, fn(selector)) } switch n := len(*_s.selector.flds); { case n == 0 && len(aggregation) > 0: selector.Select(aggregation...) case n != 0 && len(aggregation) > 0: selector.AppendSelect(aggregation...) } rows := &sql.Rows{} query, args := selector.Query() if err := _s.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } ================================================ FILE: storage/ent/db/connector_update.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/connector" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ConnectorUpdate is the builder for updating Connector entities. type ConnectorUpdate struct { config hooks []Hook mutation *ConnectorMutation } // Where appends a list predicates to the ConnectorUpdate builder. func (_u *ConnectorUpdate) Where(ps ...predicate.Connector) *ConnectorUpdate { _u.mutation.Where(ps...) return _u } // SetType sets the "type" field. func (_u *ConnectorUpdate) SetType(v string) *ConnectorUpdate { _u.mutation.SetType(v) return _u } // SetNillableType sets the "type" field if the given value is not nil. func (_u *ConnectorUpdate) SetNillableType(v *string) *ConnectorUpdate { if v != nil { _u.SetType(*v) } return _u } // SetName sets the "name" field. func (_u *ConnectorUpdate) SetName(v string) *ConnectorUpdate { _u.mutation.SetName(v) return _u } // SetNillableName sets the "name" field if the given value is not nil. func (_u *ConnectorUpdate) SetNillableName(v *string) *ConnectorUpdate { if v != nil { _u.SetName(*v) } return _u } // SetResourceVersion sets the "resource_version" field. func (_u *ConnectorUpdate) SetResourceVersion(v string) *ConnectorUpdate { _u.mutation.SetResourceVersion(v) return _u } // SetNillableResourceVersion sets the "resource_version" field if the given value is not nil. func (_u *ConnectorUpdate) SetNillableResourceVersion(v *string) *ConnectorUpdate { if v != nil { _u.SetResourceVersion(*v) } return _u } // SetConfig sets the "config" field. func (_u *ConnectorUpdate) SetConfig(v []byte) *ConnectorUpdate { _u.mutation.SetConfig(v) return _u } // SetGrantTypes sets the "grant_types" field. func (_u *ConnectorUpdate) SetGrantTypes(v []string) *ConnectorUpdate { _u.mutation.SetGrantTypes(v) return _u } // AppendGrantTypes appends value to the "grant_types" field. func (_u *ConnectorUpdate) AppendGrantTypes(v []string) *ConnectorUpdate { _u.mutation.AppendGrantTypes(v) return _u } // ClearGrantTypes clears the value of the "grant_types" field. func (_u *ConnectorUpdate) ClearGrantTypes() *ConnectorUpdate { _u.mutation.ClearGrantTypes() return _u } // Mutation returns the ConnectorMutation object of the builder. func (_u *ConnectorUpdate) Mutation() *ConnectorMutation { return _u.mutation } // Save executes the query and returns the number of nodes affected by the update operation. func (_u *ConnectorUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *ConnectorUpdate) SaveX(ctx context.Context) int { affected, err := _u.Save(ctx) if err != nil { panic(err) } return affected } // Exec executes the query. func (_u *ConnectorUpdate) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *ConnectorUpdate) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *ConnectorUpdate) check() error { if v, ok := _u.mutation.GetType(); ok { if err := connector.TypeValidator(v); err != nil { return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "Connector.type": %w`, err)} } } if v, ok := _u.mutation.Name(); ok { if err := connector.NameValidator(v); err != nil { return &ValidationError{Name: "name", err: fmt.Errorf(`db: validator failed for field "Connector.name": %w`, err)} } } return nil } func (_u *ConnectorUpdate) sqlSave(ctx context.Context) (_node int, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(connector.Table, connector.Columns, sqlgraph.NewFieldSpec(connector.FieldID, field.TypeString)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.GetType(); ok { _spec.SetField(connector.FieldType, field.TypeString, value) } if value, ok := _u.mutation.Name(); ok { _spec.SetField(connector.FieldName, field.TypeString, value) } if value, ok := _u.mutation.ResourceVersion(); ok { _spec.SetField(connector.FieldResourceVersion, field.TypeString, value) } if value, ok := _u.mutation.Config(); ok { _spec.SetField(connector.FieldConfig, field.TypeBytes, value) } if value, ok := _u.mutation.GrantTypes(); ok { _spec.SetField(connector.FieldGrantTypes, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedGrantTypes(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, connector.FieldGrantTypes, value) }) } if _u.mutation.GrantTypesCleared() { _spec.ClearField(connector.FieldGrantTypes, field.TypeJSON) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{connector.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return 0, err } _u.mutation.done = true return _node, nil } // ConnectorUpdateOne is the builder for updating a single Connector entity. type ConnectorUpdateOne struct { config fields []string hooks []Hook mutation *ConnectorMutation } // SetType sets the "type" field. func (_u *ConnectorUpdateOne) SetType(v string) *ConnectorUpdateOne { _u.mutation.SetType(v) return _u } // SetNillableType sets the "type" field if the given value is not nil. func (_u *ConnectorUpdateOne) SetNillableType(v *string) *ConnectorUpdateOne { if v != nil { _u.SetType(*v) } return _u } // SetName sets the "name" field. func (_u *ConnectorUpdateOne) SetName(v string) *ConnectorUpdateOne { _u.mutation.SetName(v) return _u } // SetNillableName sets the "name" field if the given value is not nil. func (_u *ConnectorUpdateOne) SetNillableName(v *string) *ConnectorUpdateOne { if v != nil { _u.SetName(*v) } return _u } // SetResourceVersion sets the "resource_version" field. func (_u *ConnectorUpdateOne) SetResourceVersion(v string) *ConnectorUpdateOne { _u.mutation.SetResourceVersion(v) return _u } // SetNillableResourceVersion sets the "resource_version" field if the given value is not nil. func (_u *ConnectorUpdateOne) SetNillableResourceVersion(v *string) *ConnectorUpdateOne { if v != nil { _u.SetResourceVersion(*v) } return _u } // SetConfig sets the "config" field. func (_u *ConnectorUpdateOne) SetConfig(v []byte) *ConnectorUpdateOne { _u.mutation.SetConfig(v) return _u } // SetGrantTypes sets the "grant_types" field. func (_u *ConnectorUpdateOne) SetGrantTypes(v []string) *ConnectorUpdateOne { _u.mutation.SetGrantTypes(v) return _u } // AppendGrantTypes appends value to the "grant_types" field. func (_u *ConnectorUpdateOne) AppendGrantTypes(v []string) *ConnectorUpdateOne { _u.mutation.AppendGrantTypes(v) return _u } // ClearGrantTypes clears the value of the "grant_types" field. func (_u *ConnectorUpdateOne) ClearGrantTypes() *ConnectorUpdateOne { _u.mutation.ClearGrantTypes() return _u } // Mutation returns the ConnectorMutation object of the builder. func (_u *ConnectorUpdateOne) Mutation() *ConnectorMutation { return _u.mutation } // Where appends a list predicates to the ConnectorUpdate builder. func (_u *ConnectorUpdateOne) Where(ps ...predicate.Connector) *ConnectorUpdateOne { _u.mutation.Where(ps...) return _u } // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (_u *ConnectorUpdateOne) Select(field string, fields ...string) *ConnectorUpdateOne { _u.fields = append([]string{field}, fields...) return _u } // Save executes the query and returns the updated Connector entity. func (_u *ConnectorUpdateOne) Save(ctx context.Context) (*Connector, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *ConnectorUpdateOne) SaveX(ctx context.Context) *Connector { node, err := _u.Save(ctx) if err != nil { panic(err) } return node } // Exec executes the query on the entity. func (_u *ConnectorUpdateOne) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *ConnectorUpdateOne) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *ConnectorUpdateOne) check() error { if v, ok := _u.mutation.GetType(); ok { if err := connector.TypeValidator(v); err != nil { return &ValidationError{Name: "type", err: fmt.Errorf(`db: validator failed for field "Connector.type": %w`, err)} } } if v, ok := _u.mutation.Name(); ok { if err := connector.NameValidator(v); err != nil { return &ValidationError{Name: "name", err: fmt.Errorf(`db: validator failed for field "Connector.name": %w`, err)} } } return nil } func (_u *ConnectorUpdateOne) sqlSave(ctx context.Context) (_node *Connector, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(connector.Table, connector.Columns, sqlgraph.NewFieldSpec(connector.FieldID, field.TypeString)) id, ok := _u.mutation.ID() if !ok { return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "Connector.id" for update`)} } _spec.Node.ID.Value = id if fields := _u.fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, connector.FieldID) for _, f := range fields { if !connector.ValidColumn(f) { return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } if f != connector.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, f) } } } if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.GetType(); ok { _spec.SetField(connector.FieldType, field.TypeString, value) } if value, ok := _u.mutation.Name(); ok { _spec.SetField(connector.FieldName, field.TypeString, value) } if value, ok := _u.mutation.ResourceVersion(); ok { _spec.SetField(connector.FieldResourceVersion, field.TypeString, value) } if value, ok := _u.mutation.Config(); ok { _spec.SetField(connector.FieldConfig, field.TypeBytes, value) } if value, ok := _u.mutation.GrantTypes(); ok { _spec.SetField(connector.FieldGrantTypes, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedGrantTypes(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, connector.FieldGrantTypes, value) }) } if _u.mutation.GrantTypesCleared() { _spec.ClearField(connector.FieldGrantTypes, field.TypeJSON) } _node = &Connector{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{connector.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } _u.mutation.done = true return _node, nil } ================================================ FILE: storage/ent/db/devicerequest/devicerequest.go ================================================ // Code generated by ent, DO NOT EDIT. package devicerequest import ( "entgo.io/ent/dialect/sql" ) const ( // Label holds the string label denoting the devicerequest type in the database. Label = "device_request" // FieldID holds the string denoting the id field in the database. FieldID = "id" // FieldUserCode holds the string denoting the user_code field in the database. FieldUserCode = "user_code" // FieldDeviceCode holds the string denoting the device_code field in the database. FieldDeviceCode = "device_code" // FieldClientID holds the string denoting the client_id field in the database. FieldClientID = "client_id" // FieldClientSecret holds the string denoting the client_secret field in the database. FieldClientSecret = "client_secret" // FieldScopes holds the string denoting the scopes field in the database. FieldScopes = "scopes" // FieldExpiry holds the string denoting the expiry field in the database. FieldExpiry = "expiry" // Table holds the table name of the devicerequest in the database. Table = "device_requests" ) // Columns holds all SQL columns for devicerequest fields. var Columns = []string{ FieldID, FieldUserCode, FieldDeviceCode, FieldClientID, FieldClientSecret, FieldScopes, FieldExpiry, } // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { if column == Columns[i] { return true } } return false } var ( // UserCodeValidator is a validator for the "user_code" field. It is called by the builders before save. UserCodeValidator func(string) error // DeviceCodeValidator is a validator for the "device_code" field. It is called by the builders before save. DeviceCodeValidator func(string) error // ClientIDValidator is a validator for the "client_id" field. It is called by the builders before save. ClientIDValidator func(string) error // ClientSecretValidator is a validator for the "client_secret" field. It is called by the builders before save. ClientSecretValidator func(string) error ) // OrderOption defines the ordering options for the DeviceRequest queries. type OrderOption func(*sql.Selector) // ByID orders the results by the id field. func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } // ByUserCode orders the results by the user_code field. func ByUserCode(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldUserCode, opts...).ToFunc() } // ByDeviceCode orders the results by the device_code field. func ByDeviceCode(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldDeviceCode, opts...).ToFunc() } // ByClientID orders the results by the client_id field. func ByClientID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClientID, opts...).ToFunc() } // ByClientSecret orders the results by the client_secret field. func ByClientSecret(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClientSecret, opts...).ToFunc() } // ByExpiry orders the results by the expiry field. func ByExpiry(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldExpiry, opts...).ToFunc() } ================================================ FILE: storage/ent/db/devicerequest/where.go ================================================ // Code generated by ent, DO NOT EDIT. package devicerequest import ( "time" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ID filters vertices based on their ID field. func ID(id int) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. func IDEQ(id int) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. func IDNEQ(id int) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. func IDIn(ids ...int) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. func IDNotIn(ids ...int) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. func IDGT(id int) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. func IDGTE(id int) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. func IDLT(id int) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. func IDLTE(id int) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldLTE(FieldID, id)) } // UserCode applies equality check predicate on the "user_code" field. It's identical to UserCodeEQ. func UserCode(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEQ(FieldUserCode, v)) } // DeviceCode applies equality check predicate on the "device_code" field. It's identical to DeviceCodeEQ. func DeviceCode(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEQ(FieldDeviceCode, v)) } // ClientID applies equality check predicate on the "client_id" field. It's identical to ClientIDEQ. func ClientID(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEQ(FieldClientID, v)) } // ClientSecret applies equality check predicate on the "client_secret" field. It's identical to ClientSecretEQ. func ClientSecret(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEQ(FieldClientSecret, v)) } // Expiry applies equality check predicate on the "expiry" field. It's identical to ExpiryEQ. func Expiry(v time.Time) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEQ(FieldExpiry, v)) } // UserCodeEQ applies the EQ predicate on the "user_code" field. func UserCodeEQ(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEQ(FieldUserCode, v)) } // UserCodeNEQ applies the NEQ predicate on the "user_code" field. func UserCodeNEQ(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNEQ(FieldUserCode, v)) } // UserCodeIn applies the In predicate on the "user_code" field. func UserCodeIn(vs ...string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldIn(FieldUserCode, vs...)) } // UserCodeNotIn applies the NotIn predicate on the "user_code" field. func UserCodeNotIn(vs ...string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNotIn(FieldUserCode, vs...)) } // UserCodeGT applies the GT predicate on the "user_code" field. func UserCodeGT(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldGT(FieldUserCode, v)) } // UserCodeGTE applies the GTE predicate on the "user_code" field. func UserCodeGTE(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldGTE(FieldUserCode, v)) } // UserCodeLT applies the LT predicate on the "user_code" field. func UserCodeLT(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldLT(FieldUserCode, v)) } // UserCodeLTE applies the LTE predicate on the "user_code" field. func UserCodeLTE(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldLTE(FieldUserCode, v)) } // UserCodeContains applies the Contains predicate on the "user_code" field. func UserCodeContains(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldContains(FieldUserCode, v)) } // UserCodeHasPrefix applies the HasPrefix predicate on the "user_code" field. func UserCodeHasPrefix(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldHasPrefix(FieldUserCode, v)) } // UserCodeHasSuffix applies the HasSuffix predicate on the "user_code" field. func UserCodeHasSuffix(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldHasSuffix(FieldUserCode, v)) } // UserCodeEqualFold applies the EqualFold predicate on the "user_code" field. func UserCodeEqualFold(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEqualFold(FieldUserCode, v)) } // UserCodeContainsFold applies the ContainsFold predicate on the "user_code" field. func UserCodeContainsFold(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldContainsFold(FieldUserCode, v)) } // DeviceCodeEQ applies the EQ predicate on the "device_code" field. func DeviceCodeEQ(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEQ(FieldDeviceCode, v)) } // DeviceCodeNEQ applies the NEQ predicate on the "device_code" field. func DeviceCodeNEQ(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNEQ(FieldDeviceCode, v)) } // DeviceCodeIn applies the In predicate on the "device_code" field. func DeviceCodeIn(vs ...string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldIn(FieldDeviceCode, vs...)) } // DeviceCodeNotIn applies the NotIn predicate on the "device_code" field. func DeviceCodeNotIn(vs ...string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNotIn(FieldDeviceCode, vs...)) } // DeviceCodeGT applies the GT predicate on the "device_code" field. func DeviceCodeGT(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldGT(FieldDeviceCode, v)) } // DeviceCodeGTE applies the GTE predicate on the "device_code" field. func DeviceCodeGTE(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldGTE(FieldDeviceCode, v)) } // DeviceCodeLT applies the LT predicate on the "device_code" field. func DeviceCodeLT(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldLT(FieldDeviceCode, v)) } // DeviceCodeLTE applies the LTE predicate on the "device_code" field. func DeviceCodeLTE(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldLTE(FieldDeviceCode, v)) } // DeviceCodeContains applies the Contains predicate on the "device_code" field. func DeviceCodeContains(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldContains(FieldDeviceCode, v)) } // DeviceCodeHasPrefix applies the HasPrefix predicate on the "device_code" field. func DeviceCodeHasPrefix(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldHasPrefix(FieldDeviceCode, v)) } // DeviceCodeHasSuffix applies the HasSuffix predicate on the "device_code" field. func DeviceCodeHasSuffix(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldHasSuffix(FieldDeviceCode, v)) } // DeviceCodeEqualFold applies the EqualFold predicate on the "device_code" field. func DeviceCodeEqualFold(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEqualFold(FieldDeviceCode, v)) } // DeviceCodeContainsFold applies the ContainsFold predicate on the "device_code" field. func DeviceCodeContainsFold(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldContainsFold(FieldDeviceCode, v)) } // ClientIDEQ applies the EQ predicate on the "client_id" field. func ClientIDEQ(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEQ(FieldClientID, v)) } // ClientIDNEQ applies the NEQ predicate on the "client_id" field. func ClientIDNEQ(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNEQ(FieldClientID, v)) } // ClientIDIn applies the In predicate on the "client_id" field. func ClientIDIn(vs ...string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldIn(FieldClientID, vs...)) } // ClientIDNotIn applies the NotIn predicate on the "client_id" field. func ClientIDNotIn(vs ...string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNotIn(FieldClientID, vs...)) } // ClientIDGT applies the GT predicate on the "client_id" field. func ClientIDGT(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldGT(FieldClientID, v)) } // ClientIDGTE applies the GTE predicate on the "client_id" field. func ClientIDGTE(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldGTE(FieldClientID, v)) } // ClientIDLT applies the LT predicate on the "client_id" field. func ClientIDLT(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldLT(FieldClientID, v)) } // ClientIDLTE applies the LTE predicate on the "client_id" field. func ClientIDLTE(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldLTE(FieldClientID, v)) } // ClientIDContains applies the Contains predicate on the "client_id" field. func ClientIDContains(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldContains(FieldClientID, v)) } // ClientIDHasPrefix applies the HasPrefix predicate on the "client_id" field. func ClientIDHasPrefix(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldHasPrefix(FieldClientID, v)) } // ClientIDHasSuffix applies the HasSuffix predicate on the "client_id" field. func ClientIDHasSuffix(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldHasSuffix(FieldClientID, v)) } // ClientIDEqualFold applies the EqualFold predicate on the "client_id" field. func ClientIDEqualFold(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEqualFold(FieldClientID, v)) } // ClientIDContainsFold applies the ContainsFold predicate on the "client_id" field. func ClientIDContainsFold(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldContainsFold(FieldClientID, v)) } // ClientSecretEQ applies the EQ predicate on the "client_secret" field. func ClientSecretEQ(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEQ(FieldClientSecret, v)) } // ClientSecretNEQ applies the NEQ predicate on the "client_secret" field. func ClientSecretNEQ(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNEQ(FieldClientSecret, v)) } // ClientSecretIn applies the In predicate on the "client_secret" field. func ClientSecretIn(vs ...string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldIn(FieldClientSecret, vs...)) } // ClientSecretNotIn applies the NotIn predicate on the "client_secret" field. func ClientSecretNotIn(vs ...string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNotIn(FieldClientSecret, vs...)) } // ClientSecretGT applies the GT predicate on the "client_secret" field. func ClientSecretGT(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldGT(FieldClientSecret, v)) } // ClientSecretGTE applies the GTE predicate on the "client_secret" field. func ClientSecretGTE(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldGTE(FieldClientSecret, v)) } // ClientSecretLT applies the LT predicate on the "client_secret" field. func ClientSecretLT(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldLT(FieldClientSecret, v)) } // ClientSecretLTE applies the LTE predicate on the "client_secret" field. func ClientSecretLTE(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldLTE(FieldClientSecret, v)) } // ClientSecretContains applies the Contains predicate on the "client_secret" field. func ClientSecretContains(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldContains(FieldClientSecret, v)) } // ClientSecretHasPrefix applies the HasPrefix predicate on the "client_secret" field. func ClientSecretHasPrefix(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldHasPrefix(FieldClientSecret, v)) } // ClientSecretHasSuffix applies the HasSuffix predicate on the "client_secret" field. func ClientSecretHasSuffix(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldHasSuffix(FieldClientSecret, v)) } // ClientSecretEqualFold applies the EqualFold predicate on the "client_secret" field. func ClientSecretEqualFold(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEqualFold(FieldClientSecret, v)) } // ClientSecretContainsFold applies the ContainsFold predicate on the "client_secret" field. func ClientSecretContainsFold(v string) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldContainsFold(FieldClientSecret, v)) } // ScopesIsNil applies the IsNil predicate on the "scopes" field. func ScopesIsNil() predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldIsNull(FieldScopes)) } // ScopesNotNil applies the NotNil predicate on the "scopes" field. func ScopesNotNil() predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNotNull(FieldScopes)) } // ExpiryEQ applies the EQ predicate on the "expiry" field. func ExpiryEQ(v time.Time) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldEQ(FieldExpiry, v)) } // ExpiryNEQ applies the NEQ predicate on the "expiry" field. func ExpiryNEQ(v time.Time) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNEQ(FieldExpiry, v)) } // ExpiryIn applies the In predicate on the "expiry" field. func ExpiryIn(vs ...time.Time) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldIn(FieldExpiry, vs...)) } // ExpiryNotIn applies the NotIn predicate on the "expiry" field. func ExpiryNotIn(vs ...time.Time) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldNotIn(FieldExpiry, vs...)) } // ExpiryGT applies the GT predicate on the "expiry" field. func ExpiryGT(v time.Time) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldGT(FieldExpiry, v)) } // ExpiryGTE applies the GTE predicate on the "expiry" field. func ExpiryGTE(v time.Time) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldGTE(FieldExpiry, v)) } // ExpiryLT applies the LT predicate on the "expiry" field. func ExpiryLT(v time.Time) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldLT(FieldExpiry, v)) } // ExpiryLTE applies the LTE predicate on the "expiry" field. func ExpiryLTE(v time.Time) predicate.DeviceRequest { return predicate.DeviceRequest(sql.FieldLTE(FieldExpiry, v)) } // And groups predicates with the AND operator between them. func And(predicates ...predicate.DeviceRequest) predicate.DeviceRequest { return predicate.DeviceRequest(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.DeviceRequest) predicate.DeviceRequest { return predicate.DeviceRequest(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.DeviceRequest) predicate.DeviceRequest { return predicate.DeviceRequest(sql.NotPredicates(p)) } ================================================ FILE: storage/ent/db/devicerequest.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "encoding/json" "fmt" "strings" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/devicerequest" ) // DeviceRequest is the model entity for the DeviceRequest schema. type DeviceRequest struct { config `json:"-"` // ID of the ent. ID int `json:"id,omitempty"` // UserCode holds the value of the "user_code" field. UserCode string `json:"user_code,omitempty"` // DeviceCode holds the value of the "device_code" field. DeviceCode string `json:"device_code,omitempty"` // ClientID holds the value of the "client_id" field. ClientID string `json:"client_id,omitempty"` // ClientSecret holds the value of the "client_secret" field. ClientSecret string `json:"client_secret,omitempty"` // Scopes holds the value of the "scopes" field. Scopes []string `json:"scopes,omitempty"` // Expiry holds the value of the "expiry" field. Expiry time.Time `json:"expiry,omitempty"` selectValues sql.SelectValues } // scanValues returns the types for scanning values from sql.Rows. func (*DeviceRequest) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { case devicerequest.FieldScopes: values[i] = new([]byte) case devicerequest.FieldID: values[i] = new(sql.NullInt64) case devicerequest.FieldUserCode, devicerequest.FieldDeviceCode, devicerequest.FieldClientID, devicerequest.FieldClientSecret: values[i] = new(sql.NullString) case devicerequest.FieldExpiry: values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } } return values, nil } // assignValues assigns the values that were returned from sql.Rows (after scanning) // to the DeviceRequest fields. func (_m *DeviceRequest) assignValues(columns []string, values []any) error { if m, n := len(values), len(columns); m < n { return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) } for i := range columns { switch columns[i] { case devicerequest.FieldID: value, ok := values[i].(*sql.NullInt64) if !ok { return fmt.Errorf("unexpected type %T for field id", value) } _m.ID = int(value.Int64) case devicerequest.FieldUserCode: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field user_code", values[i]) } else if value.Valid { _m.UserCode = value.String } case devicerequest.FieldDeviceCode: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field device_code", values[i]) } else if value.Valid { _m.DeviceCode = value.String } case devicerequest.FieldClientID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field client_id", values[i]) } else if value.Valid { _m.ClientID = value.String } case devicerequest.FieldClientSecret: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field client_secret", values[i]) } else if value.Valid { _m.ClientSecret = value.String } case devicerequest.FieldScopes: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field scopes", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.Scopes); err != nil { return fmt.Errorf("unmarshal field scopes: %w", err) } } case devicerequest.FieldExpiry: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field expiry", values[i]) } else if value.Valid { _m.Expiry = value.Time } default: _m.selectValues.Set(columns[i], values[i]) } } return nil } // Value returns the ent.Value that was dynamically selected and assigned to the DeviceRequest. // This includes values selected through modifiers, order, etc. func (_m *DeviceRequest) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } // Update returns a builder for updating this DeviceRequest. // Note that you need to call DeviceRequest.Unwrap() before calling this method if this DeviceRequest // was returned from a transaction, and the transaction was committed or rolled back. func (_m *DeviceRequest) Update() *DeviceRequestUpdateOne { return NewDeviceRequestClient(_m.config).UpdateOne(_m) } // Unwrap unwraps the DeviceRequest entity that was returned from a transaction after it was closed, // so that all future queries will be executed through the driver which created the transaction. func (_m *DeviceRequest) Unwrap() *DeviceRequest { _tx, ok := _m.config.driver.(*txDriver) if !ok { panic("db: DeviceRequest is not a transactional entity") } _m.config.driver = _tx.drv return _m } // String implements the fmt.Stringer. func (_m *DeviceRequest) String() string { var builder strings.Builder builder.WriteString("DeviceRequest(") builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) builder.WriteString("user_code=") builder.WriteString(_m.UserCode) builder.WriteString(", ") builder.WriteString("device_code=") builder.WriteString(_m.DeviceCode) builder.WriteString(", ") builder.WriteString("client_id=") builder.WriteString(_m.ClientID) builder.WriteString(", ") builder.WriteString("client_secret=") builder.WriteString(_m.ClientSecret) builder.WriteString(", ") builder.WriteString("scopes=") builder.WriteString(fmt.Sprintf("%v", _m.Scopes)) builder.WriteString(", ") builder.WriteString("expiry=") builder.WriteString(_m.Expiry.Format(time.ANSIC)) builder.WriteByte(')') return builder.String() } // DeviceRequests is a parsable slice of DeviceRequest. type DeviceRequests []*DeviceRequest ================================================ FILE: storage/ent/db/devicerequest_create.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/devicerequest" ) // DeviceRequestCreate is the builder for creating a DeviceRequest entity. type DeviceRequestCreate struct { config mutation *DeviceRequestMutation hooks []Hook } // SetUserCode sets the "user_code" field. func (_c *DeviceRequestCreate) SetUserCode(v string) *DeviceRequestCreate { _c.mutation.SetUserCode(v) return _c } // SetDeviceCode sets the "device_code" field. func (_c *DeviceRequestCreate) SetDeviceCode(v string) *DeviceRequestCreate { _c.mutation.SetDeviceCode(v) return _c } // SetClientID sets the "client_id" field. func (_c *DeviceRequestCreate) SetClientID(v string) *DeviceRequestCreate { _c.mutation.SetClientID(v) return _c } // SetClientSecret sets the "client_secret" field. func (_c *DeviceRequestCreate) SetClientSecret(v string) *DeviceRequestCreate { _c.mutation.SetClientSecret(v) return _c } // SetScopes sets the "scopes" field. func (_c *DeviceRequestCreate) SetScopes(v []string) *DeviceRequestCreate { _c.mutation.SetScopes(v) return _c } // SetExpiry sets the "expiry" field. func (_c *DeviceRequestCreate) SetExpiry(v time.Time) *DeviceRequestCreate { _c.mutation.SetExpiry(v) return _c } // Mutation returns the DeviceRequestMutation object of the builder. func (_c *DeviceRequestCreate) Mutation() *DeviceRequestMutation { return _c.mutation } // Save creates the DeviceRequest in the database. func (_c *DeviceRequestCreate) Save(ctx context.Context) (*DeviceRequest, error) { return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) } // SaveX calls Save and panics if Save returns an error. func (_c *DeviceRequestCreate) SaveX(ctx context.Context) *DeviceRequest { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *DeviceRequestCreate) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *DeviceRequestCreate) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_c *DeviceRequestCreate) check() error { if _, ok := _c.mutation.UserCode(); !ok { return &ValidationError{Name: "user_code", err: errors.New(`db: missing required field "DeviceRequest.user_code"`)} } if v, ok := _c.mutation.UserCode(); ok { if err := devicerequest.UserCodeValidator(v); err != nil { return &ValidationError{Name: "user_code", err: fmt.Errorf(`db: validator failed for field "DeviceRequest.user_code": %w`, err)} } } if _, ok := _c.mutation.DeviceCode(); !ok { return &ValidationError{Name: "device_code", err: errors.New(`db: missing required field "DeviceRequest.device_code"`)} } if v, ok := _c.mutation.DeviceCode(); ok { if err := devicerequest.DeviceCodeValidator(v); err != nil { return &ValidationError{Name: "device_code", err: fmt.Errorf(`db: validator failed for field "DeviceRequest.device_code": %w`, err)} } } if _, ok := _c.mutation.ClientID(); !ok { return &ValidationError{Name: "client_id", err: errors.New(`db: missing required field "DeviceRequest.client_id"`)} } if v, ok := _c.mutation.ClientID(); ok { if err := devicerequest.ClientIDValidator(v); err != nil { return &ValidationError{Name: "client_id", err: fmt.Errorf(`db: validator failed for field "DeviceRequest.client_id": %w`, err)} } } if _, ok := _c.mutation.ClientSecret(); !ok { return &ValidationError{Name: "client_secret", err: errors.New(`db: missing required field "DeviceRequest.client_secret"`)} } if v, ok := _c.mutation.ClientSecret(); ok { if err := devicerequest.ClientSecretValidator(v); err != nil { return &ValidationError{Name: "client_secret", err: fmt.Errorf(`db: validator failed for field "DeviceRequest.client_secret": %w`, err)} } } if _, ok := _c.mutation.Expiry(); !ok { return &ValidationError{Name: "expiry", err: errors.New(`db: missing required field "DeviceRequest.expiry"`)} } return nil } func (_c *DeviceRequestCreate) sqlSave(ctx context.Context) (*DeviceRequest, error) { if err := _c.check(); err != nil { return nil, err } _node, _spec := _c.createSpec() if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } id := _spec.ID.Value.(int64) _node.ID = int(id) _c.mutation.id = &_node.ID _c.mutation.done = true return _node, nil } func (_c *DeviceRequestCreate) createSpec() (*DeviceRequest, *sqlgraph.CreateSpec) { var ( _node = &DeviceRequest{config: _c.config} _spec = sqlgraph.NewCreateSpec(devicerequest.Table, sqlgraph.NewFieldSpec(devicerequest.FieldID, field.TypeInt)) ) if value, ok := _c.mutation.UserCode(); ok { _spec.SetField(devicerequest.FieldUserCode, field.TypeString, value) _node.UserCode = value } if value, ok := _c.mutation.DeviceCode(); ok { _spec.SetField(devicerequest.FieldDeviceCode, field.TypeString, value) _node.DeviceCode = value } if value, ok := _c.mutation.ClientID(); ok { _spec.SetField(devicerequest.FieldClientID, field.TypeString, value) _node.ClientID = value } if value, ok := _c.mutation.ClientSecret(); ok { _spec.SetField(devicerequest.FieldClientSecret, field.TypeString, value) _node.ClientSecret = value } if value, ok := _c.mutation.Scopes(); ok { _spec.SetField(devicerequest.FieldScopes, field.TypeJSON, value) _node.Scopes = value } if value, ok := _c.mutation.Expiry(); ok { _spec.SetField(devicerequest.FieldExpiry, field.TypeTime, value) _node.Expiry = value } return _node, _spec } // DeviceRequestCreateBulk is the builder for creating many DeviceRequest entities in bulk. type DeviceRequestCreateBulk struct { config err error builders []*DeviceRequestCreate } // Save creates the DeviceRequest entities in the database. func (_c *DeviceRequestCreateBulk) Save(ctx context.Context) ([]*DeviceRequest, error) { if _c.err != nil { return nil, _c.err } specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) nodes := make([]*DeviceRequest, len(_c.builders)) mutators := make([]Mutator, len(_c.builders)) for i := range _c.builders { func(i int, root context.Context) { builder := _c.builders[i] var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*DeviceRequestMutation) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } if err := builder.check(); err != nil { return nil, err } builder.mutation = mutation var err error nodes[i], specs[i] = builder.createSpec() if i < len(mutators)-1 { _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) } else { spec := &sqlgraph.BatchCreateSpec{Nodes: specs} // Invoke the actual operation on the latest mutation in the chain. if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } } } if err != nil { return nil, err } mutation.id = &nodes[i].ID if specs[i].ID.Value != nil { id := specs[i].ID.Value.(int64) nodes[i].ID = int(id) } mutation.done = true return nodes[i], nil }) for i := len(builder.hooks) - 1; i >= 0; i-- { mut = builder.hooks[i](mut) } mutators[i] = mut }(i, ctx) } if len(mutators) > 0 { if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { return nil, err } } return nodes, nil } // SaveX is like Save, but panics if an error occurs. func (_c *DeviceRequestCreateBulk) SaveX(ctx context.Context) []*DeviceRequest { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *DeviceRequestCreateBulk) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *DeviceRequestCreateBulk) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/devicerequest_delete.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/devicerequest" "github.com/dexidp/dex/storage/ent/db/predicate" ) // DeviceRequestDelete is the builder for deleting a DeviceRequest entity. type DeviceRequestDelete struct { config hooks []Hook mutation *DeviceRequestMutation } // Where appends a list predicates to the DeviceRequestDelete builder. func (_d *DeviceRequestDelete) Where(ps ...predicate.DeviceRequest) *DeviceRequestDelete { _d.mutation.Where(ps...) return _d } // Exec executes the deletion query and returns how many vertices were deleted. func (_d *DeviceRequestDelete) Exec(ctx context.Context) (int, error) { return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) } // ExecX is like Exec, but panics if an error occurs. func (_d *DeviceRequestDelete) ExecX(ctx context.Context) int { n, err := _d.Exec(ctx) if err != nil { panic(err) } return n } func (_d *DeviceRequestDelete) sqlExec(ctx context.Context) (int, error) { _spec := sqlgraph.NewDeleteSpec(devicerequest.Table, sqlgraph.NewFieldSpec(devicerequest.FieldID, field.TypeInt)) if ps := _d.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) if err != nil && sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } _d.mutation.done = true return affected, err } // DeviceRequestDeleteOne is the builder for deleting a single DeviceRequest entity. type DeviceRequestDeleteOne struct { _d *DeviceRequestDelete } // Where appends a list predicates to the DeviceRequestDelete builder. func (_d *DeviceRequestDeleteOne) Where(ps ...predicate.DeviceRequest) *DeviceRequestDeleteOne { _d._d.mutation.Where(ps...) return _d } // Exec executes the deletion query. func (_d *DeviceRequestDeleteOne) Exec(ctx context.Context) error { n, err := _d._d.Exec(ctx) switch { case err != nil: return err case n == 0: return &NotFoundError{devicerequest.Label} default: return nil } } // ExecX is like Exec, but panics if an error occurs. func (_d *DeviceRequestDeleteOne) ExecX(ctx context.Context) { if err := _d.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/devicerequest_query.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "fmt" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/devicerequest" "github.com/dexidp/dex/storage/ent/db/predicate" ) // DeviceRequestQuery is the builder for querying DeviceRequest entities. type DeviceRequestQuery struct { config ctx *QueryContext order []devicerequest.OrderOption inters []Interceptor predicates []predicate.DeviceRequest // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) } // Where adds a new predicate for the DeviceRequestQuery builder. func (_q *DeviceRequestQuery) Where(ps ...predicate.DeviceRequest) *DeviceRequestQuery { _q.predicates = append(_q.predicates, ps...) return _q } // Limit the number of records to be returned by this query. func (_q *DeviceRequestQuery) Limit(limit int) *DeviceRequestQuery { _q.ctx.Limit = &limit return _q } // Offset to start from. func (_q *DeviceRequestQuery) Offset(offset int) *DeviceRequestQuery { _q.ctx.Offset = &offset return _q } // Unique configures the query builder to filter duplicate records on query. // By default, unique is set to true, and can be disabled using this method. func (_q *DeviceRequestQuery) Unique(unique bool) *DeviceRequestQuery { _q.ctx.Unique = &unique return _q } // Order specifies how the records should be ordered. func (_q *DeviceRequestQuery) Order(o ...devicerequest.OrderOption) *DeviceRequestQuery { _q.order = append(_q.order, o...) return _q } // First returns the first DeviceRequest entity from the query. // Returns a *NotFoundError when no DeviceRequest was found. func (_q *DeviceRequestQuery) First(ctx context.Context) (*DeviceRequest, error) { nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) if err != nil { return nil, err } if len(nodes) == 0 { return nil, &NotFoundError{devicerequest.Label} } return nodes[0], nil } // FirstX is like First, but panics if an error occurs. func (_q *DeviceRequestQuery) FirstX(ctx context.Context) *DeviceRequest { node, err := _q.First(ctx) if err != nil && !IsNotFound(err) { panic(err) } return node } // FirstID returns the first DeviceRequest ID from the query. // Returns a *NotFoundError when no DeviceRequest ID was found. func (_q *DeviceRequestQuery) FirstID(ctx context.Context) (id int, err error) { var ids []int if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { return } if len(ids) == 0 { err = &NotFoundError{devicerequest.Label} return } return ids[0], nil } // FirstIDX is like FirstID, but panics if an error occurs. func (_q *DeviceRequestQuery) FirstIDX(ctx context.Context) int { id, err := _q.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) } return id } // Only returns a single DeviceRequest entity found by the query, ensuring it only returns one. // Returns a *NotSingularError when more than one DeviceRequest entity is found. // Returns a *NotFoundError when no DeviceRequest entities are found. func (_q *DeviceRequestQuery) Only(ctx context.Context) (*DeviceRequest, error) { nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) if err != nil { return nil, err } switch len(nodes) { case 1: return nodes[0], nil case 0: return nil, &NotFoundError{devicerequest.Label} default: return nil, &NotSingularError{devicerequest.Label} } } // OnlyX is like Only, but panics if an error occurs. func (_q *DeviceRequestQuery) OnlyX(ctx context.Context) *DeviceRequest { node, err := _q.Only(ctx) if err != nil { panic(err) } return node } // OnlyID is like Only, but returns the only DeviceRequest ID in the query. // Returns a *NotSingularError when more than one DeviceRequest ID is found. // Returns a *NotFoundError when no entities are found. func (_q *DeviceRequestQuery) OnlyID(ctx context.Context) (id int, err error) { var ids []int if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { return } switch len(ids) { case 1: id = ids[0] case 0: err = &NotFoundError{devicerequest.Label} default: err = &NotSingularError{devicerequest.Label} } return } // OnlyIDX is like OnlyID, but panics if an error occurs. func (_q *DeviceRequestQuery) OnlyIDX(ctx context.Context) int { id, err := _q.OnlyID(ctx) if err != nil { panic(err) } return id } // All executes the query and returns a list of DeviceRequests. func (_q *DeviceRequestQuery) All(ctx context.Context) ([]*DeviceRequest, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) if err := _q.prepareQuery(ctx); err != nil { return nil, err } qr := querierAll[[]*DeviceRequest, *DeviceRequestQuery]() return withInterceptors[[]*DeviceRequest](ctx, _q, qr, _q.inters) } // AllX is like All, but panics if an error occurs. func (_q *DeviceRequestQuery) AllX(ctx context.Context) []*DeviceRequest { nodes, err := _q.All(ctx) if err != nil { panic(err) } return nodes } // IDs executes the query and returns a list of DeviceRequest IDs. func (_q *DeviceRequestQuery) IDs(ctx context.Context) (ids []int, err error) { if _q.ctx.Unique == nil && _q.path != nil { _q.Unique(true) } ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) if err = _q.Select(devicerequest.FieldID).Scan(ctx, &ids); err != nil { return nil, err } return ids, nil } // IDsX is like IDs, but panics if an error occurs. func (_q *DeviceRequestQuery) IDsX(ctx context.Context) []int { ids, err := _q.IDs(ctx) if err != nil { panic(err) } return ids } // Count returns the count of the given query. func (_q *DeviceRequestQuery) Count(ctx context.Context) (int, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) if err := _q.prepareQuery(ctx); err != nil { return 0, err } return withInterceptors[int](ctx, _q, querierCount[*DeviceRequestQuery](), _q.inters) } // CountX is like Count, but panics if an error occurs. func (_q *DeviceRequestQuery) CountX(ctx context.Context) int { count, err := _q.Count(ctx) if err != nil { panic(err) } return count } // Exist returns true if the query has elements in the graph. func (_q *DeviceRequestQuery) Exist(ctx context.Context) (bool, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) switch _, err := _q.FirstID(ctx); { case IsNotFound(err): return false, nil case err != nil: return false, fmt.Errorf("db: check existence: %w", err) default: return true, nil } } // ExistX is like Exist, but panics if an error occurs. func (_q *DeviceRequestQuery) ExistX(ctx context.Context) bool { exist, err := _q.Exist(ctx) if err != nil { panic(err) } return exist } // Clone returns a duplicate of the DeviceRequestQuery builder, including all associated steps. It can be // used to prepare common query builders and use them differently after the clone is made. func (_q *DeviceRequestQuery) Clone() *DeviceRequestQuery { if _q == nil { return nil } return &DeviceRequestQuery{ config: _q.config, ctx: _q.ctx.Clone(), order: append([]devicerequest.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.DeviceRequest{}, _q.predicates...), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // // Example: // // var v []struct { // UserCode string `json:"user_code,omitempty"` // Count int `json:"count,omitempty"` // } // // client.DeviceRequest.Query(). // GroupBy(devicerequest.FieldUserCode). // Aggregate(db.Count()). // Scan(ctx, &v) func (_q *DeviceRequestQuery) GroupBy(field string, fields ...string) *DeviceRequestGroupBy { _q.ctx.Fields = append([]string{field}, fields...) grbuild := &DeviceRequestGroupBy{build: _q} grbuild.flds = &_q.ctx.Fields grbuild.label = devicerequest.Label grbuild.scan = grbuild.Scan return grbuild } // Select allows the selection one or more fields/columns for the given query, // instead of selecting all fields in the entity. // // Example: // // var v []struct { // UserCode string `json:"user_code,omitempty"` // } // // client.DeviceRequest.Query(). // Select(devicerequest.FieldUserCode). // Scan(ctx, &v) func (_q *DeviceRequestQuery) Select(fields ...string) *DeviceRequestSelect { _q.ctx.Fields = append(_q.ctx.Fields, fields...) sbuild := &DeviceRequestSelect{DeviceRequestQuery: _q} sbuild.label = devicerequest.Label sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan return sbuild } // Aggregate returns a DeviceRequestSelect configured with the given aggregations. func (_q *DeviceRequestQuery) Aggregate(fns ...AggregateFunc) *DeviceRequestSelect { return _q.Select().Aggregate(fns...) } func (_q *DeviceRequestQuery) prepareQuery(ctx context.Context) error { for _, inter := range _q.inters { if inter == nil { return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") } if trv, ok := inter.(Traverser); ok { if err := trv.Traverse(ctx, _q); err != nil { return err } } } for _, f := range _q.ctx.Fields { if !devicerequest.ValidColumn(f) { return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } } if _q.path != nil { prev, err := _q.path(ctx) if err != nil { return err } _q.sql = prev } return nil } func (_q *DeviceRequestQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*DeviceRequest, error) { var ( nodes = []*DeviceRequest{} _spec = _q.querySpec() ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*DeviceRequest).scanValues(nil, columns) } _spec.Assign = func(columns []string, values []any) error { node := &DeviceRequest{config: _q.config} nodes = append(nodes, node) return node.assignValues(columns, values) } for i := range hooks { hooks[i](ctx, _spec) } if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { return nil, err } if len(nodes) == 0 { return nodes, nil } return nodes, nil } func (_q *DeviceRequestQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields if len(_q.ctx.Fields) > 0 { _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique } return sqlgraph.CountNodes(ctx, _q.driver, _spec) } func (_q *DeviceRequestQuery) querySpec() *sqlgraph.QuerySpec { _spec := sqlgraph.NewQuerySpec(devicerequest.Table, devicerequest.Columns, sqlgraph.NewFieldSpec(devicerequest.FieldID, field.TypeInt)) _spec.From = _q.sql if unique := _q.ctx.Unique; unique != nil { _spec.Unique = *unique } else if _q.path != nil { _spec.Unique = true } if fields := _q.ctx.Fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, devicerequest.FieldID) for i := range fields { if fields[i] != devicerequest.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } } if ps := _q.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if limit := _q.ctx.Limit; limit != nil { _spec.Limit = *limit } if offset := _q.ctx.Offset; offset != nil { _spec.Offset = *offset } if ps := _q.order; len(ps) > 0 { _spec.Order = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } return _spec } func (_q *DeviceRequestQuery) sqlQuery(ctx context.Context) *sql.Selector { builder := sql.Dialect(_q.driver.Dialect()) t1 := builder.Table(devicerequest.Table) columns := _q.ctx.Fields if len(columns) == 0 { columns = devicerequest.Columns } selector := builder.Select(t1.Columns(columns...)...).From(t1) if _q.sql != nil { selector = _q.sql selector.Select(selector.Columns(columns...)...) } if _q.ctx.Unique != nil && *_q.ctx.Unique { selector.Distinct() } for _, p := range _q.predicates { p(selector) } for _, p := range _q.order { p(selector) } if offset := _q.ctx.Offset; offset != nil { // limit is mandatory for offset clause. We start // with default value, and override it below if needed. selector.Offset(*offset).Limit(math.MaxInt32) } if limit := _q.ctx.Limit; limit != nil { selector.Limit(*limit) } return selector } // DeviceRequestGroupBy is the group-by builder for DeviceRequest entities. type DeviceRequestGroupBy struct { selector build *DeviceRequestQuery } // Aggregate adds the given aggregation functions to the group-by query. func (_g *DeviceRequestGroupBy) Aggregate(fns ...AggregateFunc) *DeviceRequestGroupBy { _g.fns = append(_g.fns, fns...) return _g } // Scan applies the selector query and scans the result into the given value. func (_g *DeviceRequestGroupBy) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) if err := _g.build.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*DeviceRequestQuery, *DeviceRequestGroupBy](ctx, _g.build, _g, _g.build.inters, v) } func (_g *DeviceRequestGroupBy) sqlScan(ctx context.Context, root *DeviceRequestQuery, v any) error { selector := root.sqlQuery(ctx).Select() aggregation := make([]string, 0, len(_g.fns)) for _, fn := range _g.fns { aggregation = append(aggregation, fn(selector)) } if len(selector.SelectedColumns()) == 0 { columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) for _, f := range *_g.flds { columns = append(columns, selector.C(f)) } columns = append(columns, aggregation...) selector.Select(columns...) } selector.GroupBy(selector.Columns(*_g.flds...)...) if err := selector.Err(); err != nil { return err } rows := &sql.Rows{} query, args := selector.Query() if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } // DeviceRequestSelect is the builder for selecting fields of DeviceRequest entities. type DeviceRequestSelect struct { *DeviceRequestQuery selector } // Aggregate adds the given aggregation functions to the selector query. func (_s *DeviceRequestSelect) Aggregate(fns ...AggregateFunc) *DeviceRequestSelect { _s.fns = append(_s.fns, fns...) return _s } // Scan applies the selector query and scans the result into the given value. func (_s *DeviceRequestSelect) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) if err := _s.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*DeviceRequestQuery, *DeviceRequestSelect](ctx, _s.DeviceRequestQuery, _s, _s.inters, v) } func (_s *DeviceRequestSelect) sqlScan(ctx context.Context, root *DeviceRequestQuery, v any) error { selector := root.sqlQuery(ctx) aggregation := make([]string, 0, len(_s.fns)) for _, fn := range _s.fns { aggregation = append(aggregation, fn(selector)) } switch n := len(*_s.selector.flds); { case n == 0 && len(aggregation) > 0: selector.Select(aggregation...) case n != 0 && len(aggregation) > 0: selector.AppendSelect(aggregation...) } rows := &sql.Rows{} query, args := selector.Query() if err := _s.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } ================================================ FILE: storage/ent/db/devicerequest_update.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/devicerequest" "github.com/dexidp/dex/storage/ent/db/predicate" ) // DeviceRequestUpdate is the builder for updating DeviceRequest entities. type DeviceRequestUpdate struct { config hooks []Hook mutation *DeviceRequestMutation } // Where appends a list predicates to the DeviceRequestUpdate builder. func (_u *DeviceRequestUpdate) Where(ps ...predicate.DeviceRequest) *DeviceRequestUpdate { _u.mutation.Where(ps...) return _u } // SetUserCode sets the "user_code" field. func (_u *DeviceRequestUpdate) SetUserCode(v string) *DeviceRequestUpdate { _u.mutation.SetUserCode(v) return _u } // SetNillableUserCode sets the "user_code" field if the given value is not nil. func (_u *DeviceRequestUpdate) SetNillableUserCode(v *string) *DeviceRequestUpdate { if v != nil { _u.SetUserCode(*v) } return _u } // SetDeviceCode sets the "device_code" field. func (_u *DeviceRequestUpdate) SetDeviceCode(v string) *DeviceRequestUpdate { _u.mutation.SetDeviceCode(v) return _u } // SetNillableDeviceCode sets the "device_code" field if the given value is not nil. func (_u *DeviceRequestUpdate) SetNillableDeviceCode(v *string) *DeviceRequestUpdate { if v != nil { _u.SetDeviceCode(*v) } return _u } // SetClientID sets the "client_id" field. func (_u *DeviceRequestUpdate) SetClientID(v string) *DeviceRequestUpdate { _u.mutation.SetClientID(v) return _u } // SetNillableClientID sets the "client_id" field if the given value is not nil. func (_u *DeviceRequestUpdate) SetNillableClientID(v *string) *DeviceRequestUpdate { if v != nil { _u.SetClientID(*v) } return _u } // SetClientSecret sets the "client_secret" field. func (_u *DeviceRequestUpdate) SetClientSecret(v string) *DeviceRequestUpdate { _u.mutation.SetClientSecret(v) return _u } // SetNillableClientSecret sets the "client_secret" field if the given value is not nil. func (_u *DeviceRequestUpdate) SetNillableClientSecret(v *string) *DeviceRequestUpdate { if v != nil { _u.SetClientSecret(*v) } return _u } // SetScopes sets the "scopes" field. func (_u *DeviceRequestUpdate) SetScopes(v []string) *DeviceRequestUpdate { _u.mutation.SetScopes(v) return _u } // AppendScopes appends value to the "scopes" field. func (_u *DeviceRequestUpdate) AppendScopes(v []string) *DeviceRequestUpdate { _u.mutation.AppendScopes(v) return _u } // ClearScopes clears the value of the "scopes" field. func (_u *DeviceRequestUpdate) ClearScopes() *DeviceRequestUpdate { _u.mutation.ClearScopes() return _u } // SetExpiry sets the "expiry" field. func (_u *DeviceRequestUpdate) SetExpiry(v time.Time) *DeviceRequestUpdate { _u.mutation.SetExpiry(v) return _u } // SetNillableExpiry sets the "expiry" field if the given value is not nil. func (_u *DeviceRequestUpdate) SetNillableExpiry(v *time.Time) *DeviceRequestUpdate { if v != nil { _u.SetExpiry(*v) } return _u } // Mutation returns the DeviceRequestMutation object of the builder. func (_u *DeviceRequestUpdate) Mutation() *DeviceRequestMutation { return _u.mutation } // Save executes the query and returns the number of nodes affected by the update operation. func (_u *DeviceRequestUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *DeviceRequestUpdate) SaveX(ctx context.Context) int { affected, err := _u.Save(ctx) if err != nil { panic(err) } return affected } // Exec executes the query. func (_u *DeviceRequestUpdate) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *DeviceRequestUpdate) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *DeviceRequestUpdate) check() error { if v, ok := _u.mutation.UserCode(); ok { if err := devicerequest.UserCodeValidator(v); err != nil { return &ValidationError{Name: "user_code", err: fmt.Errorf(`db: validator failed for field "DeviceRequest.user_code": %w`, err)} } } if v, ok := _u.mutation.DeviceCode(); ok { if err := devicerequest.DeviceCodeValidator(v); err != nil { return &ValidationError{Name: "device_code", err: fmt.Errorf(`db: validator failed for field "DeviceRequest.device_code": %w`, err)} } } if v, ok := _u.mutation.ClientID(); ok { if err := devicerequest.ClientIDValidator(v); err != nil { return &ValidationError{Name: "client_id", err: fmt.Errorf(`db: validator failed for field "DeviceRequest.client_id": %w`, err)} } } if v, ok := _u.mutation.ClientSecret(); ok { if err := devicerequest.ClientSecretValidator(v); err != nil { return &ValidationError{Name: "client_secret", err: fmt.Errorf(`db: validator failed for field "DeviceRequest.client_secret": %w`, err)} } } return nil } func (_u *DeviceRequestUpdate) sqlSave(ctx context.Context) (_node int, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(devicerequest.Table, devicerequest.Columns, sqlgraph.NewFieldSpec(devicerequest.FieldID, field.TypeInt)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.UserCode(); ok { _spec.SetField(devicerequest.FieldUserCode, field.TypeString, value) } if value, ok := _u.mutation.DeviceCode(); ok { _spec.SetField(devicerequest.FieldDeviceCode, field.TypeString, value) } if value, ok := _u.mutation.ClientID(); ok { _spec.SetField(devicerequest.FieldClientID, field.TypeString, value) } if value, ok := _u.mutation.ClientSecret(); ok { _spec.SetField(devicerequest.FieldClientSecret, field.TypeString, value) } if value, ok := _u.mutation.Scopes(); ok { _spec.SetField(devicerequest.FieldScopes, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedScopes(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, devicerequest.FieldScopes, value) }) } if _u.mutation.ScopesCleared() { _spec.ClearField(devicerequest.FieldScopes, field.TypeJSON) } if value, ok := _u.mutation.Expiry(); ok { _spec.SetField(devicerequest.FieldExpiry, field.TypeTime, value) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{devicerequest.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return 0, err } _u.mutation.done = true return _node, nil } // DeviceRequestUpdateOne is the builder for updating a single DeviceRequest entity. type DeviceRequestUpdateOne struct { config fields []string hooks []Hook mutation *DeviceRequestMutation } // SetUserCode sets the "user_code" field. func (_u *DeviceRequestUpdateOne) SetUserCode(v string) *DeviceRequestUpdateOne { _u.mutation.SetUserCode(v) return _u } // SetNillableUserCode sets the "user_code" field if the given value is not nil. func (_u *DeviceRequestUpdateOne) SetNillableUserCode(v *string) *DeviceRequestUpdateOne { if v != nil { _u.SetUserCode(*v) } return _u } // SetDeviceCode sets the "device_code" field. func (_u *DeviceRequestUpdateOne) SetDeviceCode(v string) *DeviceRequestUpdateOne { _u.mutation.SetDeviceCode(v) return _u } // SetNillableDeviceCode sets the "device_code" field if the given value is not nil. func (_u *DeviceRequestUpdateOne) SetNillableDeviceCode(v *string) *DeviceRequestUpdateOne { if v != nil { _u.SetDeviceCode(*v) } return _u } // SetClientID sets the "client_id" field. func (_u *DeviceRequestUpdateOne) SetClientID(v string) *DeviceRequestUpdateOne { _u.mutation.SetClientID(v) return _u } // SetNillableClientID sets the "client_id" field if the given value is not nil. func (_u *DeviceRequestUpdateOne) SetNillableClientID(v *string) *DeviceRequestUpdateOne { if v != nil { _u.SetClientID(*v) } return _u } // SetClientSecret sets the "client_secret" field. func (_u *DeviceRequestUpdateOne) SetClientSecret(v string) *DeviceRequestUpdateOne { _u.mutation.SetClientSecret(v) return _u } // SetNillableClientSecret sets the "client_secret" field if the given value is not nil. func (_u *DeviceRequestUpdateOne) SetNillableClientSecret(v *string) *DeviceRequestUpdateOne { if v != nil { _u.SetClientSecret(*v) } return _u } // SetScopes sets the "scopes" field. func (_u *DeviceRequestUpdateOne) SetScopes(v []string) *DeviceRequestUpdateOne { _u.mutation.SetScopes(v) return _u } // AppendScopes appends value to the "scopes" field. func (_u *DeviceRequestUpdateOne) AppendScopes(v []string) *DeviceRequestUpdateOne { _u.mutation.AppendScopes(v) return _u } // ClearScopes clears the value of the "scopes" field. func (_u *DeviceRequestUpdateOne) ClearScopes() *DeviceRequestUpdateOne { _u.mutation.ClearScopes() return _u } // SetExpiry sets the "expiry" field. func (_u *DeviceRequestUpdateOne) SetExpiry(v time.Time) *DeviceRequestUpdateOne { _u.mutation.SetExpiry(v) return _u } // SetNillableExpiry sets the "expiry" field if the given value is not nil. func (_u *DeviceRequestUpdateOne) SetNillableExpiry(v *time.Time) *DeviceRequestUpdateOne { if v != nil { _u.SetExpiry(*v) } return _u } // Mutation returns the DeviceRequestMutation object of the builder. func (_u *DeviceRequestUpdateOne) Mutation() *DeviceRequestMutation { return _u.mutation } // Where appends a list predicates to the DeviceRequestUpdate builder. func (_u *DeviceRequestUpdateOne) Where(ps ...predicate.DeviceRequest) *DeviceRequestUpdateOne { _u.mutation.Where(ps...) return _u } // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (_u *DeviceRequestUpdateOne) Select(field string, fields ...string) *DeviceRequestUpdateOne { _u.fields = append([]string{field}, fields...) return _u } // Save executes the query and returns the updated DeviceRequest entity. func (_u *DeviceRequestUpdateOne) Save(ctx context.Context) (*DeviceRequest, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *DeviceRequestUpdateOne) SaveX(ctx context.Context) *DeviceRequest { node, err := _u.Save(ctx) if err != nil { panic(err) } return node } // Exec executes the query on the entity. func (_u *DeviceRequestUpdateOne) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *DeviceRequestUpdateOne) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *DeviceRequestUpdateOne) check() error { if v, ok := _u.mutation.UserCode(); ok { if err := devicerequest.UserCodeValidator(v); err != nil { return &ValidationError{Name: "user_code", err: fmt.Errorf(`db: validator failed for field "DeviceRequest.user_code": %w`, err)} } } if v, ok := _u.mutation.DeviceCode(); ok { if err := devicerequest.DeviceCodeValidator(v); err != nil { return &ValidationError{Name: "device_code", err: fmt.Errorf(`db: validator failed for field "DeviceRequest.device_code": %w`, err)} } } if v, ok := _u.mutation.ClientID(); ok { if err := devicerequest.ClientIDValidator(v); err != nil { return &ValidationError{Name: "client_id", err: fmt.Errorf(`db: validator failed for field "DeviceRequest.client_id": %w`, err)} } } if v, ok := _u.mutation.ClientSecret(); ok { if err := devicerequest.ClientSecretValidator(v); err != nil { return &ValidationError{Name: "client_secret", err: fmt.Errorf(`db: validator failed for field "DeviceRequest.client_secret": %w`, err)} } } return nil } func (_u *DeviceRequestUpdateOne) sqlSave(ctx context.Context) (_node *DeviceRequest, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(devicerequest.Table, devicerequest.Columns, sqlgraph.NewFieldSpec(devicerequest.FieldID, field.TypeInt)) id, ok := _u.mutation.ID() if !ok { return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "DeviceRequest.id" for update`)} } _spec.Node.ID.Value = id if fields := _u.fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, devicerequest.FieldID) for _, f := range fields { if !devicerequest.ValidColumn(f) { return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } if f != devicerequest.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, f) } } } if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.UserCode(); ok { _spec.SetField(devicerequest.FieldUserCode, field.TypeString, value) } if value, ok := _u.mutation.DeviceCode(); ok { _spec.SetField(devicerequest.FieldDeviceCode, field.TypeString, value) } if value, ok := _u.mutation.ClientID(); ok { _spec.SetField(devicerequest.FieldClientID, field.TypeString, value) } if value, ok := _u.mutation.ClientSecret(); ok { _spec.SetField(devicerequest.FieldClientSecret, field.TypeString, value) } if value, ok := _u.mutation.Scopes(); ok { _spec.SetField(devicerequest.FieldScopes, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedScopes(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, devicerequest.FieldScopes, value) }) } if _u.mutation.ScopesCleared() { _spec.ClearField(devicerequest.FieldScopes, field.TypeJSON) } if value, ok := _u.mutation.Expiry(); ok { _spec.SetField(devicerequest.FieldExpiry, field.TypeTime, value) } _node = &DeviceRequest{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{devicerequest.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } _u.mutation.done = true return _node, nil } ================================================ FILE: storage/ent/db/devicetoken/devicetoken.go ================================================ // Code generated by ent, DO NOT EDIT. package devicetoken import ( "entgo.io/ent/dialect/sql" ) const ( // Label holds the string label denoting the devicetoken type in the database. Label = "device_token" // FieldID holds the string denoting the id field in the database. FieldID = "id" // FieldDeviceCode holds the string denoting the device_code field in the database. FieldDeviceCode = "device_code" // FieldStatus holds the string denoting the status field in the database. FieldStatus = "status" // FieldToken holds the string denoting the token field in the database. FieldToken = "token" // FieldExpiry holds the string denoting the expiry field in the database. FieldExpiry = "expiry" // FieldLastRequest holds the string denoting the last_request field in the database. FieldLastRequest = "last_request" // FieldPollInterval holds the string denoting the poll_interval field in the database. FieldPollInterval = "poll_interval" // FieldCodeChallenge holds the string denoting the code_challenge field in the database. FieldCodeChallenge = "code_challenge" // FieldCodeChallengeMethod holds the string denoting the code_challenge_method field in the database. FieldCodeChallengeMethod = "code_challenge_method" // Table holds the table name of the devicetoken in the database. Table = "device_tokens" ) // Columns holds all SQL columns for devicetoken fields. var Columns = []string{ FieldID, FieldDeviceCode, FieldStatus, FieldToken, FieldExpiry, FieldLastRequest, FieldPollInterval, FieldCodeChallenge, FieldCodeChallengeMethod, } // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { if column == Columns[i] { return true } } return false } var ( // DeviceCodeValidator is a validator for the "device_code" field. It is called by the builders before save. DeviceCodeValidator func(string) error // StatusValidator is a validator for the "status" field. It is called by the builders before save. StatusValidator func(string) error // DefaultCodeChallenge holds the default value on creation for the "code_challenge" field. DefaultCodeChallenge string // DefaultCodeChallengeMethod holds the default value on creation for the "code_challenge_method" field. DefaultCodeChallengeMethod string ) // OrderOption defines the ordering options for the DeviceToken queries. type OrderOption func(*sql.Selector) // ByID orders the results by the id field. func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } // ByDeviceCode orders the results by the device_code field. func ByDeviceCode(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldDeviceCode, opts...).ToFunc() } // ByStatus orders the results by the status field. func ByStatus(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldStatus, opts...).ToFunc() } // ByExpiry orders the results by the expiry field. func ByExpiry(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldExpiry, opts...).ToFunc() } // ByLastRequest orders the results by the last_request field. func ByLastRequest(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldLastRequest, opts...).ToFunc() } // ByPollInterval orders the results by the poll_interval field. func ByPollInterval(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldPollInterval, opts...).ToFunc() } // ByCodeChallenge orders the results by the code_challenge field. func ByCodeChallenge(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCodeChallenge, opts...).ToFunc() } // ByCodeChallengeMethod orders the results by the code_challenge_method field. func ByCodeChallengeMethod(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCodeChallengeMethod, opts...).ToFunc() } ================================================ FILE: storage/ent/db/devicetoken/where.go ================================================ // Code generated by ent, DO NOT EDIT. package devicetoken import ( "time" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ID filters vertices based on their ID field. func ID(id int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. func IDEQ(id int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. func IDNEQ(id int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. func IDIn(ids ...int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. func IDNotIn(ids ...int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. func IDGT(id int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. func IDGTE(id int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. func IDLT(id int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. func IDLTE(id int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLTE(FieldID, id)) } // DeviceCode applies equality check predicate on the "device_code" field. It's identical to DeviceCodeEQ. func DeviceCode(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldDeviceCode, v)) } // Status applies equality check predicate on the "status" field. It's identical to StatusEQ. func Status(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldStatus, v)) } // Token applies equality check predicate on the "token" field. It's identical to TokenEQ. func Token(v []byte) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldToken, v)) } // Expiry applies equality check predicate on the "expiry" field. It's identical to ExpiryEQ. func Expiry(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldExpiry, v)) } // LastRequest applies equality check predicate on the "last_request" field. It's identical to LastRequestEQ. func LastRequest(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldLastRequest, v)) } // PollInterval applies equality check predicate on the "poll_interval" field. It's identical to PollIntervalEQ. func PollInterval(v int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldPollInterval, v)) } // CodeChallenge applies equality check predicate on the "code_challenge" field. It's identical to CodeChallengeEQ. func CodeChallenge(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldCodeChallenge, v)) } // CodeChallengeMethod applies equality check predicate on the "code_challenge_method" field. It's identical to CodeChallengeMethodEQ. func CodeChallengeMethod(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldCodeChallengeMethod, v)) } // DeviceCodeEQ applies the EQ predicate on the "device_code" field. func DeviceCodeEQ(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldDeviceCode, v)) } // DeviceCodeNEQ applies the NEQ predicate on the "device_code" field. func DeviceCodeNEQ(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNEQ(FieldDeviceCode, v)) } // DeviceCodeIn applies the In predicate on the "device_code" field. func DeviceCodeIn(vs ...string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldIn(FieldDeviceCode, vs...)) } // DeviceCodeNotIn applies the NotIn predicate on the "device_code" field. func DeviceCodeNotIn(vs ...string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNotIn(FieldDeviceCode, vs...)) } // DeviceCodeGT applies the GT predicate on the "device_code" field. func DeviceCodeGT(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGT(FieldDeviceCode, v)) } // DeviceCodeGTE applies the GTE predicate on the "device_code" field. func DeviceCodeGTE(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGTE(FieldDeviceCode, v)) } // DeviceCodeLT applies the LT predicate on the "device_code" field. func DeviceCodeLT(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLT(FieldDeviceCode, v)) } // DeviceCodeLTE applies the LTE predicate on the "device_code" field. func DeviceCodeLTE(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLTE(FieldDeviceCode, v)) } // DeviceCodeContains applies the Contains predicate on the "device_code" field. func DeviceCodeContains(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldContains(FieldDeviceCode, v)) } // DeviceCodeHasPrefix applies the HasPrefix predicate on the "device_code" field. func DeviceCodeHasPrefix(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldHasPrefix(FieldDeviceCode, v)) } // DeviceCodeHasSuffix applies the HasSuffix predicate on the "device_code" field. func DeviceCodeHasSuffix(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldHasSuffix(FieldDeviceCode, v)) } // DeviceCodeEqualFold applies the EqualFold predicate on the "device_code" field. func DeviceCodeEqualFold(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEqualFold(FieldDeviceCode, v)) } // DeviceCodeContainsFold applies the ContainsFold predicate on the "device_code" field. func DeviceCodeContainsFold(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldContainsFold(FieldDeviceCode, v)) } // StatusEQ applies the EQ predicate on the "status" field. func StatusEQ(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldStatus, v)) } // StatusNEQ applies the NEQ predicate on the "status" field. func StatusNEQ(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNEQ(FieldStatus, v)) } // StatusIn applies the In predicate on the "status" field. func StatusIn(vs ...string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldIn(FieldStatus, vs...)) } // StatusNotIn applies the NotIn predicate on the "status" field. func StatusNotIn(vs ...string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNotIn(FieldStatus, vs...)) } // StatusGT applies the GT predicate on the "status" field. func StatusGT(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGT(FieldStatus, v)) } // StatusGTE applies the GTE predicate on the "status" field. func StatusGTE(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGTE(FieldStatus, v)) } // StatusLT applies the LT predicate on the "status" field. func StatusLT(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLT(FieldStatus, v)) } // StatusLTE applies the LTE predicate on the "status" field. func StatusLTE(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLTE(FieldStatus, v)) } // StatusContains applies the Contains predicate on the "status" field. func StatusContains(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldContains(FieldStatus, v)) } // StatusHasPrefix applies the HasPrefix predicate on the "status" field. func StatusHasPrefix(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldHasPrefix(FieldStatus, v)) } // StatusHasSuffix applies the HasSuffix predicate on the "status" field. func StatusHasSuffix(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldHasSuffix(FieldStatus, v)) } // StatusEqualFold applies the EqualFold predicate on the "status" field. func StatusEqualFold(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEqualFold(FieldStatus, v)) } // StatusContainsFold applies the ContainsFold predicate on the "status" field. func StatusContainsFold(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldContainsFold(FieldStatus, v)) } // TokenEQ applies the EQ predicate on the "token" field. func TokenEQ(v []byte) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldToken, v)) } // TokenNEQ applies the NEQ predicate on the "token" field. func TokenNEQ(v []byte) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNEQ(FieldToken, v)) } // TokenIn applies the In predicate on the "token" field. func TokenIn(vs ...[]byte) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldIn(FieldToken, vs...)) } // TokenNotIn applies the NotIn predicate on the "token" field. func TokenNotIn(vs ...[]byte) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNotIn(FieldToken, vs...)) } // TokenGT applies the GT predicate on the "token" field. func TokenGT(v []byte) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGT(FieldToken, v)) } // TokenGTE applies the GTE predicate on the "token" field. func TokenGTE(v []byte) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGTE(FieldToken, v)) } // TokenLT applies the LT predicate on the "token" field. func TokenLT(v []byte) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLT(FieldToken, v)) } // TokenLTE applies the LTE predicate on the "token" field. func TokenLTE(v []byte) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLTE(FieldToken, v)) } // TokenIsNil applies the IsNil predicate on the "token" field. func TokenIsNil() predicate.DeviceToken { return predicate.DeviceToken(sql.FieldIsNull(FieldToken)) } // TokenNotNil applies the NotNil predicate on the "token" field. func TokenNotNil() predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNotNull(FieldToken)) } // ExpiryEQ applies the EQ predicate on the "expiry" field. func ExpiryEQ(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldExpiry, v)) } // ExpiryNEQ applies the NEQ predicate on the "expiry" field. func ExpiryNEQ(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNEQ(FieldExpiry, v)) } // ExpiryIn applies the In predicate on the "expiry" field. func ExpiryIn(vs ...time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldIn(FieldExpiry, vs...)) } // ExpiryNotIn applies the NotIn predicate on the "expiry" field. func ExpiryNotIn(vs ...time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNotIn(FieldExpiry, vs...)) } // ExpiryGT applies the GT predicate on the "expiry" field. func ExpiryGT(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGT(FieldExpiry, v)) } // ExpiryGTE applies the GTE predicate on the "expiry" field. func ExpiryGTE(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGTE(FieldExpiry, v)) } // ExpiryLT applies the LT predicate on the "expiry" field. func ExpiryLT(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLT(FieldExpiry, v)) } // ExpiryLTE applies the LTE predicate on the "expiry" field. func ExpiryLTE(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLTE(FieldExpiry, v)) } // LastRequestEQ applies the EQ predicate on the "last_request" field. func LastRequestEQ(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldLastRequest, v)) } // LastRequestNEQ applies the NEQ predicate on the "last_request" field. func LastRequestNEQ(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNEQ(FieldLastRequest, v)) } // LastRequestIn applies the In predicate on the "last_request" field. func LastRequestIn(vs ...time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldIn(FieldLastRequest, vs...)) } // LastRequestNotIn applies the NotIn predicate on the "last_request" field. func LastRequestNotIn(vs ...time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNotIn(FieldLastRequest, vs...)) } // LastRequestGT applies the GT predicate on the "last_request" field. func LastRequestGT(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGT(FieldLastRequest, v)) } // LastRequestGTE applies the GTE predicate on the "last_request" field. func LastRequestGTE(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGTE(FieldLastRequest, v)) } // LastRequestLT applies the LT predicate on the "last_request" field. func LastRequestLT(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLT(FieldLastRequest, v)) } // LastRequestLTE applies the LTE predicate on the "last_request" field. func LastRequestLTE(v time.Time) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLTE(FieldLastRequest, v)) } // PollIntervalEQ applies the EQ predicate on the "poll_interval" field. func PollIntervalEQ(v int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldPollInterval, v)) } // PollIntervalNEQ applies the NEQ predicate on the "poll_interval" field. func PollIntervalNEQ(v int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNEQ(FieldPollInterval, v)) } // PollIntervalIn applies the In predicate on the "poll_interval" field. func PollIntervalIn(vs ...int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldIn(FieldPollInterval, vs...)) } // PollIntervalNotIn applies the NotIn predicate on the "poll_interval" field. func PollIntervalNotIn(vs ...int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNotIn(FieldPollInterval, vs...)) } // PollIntervalGT applies the GT predicate on the "poll_interval" field. func PollIntervalGT(v int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGT(FieldPollInterval, v)) } // PollIntervalGTE applies the GTE predicate on the "poll_interval" field. func PollIntervalGTE(v int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGTE(FieldPollInterval, v)) } // PollIntervalLT applies the LT predicate on the "poll_interval" field. func PollIntervalLT(v int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLT(FieldPollInterval, v)) } // PollIntervalLTE applies the LTE predicate on the "poll_interval" field. func PollIntervalLTE(v int) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLTE(FieldPollInterval, v)) } // CodeChallengeEQ applies the EQ predicate on the "code_challenge" field. func CodeChallengeEQ(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldCodeChallenge, v)) } // CodeChallengeNEQ applies the NEQ predicate on the "code_challenge" field. func CodeChallengeNEQ(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNEQ(FieldCodeChallenge, v)) } // CodeChallengeIn applies the In predicate on the "code_challenge" field. func CodeChallengeIn(vs ...string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldIn(FieldCodeChallenge, vs...)) } // CodeChallengeNotIn applies the NotIn predicate on the "code_challenge" field. func CodeChallengeNotIn(vs ...string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNotIn(FieldCodeChallenge, vs...)) } // CodeChallengeGT applies the GT predicate on the "code_challenge" field. func CodeChallengeGT(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGT(FieldCodeChallenge, v)) } // CodeChallengeGTE applies the GTE predicate on the "code_challenge" field. func CodeChallengeGTE(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGTE(FieldCodeChallenge, v)) } // CodeChallengeLT applies the LT predicate on the "code_challenge" field. func CodeChallengeLT(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLT(FieldCodeChallenge, v)) } // CodeChallengeLTE applies the LTE predicate on the "code_challenge" field. func CodeChallengeLTE(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLTE(FieldCodeChallenge, v)) } // CodeChallengeContains applies the Contains predicate on the "code_challenge" field. func CodeChallengeContains(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldContains(FieldCodeChallenge, v)) } // CodeChallengeHasPrefix applies the HasPrefix predicate on the "code_challenge" field. func CodeChallengeHasPrefix(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldHasPrefix(FieldCodeChallenge, v)) } // CodeChallengeHasSuffix applies the HasSuffix predicate on the "code_challenge" field. func CodeChallengeHasSuffix(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldHasSuffix(FieldCodeChallenge, v)) } // CodeChallengeEqualFold applies the EqualFold predicate on the "code_challenge" field. func CodeChallengeEqualFold(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEqualFold(FieldCodeChallenge, v)) } // CodeChallengeContainsFold applies the ContainsFold predicate on the "code_challenge" field. func CodeChallengeContainsFold(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldContainsFold(FieldCodeChallenge, v)) } // CodeChallengeMethodEQ applies the EQ predicate on the "code_challenge_method" field. func CodeChallengeMethodEQ(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEQ(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodNEQ applies the NEQ predicate on the "code_challenge_method" field. func CodeChallengeMethodNEQ(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNEQ(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodIn applies the In predicate on the "code_challenge_method" field. func CodeChallengeMethodIn(vs ...string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldIn(FieldCodeChallengeMethod, vs...)) } // CodeChallengeMethodNotIn applies the NotIn predicate on the "code_challenge_method" field. func CodeChallengeMethodNotIn(vs ...string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldNotIn(FieldCodeChallengeMethod, vs...)) } // CodeChallengeMethodGT applies the GT predicate on the "code_challenge_method" field. func CodeChallengeMethodGT(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGT(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodGTE applies the GTE predicate on the "code_challenge_method" field. func CodeChallengeMethodGTE(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldGTE(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodLT applies the LT predicate on the "code_challenge_method" field. func CodeChallengeMethodLT(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLT(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodLTE applies the LTE predicate on the "code_challenge_method" field. func CodeChallengeMethodLTE(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldLTE(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodContains applies the Contains predicate on the "code_challenge_method" field. func CodeChallengeMethodContains(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldContains(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodHasPrefix applies the HasPrefix predicate on the "code_challenge_method" field. func CodeChallengeMethodHasPrefix(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldHasPrefix(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodHasSuffix applies the HasSuffix predicate on the "code_challenge_method" field. func CodeChallengeMethodHasSuffix(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldHasSuffix(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodEqualFold applies the EqualFold predicate on the "code_challenge_method" field. func CodeChallengeMethodEqualFold(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldEqualFold(FieldCodeChallengeMethod, v)) } // CodeChallengeMethodContainsFold applies the ContainsFold predicate on the "code_challenge_method" field. func CodeChallengeMethodContainsFold(v string) predicate.DeviceToken { return predicate.DeviceToken(sql.FieldContainsFold(FieldCodeChallengeMethod, v)) } // And groups predicates with the AND operator between them. func And(predicates ...predicate.DeviceToken) predicate.DeviceToken { return predicate.DeviceToken(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.DeviceToken) predicate.DeviceToken { return predicate.DeviceToken(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.DeviceToken) predicate.DeviceToken { return predicate.DeviceToken(sql.NotPredicates(p)) } ================================================ FILE: storage/ent/db/devicetoken.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "fmt" "strings" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/devicetoken" ) // DeviceToken is the model entity for the DeviceToken schema. type DeviceToken struct { config `json:"-"` // ID of the ent. ID int `json:"id,omitempty"` // DeviceCode holds the value of the "device_code" field. DeviceCode string `json:"device_code,omitempty"` // Status holds the value of the "status" field. Status string `json:"status,omitempty"` // Token holds the value of the "token" field. Token *[]byte `json:"token,omitempty"` // Expiry holds the value of the "expiry" field. Expiry time.Time `json:"expiry,omitempty"` // LastRequest holds the value of the "last_request" field. LastRequest time.Time `json:"last_request,omitempty"` // PollInterval holds the value of the "poll_interval" field. PollInterval int `json:"poll_interval,omitempty"` // CodeChallenge holds the value of the "code_challenge" field. CodeChallenge string `json:"code_challenge,omitempty"` // CodeChallengeMethod holds the value of the "code_challenge_method" field. CodeChallengeMethod string `json:"code_challenge_method,omitempty"` selectValues sql.SelectValues } // scanValues returns the types for scanning values from sql.Rows. func (*DeviceToken) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { case devicetoken.FieldToken: values[i] = new([]byte) case devicetoken.FieldID, devicetoken.FieldPollInterval: values[i] = new(sql.NullInt64) case devicetoken.FieldDeviceCode, devicetoken.FieldStatus, devicetoken.FieldCodeChallenge, devicetoken.FieldCodeChallengeMethod: values[i] = new(sql.NullString) case devicetoken.FieldExpiry, devicetoken.FieldLastRequest: values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } } return values, nil } // assignValues assigns the values that were returned from sql.Rows (after scanning) // to the DeviceToken fields. func (_m *DeviceToken) assignValues(columns []string, values []any) error { if m, n := len(values), len(columns); m < n { return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) } for i := range columns { switch columns[i] { case devicetoken.FieldID: value, ok := values[i].(*sql.NullInt64) if !ok { return fmt.Errorf("unexpected type %T for field id", value) } _m.ID = int(value.Int64) case devicetoken.FieldDeviceCode: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field device_code", values[i]) } else if value.Valid { _m.DeviceCode = value.String } case devicetoken.FieldStatus: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field status", values[i]) } else if value.Valid { _m.Status = value.String } case devicetoken.FieldToken: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field token", values[i]) } else if value != nil { _m.Token = value } case devicetoken.FieldExpiry: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field expiry", values[i]) } else if value.Valid { _m.Expiry = value.Time } case devicetoken.FieldLastRequest: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field last_request", values[i]) } else if value.Valid { _m.LastRequest = value.Time } case devicetoken.FieldPollInterval: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field poll_interval", values[i]) } else if value.Valid { _m.PollInterval = int(value.Int64) } case devicetoken.FieldCodeChallenge: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field code_challenge", values[i]) } else if value.Valid { _m.CodeChallenge = value.String } case devicetoken.FieldCodeChallengeMethod: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field code_challenge_method", values[i]) } else if value.Valid { _m.CodeChallengeMethod = value.String } default: _m.selectValues.Set(columns[i], values[i]) } } return nil } // Value returns the ent.Value that was dynamically selected and assigned to the DeviceToken. // This includes values selected through modifiers, order, etc. func (_m *DeviceToken) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } // Update returns a builder for updating this DeviceToken. // Note that you need to call DeviceToken.Unwrap() before calling this method if this DeviceToken // was returned from a transaction, and the transaction was committed or rolled back. func (_m *DeviceToken) Update() *DeviceTokenUpdateOne { return NewDeviceTokenClient(_m.config).UpdateOne(_m) } // Unwrap unwraps the DeviceToken entity that was returned from a transaction after it was closed, // so that all future queries will be executed through the driver which created the transaction. func (_m *DeviceToken) Unwrap() *DeviceToken { _tx, ok := _m.config.driver.(*txDriver) if !ok { panic("db: DeviceToken is not a transactional entity") } _m.config.driver = _tx.drv return _m } // String implements the fmt.Stringer. func (_m *DeviceToken) String() string { var builder strings.Builder builder.WriteString("DeviceToken(") builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) builder.WriteString("device_code=") builder.WriteString(_m.DeviceCode) builder.WriteString(", ") builder.WriteString("status=") builder.WriteString(_m.Status) builder.WriteString(", ") if v := _m.Token; v != nil { builder.WriteString("token=") builder.WriteString(fmt.Sprintf("%v", *v)) } builder.WriteString(", ") builder.WriteString("expiry=") builder.WriteString(_m.Expiry.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("last_request=") builder.WriteString(_m.LastRequest.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("poll_interval=") builder.WriteString(fmt.Sprintf("%v", _m.PollInterval)) builder.WriteString(", ") builder.WriteString("code_challenge=") builder.WriteString(_m.CodeChallenge) builder.WriteString(", ") builder.WriteString("code_challenge_method=") builder.WriteString(_m.CodeChallengeMethod) builder.WriteByte(')') return builder.String() } // DeviceTokens is a parsable slice of DeviceToken. type DeviceTokens []*DeviceToken ================================================ FILE: storage/ent/db/devicetoken_create.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/devicetoken" ) // DeviceTokenCreate is the builder for creating a DeviceToken entity. type DeviceTokenCreate struct { config mutation *DeviceTokenMutation hooks []Hook } // SetDeviceCode sets the "device_code" field. func (_c *DeviceTokenCreate) SetDeviceCode(v string) *DeviceTokenCreate { _c.mutation.SetDeviceCode(v) return _c } // SetStatus sets the "status" field. func (_c *DeviceTokenCreate) SetStatus(v string) *DeviceTokenCreate { _c.mutation.SetStatus(v) return _c } // SetToken sets the "token" field. func (_c *DeviceTokenCreate) SetToken(v []byte) *DeviceTokenCreate { _c.mutation.SetToken(v) return _c } // SetExpiry sets the "expiry" field. func (_c *DeviceTokenCreate) SetExpiry(v time.Time) *DeviceTokenCreate { _c.mutation.SetExpiry(v) return _c } // SetLastRequest sets the "last_request" field. func (_c *DeviceTokenCreate) SetLastRequest(v time.Time) *DeviceTokenCreate { _c.mutation.SetLastRequest(v) return _c } // SetPollInterval sets the "poll_interval" field. func (_c *DeviceTokenCreate) SetPollInterval(v int) *DeviceTokenCreate { _c.mutation.SetPollInterval(v) return _c } // SetCodeChallenge sets the "code_challenge" field. func (_c *DeviceTokenCreate) SetCodeChallenge(v string) *DeviceTokenCreate { _c.mutation.SetCodeChallenge(v) return _c } // SetNillableCodeChallenge sets the "code_challenge" field if the given value is not nil. func (_c *DeviceTokenCreate) SetNillableCodeChallenge(v *string) *DeviceTokenCreate { if v != nil { _c.SetCodeChallenge(*v) } return _c } // SetCodeChallengeMethod sets the "code_challenge_method" field. func (_c *DeviceTokenCreate) SetCodeChallengeMethod(v string) *DeviceTokenCreate { _c.mutation.SetCodeChallengeMethod(v) return _c } // SetNillableCodeChallengeMethod sets the "code_challenge_method" field if the given value is not nil. func (_c *DeviceTokenCreate) SetNillableCodeChallengeMethod(v *string) *DeviceTokenCreate { if v != nil { _c.SetCodeChallengeMethod(*v) } return _c } // Mutation returns the DeviceTokenMutation object of the builder. func (_c *DeviceTokenCreate) Mutation() *DeviceTokenMutation { return _c.mutation } // Save creates the DeviceToken in the database. func (_c *DeviceTokenCreate) Save(ctx context.Context) (*DeviceToken, error) { _c.defaults() return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) } // SaveX calls Save and panics if Save returns an error. func (_c *DeviceTokenCreate) SaveX(ctx context.Context) *DeviceToken { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *DeviceTokenCreate) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *DeviceTokenCreate) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } // defaults sets the default values of the builder before save. func (_c *DeviceTokenCreate) defaults() { if _, ok := _c.mutation.CodeChallenge(); !ok { v := devicetoken.DefaultCodeChallenge _c.mutation.SetCodeChallenge(v) } if _, ok := _c.mutation.CodeChallengeMethod(); !ok { v := devicetoken.DefaultCodeChallengeMethod _c.mutation.SetCodeChallengeMethod(v) } } // check runs all checks and user-defined validators on the builder. func (_c *DeviceTokenCreate) check() error { if _, ok := _c.mutation.DeviceCode(); !ok { return &ValidationError{Name: "device_code", err: errors.New(`db: missing required field "DeviceToken.device_code"`)} } if v, ok := _c.mutation.DeviceCode(); ok { if err := devicetoken.DeviceCodeValidator(v); err != nil { return &ValidationError{Name: "device_code", err: fmt.Errorf(`db: validator failed for field "DeviceToken.device_code": %w`, err)} } } if _, ok := _c.mutation.Status(); !ok { return &ValidationError{Name: "status", err: errors.New(`db: missing required field "DeviceToken.status"`)} } if v, ok := _c.mutation.Status(); ok { if err := devicetoken.StatusValidator(v); err != nil { return &ValidationError{Name: "status", err: fmt.Errorf(`db: validator failed for field "DeviceToken.status": %w`, err)} } } if _, ok := _c.mutation.Expiry(); !ok { return &ValidationError{Name: "expiry", err: errors.New(`db: missing required field "DeviceToken.expiry"`)} } if _, ok := _c.mutation.LastRequest(); !ok { return &ValidationError{Name: "last_request", err: errors.New(`db: missing required field "DeviceToken.last_request"`)} } if _, ok := _c.mutation.PollInterval(); !ok { return &ValidationError{Name: "poll_interval", err: errors.New(`db: missing required field "DeviceToken.poll_interval"`)} } if _, ok := _c.mutation.CodeChallenge(); !ok { return &ValidationError{Name: "code_challenge", err: errors.New(`db: missing required field "DeviceToken.code_challenge"`)} } if _, ok := _c.mutation.CodeChallengeMethod(); !ok { return &ValidationError{Name: "code_challenge_method", err: errors.New(`db: missing required field "DeviceToken.code_challenge_method"`)} } return nil } func (_c *DeviceTokenCreate) sqlSave(ctx context.Context) (*DeviceToken, error) { if err := _c.check(); err != nil { return nil, err } _node, _spec := _c.createSpec() if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } id := _spec.ID.Value.(int64) _node.ID = int(id) _c.mutation.id = &_node.ID _c.mutation.done = true return _node, nil } func (_c *DeviceTokenCreate) createSpec() (*DeviceToken, *sqlgraph.CreateSpec) { var ( _node = &DeviceToken{config: _c.config} _spec = sqlgraph.NewCreateSpec(devicetoken.Table, sqlgraph.NewFieldSpec(devicetoken.FieldID, field.TypeInt)) ) if value, ok := _c.mutation.DeviceCode(); ok { _spec.SetField(devicetoken.FieldDeviceCode, field.TypeString, value) _node.DeviceCode = value } if value, ok := _c.mutation.Status(); ok { _spec.SetField(devicetoken.FieldStatus, field.TypeString, value) _node.Status = value } if value, ok := _c.mutation.Token(); ok { _spec.SetField(devicetoken.FieldToken, field.TypeBytes, value) _node.Token = &value } if value, ok := _c.mutation.Expiry(); ok { _spec.SetField(devicetoken.FieldExpiry, field.TypeTime, value) _node.Expiry = value } if value, ok := _c.mutation.LastRequest(); ok { _spec.SetField(devicetoken.FieldLastRequest, field.TypeTime, value) _node.LastRequest = value } if value, ok := _c.mutation.PollInterval(); ok { _spec.SetField(devicetoken.FieldPollInterval, field.TypeInt, value) _node.PollInterval = value } if value, ok := _c.mutation.CodeChallenge(); ok { _spec.SetField(devicetoken.FieldCodeChallenge, field.TypeString, value) _node.CodeChallenge = value } if value, ok := _c.mutation.CodeChallengeMethod(); ok { _spec.SetField(devicetoken.FieldCodeChallengeMethod, field.TypeString, value) _node.CodeChallengeMethod = value } return _node, _spec } // DeviceTokenCreateBulk is the builder for creating many DeviceToken entities in bulk. type DeviceTokenCreateBulk struct { config err error builders []*DeviceTokenCreate } // Save creates the DeviceToken entities in the database. func (_c *DeviceTokenCreateBulk) Save(ctx context.Context) ([]*DeviceToken, error) { if _c.err != nil { return nil, _c.err } specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) nodes := make([]*DeviceToken, len(_c.builders)) mutators := make([]Mutator, len(_c.builders)) for i := range _c.builders { func(i int, root context.Context) { builder := _c.builders[i] builder.defaults() var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*DeviceTokenMutation) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } if err := builder.check(); err != nil { return nil, err } builder.mutation = mutation var err error nodes[i], specs[i] = builder.createSpec() if i < len(mutators)-1 { _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) } else { spec := &sqlgraph.BatchCreateSpec{Nodes: specs} // Invoke the actual operation on the latest mutation in the chain. if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } } } if err != nil { return nil, err } mutation.id = &nodes[i].ID if specs[i].ID.Value != nil { id := specs[i].ID.Value.(int64) nodes[i].ID = int(id) } mutation.done = true return nodes[i], nil }) for i := len(builder.hooks) - 1; i >= 0; i-- { mut = builder.hooks[i](mut) } mutators[i] = mut }(i, ctx) } if len(mutators) > 0 { if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { return nil, err } } return nodes, nil } // SaveX is like Save, but panics if an error occurs. func (_c *DeviceTokenCreateBulk) SaveX(ctx context.Context) []*DeviceToken { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *DeviceTokenCreateBulk) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *DeviceTokenCreateBulk) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/devicetoken_delete.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/devicetoken" "github.com/dexidp/dex/storage/ent/db/predicate" ) // DeviceTokenDelete is the builder for deleting a DeviceToken entity. type DeviceTokenDelete struct { config hooks []Hook mutation *DeviceTokenMutation } // Where appends a list predicates to the DeviceTokenDelete builder. func (_d *DeviceTokenDelete) Where(ps ...predicate.DeviceToken) *DeviceTokenDelete { _d.mutation.Where(ps...) return _d } // Exec executes the deletion query and returns how many vertices were deleted. func (_d *DeviceTokenDelete) Exec(ctx context.Context) (int, error) { return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) } // ExecX is like Exec, but panics if an error occurs. func (_d *DeviceTokenDelete) ExecX(ctx context.Context) int { n, err := _d.Exec(ctx) if err != nil { panic(err) } return n } func (_d *DeviceTokenDelete) sqlExec(ctx context.Context) (int, error) { _spec := sqlgraph.NewDeleteSpec(devicetoken.Table, sqlgraph.NewFieldSpec(devicetoken.FieldID, field.TypeInt)) if ps := _d.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) if err != nil && sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } _d.mutation.done = true return affected, err } // DeviceTokenDeleteOne is the builder for deleting a single DeviceToken entity. type DeviceTokenDeleteOne struct { _d *DeviceTokenDelete } // Where appends a list predicates to the DeviceTokenDelete builder. func (_d *DeviceTokenDeleteOne) Where(ps ...predicate.DeviceToken) *DeviceTokenDeleteOne { _d._d.mutation.Where(ps...) return _d } // Exec executes the deletion query. func (_d *DeviceTokenDeleteOne) Exec(ctx context.Context) error { n, err := _d._d.Exec(ctx) switch { case err != nil: return err case n == 0: return &NotFoundError{devicetoken.Label} default: return nil } } // ExecX is like Exec, but panics if an error occurs. func (_d *DeviceTokenDeleteOne) ExecX(ctx context.Context) { if err := _d.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/devicetoken_query.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "fmt" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/devicetoken" "github.com/dexidp/dex/storage/ent/db/predicate" ) // DeviceTokenQuery is the builder for querying DeviceToken entities. type DeviceTokenQuery struct { config ctx *QueryContext order []devicetoken.OrderOption inters []Interceptor predicates []predicate.DeviceToken // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) } // Where adds a new predicate for the DeviceTokenQuery builder. func (_q *DeviceTokenQuery) Where(ps ...predicate.DeviceToken) *DeviceTokenQuery { _q.predicates = append(_q.predicates, ps...) return _q } // Limit the number of records to be returned by this query. func (_q *DeviceTokenQuery) Limit(limit int) *DeviceTokenQuery { _q.ctx.Limit = &limit return _q } // Offset to start from. func (_q *DeviceTokenQuery) Offset(offset int) *DeviceTokenQuery { _q.ctx.Offset = &offset return _q } // Unique configures the query builder to filter duplicate records on query. // By default, unique is set to true, and can be disabled using this method. func (_q *DeviceTokenQuery) Unique(unique bool) *DeviceTokenQuery { _q.ctx.Unique = &unique return _q } // Order specifies how the records should be ordered. func (_q *DeviceTokenQuery) Order(o ...devicetoken.OrderOption) *DeviceTokenQuery { _q.order = append(_q.order, o...) return _q } // First returns the first DeviceToken entity from the query. // Returns a *NotFoundError when no DeviceToken was found. func (_q *DeviceTokenQuery) First(ctx context.Context) (*DeviceToken, error) { nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) if err != nil { return nil, err } if len(nodes) == 0 { return nil, &NotFoundError{devicetoken.Label} } return nodes[0], nil } // FirstX is like First, but panics if an error occurs. func (_q *DeviceTokenQuery) FirstX(ctx context.Context) *DeviceToken { node, err := _q.First(ctx) if err != nil && !IsNotFound(err) { panic(err) } return node } // FirstID returns the first DeviceToken ID from the query. // Returns a *NotFoundError when no DeviceToken ID was found. func (_q *DeviceTokenQuery) FirstID(ctx context.Context) (id int, err error) { var ids []int if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { return } if len(ids) == 0 { err = &NotFoundError{devicetoken.Label} return } return ids[0], nil } // FirstIDX is like FirstID, but panics if an error occurs. func (_q *DeviceTokenQuery) FirstIDX(ctx context.Context) int { id, err := _q.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) } return id } // Only returns a single DeviceToken entity found by the query, ensuring it only returns one. // Returns a *NotSingularError when more than one DeviceToken entity is found. // Returns a *NotFoundError when no DeviceToken entities are found. func (_q *DeviceTokenQuery) Only(ctx context.Context) (*DeviceToken, error) { nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) if err != nil { return nil, err } switch len(nodes) { case 1: return nodes[0], nil case 0: return nil, &NotFoundError{devicetoken.Label} default: return nil, &NotSingularError{devicetoken.Label} } } // OnlyX is like Only, but panics if an error occurs. func (_q *DeviceTokenQuery) OnlyX(ctx context.Context) *DeviceToken { node, err := _q.Only(ctx) if err != nil { panic(err) } return node } // OnlyID is like Only, but returns the only DeviceToken ID in the query. // Returns a *NotSingularError when more than one DeviceToken ID is found. // Returns a *NotFoundError when no entities are found. func (_q *DeviceTokenQuery) OnlyID(ctx context.Context) (id int, err error) { var ids []int if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { return } switch len(ids) { case 1: id = ids[0] case 0: err = &NotFoundError{devicetoken.Label} default: err = &NotSingularError{devicetoken.Label} } return } // OnlyIDX is like OnlyID, but panics if an error occurs. func (_q *DeviceTokenQuery) OnlyIDX(ctx context.Context) int { id, err := _q.OnlyID(ctx) if err != nil { panic(err) } return id } // All executes the query and returns a list of DeviceTokens. func (_q *DeviceTokenQuery) All(ctx context.Context) ([]*DeviceToken, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) if err := _q.prepareQuery(ctx); err != nil { return nil, err } qr := querierAll[[]*DeviceToken, *DeviceTokenQuery]() return withInterceptors[[]*DeviceToken](ctx, _q, qr, _q.inters) } // AllX is like All, but panics if an error occurs. func (_q *DeviceTokenQuery) AllX(ctx context.Context) []*DeviceToken { nodes, err := _q.All(ctx) if err != nil { panic(err) } return nodes } // IDs executes the query and returns a list of DeviceToken IDs. func (_q *DeviceTokenQuery) IDs(ctx context.Context) (ids []int, err error) { if _q.ctx.Unique == nil && _q.path != nil { _q.Unique(true) } ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) if err = _q.Select(devicetoken.FieldID).Scan(ctx, &ids); err != nil { return nil, err } return ids, nil } // IDsX is like IDs, but panics if an error occurs. func (_q *DeviceTokenQuery) IDsX(ctx context.Context) []int { ids, err := _q.IDs(ctx) if err != nil { panic(err) } return ids } // Count returns the count of the given query. func (_q *DeviceTokenQuery) Count(ctx context.Context) (int, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) if err := _q.prepareQuery(ctx); err != nil { return 0, err } return withInterceptors[int](ctx, _q, querierCount[*DeviceTokenQuery](), _q.inters) } // CountX is like Count, but panics if an error occurs. func (_q *DeviceTokenQuery) CountX(ctx context.Context) int { count, err := _q.Count(ctx) if err != nil { panic(err) } return count } // Exist returns true if the query has elements in the graph. func (_q *DeviceTokenQuery) Exist(ctx context.Context) (bool, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) switch _, err := _q.FirstID(ctx); { case IsNotFound(err): return false, nil case err != nil: return false, fmt.Errorf("db: check existence: %w", err) default: return true, nil } } // ExistX is like Exist, but panics if an error occurs. func (_q *DeviceTokenQuery) ExistX(ctx context.Context) bool { exist, err := _q.Exist(ctx) if err != nil { panic(err) } return exist } // Clone returns a duplicate of the DeviceTokenQuery builder, including all associated steps. It can be // used to prepare common query builders and use them differently after the clone is made. func (_q *DeviceTokenQuery) Clone() *DeviceTokenQuery { if _q == nil { return nil } return &DeviceTokenQuery{ config: _q.config, ctx: _q.ctx.Clone(), order: append([]devicetoken.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.DeviceToken{}, _q.predicates...), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // // Example: // // var v []struct { // DeviceCode string `json:"device_code,omitempty"` // Count int `json:"count,omitempty"` // } // // client.DeviceToken.Query(). // GroupBy(devicetoken.FieldDeviceCode). // Aggregate(db.Count()). // Scan(ctx, &v) func (_q *DeviceTokenQuery) GroupBy(field string, fields ...string) *DeviceTokenGroupBy { _q.ctx.Fields = append([]string{field}, fields...) grbuild := &DeviceTokenGroupBy{build: _q} grbuild.flds = &_q.ctx.Fields grbuild.label = devicetoken.Label grbuild.scan = grbuild.Scan return grbuild } // Select allows the selection one or more fields/columns for the given query, // instead of selecting all fields in the entity. // // Example: // // var v []struct { // DeviceCode string `json:"device_code,omitempty"` // } // // client.DeviceToken.Query(). // Select(devicetoken.FieldDeviceCode). // Scan(ctx, &v) func (_q *DeviceTokenQuery) Select(fields ...string) *DeviceTokenSelect { _q.ctx.Fields = append(_q.ctx.Fields, fields...) sbuild := &DeviceTokenSelect{DeviceTokenQuery: _q} sbuild.label = devicetoken.Label sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan return sbuild } // Aggregate returns a DeviceTokenSelect configured with the given aggregations. func (_q *DeviceTokenQuery) Aggregate(fns ...AggregateFunc) *DeviceTokenSelect { return _q.Select().Aggregate(fns...) } func (_q *DeviceTokenQuery) prepareQuery(ctx context.Context) error { for _, inter := range _q.inters { if inter == nil { return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") } if trv, ok := inter.(Traverser); ok { if err := trv.Traverse(ctx, _q); err != nil { return err } } } for _, f := range _q.ctx.Fields { if !devicetoken.ValidColumn(f) { return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } } if _q.path != nil { prev, err := _q.path(ctx) if err != nil { return err } _q.sql = prev } return nil } func (_q *DeviceTokenQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*DeviceToken, error) { var ( nodes = []*DeviceToken{} _spec = _q.querySpec() ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*DeviceToken).scanValues(nil, columns) } _spec.Assign = func(columns []string, values []any) error { node := &DeviceToken{config: _q.config} nodes = append(nodes, node) return node.assignValues(columns, values) } for i := range hooks { hooks[i](ctx, _spec) } if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { return nil, err } if len(nodes) == 0 { return nodes, nil } return nodes, nil } func (_q *DeviceTokenQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields if len(_q.ctx.Fields) > 0 { _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique } return sqlgraph.CountNodes(ctx, _q.driver, _spec) } func (_q *DeviceTokenQuery) querySpec() *sqlgraph.QuerySpec { _spec := sqlgraph.NewQuerySpec(devicetoken.Table, devicetoken.Columns, sqlgraph.NewFieldSpec(devicetoken.FieldID, field.TypeInt)) _spec.From = _q.sql if unique := _q.ctx.Unique; unique != nil { _spec.Unique = *unique } else if _q.path != nil { _spec.Unique = true } if fields := _q.ctx.Fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, devicetoken.FieldID) for i := range fields { if fields[i] != devicetoken.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } } if ps := _q.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if limit := _q.ctx.Limit; limit != nil { _spec.Limit = *limit } if offset := _q.ctx.Offset; offset != nil { _spec.Offset = *offset } if ps := _q.order; len(ps) > 0 { _spec.Order = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } return _spec } func (_q *DeviceTokenQuery) sqlQuery(ctx context.Context) *sql.Selector { builder := sql.Dialect(_q.driver.Dialect()) t1 := builder.Table(devicetoken.Table) columns := _q.ctx.Fields if len(columns) == 0 { columns = devicetoken.Columns } selector := builder.Select(t1.Columns(columns...)...).From(t1) if _q.sql != nil { selector = _q.sql selector.Select(selector.Columns(columns...)...) } if _q.ctx.Unique != nil && *_q.ctx.Unique { selector.Distinct() } for _, p := range _q.predicates { p(selector) } for _, p := range _q.order { p(selector) } if offset := _q.ctx.Offset; offset != nil { // limit is mandatory for offset clause. We start // with default value, and override it below if needed. selector.Offset(*offset).Limit(math.MaxInt32) } if limit := _q.ctx.Limit; limit != nil { selector.Limit(*limit) } return selector } // DeviceTokenGroupBy is the group-by builder for DeviceToken entities. type DeviceTokenGroupBy struct { selector build *DeviceTokenQuery } // Aggregate adds the given aggregation functions to the group-by query. func (_g *DeviceTokenGroupBy) Aggregate(fns ...AggregateFunc) *DeviceTokenGroupBy { _g.fns = append(_g.fns, fns...) return _g } // Scan applies the selector query and scans the result into the given value. func (_g *DeviceTokenGroupBy) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) if err := _g.build.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*DeviceTokenQuery, *DeviceTokenGroupBy](ctx, _g.build, _g, _g.build.inters, v) } func (_g *DeviceTokenGroupBy) sqlScan(ctx context.Context, root *DeviceTokenQuery, v any) error { selector := root.sqlQuery(ctx).Select() aggregation := make([]string, 0, len(_g.fns)) for _, fn := range _g.fns { aggregation = append(aggregation, fn(selector)) } if len(selector.SelectedColumns()) == 0 { columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) for _, f := range *_g.flds { columns = append(columns, selector.C(f)) } columns = append(columns, aggregation...) selector.Select(columns...) } selector.GroupBy(selector.Columns(*_g.flds...)...) if err := selector.Err(); err != nil { return err } rows := &sql.Rows{} query, args := selector.Query() if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } // DeviceTokenSelect is the builder for selecting fields of DeviceToken entities. type DeviceTokenSelect struct { *DeviceTokenQuery selector } // Aggregate adds the given aggregation functions to the selector query. func (_s *DeviceTokenSelect) Aggregate(fns ...AggregateFunc) *DeviceTokenSelect { _s.fns = append(_s.fns, fns...) return _s } // Scan applies the selector query and scans the result into the given value. func (_s *DeviceTokenSelect) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) if err := _s.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*DeviceTokenQuery, *DeviceTokenSelect](ctx, _s.DeviceTokenQuery, _s, _s.inters, v) } func (_s *DeviceTokenSelect) sqlScan(ctx context.Context, root *DeviceTokenQuery, v any) error { selector := root.sqlQuery(ctx) aggregation := make([]string, 0, len(_s.fns)) for _, fn := range _s.fns { aggregation = append(aggregation, fn(selector)) } switch n := len(*_s.selector.flds); { case n == 0 && len(aggregation) > 0: selector.Select(aggregation...) case n != 0 && len(aggregation) > 0: selector.AppendSelect(aggregation...) } rows := &sql.Rows{} query, args := selector.Query() if err := _s.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } ================================================ FILE: storage/ent/db/devicetoken_update.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/devicetoken" "github.com/dexidp/dex/storage/ent/db/predicate" ) // DeviceTokenUpdate is the builder for updating DeviceToken entities. type DeviceTokenUpdate struct { config hooks []Hook mutation *DeviceTokenMutation } // Where appends a list predicates to the DeviceTokenUpdate builder. func (_u *DeviceTokenUpdate) Where(ps ...predicate.DeviceToken) *DeviceTokenUpdate { _u.mutation.Where(ps...) return _u } // SetDeviceCode sets the "device_code" field. func (_u *DeviceTokenUpdate) SetDeviceCode(v string) *DeviceTokenUpdate { _u.mutation.SetDeviceCode(v) return _u } // SetNillableDeviceCode sets the "device_code" field if the given value is not nil. func (_u *DeviceTokenUpdate) SetNillableDeviceCode(v *string) *DeviceTokenUpdate { if v != nil { _u.SetDeviceCode(*v) } return _u } // SetStatus sets the "status" field. func (_u *DeviceTokenUpdate) SetStatus(v string) *DeviceTokenUpdate { _u.mutation.SetStatus(v) return _u } // SetNillableStatus sets the "status" field if the given value is not nil. func (_u *DeviceTokenUpdate) SetNillableStatus(v *string) *DeviceTokenUpdate { if v != nil { _u.SetStatus(*v) } return _u } // SetToken sets the "token" field. func (_u *DeviceTokenUpdate) SetToken(v []byte) *DeviceTokenUpdate { _u.mutation.SetToken(v) return _u } // ClearToken clears the value of the "token" field. func (_u *DeviceTokenUpdate) ClearToken() *DeviceTokenUpdate { _u.mutation.ClearToken() return _u } // SetExpiry sets the "expiry" field. func (_u *DeviceTokenUpdate) SetExpiry(v time.Time) *DeviceTokenUpdate { _u.mutation.SetExpiry(v) return _u } // SetNillableExpiry sets the "expiry" field if the given value is not nil. func (_u *DeviceTokenUpdate) SetNillableExpiry(v *time.Time) *DeviceTokenUpdate { if v != nil { _u.SetExpiry(*v) } return _u } // SetLastRequest sets the "last_request" field. func (_u *DeviceTokenUpdate) SetLastRequest(v time.Time) *DeviceTokenUpdate { _u.mutation.SetLastRequest(v) return _u } // SetNillableLastRequest sets the "last_request" field if the given value is not nil. func (_u *DeviceTokenUpdate) SetNillableLastRequest(v *time.Time) *DeviceTokenUpdate { if v != nil { _u.SetLastRequest(*v) } return _u } // SetPollInterval sets the "poll_interval" field. func (_u *DeviceTokenUpdate) SetPollInterval(v int) *DeviceTokenUpdate { _u.mutation.ResetPollInterval() _u.mutation.SetPollInterval(v) return _u } // SetNillablePollInterval sets the "poll_interval" field if the given value is not nil. func (_u *DeviceTokenUpdate) SetNillablePollInterval(v *int) *DeviceTokenUpdate { if v != nil { _u.SetPollInterval(*v) } return _u } // AddPollInterval adds value to the "poll_interval" field. func (_u *DeviceTokenUpdate) AddPollInterval(v int) *DeviceTokenUpdate { _u.mutation.AddPollInterval(v) return _u } // SetCodeChallenge sets the "code_challenge" field. func (_u *DeviceTokenUpdate) SetCodeChallenge(v string) *DeviceTokenUpdate { _u.mutation.SetCodeChallenge(v) return _u } // SetNillableCodeChallenge sets the "code_challenge" field if the given value is not nil. func (_u *DeviceTokenUpdate) SetNillableCodeChallenge(v *string) *DeviceTokenUpdate { if v != nil { _u.SetCodeChallenge(*v) } return _u } // SetCodeChallengeMethod sets the "code_challenge_method" field. func (_u *DeviceTokenUpdate) SetCodeChallengeMethod(v string) *DeviceTokenUpdate { _u.mutation.SetCodeChallengeMethod(v) return _u } // SetNillableCodeChallengeMethod sets the "code_challenge_method" field if the given value is not nil. func (_u *DeviceTokenUpdate) SetNillableCodeChallengeMethod(v *string) *DeviceTokenUpdate { if v != nil { _u.SetCodeChallengeMethod(*v) } return _u } // Mutation returns the DeviceTokenMutation object of the builder. func (_u *DeviceTokenUpdate) Mutation() *DeviceTokenMutation { return _u.mutation } // Save executes the query and returns the number of nodes affected by the update operation. func (_u *DeviceTokenUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *DeviceTokenUpdate) SaveX(ctx context.Context) int { affected, err := _u.Save(ctx) if err != nil { panic(err) } return affected } // Exec executes the query. func (_u *DeviceTokenUpdate) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *DeviceTokenUpdate) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *DeviceTokenUpdate) check() error { if v, ok := _u.mutation.DeviceCode(); ok { if err := devicetoken.DeviceCodeValidator(v); err != nil { return &ValidationError{Name: "device_code", err: fmt.Errorf(`db: validator failed for field "DeviceToken.device_code": %w`, err)} } } if v, ok := _u.mutation.Status(); ok { if err := devicetoken.StatusValidator(v); err != nil { return &ValidationError{Name: "status", err: fmt.Errorf(`db: validator failed for field "DeviceToken.status": %w`, err)} } } return nil } func (_u *DeviceTokenUpdate) sqlSave(ctx context.Context) (_node int, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(devicetoken.Table, devicetoken.Columns, sqlgraph.NewFieldSpec(devicetoken.FieldID, field.TypeInt)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.DeviceCode(); ok { _spec.SetField(devicetoken.FieldDeviceCode, field.TypeString, value) } if value, ok := _u.mutation.Status(); ok { _spec.SetField(devicetoken.FieldStatus, field.TypeString, value) } if value, ok := _u.mutation.Token(); ok { _spec.SetField(devicetoken.FieldToken, field.TypeBytes, value) } if _u.mutation.TokenCleared() { _spec.ClearField(devicetoken.FieldToken, field.TypeBytes) } if value, ok := _u.mutation.Expiry(); ok { _spec.SetField(devicetoken.FieldExpiry, field.TypeTime, value) } if value, ok := _u.mutation.LastRequest(); ok { _spec.SetField(devicetoken.FieldLastRequest, field.TypeTime, value) } if value, ok := _u.mutation.PollInterval(); ok { _spec.SetField(devicetoken.FieldPollInterval, field.TypeInt, value) } if value, ok := _u.mutation.AddedPollInterval(); ok { _spec.AddField(devicetoken.FieldPollInterval, field.TypeInt, value) } if value, ok := _u.mutation.CodeChallenge(); ok { _spec.SetField(devicetoken.FieldCodeChallenge, field.TypeString, value) } if value, ok := _u.mutation.CodeChallengeMethod(); ok { _spec.SetField(devicetoken.FieldCodeChallengeMethod, field.TypeString, value) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{devicetoken.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return 0, err } _u.mutation.done = true return _node, nil } // DeviceTokenUpdateOne is the builder for updating a single DeviceToken entity. type DeviceTokenUpdateOne struct { config fields []string hooks []Hook mutation *DeviceTokenMutation } // SetDeviceCode sets the "device_code" field. func (_u *DeviceTokenUpdateOne) SetDeviceCode(v string) *DeviceTokenUpdateOne { _u.mutation.SetDeviceCode(v) return _u } // SetNillableDeviceCode sets the "device_code" field if the given value is not nil. func (_u *DeviceTokenUpdateOne) SetNillableDeviceCode(v *string) *DeviceTokenUpdateOne { if v != nil { _u.SetDeviceCode(*v) } return _u } // SetStatus sets the "status" field. func (_u *DeviceTokenUpdateOne) SetStatus(v string) *DeviceTokenUpdateOne { _u.mutation.SetStatus(v) return _u } // SetNillableStatus sets the "status" field if the given value is not nil. func (_u *DeviceTokenUpdateOne) SetNillableStatus(v *string) *DeviceTokenUpdateOne { if v != nil { _u.SetStatus(*v) } return _u } // SetToken sets the "token" field. func (_u *DeviceTokenUpdateOne) SetToken(v []byte) *DeviceTokenUpdateOne { _u.mutation.SetToken(v) return _u } // ClearToken clears the value of the "token" field. func (_u *DeviceTokenUpdateOne) ClearToken() *DeviceTokenUpdateOne { _u.mutation.ClearToken() return _u } // SetExpiry sets the "expiry" field. func (_u *DeviceTokenUpdateOne) SetExpiry(v time.Time) *DeviceTokenUpdateOne { _u.mutation.SetExpiry(v) return _u } // SetNillableExpiry sets the "expiry" field if the given value is not nil. func (_u *DeviceTokenUpdateOne) SetNillableExpiry(v *time.Time) *DeviceTokenUpdateOne { if v != nil { _u.SetExpiry(*v) } return _u } // SetLastRequest sets the "last_request" field. func (_u *DeviceTokenUpdateOne) SetLastRequest(v time.Time) *DeviceTokenUpdateOne { _u.mutation.SetLastRequest(v) return _u } // SetNillableLastRequest sets the "last_request" field if the given value is not nil. func (_u *DeviceTokenUpdateOne) SetNillableLastRequest(v *time.Time) *DeviceTokenUpdateOne { if v != nil { _u.SetLastRequest(*v) } return _u } // SetPollInterval sets the "poll_interval" field. func (_u *DeviceTokenUpdateOne) SetPollInterval(v int) *DeviceTokenUpdateOne { _u.mutation.ResetPollInterval() _u.mutation.SetPollInterval(v) return _u } // SetNillablePollInterval sets the "poll_interval" field if the given value is not nil. func (_u *DeviceTokenUpdateOne) SetNillablePollInterval(v *int) *DeviceTokenUpdateOne { if v != nil { _u.SetPollInterval(*v) } return _u } // AddPollInterval adds value to the "poll_interval" field. func (_u *DeviceTokenUpdateOne) AddPollInterval(v int) *DeviceTokenUpdateOne { _u.mutation.AddPollInterval(v) return _u } // SetCodeChallenge sets the "code_challenge" field. func (_u *DeviceTokenUpdateOne) SetCodeChallenge(v string) *DeviceTokenUpdateOne { _u.mutation.SetCodeChallenge(v) return _u } // SetNillableCodeChallenge sets the "code_challenge" field if the given value is not nil. func (_u *DeviceTokenUpdateOne) SetNillableCodeChallenge(v *string) *DeviceTokenUpdateOne { if v != nil { _u.SetCodeChallenge(*v) } return _u } // SetCodeChallengeMethod sets the "code_challenge_method" field. func (_u *DeviceTokenUpdateOne) SetCodeChallengeMethod(v string) *DeviceTokenUpdateOne { _u.mutation.SetCodeChallengeMethod(v) return _u } // SetNillableCodeChallengeMethod sets the "code_challenge_method" field if the given value is not nil. func (_u *DeviceTokenUpdateOne) SetNillableCodeChallengeMethod(v *string) *DeviceTokenUpdateOne { if v != nil { _u.SetCodeChallengeMethod(*v) } return _u } // Mutation returns the DeviceTokenMutation object of the builder. func (_u *DeviceTokenUpdateOne) Mutation() *DeviceTokenMutation { return _u.mutation } // Where appends a list predicates to the DeviceTokenUpdate builder. func (_u *DeviceTokenUpdateOne) Where(ps ...predicate.DeviceToken) *DeviceTokenUpdateOne { _u.mutation.Where(ps...) return _u } // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (_u *DeviceTokenUpdateOne) Select(field string, fields ...string) *DeviceTokenUpdateOne { _u.fields = append([]string{field}, fields...) return _u } // Save executes the query and returns the updated DeviceToken entity. func (_u *DeviceTokenUpdateOne) Save(ctx context.Context) (*DeviceToken, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *DeviceTokenUpdateOne) SaveX(ctx context.Context) *DeviceToken { node, err := _u.Save(ctx) if err != nil { panic(err) } return node } // Exec executes the query on the entity. func (_u *DeviceTokenUpdateOne) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *DeviceTokenUpdateOne) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *DeviceTokenUpdateOne) check() error { if v, ok := _u.mutation.DeviceCode(); ok { if err := devicetoken.DeviceCodeValidator(v); err != nil { return &ValidationError{Name: "device_code", err: fmt.Errorf(`db: validator failed for field "DeviceToken.device_code": %w`, err)} } } if v, ok := _u.mutation.Status(); ok { if err := devicetoken.StatusValidator(v); err != nil { return &ValidationError{Name: "status", err: fmt.Errorf(`db: validator failed for field "DeviceToken.status": %w`, err)} } } return nil } func (_u *DeviceTokenUpdateOne) sqlSave(ctx context.Context) (_node *DeviceToken, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(devicetoken.Table, devicetoken.Columns, sqlgraph.NewFieldSpec(devicetoken.FieldID, field.TypeInt)) id, ok := _u.mutation.ID() if !ok { return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "DeviceToken.id" for update`)} } _spec.Node.ID.Value = id if fields := _u.fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, devicetoken.FieldID) for _, f := range fields { if !devicetoken.ValidColumn(f) { return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } if f != devicetoken.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, f) } } } if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.DeviceCode(); ok { _spec.SetField(devicetoken.FieldDeviceCode, field.TypeString, value) } if value, ok := _u.mutation.Status(); ok { _spec.SetField(devicetoken.FieldStatus, field.TypeString, value) } if value, ok := _u.mutation.Token(); ok { _spec.SetField(devicetoken.FieldToken, field.TypeBytes, value) } if _u.mutation.TokenCleared() { _spec.ClearField(devicetoken.FieldToken, field.TypeBytes) } if value, ok := _u.mutation.Expiry(); ok { _spec.SetField(devicetoken.FieldExpiry, field.TypeTime, value) } if value, ok := _u.mutation.LastRequest(); ok { _spec.SetField(devicetoken.FieldLastRequest, field.TypeTime, value) } if value, ok := _u.mutation.PollInterval(); ok { _spec.SetField(devicetoken.FieldPollInterval, field.TypeInt, value) } if value, ok := _u.mutation.AddedPollInterval(); ok { _spec.AddField(devicetoken.FieldPollInterval, field.TypeInt, value) } if value, ok := _u.mutation.CodeChallenge(); ok { _spec.SetField(devicetoken.FieldCodeChallenge, field.TypeString, value) } if value, ok := _u.mutation.CodeChallengeMethod(); ok { _spec.SetField(devicetoken.FieldCodeChallengeMethod, field.TypeString, value) } _node = &DeviceToken{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{devicetoken.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } _u.mutation.done = true return _node, nil } ================================================ FILE: storage/ent/db/ent.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "reflect" "sync" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "github.com/dexidp/dex/storage/ent/db/authcode" "github.com/dexidp/dex/storage/ent/db/authrequest" "github.com/dexidp/dex/storage/ent/db/authsession" "github.com/dexidp/dex/storage/ent/db/connector" "github.com/dexidp/dex/storage/ent/db/devicerequest" "github.com/dexidp/dex/storage/ent/db/devicetoken" "github.com/dexidp/dex/storage/ent/db/keys" "github.com/dexidp/dex/storage/ent/db/oauth2client" "github.com/dexidp/dex/storage/ent/db/offlinesession" "github.com/dexidp/dex/storage/ent/db/password" "github.com/dexidp/dex/storage/ent/db/refreshtoken" "github.com/dexidp/dex/storage/ent/db/useridentity" ) // ent aliases to avoid import conflicts in user's code. type ( Op = ent.Op Hook = ent.Hook Value = ent.Value Query = ent.Query QueryContext = ent.QueryContext Querier = ent.Querier QuerierFunc = ent.QuerierFunc Interceptor = ent.Interceptor InterceptFunc = ent.InterceptFunc Traverser = ent.Traverser TraverseFunc = ent.TraverseFunc Policy = ent.Policy Mutator = ent.Mutator Mutation = ent.Mutation MutateFunc = ent.MutateFunc ) type clientCtxKey struct{} // FromContext returns a Client stored inside a context, or nil if there isn't one. func FromContext(ctx context.Context) *Client { c, _ := ctx.Value(clientCtxKey{}).(*Client) return c } // NewContext returns a new context with the given Client attached. func NewContext(parent context.Context, c *Client) context.Context { return context.WithValue(parent, clientCtxKey{}, c) } type txCtxKey struct{} // TxFromContext returns a Tx stored inside a context, or nil if there isn't one. func TxFromContext(ctx context.Context) *Tx { tx, _ := ctx.Value(txCtxKey{}).(*Tx) return tx } // NewTxContext returns a new context with the given Tx attached. func NewTxContext(parent context.Context, tx *Tx) context.Context { return context.WithValue(parent, txCtxKey{}, tx) } // OrderFunc applies an ordering on the sql selector. // Deprecated: Use Asc/Desc functions or the package builders instead. type OrderFunc func(*sql.Selector) var ( initCheck sync.Once columnCheck sql.ColumnCheck ) // checkColumn checks if the column exists in the given table. func checkColumn(t, c string) error { initCheck.Do(func() { columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ authcode.Table: authcode.ValidColumn, authrequest.Table: authrequest.ValidColumn, authsession.Table: authsession.ValidColumn, connector.Table: connector.ValidColumn, devicerequest.Table: devicerequest.ValidColumn, devicetoken.Table: devicetoken.ValidColumn, keys.Table: keys.ValidColumn, oauth2client.Table: oauth2client.ValidColumn, offlinesession.Table: offlinesession.ValidColumn, password.Table: password.ValidColumn, refreshtoken.Table: refreshtoken.ValidColumn, useridentity.Table: useridentity.ValidColumn, }) }) return columnCheck(t, c) } // Asc applies the given fields in ASC order. func Asc(fields ...string) func(*sql.Selector) { return func(s *sql.Selector) { for _, f := range fields { if err := checkColumn(s.TableName(), f); err != nil { s.AddError(&ValidationError{Name: f, err: fmt.Errorf("db: %w", err)}) } s.OrderBy(sql.Asc(s.C(f))) } } } // Desc applies the given fields in DESC order. func Desc(fields ...string) func(*sql.Selector) { return func(s *sql.Selector) { for _, f := range fields { if err := checkColumn(s.TableName(), f); err != nil { s.AddError(&ValidationError{Name: f, err: fmt.Errorf("db: %w", err)}) } s.OrderBy(sql.Desc(s.C(f))) } } } // AggregateFunc applies an aggregation step on the group-by traversal/selector. type AggregateFunc func(*sql.Selector) string // As is a pseudo aggregation function for renaming another other functions with custom names. For example: // // GroupBy(field1, field2). // Aggregate(db.As(db.Sum(field1), "sum_field1"), (db.As(db.Sum(field2), "sum_field2")). // Scan(ctx, &v) func As(fn AggregateFunc, end string) AggregateFunc { return func(s *sql.Selector) string { return sql.As(fn(s), end) } } // Count applies the "count" aggregation function on each group. func Count() AggregateFunc { return func(s *sql.Selector) string { return sql.Count("*") } } // Max applies the "max" aggregation function on the given field of each group. func Max(field string) AggregateFunc { return func(s *sql.Selector) string { if err := checkColumn(s.TableName(), field); err != nil { s.AddError(&ValidationError{Name: field, err: fmt.Errorf("db: %w", err)}) return "" } return sql.Max(s.C(field)) } } // Mean applies the "mean" aggregation function on the given field of each group. func Mean(field string) AggregateFunc { return func(s *sql.Selector) string { if err := checkColumn(s.TableName(), field); err != nil { s.AddError(&ValidationError{Name: field, err: fmt.Errorf("db: %w", err)}) return "" } return sql.Avg(s.C(field)) } } // Min applies the "min" aggregation function on the given field of each group. func Min(field string) AggregateFunc { return func(s *sql.Selector) string { if err := checkColumn(s.TableName(), field); err != nil { s.AddError(&ValidationError{Name: field, err: fmt.Errorf("db: %w", err)}) return "" } return sql.Min(s.C(field)) } } // Sum applies the "sum" aggregation function on the given field of each group. func Sum(field string) AggregateFunc { return func(s *sql.Selector) string { if err := checkColumn(s.TableName(), field); err != nil { s.AddError(&ValidationError{Name: field, err: fmt.Errorf("db: %w", err)}) return "" } return sql.Sum(s.C(field)) } } // ValidationError returns when validating a field or edge fails. type ValidationError struct { Name string // Field or edge name. err error } // Error implements the error interface. func (e *ValidationError) Error() string { return e.err.Error() } // Unwrap implements the errors.Wrapper interface. func (e *ValidationError) Unwrap() error { return e.err } // IsValidationError returns a boolean indicating whether the error is a validation error. func IsValidationError(err error) bool { if err == nil { return false } var e *ValidationError return errors.As(err, &e) } // NotFoundError returns when trying to fetch a specific entity and it was not found in the database. type NotFoundError struct { label string } // Error implements the error interface. func (e *NotFoundError) Error() string { return "db: " + e.label + " not found" } // IsNotFound returns a boolean indicating whether the error is a not found error. func IsNotFound(err error) bool { if err == nil { return false } var e *NotFoundError return errors.As(err, &e) } // MaskNotFound masks not found error. func MaskNotFound(err error) error { if IsNotFound(err) { return nil } return err } // NotSingularError returns when trying to fetch a singular entity and more then one was found in the database. type NotSingularError struct { label string } // Error implements the error interface. func (e *NotSingularError) Error() string { return "db: " + e.label + " not singular" } // IsNotSingular returns a boolean indicating whether the error is a not singular error. func IsNotSingular(err error) bool { if err == nil { return false } var e *NotSingularError return errors.As(err, &e) } // NotLoadedError returns when trying to get a node that was not loaded by the query. type NotLoadedError struct { edge string } // Error implements the error interface. func (e *NotLoadedError) Error() string { return "db: " + e.edge + " edge was not loaded" } // IsNotLoaded returns a boolean indicating whether the error is a not loaded error. func IsNotLoaded(err error) bool { if err == nil { return false } var e *NotLoadedError return errors.As(err, &e) } // ConstraintError returns when trying to create/update one or more entities and // one or more of their constraints failed. For example, violation of edge or // field uniqueness. type ConstraintError struct { msg string wrap error } // Error implements the error interface. func (e ConstraintError) Error() string { return "db: constraint failed: " + e.msg } // Unwrap implements the errors.Wrapper interface. func (e *ConstraintError) Unwrap() error { return e.wrap } // IsConstraintError returns a boolean indicating whether the error is a constraint failure. func IsConstraintError(err error) bool { if err == nil { return false } var e *ConstraintError return errors.As(err, &e) } // selector embedded by the different Select/GroupBy builders. type selector struct { label string flds *[]string fns []AggregateFunc scan func(context.Context, any) error } // ScanX is like Scan, but panics if an error occurs. func (s *selector) ScanX(ctx context.Context, v any) { if err := s.scan(ctx, v); err != nil { panic(err) } } // Strings returns list of strings from a selector. It is only allowed when selecting one field. func (s *selector) Strings(ctx context.Context) ([]string, error) { if len(*s.flds) > 1 { return nil, errors.New("db: Strings is not achievable when selecting more than 1 field") } var v []string if err := s.scan(ctx, &v); err != nil { return nil, err } return v, nil } // StringsX is like Strings, but panics if an error occurs. func (s *selector) StringsX(ctx context.Context) []string { v, err := s.Strings(ctx) if err != nil { panic(err) } return v } // String returns a single string from a selector. It is only allowed when selecting one field. func (s *selector) String(ctx context.Context) (_ string, err error) { var v []string if v, err = s.Strings(ctx); err != nil { return } switch len(v) { case 1: return v[0], nil case 0: err = &NotFoundError{s.label} default: err = fmt.Errorf("db: Strings returned %d results when one was expected", len(v)) } return } // StringX is like String, but panics if an error occurs. func (s *selector) StringX(ctx context.Context) string { v, err := s.String(ctx) if err != nil { panic(err) } return v } // Ints returns list of ints from a selector. It is only allowed when selecting one field. func (s *selector) Ints(ctx context.Context) ([]int, error) { if len(*s.flds) > 1 { return nil, errors.New("db: Ints is not achievable when selecting more than 1 field") } var v []int if err := s.scan(ctx, &v); err != nil { return nil, err } return v, nil } // IntsX is like Ints, but panics if an error occurs. func (s *selector) IntsX(ctx context.Context) []int { v, err := s.Ints(ctx) if err != nil { panic(err) } return v } // Int returns a single int from a selector. It is only allowed when selecting one field. func (s *selector) Int(ctx context.Context) (_ int, err error) { var v []int if v, err = s.Ints(ctx); err != nil { return } switch len(v) { case 1: return v[0], nil case 0: err = &NotFoundError{s.label} default: err = fmt.Errorf("db: Ints returned %d results when one was expected", len(v)) } return } // IntX is like Int, but panics if an error occurs. func (s *selector) IntX(ctx context.Context) int { v, err := s.Int(ctx) if err != nil { panic(err) } return v } // Float64s returns list of float64s from a selector. It is only allowed when selecting one field. func (s *selector) Float64s(ctx context.Context) ([]float64, error) { if len(*s.flds) > 1 { return nil, errors.New("db: Float64s is not achievable when selecting more than 1 field") } var v []float64 if err := s.scan(ctx, &v); err != nil { return nil, err } return v, nil } // Float64sX is like Float64s, but panics if an error occurs. func (s *selector) Float64sX(ctx context.Context) []float64 { v, err := s.Float64s(ctx) if err != nil { panic(err) } return v } // Float64 returns a single float64 from a selector. It is only allowed when selecting one field. func (s *selector) Float64(ctx context.Context) (_ float64, err error) { var v []float64 if v, err = s.Float64s(ctx); err != nil { return } switch len(v) { case 1: return v[0], nil case 0: err = &NotFoundError{s.label} default: err = fmt.Errorf("db: Float64s returned %d results when one was expected", len(v)) } return } // Float64X is like Float64, but panics if an error occurs. func (s *selector) Float64X(ctx context.Context) float64 { v, err := s.Float64(ctx) if err != nil { panic(err) } return v } // Bools returns list of bools from a selector. It is only allowed when selecting one field. func (s *selector) Bools(ctx context.Context) ([]bool, error) { if len(*s.flds) > 1 { return nil, errors.New("db: Bools is not achievable when selecting more than 1 field") } var v []bool if err := s.scan(ctx, &v); err != nil { return nil, err } return v, nil } // BoolsX is like Bools, but panics if an error occurs. func (s *selector) BoolsX(ctx context.Context) []bool { v, err := s.Bools(ctx) if err != nil { panic(err) } return v } // Bool returns a single bool from a selector. It is only allowed when selecting one field. func (s *selector) Bool(ctx context.Context) (_ bool, err error) { var v []bool if v, err = s.Bools(ctx); err != nil { return } switch len(v) { case 1: return v[0], nil case 0: err = &NotFoundError{s.label} default: err = fmt.Errorf("db: Bools returned %d results when one was expected", len(v)) } return } // BoolX is like Bool, but panics if an error occurs. func (s *selector) BoolX(ctx context.Context) bool { v, err := s.Bool(ctx) if err != nil { panic(err) } return v } // withHooks invokes the builder operation with the given hooks, if any. func withHooks[V Value, M any, PM interface { *M Mutation }](ctx context.Context, exec func(context.Context) (V, error), mutation PM, hooks []Hook) (value V, err error) { if len(hooks) == 0 { return exec(ctx) } var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutationT, ok := any(m).(PM) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } // Set the mutation to the builder. *mutation = *mutationT return exec(ctx) }) for i := len(hooks) - 1; i >= 0; i-- { if hooks[i] == nil { return value, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") } mut = hooks[i](mut) } v, err := mut.Mutate(ctx, mutation) if err != nil { return value, err } nv, ok := v.(V) if !ok { return value, fmt.Errorf("unexpected node type %T returned from %T", v, mutation) } return nv, nil } // setContextOp returns a new context with the given QueryContext attached (including its op) in case it does not exist. func setContextOp(ctx context.Context, qc *QueryContext, op string) context.Context { if ent.QueryFromContext(ctx) == nil { qc.Op = op ctx = ent.NewQueryContext(ctx, qc) } return ctx } func querierAll[V Value, Q interface { sqlAll(context.Context, ...queryHook) (V, error) }]() Querier { return QuerierFunc(func(ctx context.Context, q Query) (Value, error) { query, ok := q.(Q) if !ok { return nil, fmt.Errorf("unexpected query type %T", q) } return query.sqlAll(ctx) }) } func querierCount[Q interface { sqlCount(context.Context) (int, error) }]() Querier { return QuerierFunc(func(ctx context.Context, q Query) (Value, error) { query, ok := q.(Q) if !ok { return nil, fmt.Errorf("unexpected query type %T", q) } return query.sqlCount(ctx) }) } func withInterceptors[V Value](ctx context.Context, q Query, qr Querier, inters []Interceptor) (v V, err error) { for i := len(inters) - 1; i >= 0; i-- { qr = inters[i].Intercept(qr) } rv, err := qr.Query(ctx, q) if err != nil { return v, err } vt, ok := rv.(V) if !ok { return v, fmt.Errorf("unexpected type %T returned from %T. expected type: %T", vt, q, v) } return vt, nil } func scanWithInterceptors[Q1 ent.Query, Q2 interface { sqlScan(context.Context, Q1, any) error }](ctx context.Context, rootQuery Q1, selectOrGroup Q2, inters []Interceptor, v any) error { rv := reflect.ValueOf(v) var qr Querier = QuerierFunc(func(ctx context.Context, q Query) (Value, error) { query, ok := q.(Q1) if !ok { return nil, fmt.Errorf("unexpected query type %T", q) } if err := selectOrGroup.sqlScan(ctx, query, v); err != nil { return nil, err } if k := rv.Kind(); k == reflect.Pointer && rv.Elem().CanInterface() { return rv.Elem().Interface(), nil } return v, nil }) for i := len(inters) - 1; i >= 0; i-- { qr = inters[i].Intercept(qr) } vv, err := qr.Query(ctx, rootQuery) if err != nil { return err } switch rv2 := reflect.ValueOf(vv); { case rv.IsNil(), rv2.IsNil(), rv.Kind() != reflect.Pointer: case rv.Type() == rv2.Type(): rv.Elem().Set(rv2.Elem()) case rv.Elem().Type() == rv2.Type(): rv.Elem().Set(rv2) } return nil } // queryHook describes an internal hook for the different sqlAll methods. type queryHook func(context.Context, *sqlgraph.QuerySpec) ================================================ FILE: storage/ent/db/enttest/enttest.go ================================================ // Code generated by ent, DO NOT EDIT. package enttest import ( "context" "github.com/dexidp/dex/storage/ent/db" // required by schema hooks. _ "github.com/dexidp/dex/storage/ent/db/runtime" "entgo.io/ent/dialect/sql/schema" "github.com/dexidp/dex/storage/ent/db/migrate" ) type ( // TestingT is the interface that is shared between // testing.T and testing.B and used by enttest. TestingT interface { FailNow() Error(...any) } // Option configures client creation. Option func(*options) options struct { opts []db.Option migrateOpts []schema.MigrateOption } ) // WithOptions forwards options to client creation. func WithOptions(opts ...db.Option) Option { return func(o *options) { o.opts = append(o.opts, opts...) } } // WithMigrateOptions forwards options to auto migration. func WithMigrateOptions(opts ...schema.MigrateOption) Option { return func(o *options) { o.migrateOpts = append(o.migrateOpts, opts...) } } func newOptions(opts []Option) *options { o := &options{} for _, opt := range opts { opt(o) } return o } // Open calls db.Open and auto-run migration. func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *db.Client { o := newOptions(opts) c, err := db.Open(driverName, dataSourceName, o.opts...) if err != nil { t.Error(err) t.FailNow() } migrateSchema(t, c, o) return c } // NewClient calls db.NewClient and auto-run migration. func NewClient(t TestingT, opts ...Option) *db.Client { o := newOptions(opts) c := db.NewClient(o.opts...) migrateSchema(t, c, o) return c } func migrateSchema(t TestingT, c *db.Client, o *options) { tables, err := schema.CopyTables(migrate.Tables) if err != nil { t.Error(err) t.FailNow() } if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil { t.Error(err) t.FailNow() } } ================================================ FILE: storage/ent/db/hook/hook.go ================================================ // Code generated by ent, DO NOT EDIT. package hook import ( "context" "fmt" "github.com/dexidp/dex/storage/ent/db" ) // The AuthCodeFunc type is an adapter to allow the use of ordinary // function as AuthCode mutator. type AuthCodeFunc func(context.Context, *db.AuthCodeMutation) (db.Value, error) // Mutate calls f(ctx, m). func (f AuthCodeFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { if mv, ok := m.(*db.AuthCodeMutation); ok { return f(ctx, mv) } return nil, fmt.Errorf("unexpected mutation type %T. expect *db.AuthCodeMutation", m) } // The AuthRequestFunc type is an adapter to allow the use of ordinary // function as AuthRequest mutator. type AuthRequestFunc func(context.Context, *db.AuthRequestMutation) (db.Value, error) // Mutate calls f(ctx, m). func (f AuthRequestFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { if mv, ok := m.(*db.AuthRequestMutation); ok { return f(ctx, mv) } return nil, fmt.Errorf("unexpected mutation type %T. expect *db.AuthRequestMutation", m) } // The AuthSessionFunc type is an adapter to allow the use of ordinary // function as AuthSession mutator. type AuthSessionFunc func(context.Context, *db.AuthSessionMutation) (db.Value, error) // Mutate calls f(ctx, m). func (f AuthSessionFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { if mv, ok := m.(*db.AuthSessionMutation); ok { return f(ctx, mv) } return nil, fmt.Errorf("unexpected mutation type %T. expect *db.AuthSessionMutation", m) } // The ConnectorFunc type is an adapter to allow the use of ordinary // function as Connector mutator. type ConnectorFunc func(context.Context, *db.ConnectorMutation) (db.Value, error) // Mutate calls f(ctx, m). func (f ConnectorFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { if mv, ok := m.(*db.ConnectorMutation); ok { return f(ctx, mv) } return nil, fmt.Errorf("unexpected mutation type %T. expect *db.ConnectorMutation", m) } // The DeviceRequestFunc type is an adapter to allow the use of ordinary // function as DeviceRequest mutator. type DeviceRequestFunc func(context.Context, *db.DeviceRequestMutation) (db.Value, error) // Mutate calls f(ctx, m). func (f DeviceRequestFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { if mv, ok := m.(*db.DeviceRequestMutation); ok { return f(ctx, mv) } return nil, fmt.Errorf("unexpected mutation type %T. expect *db.DeviceRequestMutation", m) } // The DeviceTokenFunc type is an adapter to allow the use of ordinary // function as DeviceToken mutator. type DeviceTokenFunc func(context.Context, *db.DeviceTokenMutation) (db.Value, error) // Mutate calls f(ctx, m). func (f DeviceTokenFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { if mv, ok := m.(*db.DeviceTokenMutation); ok { return f(ctx, mv) } return nil, fmt.Errorf("unexpected mutation type %T. expect *db.DeviceTokenMutation", m) } // The KeysFunc type is an adapter to allow the use of ordinary // function as Keys mutator. type KeysFunc func(context.Context, *db.KeysMutation) (db.Value, error) // Mutate calls f(ctx, m). func (f KeysFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { if mv, ok := m.(*db.KeysMutation); ok { return f(ctx, mv) } return nil, fmt.Errorf("unexpected mutation type %T. expect *db.KeysMutation", m) } // The OAuth2ClientFunc type is an adapter to allow the use of ordinary // function as OAuth2Client mutator. type OAuth2ClientFunc func(context.Context, *db.OAuth2ClientMutation) (db.Value, error) // Mutate calls f(ctx, m). func (f OAuth2ClientFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { if mv, ok := m.(*db.OAuth2ClientMutation); ok { return f(ctx, mv) } return nil, fmt.Errorf("unexpected mutation type %T. expect *db.OAuth2ClientMutation", m) } // The OfflineSessionFunc type is an adapter to allow the use of ordinary // function as OfflineSession mutator. type OfflineSessionFunc func(context.Context, *db.OfflineSessionMutation) (db.Value, error) // Mutate calls f(ctx, m). func (f OfflineSessionFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { if mv, ok := m.(*db.OfflineSessionMutation); ok { return f(ctx, mv) } return nil, fmt.Errorf("unexpected mutation type %T. expect *db.OfflineSessionMutation", m) } // The PasswordFunc type is an adapter to allow the use of ordinary // function as Password mutator. type PasswordFunc func(context.Context, *db.PasswordMutation) (db.Value, error) // Mutate calls f(ctx, m). func (f PasswordFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { if mv, ok := m.(*db.PasswordMutation); ok { return f(ctx, mv) } return nil, fmt.Errorf("unexpected mutation type %T. expect *db.PasswordMutation", m) } // The RefreshTokenFunc type is an adapter to allow the use of ordinary // function as RefreshToken mutator. type RefreshTokenFunc func(context.Context, *db.RefreshTokenMutation) (db.Value, error) // Mutate calls f(ctx, m). func (f RefreshTokenFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { if mv, ok := m.(*db.RefreshTokenMutation); ok { return f(ctx, mv) } return nil, fmt.Errorf("unexpected mutation type %T. expect *db.RefreshTokenMutation", m) } // The UserIdentityFunc type is an adapter to allow the use of ordinary // function as UserIdentity mutator. type UserIdentityFunc func(context.Context, *db.UserIdentityMutation) (db.Value, error) // Mutate calls f(ctx, m). func (f UserIdentityFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { if mv, ok := m.(*db.UserIdentityMutation); ok { return f(ctx, mv) } return nil, fmt.Errorf("unexpected mutation type %T. expect *db.UserIdentityMutation", m) } // Condition is a hook condition function. type Condition func(context.Context, db.Mutation) bool // And groups conditions with the AND operator. func And(first, second Condition, rest ...Condition) Condition { return func(ctx context.Context, m db.Mutation) bool { if !first(ctx, m) || !second(ctx, m) { return false } for _, cond := range rest { if !cond(ctx, m) { return false } } return true } } // Or groups conditions with the OR operator. func Or(first, second Condition, rest ...Condition) Condition { return func(ctx context.Context, m db.Mutation) bool { if first(ctx, m) || second(ctx, m) { return true } for _, cond := range rest { if cond(ctx, m) { return true } } return false } } // Not negates a given condition. func Not(cond Condition) Condition { return func(ctx context.Context, m db.Mutation) bool { return !cond(ctx, m) } } // HasOp is a condition testing mutation operation. func HasOp(op db.Op) Condition { return func(_ context.Context, m db.Mutation) bool { return m.Op().Is(op) } } // HasAddedFields is a condition validating `.AddedField` on fields. func HasAddedFields(field string, fields ...string) Condition { return func(_ context.Context, m db.Mutation) bool { if _, exists := m.AddedField(field); !exists { return false } for _, field := range fields { if _, exists := m.AddedField(field); !exists { return false } } return true } } // HasClearedFields is a condition validating `.FieldCleared` on fields. func HasClearedFields(field string, fields ...string) Condition { return func(_ context.Context, m db.Mutation) bool { if exists := m.FieldCleared(field); !exists { return false } for _, field := range fields { if exists := m.FieldCleared(field); !exists { return false } } return true } } // HasFields is a condition validating `.Field` on fields. func HasFields(field string, fields ...string) Condition { return func(_ context.Context, m db.Mutation) bool { if _, exists := m.Field(field); !exists { return false } for _, field := range fields { if _, exists := m.Field(field); !exists { return false } } return true } } // If executes the given hook under condition. // // hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...))) func If(hk db.Hook, cond Condition) db.Hook { return func(next db.Mutator) db.Mutator { return db.MutateFunc(func(ctx context.Context, m db.Mutation) (db.Value, error) { if cond(ctx, m) { return hk(next).Mutate(ctx, m) } return next.Mutate(ctx, m) }) } } // On executes the given hook only for the given operation. // // hook.On(Log, db.Delete|db.Create) func On(hk db.Hook, op db.Op) db.Hook { return If(hk, HasOp(op)) } // Unless skips the given hook only for the given operation. // // hook.Unless(Log, db.Update|db.UpdateOne) func Unless(hk db.Hook, op db.Op) db.Hook { return If(hk, Not(HasOp(op))) } // FixedError is a hook returning a fixed error. func FixedError(err error) db.Hook { return func(db.Mutator) db.Mutator { return db.MutateFunc(func(context.Context, db.Mutation) (db.Value, error) { return nil, err }) } } // Reject returns a hook that rejects all operations that match op. // // func (T) Hooks() []db.Hook { // return []db.Hook{ // Reject(db.Delete|db.Update), // } // } func Reject(op db.Op) db.Hook { hk := FixedError(fmt.Errorf("%s operation is not allowed", op)) return On(hk, op) } // Chain acts as a list of hooks and is effectively immutable. // Once created, it will always hold the same set of hooks in the same order. type Chain struct { hooks []db.Hook } // NewChain creates a new chain of hooks. func NewChain(hooks ...db.Hook) Chain { return Chain{append([]db.Hook(nil), hooks...)} } // Hook chains the list of hooks and returns the final hook. func (c Chain) Hook() db.Hook { return func(mutator db.Mutator) db.Mutator { for i := len(c.hooks) - 1; i >= 0; i-- { mutator = c.hooks[i](mutator) } return mutator } } // Append extends a chain, adding the specified hook // as the last ones in the mutation flow. func (c Chain) Append(hooks ...db.Hook) Chain { newHooks := make([]db.Hook, 0, len(c.hooks)+len(hooks)) newHooks = append(newHooks, c.hooks...) newHooks = append(newHooks, hooks...) return Chain{newHooks} } // Extend extends a chain, adding the specified chain // as the last ones in the mutation flow. func (c Chain) Extend(chain Chain) Chain { return c.Append(chain.hooks...) } ================================================ FILE: storage/ent/db/keys/keys.go ================================================ // Code generated by ent, DO NOT EDIT. package keys import ( "entgo.io/ent/dialect/sql" ) const ( // Label holds the string label denoting the keys type in the database. Label = "keys" // FieldID holds the string denoting the id field in the database. FieldID = "id" // FieldVerificationKeys holds the string denoting the verification_keys field in the database. FieldVerificationKeys = "verification_keys" // FieldSigningKey holds the string denoting the signing_key field in the database. FieldSigningKey = "signing_key" // FieldSigningKeyPub holds the string denoting the signing_key_pub field in the database. FieldSigningKeyPub = "signing_key_pub" // FieldNextRotation holds the string denoting the next_rotation field in the database. FieldNextRotation = "next_rotation" // Table holds the table name of the keys in the database. Table = "keys" ) // Columns holds all SQL columns for keys fields. var Columns = []string{ FieldID, FieldVerificationKeys, FieldSigningKey, FieldSigningKeyPub, FieldNextRotation, } // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { if column == Columns[i] { return true } } return false } var ( // IDValidator is a validator for the "id" field. It is called by the builders before save. IDValidator func(string) error ) // OrderOption defines the ordering options for the Keys queries. type OrderOption func(*sql.Selector) // ByID orders the results by the id field. func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } // ByNextRotation orders the results by the next_rotation field. func ByNextRotation(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldNextRotation, opts...).ToFunc() } ================================================ FILE: storage/ent/db/keys/where.go ================================================ // Code generated by ent, DO NOT EDIT. package keys import ( "time" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ID filters vertices based on their ID field. func ID(id string) predicate.Keys { return predicate.Keys(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. func IDEQ(id string) predicate.Keys { return predicate.Keys(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. func IDNEQ(id string) predicate.Keys { return predicate.Keys(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. func IDIn(ids ...string) predicate.Keys { return predicate.Keys(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. func IDNotIn(ids ...string) predicate.Keys { return predicate.Keys(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. func IDGT(id string) predicate.Keys { return predicate.Keys(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. func IDGTE(id string) predicate.Keys { return predicate.Keys(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. func IDLT(id string) predicate.Keys { return predicate.Keys(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. func IDLTE(id string) predicate.Keys { return predicate.Keys(sql.FieldLTE(FieldID, id)) } // IDEqualFold applies the EqualFold predicate on the ID field. func IDEqualFold(id string) predicate.Keys { return predicate.Keys(sql.FieldEqualFold(FieldID, id)) } // IDContainsFold applies the ContainsFold predicate on the ID field. func IDContainsFold(id string) predicate.Keys { return predicate.Keys(sql.FieldContainsFold(FieldID, id)) } // NextRotation applies equality check predicate on the "next_rotation" field. It's identical to NextRotationEQ. func NextRotation(v time.Time) predicate.Keys { return predicate.Keys(sql.FieldEQ(FieldNextRotation, v)) } // NextRotationEQ applies the EQ predicate on the "next_rotation" field. func NextRotationEQ(v time.Time) predicate.Keys { return predicate.Keys(sql.FieldEQ(FieldNextRotation, v)) } // NextRotationNEQ applies the NEQ predicate on the "next_rotation" field. func NextRotationNEQ(v time.Time) predicate.Keys { return predicate.Keys(sql.FieldNEQ(FieldNextRotation, v)) } // NextRotationIn applies the In predicate on the "next_rotation" field. func NextRotationIn(vs ...time.Time) predicate.Keys { return predicate.Keys(sql.FieldIn(FieldNextRotation, vs...)) } // NextRotationNotIn applies the NotIn predicate on the "next_rotation" field. func NextRotationNotIn(vs ...time.Time) predicate.Keys { return predicate.Keys(sql.FieldNotIn(FieldNextRotation, vs...)) } // NextRotationGT applies the GT predicate on the "next_rotation" field. func NextRotationGT(v time.Time) predicate.Keys { return predicate.Keys(sql.FieldGT(FieldNextRotation, v)) } // NextRotationGTE applies the GTE predicate on the "next_rotation" field. func NextRotationGTE(v time.Time) predicate.Keys { return predicate.Keys(sql.FieldGTE(FieldNextRotation, v)) } // NextRotationLT applies the LT predicate on the "next_rotation" field. func NextRotationLT(v time.Time) predicate.Keys { return predicate.Keys(sql.FieldLT(FieldNextRotation, v)) } // NextRotationLTE applies the LTE predicate on the "next_rotation" field. func NextRotationLTE(v time.Time) predicate.Keys { return predicate.Keys(sql.FieldLTE(FieldNextRotation, v)) } // And groups predicates with the AND operator between them. func And(predicates ...predicate.Keys) predicate.Keys { return predicate.Keys(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.Keys) predicate.Keys { return predicate.Keys(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.Keys) predicate.Keys { return predicate.Keys(sql.NotPredicates(p)) } ================================================ FILE: storage/ent/db/keys.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "encoding/json" "fmt" "strings" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db/keys" jose "github.com/go-jose/go-jose/v4" ) // Keys is the model entity for the Keys schema. type Keys struct { config `json:"-"` // ID of the ent. ID string `json:"id,omitempty"` // VerificationKeys holds the value of the "verification_keys" field. VerificationKeys []storage.VerificationKey `json:"verification_keys,omitempty"` // SigningKey holds the value of the "signing_key" field. SigningKey jose.JSONWebKey `json:"signing_key,omitempty"` // SigningKeyPub holds the value of the "signing_key_pub" field. SigningKeyPub jose.JSONWebKey `json:"signing_key_pub,omitempty"` // NextRotation holds the value of the "next_rotation" field. NextRotation time.Time `json:"next_rotation,omitempty"` selectValues sql.SelectValues } // scanValues returns the types for scanning values from sql.Rows. func (*Keys) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { case keys.FieldVerificationKeys, keys.FieldSigningKey, keys.FieldSigningKeyPub: values[i] = new([]byte) case keys.FieldID: values[i] = new(sql.NullString) case keys.FieldNextRotation: values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } } return values, nil } // assignValues assigns the values that were returned from sql.Rows (after scanning) // to the Keys fields. func (_m *Keys) assignValues(columns []string, values []any) error { if m, n := len(values), len(columns); m < n { return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) } for i := range columns { switch columns[i] { case keys.FieldID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field id", values[i]) } else if value.Valid { _m.ID = value.String } case keys.FieldVerificationKeys: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field verification_keys", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.VerificationKeys); err != nil { return fmt.Errorf("unmarshal field verification_keys: %w", err) } } case keys.FieldSigningKey: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field signing_key", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.SigningKey); err != nil { return fmt.Errorf("unmarshal field signing_key: %w", err) } } case keys.FieldSigningKeyPub: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field signing_key_pub", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.SigningKeyPub); err != nil { return fmt.Errorf("unmarshal field signing_key_pub: %w", err) } } case keys.FieldNextRotation: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field next_rotation", values[i]) } else if value.Valid { _m.NextRotation = value.Time } default: _m.selectValues.Set(columns[i], values[i]) } } return nil } // Value returns the ent.Value that was dynamically selected and assigned to the Keys. // This includes values selected through modifiers, order, etc. func (_m *Keys) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } // Update returns a builder for updating this Keys. // Note that you need to call Keys.Unwrap() before calling this method if this Keys // was returned from a transaction, and the transaction was committed or rolled back. func (_m *Keys) Update() *KeysUpdateOne { return NewKeysClient(_m.config).UpdateOne(_m) } // Unwrap unwraps the Keys entity that was returned from a transaction after it was closed, // so that all future queries will be executed through the driver which created the transaction. func (_m *Keys) Unwrap() *Keys { _tx, ok := _m.config.driver.(*txDriver) if !ok { panic("db: Keys is not a transactional entity") } _m.config.driver = _tx.drv return _m } // String implements the fmt.Stringer. func (_m *Keys) String() string { var builder strings.Builder builder.WriteString("Keys(") builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) builder.WriteString("verification_keys=") builder.WriteString(fmt.Sprintf("%v", _m.VerificationKeys)) builder.WriteString(", ") builder.WriteString("signing_key=") builder.WriteString(fmt.Sprintf("%v", _m.SigningKey)) builder.WriteString(", ") builder.WriteString("signing_key_pub=") builder.WriteString(fmt.Sprintf("%v", _m.SigningKeyPub)) builder.WriteString(", ") builder.WriteString("next_rotation=") builder.WriteString(_m.NextRotation.Format(time.ANSIC)) builder.WriteByte(')') return builder.String() } // KeysSlice is a parsable slice of Keys. type KeysSlice []*Keys ================================================ FILE: storage/ent/db/keys_create.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db/keys" jose "github.com/go-jose/go-jose/v4" ) // KeysCreate is the builder for creating a Keys entity. type KeysCreate struct { config mutation *KeysMutation hooks []Hook } // SetVerificationKeys sets the "verification_keys" field. func (_c *KeysCreate) SetVerificationKeys(v []storage.VerificationKey) *KeysCreate { _c.mutation.SetVerificationKeys(v) return _c } // SetSigningKey sets the "signing_key" field. func (_c *KeysCreate) SetSigningKey(v jose.JSONWebKey) *KeysCreate { _c.mutation.SetSigningKey(v) return _c } // SetSigningKeyPub sets the "signing_key_pub" field. func (_c *KeysCreate) SetSigningKeyPub(v jose.JSONWebKey) *KeysCreate { _c.mutation.SetSigningKeyPub(v) return _c } // SetNextRotation sets the "next_rotation" field. func (_c *KeysCreate) SetNextRotation(v time.Time) *KeysCreate { _c.mutation.SetNextRotation(v) return _c } // SetID sets the "id" field. func (_c *KeysCreate) SetID(v string) *KeysCreate { _c.mutation.SetID(v) return _c } // Mutation returns the KeysMutation object of the builder. func (_c *KeysCreate) Mutation() *KeysMutation { return _c.mutation } // Save creates the Keys in the database. func (_c *KeysCreate) Save(ctx context.Context) (*Keys, error) { return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) } // SaveX calls Save and panics if Save returns an error. func (_c *KeysCreate) SaveX(ctx context.Context) *Keys { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *KeysCreate) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *KeysCreate) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_c *KeysCreate) check() error { if _, ok := _c.mutation.VerificationKeys(); !ok { return &ValidationError{Name: "verification_keys", err: errors.New(`db: missing required field "Keys.verification_keys"`)} } if _, ok := _c.mutation.SigningKey(); !ok { return &ValidationError{Name: "signing_key", err: errors.New(`db: missing required field "Keys.signing_key"`)} } if _, ok := _c.mutation.SigningKeyPub(); !ok { return &ValidationError{Name: "signing_key_pub", err: errors.New(`db: missing required field "Keys.signing_key_pub"`)} } if _, ok := _c.mutation.NextRotation(); !ok { return &ValidationError{Name: "next_rotation", err: errors.New(`db: missing required field "Keys.next_rotation"`)} } if v, ok := _c.mutation.ID(); ok { if err := keys.IDValidator(v); err != nil { return &ValidationError{Name: "id", err: fmt.Errorf(`db: validator failed for field "Keys.id": %w`, err)} } } return nil } func (_c *KeysCreate) sqlSave(ctx context.Context) (*Keys, error) { if err := _c.check(); err != nil { return nil, err } _node, _spec := _c.createSpec() if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } if _spec.ID.Value != nil { if id, ok := _spec.ID.Value.(string); ok { _node.ID = id } else { return nil, fmt.Errorf("unexpected Keys.ID type: %T", _spec.ID.Value) } } _c.mutation.id = &_node.ID _c.mutation.done = true return _node, nil } func (_c *KeysCreate) createSpec() (*Keys, *sqlgraph.CreateSpec) { var ( _node = &Keys{config: _c.config} _spec = sqlgraph.NewCreateSpec(keys.Table, sqlgraph.NewFieldSpec(keys.FieldID, field.TypeString)) ) if id, ok := _c.mutation.ID(); ok { _node.ID = id _spec.ID.Value = id } if value, ok := _c.mutation.VerificationKeys(); ok { _spec.SetField(keys.FieldVerificationKeys, field.TypeJSON, value) _node.VerificationKeys = value } if value, ok := _c.mutation.SigningKey(); ok { _spec.SetField(keys.FieldSigningKey, field.TypeJSON, value) _node.SigningKey = value } if value, ok := _c.mutation.SigningKeyPub(); ok { _spec.SetField(keys.FieldSigningKeyPub, field.TypeJSON, value) _node.SigningKeyPub = value } if value, ok := _c.mutation.NextRotation(); ok { _spec.SetField(keys.FieldNextRotation, field.TypeTime, value) _node.NextRotation = value } return _node, _spec } // KeysCreateBulk is the builder for creating many Keys entities in bulk. type KeysCreateBulk struct { config err error builders []*KeysCreate } // Save creates the Keys entities in the database. func (_c *KeysCreateBulk) Save(ctx context.Context) ([]*Keys, error) { if _c.err != nil { return nil, _c.err } specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) nodes := make([]*Keys, len(_c.builders)) mutators := make([]Mutator, len(_c.builders)) for i := range _c.builders { func(i int, root context.Context) { builder := _c.builders[i] var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*KeysMutation) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } if err := builder.check(); err != nil { return nil, err } builder.mutation = mutation var err error nodes[i], specs[i] = builder.createSpec() if i < len(mutators)-1 { _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) } else { spec := &sqlgraph.BatchCreateSpec{Nodes: specs} // Invoke the actual operation on the latest mutation in the chain. if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } } } if err != nil { return nil, err } mutation.id = &nodes[i].ID mutation.done = true return nodes[i], nil }) for i := len(builder.hooks) - 1; i >= 0; i-- { mut = builder.hooks[i](mut) } mutators[i] = mut }(i, ctx) } if len(mutators) > 0 { if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { return nil, err } } return nodes, nil } // SaveX is like Save, but panics if an error occurs. func (_c *KeysCreateBulk) SaveX(ctx context.Context) []*Keys { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *KeysCreateBulk) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *KeysCreateBulk) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/keys_delete.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/keys" "github.com/dexidp/dex/storage/ent/db/predicate" ) // KeysDelete is the builder for deleting a Keys entity. type KeysDelete struct { config hooks []Hook mutation *KeysMutation } // Where appends a list predicates to the KeysDelete builder. func (_d *KeysDelete) Where(ps ...predicate.Keys) *KeysDelete { _d.mutation.Where(ps...) return _d } // Exec executes the deletion query and returns how many vertices were deleted. func (_d *KeysDelete) Exec(ctx context.Context) (int, error) { return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) } // ExecX is like Exec, but panics if an error occurs. func (_d *KeysDelete) ExecX(ctx context.Context) int { n, err := _d.Exec(ctx) if err != nil { panic(err) } return n } func (_d *KeysDelete) sqlExec(ctx context.Context) (int, error) { _spec := sqlgraph.NewDeleteSpec(keys.Table, sqlgraph.NewFieldSpec(keys.FieldID, field.TypeString)) if ps := _d.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) if err != nil && sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } _d.mutation.done = true return affected, err } // KeysDeleteOne is the builder for deleting a single Keys entity. type KeysDeleteOne struct { _d *KeysDelete } // Where appends a list predicates to the KeysDelete builder. func (_d *KeysDeleteOne) Where(ps ...predicate.Keys) *KeysDeleteOne { _d._d.mutation.Where(ps...) return _d } // Exec executes the deletion query. func (_d *KeysDeleteOne) Exec(ctx context.Context) error { n, err := _d._d.Exec(ctx) switch { case err != nil: return err case n == 0: return &NotFoundError{keys.Label} default: return nil } } // ExecX is like Exec, but panics if an error occurs. func (_d *KeysDeleteOne) ExecX(ctx context.Context) { if err := _d.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/keys_query.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "fmt" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/keys" "github.com/dexidp/dex/storage/ent/db/predicate" ) // KeysQuery is the builder for querying Keys entities. type KeysQuery struct { config ctx *QueryContext order []keys.OrderOption inters []Interceptor predicates []predicate.Keys // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) } // Where adds a new predicate for the KeysQuery builder. func (_q *KeysQuery) Where(ps ...predicate.Keys) *KeysQuery { _q.predicates = append(_q.predicates, ps...) return _q } // Limit the number of records to be returned by this query. func (_q *KeysQuery) Limit(limit int) *KeysQuery { _q.ctx.Limit = &limit return _q } // Offset to start from. func (_q *KeysQuery) Offset(offset int) *KeysQuery { _q.ctx.Offset = &offset return _q } // Unique configures the query builder to filter duplicate records on query. // By default, unique is set to true, and can be disabled using this method. func (_q *KeysQuery) Unique(unique bool) *KeysQuery { _q.ctx.Unique = &unique return _q } // Order specifies how the records should be ordered. func (_q *KeysQuery) Order(o ...keys.OrderOption) *KeysQuery { _q.order = append(_q.order, o...) return _q } // First returns the first Keys entity from the query. // Returns a *NotFoundError when no Keys was found. func (_q *KeysQuery) First(ctx context.Context) (*Keys, error) { nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) if err != nil { return nil, err } if len(nodes) == 0 { return nil, &NotFoundError{keys.Label} } return nodes[0], nil } // FirstX is like First, but panics if an error occurs. func (_q *KeysQuery) FirstX(ctx context.Context) *Keys { node, err := _q.First(ctx) if err != nil && !IsNotFound(err) { panic(err) } return node } // FirstID returns the first Keys ID from the query. // Returns a *NotFoundError when no Keys ID was found. func (_q *KeysQuery) FirstID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { return } if len(ids) == 0 { err = &NotFoundError{keys.Label} return } return ids[0], nil } // FirstIDX is like FirstID, but panics if an error occurs. func (_q *KeysQuery) FirstIDX(ctx context.Context) string { id, err := _q.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) } return id } // Only returns a single Keys entity found by the query, ensuring it only returns one. // Returns a *NotSingularError when more than one Keys entity is found. // Returns a *NotFoundError when no Keys entities are found. func (_q *KeysQuery) Only(ctx context.Context) (*Keys, error) { nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) if err != nil { return nil, err } switch len(nodes) { case 1: return nodes[0], nil case 0: return nil, &NotFoundError{keys.Label} default: return nil, &NotSingularError{keys.Label} } } // OnlyX is like Only, but panics if an error occurs. func (_q *KeysQuery) OnlyX(ctx context.Context) *Keys { node, err := _q.Only(ctx) if err != nil { panic(err) } return node } // OnlyID is like Only, but returns the only Keys ID in the query. // Returns a *NotSingularError when more than one Keys ID is found. // Returns a *NotFoundError when no entities are found. func (_q *KeysQuery) OnlyID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { return } switch len(ids) { case 1: id = ids[0] case 0: err = &NotFoundError{keys.Label} default: err = &NotSingularError{keys.Label} } return } // OnlyIDX is like OnlyID, but panics if an error occurs. func (_q *KeysQuery) OnlyIDX(ctx context.Context) string { id, err := _q.OnlyID(ctx) if err != nil { panic(err) } return id } // All executes the query and returns a list of KeysSlice. func (_q *KeysQuery) All(ctx context.Context) ([]*Keys, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) if err := _q.prepareQuery(ctx); err != nil { return nil, err } qr := querierAll[[]*Keys, *KeysQuery]() return withInterceptors[[]*Keys](ctx, _q, qr, _q.inters) } // AllX is like All, but panics if an error occurs. func (_q *KeysQuery) AllX(ctx context.Context) []*Keys { nodes, err := _q.All(ctx) if err != nil { panic(err) } return nodes } // IDs executes the query and returns a list of Keys IDs. func (_q *KeysQuery) IDs(ctx context.Context) (ids []string, err error) { if _q.ctx.Unique == nil && _q.path != nil { _q.Unique(true) } ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) if err = _q.Select(keys.FieldID).Scan(ctx, &ids); err != nil { return nil, err } return ids, nil } // IDsX is like IDs, but panics if an error occurs. func (_q *KeysQuery) IDsX(ctx context.Context) []string { ids, err := _q.IDs(ctx) if err != nil { panic(err) } return ids } // Count returns the count of the given query. func (_q *KeysQuery) Count(ctx context.Context) (int, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) if err := _q.prepareQuery(ctx); err != nil { return 0, err } return withInterceptors[int](ctx, _q, querierCount[*KeysQuery](), _q.inters) } // CountX is like Count, but panics if an error occurs. func (_q *KeysQuery) CountX(ctx context.Context) int { count, err := _q.Count(ctx) if err != nil { panic(err) } return count } // Exist returns true if the query has elements in the graph. func (_q *KeysQuery) Exist(ctx context.Context) (bool, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) switch _, err := _q.FirstID(ctx); { case IsNotFound(err): return false, nil case err != nil: return false, fmt.Errorf("db: check existence: %w", err) default: return true, nil } } // ExistX is like Exist, but panics if an error occurs. func (_q *KeysQuery) ExistX(ctx context.Context) bool { exist, err := _q.Exist(ctx) if err != nil { panic(err) } return exist } // Clone returns a duplicate of the KeysQuery builder, including all associated steps. It can be // used to prepare common query builders and use them differently after the clone is made. func (_q *KeysQuery) Clone() *KeysQuery { if _q == nil { return nil } return &KeysQuery{ config: _q.config, ctx: _q.ctx.Clone(), order: append([]keys.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.Keys{}, _q.predicates...), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // // Example: // // var v []struct { // VerificationKeys []storage.VerificationKey `json:"verification_keys,omitempty"` // Count int `json:"count,omitempty"` // } // // client.Keys.Query(). // GroupBy(keys.FieldVerificationKeys). // Aggregate(db.Count()). // Scan(ctx, &v) func (_q *KeysQuery) GroupBy(field string, fields ...string) *KeysGroupBy { _q.ctx.Fields = append([]string{field}, fields...) grbuild := &KeysGroupBy{build: _q} grbuild.flds = &_q.ctx.Fields grbuild.label = keys.Label grbuild.scan = grbuild.Scan return grbuild } // Select allows the selection one or more fields/columns for the given query, // instead of selecting all fields in the entity. // // Example: // // var v []struct { // VerificationKeys []storage.VerificationKey `json:"verification_keys,omitempty"` // } // // client.Keys.Query(). // Select(keys.FieldVerificationKeys). // Scan(ctx, &v) func (_q *KeysQuery) Select(fields ...string) *KeysSelect { _q.ctx.Fields = append(_q.ctx.Fields, fields...) sbuild := &KeysSelect{KeysQuery: _q} sbuild.label = keys.Label sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan return sbuild } // Aggregate returns a KeysSelect configured with the given aggregations. func (_q *KeysQuery) Aggregate(fns ...AggregateFunc) *KeysSelect { return _q.Select().Aggregate(fns...) } func (_q *KeysQuery) prepareQuery(ctx context.Context) error { for _, inter := range _q.inters { if inter == nil { return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") } if trv, ok := inter.(Traverser); ok { if err := trv.Traverse(ctx, _q); err != nil { return err } } } for _, f := range _q.ctx.Fields { if !keys.ValidColumn(f) { return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } } if _q.path != nil { prev, err := _q.path(ctx) if err != nil { return err } _q.sql = prev } return nil } func (_q *KeysQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Keys, error) { var ( nodes = []*Keys{} _spec = _q.querySpec() ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*Keys).scanValues(nil, columns) } _spec.Assign = func(columns []string, values []any) error { node := &Keys{config: _q.config} nodes = append(nodes, node) return node.assignValues(columns, values) } for i := range hooks { hooks[i](ctx, _spec) } if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { return nil, err } if len(nodes) == 0 { return nodes, nil } return nodes, nil } func (_q *KeysQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields if len(_q.ctx.Fields) > 0 { _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique } return sqlgraph.CountNodes(ctx, _q.driver, _spec) } func (_q *KeysQuery) querySpec() *sqlgraph.QuerySpec { _spec := sqlgraph.NewQuerySpec(keys.Table, keys.Columns, sqlgraph.NewFieldSpec(keys.FieldID, field.TypeString)) _spec.From = _q.sql if unique := _q.ctx.Unique; unique != nil { _spec.Unique = *unique } else if _q.path != nil { _spec.Unique = true } if fields := _q.ctx.Fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, keys.FieldID) for i := range fields { if fields[i] != keys.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } } if ps := _q.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if limit := _q.ctx.Limit; limit != nil { _spec.Limit = *limit } if offset := _q.ctx.Offset; offset != nil { _spec.Offset = *offset } if ps := _q.order; len(ps) > 0 { _spec.Order = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } return _spec } func (_q *KeysQuery) sqlQuery(ctx context.Context) *sql.Selector { builder := sql.Dialect(_q.driver.Dialect()) t1 := builder.Table(keys.Table) columns := _q.ctx.Fields if len(columns) == 0 { columns = keys.Columns } selector := builder.Select(t1.Columns(columns...)...).From(t1) if _q.sql != nil { selector = _q.sql selector.Select(selector.Columns(columns...)...) } if _q.ctx.Unique != nil && *_q.ctx.Unique { selector.Distinct() } for _, p := range _q.predicates { p(selector) } for _, p := range _q.order { p(selector) } if offset := _q.ctx.Offset; offset != nil { // limit is mandatory for offset clause. We start // with default value, and override it below if needed. selector.Offset(*offset).Limit(math.MaxInt32) } if limit := _q.ctx.Limit; limit != nil { selector.Limit(*limit) } return selector } // KeysGroupBy is the group-by builder for Keys entities. type KeysGroupBy struct { selector build *KeysQuery } // Aggregate adds the given aggregation functions to the group-by query. func (_g *KeysGroupBy) Aggregate(fns ...AggregateFunc) *KeysGroupBy { _g.fns = append(_g.fns, fns...) return _g } // Scan applies the selector query and scans the result into the given value. func (_g *KeysGroupBy) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) if err := _g.build.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*KeysQuery, *KeysGroupBy](ctx, _g.build, _g, _g.build.inters, v) } func (_g *KeysGroupBy) sqlScan(ctx context.Context, root *KeysQuery, v any) error { selector := root.sqlQuery(ctx).Select() aggregation := make([]string, 0, len(_g.fns)) for _, fn := range _g.fns { aggregation = append(aggregation, fn(selector)) } if len(selector.SelectedColumns()) == 0 { columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) for _, f := range *_g.flds { columns = append(columns, selector.C(f)) } columns = append(columns, aggregation...) selector.Select(columns...) } selector.GroupBy(selector.Columns(*_g.flds...)...) if err := selector.Err(); err != nil { return err } rows := &sql.Rows{} query, args := selector.Query() if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } // KeysSelect is the builder for selecting fields of Keys entities. type KeysSelect struct { *KeysQuery selector } // Aggregate adds the given aggregation functions to the selector query. func (_s *KeysSelect) Aggregate(fns ...AggregateFunc) *KeysSelect { _s.fns = append(_s.fns, fns...) return _s } // Scan applies the selector query and scans the result into the given value. func (_s *KeysSelect) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) if err := _s.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*KeysQuery, *KeysSelect](ctx, _s.KeysQuery, _s, _s.inters, v) } func (_s *KeysSelect) sqlScan(ctx context.Context, root *KeysQuery, v any) error { selector := root.sqlQuery(ctx) aggregation := make([]string, 0, len(_s.fns)) for _, fn := range _s.fns { aggregation = append(aggregation, fn(selector)) } switch n := len(*_s.selector.flds); { case n == 0 && len(aggregation) > 0: selector.Select(aggregation...) case n != 0 && len(aggregation) > 0: selector.AppendSelect(aggregation...) } rows := &sql.Rows{} query, args := selector.Query() if err := _s.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } ================================================ FILE: storage/ent/db/keys_update.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db/keys" "github.com/dexidp/dex/storage/ent/db/predicate" jose "github.com/go-jose/go-jose/v4" ) // KeysUpdate is the builder for updating Keys entities. type KeysUpdate struct { config hooks []Hook mutation *KeysMutation } // Where appends a list predicates to the KeysUpdate builder. func (_u *KeysUpdate) Where(ps ...predicate.Keys) *KeysUpdate { _u.mutation.Where(ps...) return _u } // SetVerificationKeys sets the "verification_keys" field. func (_u *KeysUpdate) SetVerificationKeys(v []storage.VerificationKey) *KeysUpdate { _u.mutation.SetVerificationKeys(v) return _u } // AppendVerificationKeys appends value to the "verification_keys" field. func (_u *KeysUpdate) AppendVerificationKeys(v []storage.VerificationKey) *KeysUpdate { _u.mutation.AppendVerificationKeys(v) return _u } // SetSigningKey sets the "signing_key" field. func (_u *KeysUpdate) SetSigningKey(v jose.JSONWebKey) *KeysUpdate { _u.mutation.SetSigningKey(v) return _u } // SetNillableSigningKey sets the "signing_key" field if the given value is not nil. func (_u *KeysUpdate) SetNillableSigningKey(v *jose.JSONWebKey) *KeysUpdate { if v != nil { _u.SetSigningKey(*v) } return _u } // SetSigningKeyPub sets the "signing_key_pub" field. func (_u *KeysUpdate) SetSigningKeyPub(v jose.JSONWebKey) *KeysUpdate { _u.mutation.SetSigningKeyPub(v) return _u } // SetNillableSigningKeyPub sets the "signing_key_pub" field if the given value is not nil. func (_u *KeysUpdate) SetNillableSigningKeyPub(v *jose.JSONWebKey) *KeysUpdate { if v != nil { _u.SetSigningKeyPub(*v) } return _u } // SetNextRotation sets the "next_rotation" field. func (_u *KeysUpdate) SetNextRotation(v time.Time) *KeysUpdate { _u.mutation.SetNextRotation(v) return _u } // SetNillableNextRotation sets the "next_rotation" field if the given value is not nil. func (_u *KeysUpdate) SetNillableNextRotation(v *time.Time) *KeysUpdate { if v != nil { _u.SetNextRotation(*v) } return _u } // Mutation returns the KeysMutation object of the builder. func (_u *KeysUpdate) Mutation() *KeysMutation { return _u.mutation } // Save executes the query and returns the number of nodes affected by the update operation. func (_u *KeysUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *KeysUpdate) SaveX(ctx context.Context) int { affected, err := _u.Save(ctx) if err != nil { panic(err) } return affected } // Exec executes the query. func (_u *KeysUpdate) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *KeysUpdate) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } func (_u *KeysUpdate) sqlSave(ctx context.Context) (_node int, err error) { _spec := sqlgraph.NewUpdateSpec(keys.Table, keys.Columns, sqlgraph.NewFieldSpec(keys.FieldID, field.TypeString)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.VerificationKeys(); ok { _spec.SetField(keys.FieldVerificationKeys, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedVerificationKeys(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, keys.FieldVerificationKeys, value) }) } if value, ok := _u.mutation.SigningKey(); ok { _spec.SetField(keys.FieldSigningKey, field.TypeJSON, value) } if value, ok := _u.mutation.SigningKeyPub(); ok { _spec.SetField(keys.FieldSigningKeyPub, field.TypeJSON, value) } if value, ok := _u.mutation.NextRotation(); ok { _spec.SetField(keys.FieldNextRotation, field.TypeTime, value) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{keys.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return 0, err } _u.mutation.done = true return _node, nil } // KeysUpdateOne is the builder for updating a single Keys entity. type KeysUpdateOne struct { config fields []string hooks []Hook mutation *KeysMutation } // SetVerificationKeys sets the "verification_keys" field. func (_u *KeysUpdateOne) SetVerificationKeys(v []storage.VerificationKey) *KeysUpdateOne { _u.mutation.SetVerificationKeys(v) return _u } // AppendVerificationKeys appends value to the "verification_keys" field. func (_u *KeysUpdateOne) AppendVerificationKeys(v []storage.VerificationKey) *KeysUpdateOne { _u.mutation.AppendVerificationKeys(v) return _u } // SetSigningKey sets the "signing_key" field. func (_u *KeysUpdateOne) SetSigningKey(v jose.JSONWebKey) *KeysUpdateOne { _u.mutation.SetSigningKey(v) return _u } // SetNillableSigningKey sets the "signing_key" field if the given value is not nil. func (_u *KeysUpdateOne) SetNillableSigningKey(v *jose.JSONWebKey) *KeysUpdateOne { if v != nil { _u.SetSigningKey(*v) } return _u } // SetSigningKeyPub sets the "signing_key_pub" field. func (_u *KeysUpdateOne) SetSigningKeyPub(v jose.JSONWebKey) *KeysUpdateOne { _u.mutation.SetSigningKeyPub(v) return _u } // SetNillableSigningKeyPub sets the "signing_key_pub" field if the given value is not nil. func (_u *KeysUpdateOne) SetNillableSigningKeyPub(v *jose.JSONWebKey) *KeysUpdateOne { if v != nil { _u.SetSigningKeyPub(*v) } return _u } // SetNextRotation sets the "next_rotation" field. func (_u *KeysUpdateOne) SetNextRotation(v time.Time) *KeysUpdateOne { _u.mutation.SetNextRotation(v) return _u } // SetNillableNextRotation sets the "next_rotation" field if the given value is not nil. func (_u *KeysUpdateOne) SetNillableNextRotation(v *time.Time) *KeysUpdateOne { if v != nil { _u.SetNextRotation(*v) } return _u } // Mutation returns the KeysMutation object of the builder. func (_u *KeysUpdateOne) Mutation() *KeysMutation { return _u.mutation } // Where appends a list predicates to the KeysUpdate builder. func (_u *KeysUpdateOne) Where(ps ...predicate.Keys) *KeysUpdateOne { _u.mutation.Where(ps...) return _u } // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (_u *KeysUpdateOne) Select(field string, fields ...string) *KeysUpdateOne { _u.fields = append([]string{field}, fields...) return _u } // Save executes the query and returns the updated Keys entity. func (_u *KeysUpdateOne) Save(ctx context.Context) (*Keys, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *KeysUpdateOne) SaveX(ctx context.Context) *Keys { node, err := _u.Save(ctx) if err != nil { panic(err) } return node } // Exec executes the query on the entity. func (_u *KeysUpdateOne) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *KeysUpdateOne) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } func (_u *KeysUpdateOne) sqlSave(ctx context.Context) (_node *Keys, err error) { _spec := sqlgraph.NewUpdateSpec(keys.Table, keys.Columns, sqlgraph.NewFieldSpec(keys.FieldID, field.TypeString)) id, ok := _u.mutation.ID() if !ok { return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "Keys.id" for update`)} } _spec.Node.ID.Value = id if fields := _u.fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, keys.FieldID) for _, f := range fields { if !keys.ValidColumn(f) { return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } if f != keys.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, f) } } } if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.VerificationKeys(); ok { _spec.SetField(keys.FieldVerificationKeys, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedVerificationKeys(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, keys.FieldVerificationKeys, value) }) } if value, ok := _u.mutation.SigningKey(); ok { _spec.SetField(keys.FieldSigningKey, field.TypeJSON, value) } if value, ok := _u.mutation.SigningKeyPub(); ok { _spec.SetField(keys.FieldSigningKeyPub, field.TypeJSON, value) } if value, ok := _u.mutation.NextRotation(); ok { _spec.SetField(keys.FieldNextRotation, field.TypeTime, value) } _node = &Keys{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{keys.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } _u.mutation.done = true return _node, nil } ================================================ FILE: storage/ent/db/migrate/migrate.go ================================================ // Code generated by ent, DO NOT EDIT. package migrate import ( "context" "fmt" "io" "entgo.io/ent/dialect" "entgo.io/ent/dialect/sql/schema" ) var ( // WithGlobalUniqueID sets the universal ids options to the migration. // If this option is enabled, ent migration will allocate a 1<<32 range // for the ids of each entity (table). // Note that this option cannot be applied on tables that already exist. WithGlobalUniqueID = schema.WithGlobalUniqueID // WithDropColumn sets the drop column option to the migration. // If this option is enabled, ent migration will drop old columns // that were used for both fields and edges. This defaults to false. WithDropColumn = schema.WithDropColumn // WithDropIndex sets the drop index option to the migration. // If this option is enabled, ent migration will drop old indexes // that were defined in the schema. This defaults to false. // Note that unique constraints are defined using `UNIQUE INDEX`, // and therefore, it's recommended to enable this option to get more // flexibility in the schema changes. WithDropIndex = schema.WithDropIndex // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. WithForeignKeys = schema.WithForeignKeys ) // Schema is the API for creating, migrating and dropping a schema. type Schema struct { drv dialect.Driver } // NewSchema creates a new schema client. func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } // Create creates all schema resources. func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { return Create(ctx, s, Tables, opts...) } // Create creates all table resources using the given schema driver. func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error { migrate, err := schema.NewMigrate(s.drv, opts...) if err != nil { return fmt.Errorf("ent/migrate: %w", err) } return migrate.Create(ctx, tables...) } // WriteTo writes the schema changes to w instead of running them against the database. // // if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { // log.Fatal(err) // } func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...) } ================================================ FILE: storage/ent/db/migrate/schema.go ================================================ // Code generated by ent, DO NOT EDIT. package migrate import ( "entgo.io/ent/dialect/sql/schema" "entgo.io/ent/schema/field" ) var ( // AuthCodesColumns holds the columns for the "auth_codes" table. AuthCodesColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "client_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "scopes", Type: field.TypeJSON, Nullable: true}, {Name: "nonce", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "redirect_uri", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_user_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_username", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_email", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_email_verified", Type: field.TypeBool}, {Name: "claims_groups", Type: field.TypeJSON, Nullable: true}, {Name: "claims_preferred_username", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "connector_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "connector_data", Type: field.TypeBytes, Nullable: true}, {Name: "expiry", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, {Name: "code_challenge", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "code_challenge_method", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "auth_time", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, } // AuthCodesTable holds the schema information for the "auth_codes" table. AuthCodesTable = &schema.Table{ Name: "auth_codes", Columns: AuthCodesColumns, PrimaryKey: []*schema.Column{AuthCodesColumns[0]}, } // AuthRequestsColumns holds the columns for the "auth_requests" table. AuthRequestsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "client_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "scopes", Type: field.TypeJSON, Nullable: true}, {Name: "response_types", Type: field.TypeJSON, Nullable: true}, {Name: "redirect_uri", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "nonce", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "state", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "force_approval_prompt", Type: field.TypeBool}, {Name: "logged_in", Type: field.TypeBool}, {Name: "claims_user_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_username", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_email", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_email_verified", Type: field.TypeBool}, {Name: "claims_groups", Type: field.TypeJSON, Nullable: true}, {Name: "claims_preferred_username", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "connector_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "connector_data", Type: field.TypeBytes, Nullable: true}, {Name: "expiry", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, {Name: "code_challenge", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "code_challenge_method", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "hmac_key", Type: field.TypeBytes}, {Name: "mfa_validated", Type: field.TypeBool, Default: false}, {Name: "prompt", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "max_age", Type: field.TypeInt, Default: -1}, {Name: "auth_time", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, } // AuthRequestsTable holds the schema information for the "auth_requests" table. AuthRequestsTable = &schema.Table{ Name: "auth_requests", Columns: AuthRequestsColumns, PrimaryKey: []*schema.Column{AuthRequestsColumns[0]}, } // AuthSessionsColumns holds the columns for the "auth_sessions" table. AuthSessionsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "user_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "connector_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "nonce", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "client_states", Type: field.TypeBytes}, {Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, {Name: "last_activity", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, {Name: "ip_address", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "user_agent", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "absolute_expiry", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, {Name: "idle_expiry", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, } // AuthSessionsTable holds the schema information for the "auth_sessions" table. AuthSessionsTable = &schema.Table{ Name: "auth_sessions", Columns: AuthSessionsColumns, PrimaryKey: []*schema.Column{AuthSessionsColumns[0]}, } // ConnectorsColumns holds the columns for the "connectors" table. ConnectorsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true, Size: 100, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "type", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "name", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "resource_version", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "config", Type: field.TypeBytes}, {Name: "grant_types", Type: field.TypeJSON, Nullable: true}, } // ConnectorsTable holds the schema information for the "connectors" table. ConnectorsTable = &schema.Table{ Name: "connectors", Columns: ConnectorsColumns, PrimaryKey: []*schema.Column{ConnectorsColumns[0]}, } // DeviceRequestsColumns holds the columns for the "device_requests" table. DeviceRequestsColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "user_code", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "device_code", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "client_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "client_secret", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "scopes", Type: field.TypeJSON, Nullable: true}, {Name: "expiry", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, } // DeviceRequestsTable holds the schema information for the "device_requests" table. DeviceRequestsTable = &schema.Table{ Name: "device_requests", Columns: DeviceRequestsColumns, PrimaryKey: []*schema.Column{DeviceRequestsColumns[0]}, } // DeviceTokensColumns holds the columns for the "device_tokens" table. DeviceTokensColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "device_code", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "status", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "token", Type: field.TypeBytes, Nullable: true}, {Name: "expiry", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, {Name: "last_request", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, {Name: "poll_interval", Type: field.TypeInt}, {Name: "code_challenge", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "code_challenge_method", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, } // DeviceTokensTable holds the schema information for the "device_tokens" table. DeviceTokensTable = &schema.Table{ Name: "device_tokens", Columns: DeviceTokensColumns, PrimaryKey: []*schema.Column{DeviceTokensColumns[0]}, } // KeysColumns holds the columns for the "keys" table. KeysColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "verification_keys", Type: field.TypeJSON}, {Name: "signing_key", Type: field.TypeJSON}, {Name: "signing_key_pub", Type: field.TypeJSON}, {Name: "next_rotation", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, } // KeysTable holds the schema information for the "keys" table. KeysTable = &schema.Table{ Name: "keys", Columns: KeysColumns, PrimaryKey: []*schema.Column{KeysColumns[0]}, } // Oauth2clientsColumns holds the columns for the "oauth2clients" table. Oauth2clientsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true, Size: 100, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "secret", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "redirect_uris", Type: field.TypeJSON, Nullable: true}, {Name: "trusted_peers", Type: field.TypeJSON, Nullable: true}, {Name: "public", Type: field.TypeBool}, {Name: "name", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "logo_url", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "allowed_connectors", Type: field.TypeJSON, Nullable: true}, {Name: "mfa_chain", Type: field.TypeJSON, Nullable: true}, } // Oauth2clientsTable holds the schema information for the "oauth2clients" table. Oauth2clientsTable = &schema.Table{ Name: "oauth2clients", Columns: Oauth2clientsColumns, PrimaryKey: []*schema.Column{Oauth2clientsColumns[0]}, } // OfflineSessionsColumns holds the columns for the "offline_sessions" table. OfflineSessionsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "user_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "conn_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "refresh", Type: field.TypeBytes}, {Name: "connector_data", Type: field.TypeBytes, Nullable: true}, } // OfflineSessionsTable holds the schema information for the "offline_sessions" table. OfflineSessionsTable = &schema.Table{ Name: "offline_sessions", Columns: OfflineSessionsColumns, PrimaryKey: []*schema.Column{OfflineSessionsColumns[0]}, } // PasswordsColumns holds the columns for the "passwords" table. PasswordsColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "email", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "hash", Type: field.TypeBytes}, {Name: "username", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "name", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "preferred_username", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "email_verified", Type: field.TypeBool, Nullable: true}, {Name: "user_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "groups", Type: field.TypeJSON, Nullable: true}, } // PasswordsTable holds the schema information for the "passwords" table. PasswordsTable = &schema.Table{ Name: "passwords", Columns: PasswordsColumns, PrimaryKey: []*schema.Column{PasswordsColumns[0]}, } // RefreshTokensColumns holds the columns for the "refresh_tokens" table. RefreshTokensColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "client_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "scopes", Type: field.TypeJSON, Nullable: true}, {Name: "nonce", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_user_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_username", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_email", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_email_verified", Type: field.TypeBool}, {Name: "claims_groups", Type: field.TypeJSON, Nullable: true}, {Name: "claims_preferred_username", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "connector_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "connector_data", Type: field.TypeBytes, Nullable: true}, {Name: "token", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "obsolete_token", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, {Name: "last_used", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, } // RefreshTokensTable holds the schema information for the "refresh_tokens" table. RefreshTokensTable = &schema.Table{ Name: "refresh_tokens", Columns: RefreshTokensColumns, PrimaryKey: []*schema.Column{RefreshTokensColumns[0]}, } // UserIdentitiesColumns holds the columns for the "user_identities" table. UserIdentitiesColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "user_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "connector_id", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_user_id", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_username", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_preferred_username", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_email", Type: field.TypeString, Size: 2147483647, Default: "", SchemaType: map[string]string{"mysql": "varchar(384)", "postgres": "text", "sqlite3": "text"}}, {Name: "claims_email_verified", Type: field.TypeBool, Default: false}, {Name: "claims_groups", Type: field.TypeJSON, Nullable: true}, {Name: "consents", Type: field.TypeBytes}, {Name: "mfa_secrets", Type: field.TypeBytes, Nullable: true}, {Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, {Name: "last_login", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, {Name: "blocked_until", Type: field.TypeTime, SchemaType: map[string]string{"mysql": "datetime(3)", "postgres": "timestamptz", "sqlite3": "timestamp"}}, } // UserIdentitiesTable holds the schema information for the "user_identities" table. UserIdentitiesTable = &schema.Table{ Name: "user_identities", Columns: UserIdentitiesColumns, PrimaryKey: []*schema.Column{UserIdentitiesColumns[0]}, } // Tables holds all the tables in the schema. Tables = []*schema.Table{ AuthCodesTable, AuthRequestsTable, AuthSessionsTable, ConnectorsTable, DeviceRequestsTable, DeviceTokensTable, KeysTable, Oauth2clientsTable, OfflineSessionsTable, PasswordsTable, RefreshTokensTable, UserIdentitiesTable, } ) func init() { } ================================================ FILE: storage/ent/db/mutation.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "sync" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/db/authcode" "github.com/dexidp/dex/storage/ent/db/authrequest" "github.com/dexidp/dex/storage/ent/db/authsession" "github.com/dexidp/dex/storage/ent/db/connector" "github.com/dexidp/dex/storage/ent/db/devicerequest" "github.com/dexidp/dex/storage/ent/db/devicetoken" "github.com/dexidp/dex/storage/ent/db/keys" "github.com/dexidp/dex/storage/ent/db/oauth2client" "github.com/dexidp/dex/storage/ent/db/offlinesession" "github.com/dexidp/dex/storage/ent/db/password" "github.com/dexidp/dex/storage/ent/db/predicate" "github.com/dexidp/dex/storage/ent/db/refreshtoken" "github.com/dexidp/dex/storage/ent/db/useridentity" jose "github.com/go-jose/go-jose/v4" ) const ( // Operation types. OpCreate = ent.OpCreate OpDelete = ent.OpDelete OpDeleteOne = ent.OpDeleteOne OpUpdate = ent.OpUpdate OpUpdateOne = ent.OpUpdateOne // Node types. TypeAuthCode = "AuthCode" TypeAuthRequest = "AuthRequest" TypeAuthSession = "AuthSession" TypeConnector = "Connector" TypeDeviceRequest = "DeviceRequest" TypeDeviceToken = "DeviceToken" TypeKeys = "Keys" TypeOAuth2Client = "OAuth2Client" TypeOfflineSession = "OfflineSession" TypePassword = "Password" TypeRefreshToken = "RefreshToken" TypeUserIdentity = "UserIdentity" ) // AuthCodeMutation represents an operation that mutates the AuthCode nodes in the graph. type AuthCodeMutation struct { config op Op typ string id *string client_id *string scopes *[]string appendscopes []string nonce *string redirect_uri *string claims_user_id *string claims_username *string claims_email *string claims_email_verified *bool claims_groups *[]string appendclaims_groups []string claims_preferred_username *string connector_id *string connector_data *[]byte expiry *time.Time code_challenge *string code_challenge_method *string auth_time *time.Time clearedFields map[string]struct{} done bool oldValue func(context.Context) (*AuthCode, error) predicates []predicate.AuthCode } var _ ent.Mutation = (*AuthCodeMutation)(nil) // authcodeOption allows management of the mutation configuration using functional options. type authcodeOption func(*AuthCodeMutation) // newAuthCodeMutation creates new mutation for the AuthCode entity. func newAuthCodeMutation(c config, op Op, opts ...authcodeOption) *AuthCodeMutation { m := &AuthCodeMutation{ config: c, op: op, typ: TypeAuthCode, clearedFields: make(map[string]struct{}), } for _, opt := range opts { opt(m) } return m } // withAuthCodeID sets the ID field of the mutation. func withAuthCodeID(id string) authcodeOption { return func(m *AuthCodeMutation) { var ( err error once sync.Once value *AuthCode ) m.oldValue = func(ctx context.Context) (*AuthCode, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { value, err = m.Client().AuthCode.Get(ctx, id) } }) return value, err } m.id = &id } } // withAuthCode sets the old AuthCode of the mutation. func withAuthCode(node *AuthCode) authcodeOption { return func(m *AuthCodeMutation) { m.oldValue = func(context.Context) (*AuthCode, error) { return node, nil } m.id = &node.ID } } // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. func (m AuthCodeMutation) Client() *Client { client := &Client{config: m.config} client.init() return client } // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. func (m AuthCodeMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } tx := &Tx{config: m.config} tx.init() return tx, nil } // SetID sets the value of the id field. Note that this // operation is only accepted on creation of AuthCode entities. func (m *AuthCodeMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. func (m *AuthCodeMutation) ID() (id string, exists bool) { if m.id == nil { return } return *m.id, true } // IDs queries the database and returns the entity ids that match the mutation's predicate. // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. func (m *AuthCodeMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { return []string{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): return m.Client().AuthCode.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetClientID sets the "client_id" field. func (m *AuthCodeMutation) SetClientID(s string) { m.client_id = &s } // ClientID returns the value of the "client_id" field in the mutation. func (m *AuthCodeMutation) ClientID() (r string, exists bool) { v := m.client_id if v == nil { return } return *v, true } // OldClientID returns the old "client_id" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldClientID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClientID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClientID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClientID: %w", err) } return oldValue.ClientID, nil } // ResetClientID resets all changes to the "client_id" field. func (m *AuthCodeMutation) ResetClientID() { m.client_id = nil } // SetScopes sets the "scopes" field. func (m *AuthCodeMutation) SetScopes(s []string) { m.scopes = &s m.appendscopes = nil } // Scopes returns the value of the "scopes" field in the mutation. func (m *AuthCodeMutation) Scopes() (r []string, exists bool) { v := m.scopes if v == nil { return } return *v, true } // OldScopes returns the old "scopes" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldScopes(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldScopes is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldScopes requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldScopes: %w", err) } return oldValue.Scopes, nil } // AppendScopes adds s to the "scopes" field. func (m *AuthCodeMutation) AppendScopes(s []string) { m.appendscopes = append(m.appendscopes, s...) } // AppendedScopes returns the list of values that were appended to the "scopes" field in this mutation. func (m *AuthCodeMutation) AppendedScopes() ([]string, bool) { if len(m.appendscopes) == 0 { return nil, false } return m.appendscopes, true } // ClearScopes clears the value of the "scopes" field. func (m *AuthCodeMutation) ClearScopes() { m.scopes = nil m.appendscopes = nil m.clearedFields[authcode.FieldScopes] = struct{}{} } // ScopesCleared returns if the "scopes" field was cleared in this mutation. func (m *AuthCodeMutation) ScopesCleared() bool { _, ok := m.clearedFields[authcode.FieldScopes] return ok } // ResetScopes resets all changes to the "scopes" field. func (m *AuthCodeMutation) ResetScopes() { m.scopes = nil m.appendscopes = nil delete(m.clearedFields, authcode.FieldScopes) } // SetNonce sets the "nonce" field. func (m *AuthCodeMutation) SetNonce(s string) { m.nonce = &s } // Nonce returns the value of the "nonce" field in the mutation. func (m *AuthCodeMutation) Nonce() (r string, exists bool) { v := m.nonce if v == nil { return } return *v, true } // OldNonce returns the old "nonce" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldNonce(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldNonce is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldNonce requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldNonce: %w", err) } return oldValue.Nonce, nil } // ResetNonce resets all changes to the "nonce" field. func (m *AuthCodeMutation) ResetNonce() { m.nonce = nil } // SetRedirectURI sets the "redirect_uri" field. func (m *AuthCodeMutation) SetRedirectURI(s string) { m.redirect_uri = &s } // RedirectURI returns the value of the "redirect_uri" field in the mutation. func (m *AuthCodeMutation) RedirectURI() (r string, exists bool) { v := m.redirect_uri if v == nil { return } return *v, true } // OldRedirectURI returns the old "redirect_uri" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldRedirectURI(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldRedirectURI is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldRedirectURI requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldRedirectURI: %w", err) } return oldValue.RedirectURI, nil } // ResetRedirectURI resets all changes to the "redirect_uri" field. func (m *AuthCodeMutation) ResetRedirectURI() { m.redirect_uri = nil } // SetClaimsUserID sets the "claims_user_id" field. func (m *AuthCodeMutation) SetClaimsUserID(s string) { m.claims_user_id = &s } // ClaimsUserID returns the value of the "claims_user_id" field in the mutation. func (m *AuthCodeMutation) ClaimsUserID() (r string, exists bool) { v := m.claims_user_id if v == nil { return } return *v, true } // OldClaimsUserID returns the old "claims_user_id" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldClaimsUserID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsUserID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsUserID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsUserID: %w", err) } return oldValue.ClaimsUserID, nil } // ResetClaimsUserID resets all changes to the "claims_user_id" field. func (m *AuthCodeMutation) ResetClaimsUserID() { m.claims_user_id = nil } // SetClaimsUsername sets the "claims_username" field. func (m *AuthCodeMutation) SetClaimsUsername(s string) { m.claims_username = &s } // ClaimsUsername returns the value of the "claims_username" field in the mutation. func (m *AuthCodeMutation) ClaimsUsername() (r string, exists bool) { v := m.claims_username if v == nil { return } return *v, true } // OldClaimsUsername returns the old "claims_username" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldClaimsUsername(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsUsername is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsUsername requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsUsername: %w", err) } return oldValue.ClaimsUsername, nil } // ResetClaimsUsername resets all changes to the "claims_username" field. func (m *AuthCodeMutation) ResetClaimsUsername() { m.claims_username = nil } // SetClaimsEmail sets the "claims_email" field. func (m *AuthCodeMutation) SetClaimsEmail(s string) { m.claims_email = &s } // ClaimsEmail returns the value of the "claims_email" field in the mutation. func (m *AuthCodeMutation) ClaimsEmail() (r string, exists bool) { v := m.claims_email if v == nil { return } return *v, true } // OldClaimsEmail returns the old "claims_email" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldClaimsEmail(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsEmail is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsEmail requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsEmail: %w", err) } return oldValue.ClaimsEmail, nil } // ResetClaimsEmail resets all changes to the "claims_email" field. func (m *AuthCodeMutation) ResetClaimsEmail() { m.claims_email = nil } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (m *AuthCodeMutation) SetClaimsEmailVerified(b bool) { m.claims_email_verified = &b } // ClaimsEmailVerified returns the value of the "claims_email_verified" field in the mutation. func (m *AuthCodeMutation) ClaimsEmailVerified() (r bool, exists bool) { v := m.claims_email_verified if v == nil { return } return *v, true } // OldClaimsEmailVerified returns the old "claims_email_verified" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldClaimsEmailVerified(ctx context.Context) (v bool, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsEmailVerified is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsEmailVerified requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsEmailVerified: %w", err) } return oldValue.ClaimsEmailVerified, nil } // ResetClaimsEmailVerified resets all changes to the "claims_email_verified" field. func (m *AuthCodeMutation) ResetClaimsEmailVerified() { m.claims_email_verified = nil } // SetClaimsGroups sets the "claims_groups" field. func (m *AuthCodeMutation) SetClaimsGroups(s []string) { m.claims_groups = &s m.appendclaims_groups = nil } // ClaimsGroups returns the value of the "claims_groups" field in the mutation. func (m *AuthCodeMutation) ClaimsGroups() (r []string, exists bool) { v := m.claims_groups if v == nil { return } return *v, true } // OldClaimsGroups returns the old "claims_groups" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldClaimsGroups(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsGroups is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsGroups requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsGroups: %w", err) } return oldValue.ClaimsGroups, nil } // AppendClaimsGroups adds s to the "claims_groups" field. func (m *AuthCodeMutation) AppendClaimsGroups(s []string) { m.appendclaims_groups = append(m.appendclaims_groups, s...) } // AppendedClaimsGroups returns the list of values that were appended to the "claims_groups" field in this mutation. func (m *AuthCodeMutation) AppendedClaimsGroups() ([]string, bool) { if len(m.appendclaims_groups) == 0 { return nil, false } return m.appendclaims_groups, true } // ClearClaimsGroups clears the value of the "claims_groups" field. func (m *AuthCodeMutation) ClearClaimsGroups() { m.claims_groups = nil m.appendclaims_groups = nil m.clearedFields[authcode.FieldClaimsGroups] = struct{}{} } // ClaimsGroupsCleared returns if the "claims_groups" field was cleared in this mutation. func (m *AuthCodeMutation) ClaimsGroupsCleared() bool { _, ok := m.clearedFields[authcode.FieldClaimsGroups] return ok } // ResetClaimsGroups resets all changes to the "claims_groups" field. func (m *AuthCodeMutation) ResetClaimsGroups() { m.claims_groups = nil m.appendclaims_groups = nil delete(m.clearedFields, authcode.FieldClaimsGroups) } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (m *AuthCodeMutation) SetClaimsPreferredUsername(s string) { m.claims_preferred_username = &s } // ClaimsPreferredUsername returns the value of the "claims_preferred_username" field in the mutation. func (m *AuthCodeMutation) ClaimsPreferredUsername() (r string, exists bool) { v := m.claims_preferred_username if v == nil { return } return *v, true } // OldClaimsPreferredUsername returns the old "claims_preferred_username" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldClaimsPreferredUsername(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsPreferredUsername is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsPreferredUsername requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsPreferredUsername: %w", err) } return oldValue.ClaimsPreferredUsername, nil } // ResetClaimsPreferredUsername resets all changes to the "claims_preferred_username" field. func (m *AuthCodeMutation) ResetClaimsPreferredUsername() { m.claims_preferred_username = nil } // SetConnectorID sets the "connector_id" field. func (m *AuthCodeMutation) SetConnectorID(s string) { m.connector_id = &s } // ConnectorID returns the value of the "connector_id" field in the mutation. func (m *AuthCodeMutation) ConnectorID() (r string, exists bool) { v := m.connector_id if v == nil { return } return *v, true } // OldConnectorID returns the old "connector_id" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldConnectorID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldConnectorID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldConnectorID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldConnectorID: %w", err) } return oldValue.ConnectorID, nil } // ResetConnectorID resets all changes to the "connector_id" field. func (m *AuthCodeMutation) ResetConnectorID() { m.connector_id = nil } // SetConnectorData sets the "connector_data" field. func (m *AuthCodeMutation) SetConnectorData(b []byte) { m.connector_data = &b } // ConnectorData returns the value of the "connector_data" field in the mutation. func (m *AuthCodeMutation) ConnectorData() (r []byte, exists bool) { v := m.connector_data if v == nil { return } return *v, true } // OldConnectorData returns the old "connector_data" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldConnectorData(ctx context.Context) (v *[]byte, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldConnectorData is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldConnectorData requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldConnectorData: %w", err) } return oldValue.ConnectorData, nil } // ClearConnectorData clears the value of the "connector_data" field. func (m *AuthCodeMutation) ClearConnectorData() { m.connector_data = nil m.clearedFields[authcode.FieldConnectorData] = struct{}{} } // ConnectorDataCleared returns if the "connector_data" field was cleared in this mutation. func (m *AuthCodeMutation) ConnectorDataCleared() bool { _, ok := m.clearedFields[authcode.FieldConnectorData] return ok } // ResetConnectorData resets all changes to the "connector_data" field. func (m *AuthCodeMutation) ResetConnectorData() { m.connector_data = nil delete(m.clearedFields, authcode.FieldConnectorData) } // SetExpiry sets the "expiry" field. func (m *AuthCodeMutation) SetExpiry(t time.Time) { m.expiry = &t } // Expiry returns the value of the "expiry" field in the mutation. func (m *AuthCodeMutation) Expiry() (r time.Time, exists bool) { v := m.expiry if v == nil { return } return *v, true } // OldExpiry returns the old "expiry" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldExpiry(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldExpiry is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldExpiry requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldExpiry: %w", err) } return oldValue.Expiry, nil } // ResetExpiry resets all changes to the "expiry" field. func (m *AuthCodeMutation) ResetExpiry() { m.expiry = nil } // SetCodeChallenge sets the "code_challenge" field. func (m *AuthCodeMutation) SetCodeChallenge(s string) { m.code_challenge = &s } // CodeChallenge returns the value of the "code_challenge" field in the mutation. func (m *AuthCodeMutation) CodeChallenge() (r string, exists bool) { v := m.code_challenge if v == nil { return } return *v, true } // OldCodeChallenge returns the old "code_challenge" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldCodeChallenge(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCodeChallenge is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldCodeChallenge requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldCodeChallenge: %w", err) } return oldValue.CodeChallenge, nil } // ResetCodeChallenge resets all changes to the "code_challenge" field. func (m *AuthCodeMutation) ResetCodeChallenge() { m.code_challenge = nil } // SetCodeChallengeMethod sets the "code_challenge_method" field. func (m *AuthCodeMutation) SetCodeChallengeMethod(s string) { m.code_challenge_method = &s } // CodeChallengeMethod returns the value of the "code_challenge_method" field in the mutation. func (m *AuthCodeMutation) CodeChallengeMethod() (r string, exists bool) { v := m.code_challenge_method if v == nil { return } return *v, true } // OldCodeChallengeMethod returns the old "code_challenge_method" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldCodeChallengeMethod(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCodeChallengeMethod is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldCodeChallengeMethod requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldCodeChallengeMethod: %w", err) } return oldValue.CodeChallengeMethod, nil } // ResetCodeChallengeMethod resets all changes to the "code_challenge_method" field. func (m *AuthCodeMutation) ResetCodeChallengeMethod() { m.code_challenge_method = nil } // SetAuthTime sets the "auth_time" field. func (m *AuthCodeMutation) SetAuthTime(t time.Time) { m.auth_time = &t } // AuthTime returns the value of the "auth_time" field in the mutation. func (m *AuthCodeMutation) AuthTime() (r time.Time, exists bool) { v := m.auth_time if v == nil { return } return *v, true } // OldAuthTime returns the old "auth_time" field's value of the AuthCode entity. // If the AuthCode object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthCodeMutation) OldAuthTime(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldAuthTime is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldAuthTime requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldAuthTime: %w", err) } return oldValue.AuthTime, nil } // ClearAuthTime clears the value of the "auth_time" field. func (m *AuthCodeMutation) ClearAuthTime() { m.auth_time = nil m.clearedFields[authcode.FieldAuthTime] = struct{}{} } // AuthTimeCleared returns if the "auth_time" field was cleared in this mutation. func (m *AuthCodeMutation) AuthTimeCleared() bool { _, ok := m.clearedFields[authcode.FieldAuthTime] return ok } // ResetAuthTime resets all changes to the "auth_time" field. func (m *AuthCodeMutation) ResetAuthTime() { m.auth_time = nil delete(m.clearedFields, authcode.FieldAuthTime) } // Where appends a list predicates to the AuthCodeMutation builder. func (m *AuthCodeMutation) Where(ps ...predicate.AuthCode) { m.predicates = append(m.predicates, ps...) } // WhereP appends storage-level predicates to the AuthCodeMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. func (m *AuthCodeMutation) WhereP(ps ...func(*sql.Selector)) { p := make([]predicate.AuthCode, len(ps)) for i := range ps { p[i] = ps[i] } m.Where(p...) } // Op returns the operation name. func (m *AuthCodeMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. func (m *AuthCodeMutation) SetOp(op Op) { m.op = op } // Type returns the node type of this mutation (AuthCode). func (m *AuthCodeMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *AuthCodeMutation) Fields() []string { fields := make([]string, 0, 16) if m.client_id != nil { fields = append(fields, authcode.FieldClientID) } if m.scopes != nil { fields = append(fields, authcode.FieldScopes) } if m.nonce != nil { fields = append(fields, authcode.FieldNonce) } if m.redirect_uri != nil { fields = append(fields, authcode.FieldRedirectURI) } if m.claims_user_id != nil { fields = append(fields, authcode.FieldClaimsUserID) } if m.claims_username != nil { fields = append(fields, authcode.FieldClaimsUsername) } if m.claims_email != nil { fields = append(fields, authcode.FieldClaimsEmail) } if m.claims_email_verified != nil { fields = append(fields, authcode.FieldClaimsEmailVerified) } if m.claims_groups != nil { fields = append(fields, authcode.FieldClaimsGroups) } if m.claims_preferred_username != nil { fields = append(fields, authcode.FieldClaimsPreferredUsername) } if m.connector_id != nil { fields = append(fields, authcode.FieldConnectorID) } if m.connector_data != nil { fields = append(fields, authcode.FieldConnectorData) } if m.expiry != nil { fields = append(fields, authcode.FieldExpiry) } if m.code_challenge != nil { fields = append(fields, authcode.FieldCodeChallenge) } if m.code_challenge_method != nil { fields = append(fields, authcode.FieldCodeChallengeMethod) } if m.auth_time != nil { fields = append(fields, authcode.FieldAuthTime) } return fields } // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. func (m *AuthCodeMutation) Field(name string) (ent.Value, bool) { switch name { case authcode.FieldClientID: return m.ClientID() case authcode.FieldScopes: return m.Scopes() case authcode.FieldNonce: return m.Nonce() case authcode.FieldRedirectURI: return m.RedirectURI() case authcode.FieldClaimsUserID: return m.ClaimsUserID() case authcode.FieldClaimsUsername: return m.ClaimsUsername() case authcode.FieldClaimsEmail: return m.ClaimsEmail() case authcode.FieldClaimsEmailVerified: return m.ClaimsEmailVerified() case authcode.FieldClaimsGroups: return m.ClaimsGroups() case authcode.FieldClaimsPreferredUsername: return m.ClaimsPreferredUsername() case authcode.FieldConnectorID: return m.ConnectorID() case authcode.FieldConnectorData: return m.ConnectorData() case authcode.FieldExpiry: return m.Expiry() case authcode.FieldCodeChallenge: return m.CodeChallenge() case authcode.FieldCodeChallengeMethod: return m.CodeChallengeMethod() case authcode.FieldAuthTime: return m.AuthTime() } return nil, false } // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. func (m *AuthCodeMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { case authcode.FieldClientID: return m.OldClientID(ctx) case authcode.FieldScopes: return m.OldScopes(ctx) case authcode.FieldNonce: return m.OldNonce(ctx) case authcode.FieldRedirectURI: return m.OldRedirectURI(ctx) case authcode.FieldClaimsUserID: return m.OldClaimsUserID(ctx) case authcode.FieldClaimsUsername: return m.OldClaimsUsername(ctx) case authcode.FieldClaimsEmail: return m.OldClaimsEmail(ctx) case authcode.FieldClaimsEmailVerified: return m.OldClaimsEmailVerified(ctx) case authcode.FieldClaimsGroups: return m.OldClaimsGroups(ctx) case authcode.FieldClaimsPreferredUsername: return m.OldClaimsPreferredUsername(ctx) case authcode.FieldConnectorID: return m.OldConnectorID(ctx) case authcode.FieldConnectorData: return m.OldConnectorData(ctx) case authcode.FieldExpiry: return m.OldExpiry(ctx) case authcode.FieldCodeChallenge: return m.OldCodeChallenge(ctx) case authcode.FieldCodeChallengeMethod: return m.OldCodeChallengeMethod(ctx) case authcode.FieldAuthTime: return m.OldAuthTime(ctx) } return nil, fmt.Errorf("unknown AuthCode field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *AuthCodeMutation) SetField(name string, value ent.Value) error { switch name { case authcode.FieldClientID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClientID(v) return nil case authcode.FieldScopes: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetScopes(v) return nil case authcode.FieldNonce: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetNonce(v) return nil case authcode.FieldRedirectURI: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetRedirectURI(v) return nil case authcode.FieldClaimsUserID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsUserID(v) return nil case authcode.FieldClaimsUsername: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsUsername(v) return nil case authcode.FieldClaimsEmail: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsEmail(v) return nil case authcode.FieldClaimsEmailVerified: v, ok := value.(bool) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsEmailVerified(v) return nil case authcode.FieldClaimsGroups: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsGroups(v) return nil case authcode.FieldClaimsPreferredUsername: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsPreferredUsername(v) return nil case authcode.FieldConnectorID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetConnectorID(v) return nil case authcode.FieldConnectorData: v, ok := value.([]byte) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetConnectorData(v) return nil case authcode.FieldExpiry: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetExpiry(v) return nil case authcode.FieldCodeChallenge: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCodeChallenge(v) return nil case authcode.FieldCodeChallengeMethod: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCodeChallengeMethod(v) return nil case authcode.FieldAuthTime: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetAuthTime(v) return nil } return fmt.Errorf("unknown AuthCode field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *AuthCodeMutation) AddedFields() []string { return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *AuthCodeMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *AuthCodeMutation) AddField(name string, value ent.Value) error { switch name { } return fmt.Errorf("unknown AuthCode numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *AuthCodeMutation) ClearedFields() []string { var fields []string if m.FieldCleared(authcode.FieldScopes) { fields = append(fields, authcode.FieldScopes) } if m.FieldCleared(authcode.FieldClaimsGroups) { fields = append(fields, authcode.FieldClaimsGroups) } if m.FieldCleared(authcode.FieldConnectorData) { fields = append(fields, authcode.FieldConnectorData) } if m.FieldCleared(authcode.FieldAuthTime) { fields = append(fields, authcode.FieldAuthTime) } return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. func (m *AuthCodeMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *AuthCodeMutation) ClearField(name string) error { switch name { case authcode.FieldScopes: m.ClearScopes() return nil case authcode.FieldClaimsGroups: m.ClearClaimsGroups() return nil case authcode.FieldConnectorData: m.ClearConnectorData() return nil case authcode.FieldAuthTime: m.ClearAuthTime() return nil } return fmt.Errorf("unknown AuthCode nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. func (m *AuthCodeMutation) ResetField(name string) error { switch name { case authcode.FieldClientID: m.ResetClientID() return nil case authcode.FieldScopes: m.ResetScopes() return nil case authcode.FieldNonce: m.ResetNonce() return nil case authcode.FieldRedirectURI: m.ResetRedirectURI() return nil case authcode.FieldClaimsUserID: m.ResetClaimsUserID() return nil case authcode.FieldClaimsUsername: m.ResetClaimsUsername() return nil case authcode.FieldClaimsEmail: m.ResetClaimsEmail() return nil case authcode.FieldClaimsEmailVerified: m.ResetClaimsEmailVerified() return nil case authcode.FieldClaimsGroups: m.ResetClaimsGroups() return nil case authcode.FieldClaimsPreferredUsername: m.ResetClaimsPreferredUsername() return nil case authcode.FieldConnectorID: m.ResetConnectorID() return nil case authcode.FieldConnectorData: m.ResetConnectorData() return nil case authcode.FieldExpiry: m.ResetExpiry() return nil case authcode.FieldCodeChallenge: m.ResetCodeChallenge() return nil case authcode.FieldCodeChallengeMethod: m.ResetCodeChallengeMethod() return nil case authcode.FieldAuthTime: m.ResetAuthTime() return nil } return fmt.Errorf("unknown AuthCode field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *AuthCodeMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *AuthCodeMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *AuthCodeMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *AuthCodeMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *AuthCodeMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *AuthCodeMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *AuthCodeMutation) ClearEdge(name string) error { return fmt.Errorf("unknown AuthCode unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *AuthCodeMutation) ResetEdge(name string) error { return fmt.Errorf("unknown AuthCode edge %s", name) } // AuthRequestMutation represents an operation that mutates the AuthRequest nodes in the graph. type AuthRequestMutation struct { config op Op typ string id *string client_id *string scopes *[]string appendscopes []string response_types *[]string appendresponse_types []string redirect_uri *string nonce *string state *string force_approval_prompt *bool logged_in *bool claims_user_id *string claims_username *string claims_email *string claims_email_verified *bool claims_groups *[]string appendclaims_groups []string claims_preferred_username *string connector_id *string connector_data *[]byte expiry *time.Time code_challenge *string code_challenge_method *string hmac_key *[]byte mfa_validated *bool prompt *string max_age *int addmax_age *int auth_time *time.Time clearedFields map[string]struct{} done bool oldValue func(context.Context) (*AuthRequest, error) predicates []predicate.AuthRequest } var _ ent.Mutation = (*AuthRequestMutation)(nil) // authrequestOption allows management of the mutation configuration using functional options. type authrequestOption func(*AuthRequestMutation) // newAuthRequestMutation creates new mutation for the AuthRequest entity. func newAuthRequestMutation(c config, op Op, opts ...authrequestOption) *AuthRequestMutation { m := &AuthRequestMutation{ config: c, op: op, typ: TypeAuthRequest, clearedFields: make(map[string]struct{}), } for _, opt := range opts { opt(m) } return m } // withAuthRequestID sets the ID field of the mutation. func withAuthRequestID(id string) authrequestOption { return func(m *AuthRequestMutation) { var ( err error once sync.Once value *AuthRequest ) m.oldValue = func(ctx context.Context) (*AuthRequest, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { value, err = m.Client().AuthRequest.Get(ctx, id) } }) return value, err } m.id = &id } } // withAuthRequest sets the old AuthRequest of the mutation. func withAuthRequest(node *AuthRequest) authrequestOption { return func(m *AuthRequestMutation) { m.oldValue = func(context.Context) (*AuthRequest, error) { return node, nil } m.id = &node.ID } } // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. func (m AuthRequestMutation) Client() *Client { client := &Client{config: m.config} client.init() return client } // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. func (m AuthRequestMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } tx := &Tx{config: m.config} tx.init() return tx, nil } // SetID sets the value of the id field. Note that this // operation is only accepted on creation of AuthRequest entities. func (m *AuthRequestMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. func (m *AuthRequestMutation) ID() (id string, exists bool) { if m.id == nil { return } return *m.id, true } // IDs queries the database and returns the entity ids that match the mutation's predicate. // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. func (m *AuthRequestMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { return []string{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): return m.Client().AuthRequest.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetClientID sets the "client_id" field. func (m *AuthRequestMutation) SetClientID(s string) { m.client_id = &s } // ClientID returns the value of the "client_id" field in the mutation. func (m *AuthRequestMutation) ClientID() (r string, exists bool) { v := m.client_id if v == nil { return } return *v, true } // OldClientID returns the old "client_id" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldClientID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClientID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClientID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClientID: %w", err) } return oldValue.ClientID, nil } // ResetClientID resets all changes to the "client_id" field. func (m *AuthRequestMutation) ResetClientID() { m.client_id = nil } // SetScopes sets the "scopes" field. func (m *AuthRequestMutation) SetScopes(s []string) { m.scopes = &s m.appendscopes = nil } // Scopes returns the value of the "scopes" field in the mutation. func (m *AuthRequestMutation) Scopes() (r []string, exists bool) { v := m.scopes if v == nil { return } return *v, true } // OldScopes returns the old "scopes" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldScopes(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldScopes is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldScopes requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldScopes: %w", err) } return oldValue.Scopes, nil } // AppendScopes adds s to the "scopes" field. func (m *AuthRequestMutation) AppendScopes(s []string) { m.appendscopes = append(m.appendscopes, s...) } // AppendedScopes returns the list of values that were appended to the "scopes" field in this mutation. func (m *AuthRequestMutation) AppendedScopes() ([]string, bool) { if len(m.appendscopes) == 0 { return nil, false } return m.appendscopes, true } // ClearScopes clears the value of the "scopes" field. func (m *AuthRequestMutation) ClearScopes() { m.scopes = nil m.appendscopes = nil m.clearedFields[authrequest.FieldScopes] = struct{}{} } // ScopesCleared returns if the "scopes" field was cleared in this mutation. func (m *AuthRequestMutation) ScopesCleared() bool { _, ok := m.clearedFields[authrequest.FieldScopes] return ok } // ResetScopes resets all changes to the "scopes" field. func (m *AuthRequestMutation) ResetScopes() { m.scopes = nil m.appendscopes = nil delete(m.clearedFields, authrequest.FieldScopes) } // SetResponseTypes sets the "response_types" field. func (m *AuthRequestMutation) SetResponseTypes(s []string) { m.response_types = &s m.appendresponse_types = nil } // ResponseTypes returns the value of the "response_types" field in the mutation. func (m *AuthRequestMutation) ResponseTypes() (r []string, exists bool) { v := m.response_types if v == nil { return } return *v, true } // OldResponseTypes returns the old "response_types" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldResponseTypes(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldResponseTypes is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldResponseTypes requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldResponseTypes: %w", err) } return oldValue.ResponseTypes, nil } // AppendResponseTypes adds s to the "response_types" field. func (m *AuthRequestMutation) AppendResponseTypes(s []string) { m.appendresponse_types = append(m.appendresponse_types, s...) } // AppendedResponseTypes returns the list of values that were appended to the "response_types" field in this mutation. func (m *AuthRequestMutation) AppendedResponseTypes() ([]string, bool) { if len(m.appendresponse_types) == 0 { return nil, false } return m.appendresponse_types, true } // ClearResponseTypes clears the value of the "response_types" field. func (m *AuthRequestMutation) ClearResponseTypes() { m.response_types = nil m.appendresponse_types = nil m.clearedFields[authrequest.FieldResponseTypes] = struct{}{} } // ResponseTypesCleared returns if the "response_types" field was cleared in this mutation. func (m *AuthRequestMutation) ResponseTypesCleared() bool { _, ok := m.clearedFields[authrequest.FieldResponseTypes] return ok } // ResetResponseTypes resets all changes to the "response_types" field. func (m *AuthRequestMutation) ResetResponseTypes() { m.response_types = nil m.appendresponse_types = nil delete(m.clearedFields, authrequest.FieldResponseTypes) } // SetRedirectURI sets the "redirect_uri" field. func (m *AuthRequestMutation) SetRedirectURI(s string) { m.redirect_uri = &s } // RedirectURI returns the value of the "redirect_uri" field in the mutation. func (m *AuthRequestMutation) RedirectURI() (r string, exists bool) { v := m.redirect_uri if v == nil { return } return *v, true } // OldRedirectURI returns the old "redirect_uri" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldRedirectURI(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldRedirectURI is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldRedirectURI requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldRedirectURI: %w", err) } return oldValue.RedirectURI, nil } // ResetRedirectURI resets all changes to the "redirect_uri" field. func (m *AuthRequestMutation) ResetRedirectURI() { m.redirect_uri = nil } // SetNonce sets the "nonce" field. func (m *AuthRequestMutation) SetNonce(s string) { m.nonce = &s } // Nonce returns the value of the "nonce" field in the mutation. func (m *AuthRequestMutation) Nonce() (r string, exists bool) { v := m.nonce if v == nil { return } return *v, true } // OldNonce returns the old "nonce" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldNonce(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldNonce is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldNonce requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldNonce: %w", err) } return oldValue.Nonce, nil } // ResetNonce resets all changes to the "nonce" field. func (m *AuthRequestMutation) ResetNonce() { m.nonce = nil } // SetState sets the "state" field. func (m *AuthRequestMutation) SetState(s string) { m.state = &s } // State returns the value of the "state" field in the mutation. func (m *AuthRequestMutation) State() (r string, exists bool) { v := m.state if v == nil { return } return *v, true } // OldState returns the old "state" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldState(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldState is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldState requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldState: %w", err) } return oldValue.State, nil } // ResetState resets all changes to the "state" field. func (m *AuthRequestMutation) ResetState() { m.state = nil } // SetForceApprovalPrompt sets the "force_approval_prompt" field. func (m *AuthRequestMutation) SetForceApprovalPrompt(b bool) { m.force_approval_prompt = &b } // ForceApprovalPrompt returns the value of the "force_approval_prompt" field in the mutation. func (m *AuthRequestMutation) ForceApprovalPrompt() (r bool, exists bool) { v := m.force_approval_prompt if v == nil { return } return *v, true } // OldForceApprovalPrompt returns the old "force_approval_prompt" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldForceApprovalPrompt(ctx context.Context) (v bool, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldForceApprovalPrompt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldForceApprovalPrompt requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldForceApprovalPrompt: %w", err) } return oldValue.ForceApprovalPrompt, nil } // ResetForceApprovalPrompt resets all changes to the "force_approval_prompt" field. func (m *AuthRequestMutation) ResetForceApprovalPrompt() { m.force_approval_prompt = nil } // SetLoggedIn sets the "logged_in" field. func (m *AuthRequestMutation) SetLoggedIn(b bool) { m.logged_in = &b } // LoggedIn returns the value of the "logged_in" field in the mutation. func (m *AuthRequestMutation) LoggedIn() (r bool, exists bool) { v := m.logged_in if v == nil { return } return *v, true } // OldLoggedIn returns the old "logged_in" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldLoggedIn(ctx context.Context) (v bool, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLoggedIn is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldLoggedIn requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldLoggedIn: %w", err) } return oldValue.LoggedIn, nil } // ResetLoggedIn resets all changes to the "logged_in" field. func (m *AuthRequestMutation) ResetLoggedIn() { m.logged_in = nil } // SetClaimsUserID sets the "claims_user_id" field. func (m *AuthRequestMutation) SetClaimsUserID(s string) { m.claims_user_id = &s } // ClaimsUserID returns the value of the "claims_user_id" field in the mutation. func (m *AuthRequestMutation) ClaimsUserID() (r string, exists bool) { v := m.claims_user_id if v == nil { return } return *v, true } // OldClaimsUserID returns the old "claims_user_id" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldClaimsUserID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsUserID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsUserID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsUserID: %w", err) } return oldValue.ClaimsUserID, nil } // ResetClaimsUserID resets all changes to the "claims_user_id" field. func (m *AuthRequestMutation) ResetClaimsUserID() { m.claims_user_id = nil } // SetClaimsUsername sets the "claims_username" field. func (m *AuthRequestMutation) SetClaimsUsername(s string) { m.claims_username = &s } // ClaimsUsername returns the value of the "claims_username" field in the mutation. func (m *AuthRequestMutation) ClaimsUsername() (r string, exists bool) { v := m.claims_username if v == nil { return } return *v, true } // OldClaimsUsername returns the old "claims_username" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldClaimsUsername(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsUsername is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsUsername requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsUsername: %w", err) } return oldValue.ClaimsUsername, nil } // ResetClaimsUsername resets all changes to the "claims_username" field. func (m *AuthRequestMutation) ResetClaimsUsername() { m.claims_username = nil } // SetClaimsEmail sets the "claims_email" field. func (m *AuthRequestMutation) SetClaimsEmail(s string) { m.claims_email = &s } // ClaimsEmail returns the value of the "claims_email" field in the mutation. func (m *AuthRequestMutation) ClaimsEmail() (r string, exists bool) { v := m.claims_email if v == nil { return } return *v, true } // OldClaimsEmail returns the old "claims_email" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldClaimsEmail(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsEmail is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsEmail requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsEmail: %w", err) } return oldValue.ClaimsEmail, nil } // ResetClaimsEmail resets all changes to the "claims_email" field. func (m *AuthRequestMutation) ResetClaimsEmail() { m.claims_email = nil } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (m *AuthRequestMutation) SetClaimsEmailVerified(b bool) { m.claims_email_verified = &b } // ClaimsEmailVerified returns the value of the "claims_email_verified" field in the mutation. func (m *AuthRequestMutation) ClaimsEmailVerified() (r bool, exists bool) { v := m.claims_email_verified if v == nil { return } return *v, true } // OldClaimsEmailVerified returns the old "claims_email_verified" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldClaimsEmailVerified(ctx context.Context) (v bool, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsEmailVerified is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsEmailVerified requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsEmailVerified: %w", err) } return oldValue.ClaimsEmailVerified, nil } // ResetClaimsEmailVerified resets all changes to the "claims_email_verified" field. func (m *AuthRequestMutation) ResetClaimsEmailVerified() { m.claims_email_verified = nil } // SetClaimsGroups sets the "claims_groups" field. func (m *AuthRequestMutation) SetClaimsGroups(s []string) { m.claims_groups = &s m.appendclaims_groups = nil } // ClaimsGroups returns the value of the "claims_groups" field in the mutation. func (m *AuthRequestMutation) ClaimsGroups() (r []string, exists bool) { v := m.claims_groups if v == nil { return } return *v, true } // OldClaimsGroups returns the old "claims_groups" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldClaimsGroups(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsGroups is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsGroups requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsGroups: %w", err) } return oldValue.ClaimsGroups, nil } // AppendClaimsGroups adds s to the "claims_groups" field. func (m *AuthRequestMutation) AppendClaimsGroups(s []string) { m.appendclaims_groups = append(m.appendclaims_groups, s...) } // AppendedClaimsGroups returns the list of values that were appended to the "claims_groups" field in this mutation. func (m *AuthRequestMutation) AppendedClaimsGroups() ([]string, bool) { if len(m.appendclaims_groups) == 0 { return nil, false } return m.appendclaims_groups, true } // ClearClaimsGroups clears the value of the "claims_groups" field. func (m *AuthRequestMutation) ClearClaimsGroups() { m.claims_groups = nil m.appendclaims_groups = nil m.clearedFields[authrequest.FieldClaimsGroups] = struct{}{} } // ClaimsGroupsCleared returns if the "claims_groups" field was cleared in this mutation. func (m *AuthRequestMutation) ClaimsGroupsCleared() bool { _, ok := m.clearedFields[authrequest.FieldClaimsGroups] return ok } // ResetClaimsGroups resets all changes to the "claims_groups" field. func (m *AuthRequestMutation) ResetClaimsGroups() { m.claims_groups = nil m.appendclaims_groups = nil delete(m.clearedFields, authrequest.FieldClaimsGroups) } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (m *AuthRequestMutation) SetClaimsPreferredUsername(s string) { m.claims_preferred_username = &s } // ClaimsPreferredUsername returns the value of the "claims_preferred_username" field in the mutation. func (m *AuthRequestMutation) ClaimsPreferredUsername() (r string, exists bool) { v := m.claims_preferred_username if v == nil { return } return *v, true } // OldClaimsPreferredUsername returns the old "claims_preferred_username" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldClaimsPreferredUsername(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsPreferredUsername is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsPreferredUsername requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsPreferredUsername: %w", err) } return oldValue.ClaimsPreferredUsername, nil } // ResetClaimsPreferredUsername resets all changes to the "claims_preferred_username" field. func (m *AuthRequestMutation) ResetClaimsPreferredUsername() { m.claims_preferred_username = nil } // SetConnectorID sets the "connector_id" field. func (m *AuthRequestMutation) SetConnectorID(s string) { m.connector_id = &s } // ConnectorID returns the value of the "connector_id" field in the mutation. func (m *AuthRequestMutation) ConnectorID() (r string, exists bool) { v := m.connector_id if v == nil { return } return *v, true } // OldConnectorID returns the old "connector_id" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldConnectorID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldConnectorID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldConnectorID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldConnectorID: %w", err) } return oldValue.ConnectorID, nil } // ResetConnectorID resets all changes to the "connector_id" field. func (m *AuthRequestMutation) ResetConnectorID() { m.connector_id = nil } // SetConnectorData sets the "connector_data" field. func (m *AuthRequestMutation) SetConnectorData(b []byte) { m.connector_data = &b } // ConnectorData returns the value of the "connector_data" field in the mutation. func (m *AuthRequestMutation) ConnectorData() (r []byte, exists bool) { v := m.connector_data if v == nil { return } return *v, true } // OldConnectorData returns the old "connector_data" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldConnectorData(ctx context.Context) (v *[]byte, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldConnectorData is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldConnectorData requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldConnectorData: %w", err) } return oldValue.ConnectorData, nil } // ClearConnectorData clears the value of the "connector_data" field. func (m *AuthRequestMutation) ClearConnectorData() { m.connector_data = nil m.clearedFields[authrequest.FieldConnectorData] = struct{}{} } // ConnectorDataCleared returns if the "connector_data" field was cleared in this mutation. func (m *AuthRequestMutation) ConnectorDataCleared() bool { _, ok := m.clearedFields[authrequest.FieldConnectorData] return ok } // ResetConnectorData resets all changes to the "connector_data" field. func (m *AuthRequestMutation) ResetConnectorData() { m.connector_data = nil delete(m.clearedFields, authrequest.FieldConnectorData) } // SetExpiry sets the "expiry" field. func (m *AuthRequestMutation) SetExpiry(t time.Time) { m.expiry = &t } // Expiry returns the value of the "expiry" field in the mutation. func (m *AuthRequestMutation) Expiry() (r time.Time, exists bool) { v := m.expiry if v == nil { return } return *v, true } // OldExpiry returns the old "expiry" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldExpiry(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldExpiry is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldExpiry requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldExpiry: %w", err) } return oldValue.Expiry, nil } // ResetExpiry resets all changes to the "expiry" field. func (m *AuthRequestMutation) ResetExpiry() { m.expiry = nil } // SetCodeChallenge sets the "code_challenge" field. func (m *AuthRequestMutation) SetCodeChallenge(s string) { m.code_challenge = &s } // CodeChallenge returns the value of the "code_challenge" field in the mutation. func (m *AuthRequestMutation) CodeChallenge() (r string, exists bool) { v := m.code_challenge if v == nil { return } return *v, true } // OldCodeChallenge returns the old "code_challenge" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldCodeChallenge(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCodeChallenge is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldCodeChallenge requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldCodeChallenge: %w", err) } return oldValue.CodeChallenge, nil } // ResetCodeChallenge resets all changes to the "code_challenge" field. func (m *AuthRequestMutation) ResetCodeChallenge() { m.code_challenge = nil } // SetCodeChallengeMethod sets the "code_challenge_method" field. func (m *AuthRequestMutation) SetCodeChallengeMethod(s string) { m.code_challenge_method = &s } // CodeChallengeMethod returns the value of the "code_challenge_method" field in the mutation. func (m *AuthRequestMutation) CodeChallengeMethod() (r string, exists bool) { v := m.code_challenge_method if v == nil { return } return *v, true } // OldCodeChallengeMethod returns the old "code_challenge_method" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldCodeChallengeMethod(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCodeChallengeMethod is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldCodeChallengeMethod requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldCodeChallengeMethod: %w", err) } return oldValue.CodeChallengeMethod, nil } // ResetCodeChallengeMethod resets all changes to the "code_challenge_method" field. func (m *AuthRequestMutation) ResetCodeChallengeMethod() { m.code_challenge_method = nil } // SetHmacKey sets the "hmac_key" field. func (m *AuthRequestMutation) SetHmacKey(b []byte) { m.hmac_key = &b } // HmacKey returns the value of the "hmac_key" field in the mutation. func (m *AuthRequestMutation) HmacKey() (r []byte, exists bool) { v := m.hmac_key if v == nil { return } return *v, true } // OldHmacKey returns the old "hmac_key" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldHmacKey(ctx context.Context) (v []byte, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldHmacKey is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldHmacKey requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldHmacKey: %w", err) } return oldValue.HmacKey, nil } // ResetHmacKey resets all changes to the "hmac_key" field. func (m *AuthRequestMutation) ResetHmacKey() { m.hmac_key = nil } // SetMfaValidated sets the "mfa_validated" field. func (m *AuthRequestMutation) SetMfaValidated(b bool) { m.mfa_validated = &b } // MfaValidated returns the value of the "mfa_validated" field in the mutation. func (m *AuthRequestMutation) MfaValidated() (r bool, exists bool) { v := m.mfa_validated if v == nil { return } return *v, true } // OldMfaValidated returns the old "mfa_validated" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldMfaValidated(ctx context.Context) (v bool, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldMfaValidated is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldMfaValidated requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldMfaValidated: %w", err) } return oldValue.MfaValidated, nil } // ResetMfaValidated resets all changes to the "mfa_validated" field. func (m *AuthRequestMutation) ResetMfaValidated() { m.mfa_validated = nil } // SetPrompt sets the "prompt" field. func (m *AuthRequestMutation) SetPrompt(s string) { m.prompt = &s } // Prompt returns the value of the "prompt" field in the mutation. func (m *AuthRequestMutation) Prompt() (r string, exists bool) { v := m.prompt if v == nil { return } return *v, true } // OldPrompt returns the old "prompt" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldPrompt(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldPrompt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldPrompt requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldPrompt: %w", err) } return oldValue.Prompt, nil } // ResetPrompt resets all changes to the "prompt" field. func (m *AuthRequestMutation) ResetPrompt() { m.prompt = nil } // SetMaxAge sets the "max_age" field. func (m *AuthRequestMutation) SetMaxAge(i int) { m.max_age = &i m.addmax_age = nil } // MaxAge returns the value of the "max_age" field in the mutation. func (m *AuthRequestMutation) MaxAge() (r int, exists bool) { v := m.max_age if v == nil { return } return *v, true } // OldMaxAge returns the old "max_age" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldMaxAge(ctx context.Context) (v int, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldMaxAge is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldMaxAge requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldMaxAge: %w", err) } return oldValue.MaxAge, nil } // AddMaxAge adds i to the "max_age" field. func (m *AuthRequestMutation) AddMaxAge(i int) { if m.addmax_age != nil { *m.addmax_age += i } else { m.addmax_age = &i } } // AddedMaxAge returns the value that was added to the "max_age" field in this mutation. func (m *AuthRequestMutation) AddedMaxAge() (r int, exists bool) { v := m.addmax_age if v == nil { return } return *v, true } // ResetMaxAge resets all changes to the "max_age" field. func (m *AuthRequestMutation) ResetMaxAge() { m.max_age = nil m.addmax_age = nil } // SetAuthTime sets the "auth_time" field. func (m *AuthRequestMutation) SetAuthTime(t time.Time) { m.auth_time = &t } // AuthTime returns the value of the "auth_time" field in the mutation. func (m *AuthRequestMutation) AuthTime() (r time.Time, exists bool) { v := m.auth_time if v == nil { return } return *v, true } // OldAuthTime returns the old "auth_time" field's value of the AuthRequest entity. // If the AuthRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthRequestMutation) OldAuthTime(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldAuthTime is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldAuthTime requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldAuthTime: %w", err) } return oldValue.AuthTime, nil } // ClearAuthTime clears the value of the "auth_time" field. func (m *AuthRequestMutation) ClearAuthTime() { m.auth_time = nil m.clearedFields[authrequest.FieldAuthTime] = struct{}{} } // AuthTimeCleared returns if the "auth_time" field was cleared in this mutation. func (m *AuthRequestMutation) AuthTimeCleared() bool { _, ok := m.clearedFields[authrequest.FieldAuthTime] return ok } // ResetAuthTime resets all changes to the "auth_time" field. func (m *AuthRequestMutation) ResetAuthTime() { m.auth_time = nil delete(m.clearedFields, authrequest.FieldAuthTime) } // Where appends a list predicates to the AuthRequestMutation builder. func (m *AuthRequestMutation) Where(ps ...predicate.AuthRequest) { m.predicates = append(m.predicates, ps...) } // WhereP appends storage-level predicates to the AuthRequestMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. func (m *AuthRequestMutation) WhereP(ps ...func(*sql.Selector)) { p := make([]predicate.AuthRequest, len(ps)) for i := range ps { p[i] = ps[i] } m.Where(p...) } // Op returns the operation name. func (m *AuthRequestMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. func (m *AuthRequestMutation) SetOp(op Op) { m.op = op } // Type returns the node type of this mutation (AuthRequest). func (m *AuthRequestMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *AuthRequestMutation) Fields() []string { fields := make([]string, 0, 24) if m.client_id != nil { fields = append(fields, authrequest.FieldClientID) } if m.scopes != nil { fields = append(fields, authrequest.FieldScopes) } if m.response_types != nil { fields = append(fields, authrequest.FieldResponseTypes) } if m.redirect_uri != nil { fields = append(fields, authrequest.FieldRedirectURI) } if m.nonce != nil { fields = append(fields, authrequest.FieldNonce) } if m.state != nil { fields = append(fields, authrequest.FieldState) } if m.force_approval_prompt != nil { fields = append(fields, authrequest.FieldForceApprovalPrompt) } if m.logged_in != nil { fields = append(fields, authrequest.FieldLoggedIn) } if m.claims_user_id != nil { fields = append(fields, authrequest.FieldClaimsUserID) } if m.claims_username != nil { fields = append(fields, authrequest.FieldClaimsUsername) } if m.claims_email != nil { fields = append(fields, authrequest.FieldClaimsEmail) } if m.claims_email_verified != nil { fields = append(fields, authrequest.FieldClaimsEmailVerified) } if m.claims_groups != nil { fields = append(fields, authrequest.FieldClaimsGroups) } if m.claims_preferred_username != nil { fields = append(fields, authrequest.FieldClaimsPreferredUsername) } if m.connector_id != nil { fields = append(fields, authrequest.FieldConnectorID) } if m.connector_data != nil { fields = append(fields, authrequest.FieldConnectorData) } if m.expiry != nil { fields = append(fields, authrequest.FieldExpiry) } if m.code_challenge != nil { fields = append(fields, authrequest.FieldCodeChallenge) } if m.code_challenge_method != nil { fields = append(fields, authrequest.FieldCodeChallengeMethod) } if m.hmac_key != nil { fields = append(fields, authrequest.FieldHmacKey) } if m.mfa_validated != nil { fields = append(fields, authrequest.FieldMfaValidated) } if m.prompt != nil { fields = append(fields, authrequest.FieldPrompt) } if m.max_age != nil { fields = append(fields, authrequest.FieldMaxAge) } if m.auth_time != nil { fields = append(fields, authrequest.FieldAuthTime) } return fields } // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. func (m *AuthRequestMutation) Field(name string) (ent.Value, bool) { switch name { case authrequest.FieldClientID: return m.ClientID() case authrequest.FieldScopes: return m.Scopes() case authrequest.FieldResponseTypes: return m.ResponseTypes() case authrequest.FieldRedirectURI: return m.RedirectURI() case authrequest.FieldNonce: return m.Nonce() case authrequest.FieldState: return m.State() case authrequest.FieldForceApprovalPrompt: return m.ForceApprovalPrompt() case authrequest.FieldLoggedIn: return m.LoggedIn() case authrequest.FieldClaimsUserID: return m.ClaimsUserID() case authrequest.FieldClaimsUsername: return m.ClaimsUsername() case authrequest.FieldClaimsEmail: return m.ClaimsEmail() case authrequest.FieldClaimsEmailVerified: return m.ClaimsEmailVerified() case authrequest.FieldClaimsGroups: return m.ClaimsGroups() case authrequest.FieldClaimsPreferredUsername: return m.ClaimsPreferredUsername() case authrequest.FieldConnectorID: return m.ConnectorID() case authrequest.FieldConnectorData: return m.ConnectorData() case authrequest.FieldExpiry: return m.Expiry() case authrequest.FieldCodeChallenge: return m.CodeChallenge() case authrequest.FieldCodeChallengeMethod: return m.CodeChallengeMethod() case authrequest.FieldHmacKey: return m.HmacKey() case authrequest.FieldMfaValidated: return m.MfaValidated() case authrequest.FieldPrompt: return m.Prompt() case authrequest.FieldMaxAge: return m.MaxAge() case authrequest.FieldAuthTime: return m.AuthTime() } return nil, false } // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. func (m *AuthRequestMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { case authrequest.FieldClientID: return m.OldClientID(ctx) case authrequest.FieldScopes: return m.OldScopes(ctx) case authrequest.FieldResponseTypes: return m.OldResponseTypes(ctx) case authrequest.FieldRedirectURI: return m.OldRedirectURI(ctx) case authrequest.FieldNonce: return m.OldNonce(ctx) case authrequest.FieldState: return m.OldState(ctx) case authrequest.FieldForceApprovalPrompt: return m.OldForceApprovalPrompt(ctx) case authrequest.FieldLoggedIn: return m.OldLoggedIn(ctx) case authrequest.FieldClaimsUserID: return m.OldClaimsUserID(ctx) case authrequest.FieldClaimsUsername: return m.OldClaimsUsername(ctx) case authrequest.FieldClaimsEmail: return m.OldClaimsEmail(ctx) case authrequest.FieldClaimsEmailVerified: return m.OldClaimsEmailVerified(ctx) case authrequest.FieldClaimsGroups: return m.OldClaimsGroups(ctx) case authrequest.FieldClaimsPreferredUsername: return m.OldClaimsPreferredUsername(ctx) case authrequest.FieldConnectorID: return m.OldConnectorID(ctx) case authrequest.FieldConnectorData: return m.OldConnectorData(ctx) case authrequest.FieldExpiry: return m.OldExpiry(ctx) case authrequest.FieldCodeChallenge: return m.OldCodeChallenge(ctx) case authrequest.FieldCodeChallengeMethod: return m.OldCodeChallengeMethod(ctx) case authrequest.FieldHmacKey: return m.OldHmacKey(ctx) case authrequest.FieldMfaValidated: return m.OldMfaValidated(ctx) case authrequest.FieldPrompt: return m.OldPrompt(ctx) case authrequest.FieldMaxAge: return m.OldMaxAge(ctx) case authrequest.FieldAuthTime: return m.OldAuthTime(ctx) } return nil, fmt.Errorf("unknown AuthRequest field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *AuthRequestMutation) SetField(name string, value ent.Value) error { switch name { case authrequest.FieldClientID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClientID(v) return nil case authrequest.FieldScopes: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetScopes(v) return nil case authrequest.FieldResponseTypes: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetResponseTypes(v) return nil case authrequest.FieldRedirectURI: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetRedirectURI(v) return nil case authrequest.FieldNonce: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetNonce(v) return nil case authrequest.FieldState: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetState(v) return nil case authrequest.FieldForceApprovalPrompt: v, ok := value.(bool) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetForceApprovalPrompt(v) return nil case authrequest.FieldLoggedIn: v, ok := value.(bool) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetLoggedIn(v) return nil case authrequest.FieldClaimsUserID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsUserID(v) return nil case authrequest.FieldClaimsUsername: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsUsername(v) return nil case authrequest.FieldClaimsEmail: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsEmail(v) return nil case authrequest.FieldClaimsEmailVerified: v, ok := value.(bool) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsEmailVerified(v) return nil case authrequest.FieldClaimsGroups: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsGroups(v) return nil case authrequest.FieldClaimsPreferredUsername: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsPreferredUsername(v) return nil case authrequest.FieldConnectorID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetConnectorID(v) return nil case authrequest.FieldConnectorData: v, ok := value.([]byte) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetConnectorData(v) return nil case authrequest.FieldExpiry: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetExpiry(v) return nil case authrequest.FieldCodeChallenge: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCodeChallenge(v) return nil case authrequest.FieldCodeChallengeMethod: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCodeChallengeMethod(v) return nil case authrequest.FieldHmacKey: v, ok := value.([]byte) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetHmacKey(v) return nil case authrequest.FieldMfaValidated: v, ok := value.(bool) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetMfaValidated(v) return nil case authrequest.FieldPrompt: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetPrompt(v) return nil case authrequest.FieldMaxAge: v, ok := value.(int) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetMaxAge(v) return nil case authrequest.FieldAuthTime: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetAuthTime(v) return nil } return fmt.Errorf("unknown AuthRequest field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *AuthRequestMutation) AddedFields() []string { var fields []string if m.addmax_age != nil { fields = append(fields, authrequest.FieldMaxAge) } return fields } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *AuthRequestMutation) AddedField(name string) (ent.Value, bool) { switch name { case authrequest.FieldMaxAge: return m.AddedMaxAge() } return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *AuthRequestMutation) AddField(name string, value ent.Value) error { switch name { case authrequest.FieldMaxAge: v, ok := value.(int) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddMaxAge(v) return nil } return fmt.Errorf("unknown AuthRequest numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *AuthRequestMutation) ClearedFields() []string { var fields []string if m.FieldCleared(authrequest.FieldScopes) { fields = append(fields, authrequest.FieldScopes) } if m.FieldCleared(authrequest.FieldResponseTypes) { fields = append(fields, authrequest.FieldResponseTypes) } if m.FieldCleared(authrequest.FieldClaimsGroups) { fields = append(fields, authrequest.FieldClaimsGroups) } if m.FieldCleared(authrequest.FieldConnectorData) { fields = append(fields, authrequest.FieldConnectorData) } if m.FieldCleared(authrequest.FieldAuthTime) { fields = append(fields, authrequest.FieldAuthTime) } return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. func (m *AuthRequestMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *AuthRequestMutation) ClearField(name string) error { switch name { case authrequest.FieldScopes: m.ClearScopes() return nil case authrequest.FieldResponseTypes: m.ClearResponseTypes() return nil case authrequest.FieldClaimsGroups: m.ClearClaimsGroups() return nil case authrequest.FieldConnectorData: m.ClearConnectorData() return nil case authrequest.FieldAuthTime: m.ClearAuthTime() return nil } return fmt.Errorf("unknown AuthRequest nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. func (m *AuthRequestMutation) ResetField(name string) error { switch name { case authrequest.FieldClientID: m.ResetClientID() return nil case authrequest.FieldScopes: m.ResetScopes() return nil case authrequest.FieldResponseTypes: m.ResetResponseTypes() return nil case authrequest.FieldRedirectURI: m.ResetRedirectURI() return nil case authrequest.FieldNonce: m.ResetNonce() return nil case authrequest.FieldState: m.ResetState() return nil case authrequest.FieldForceApprovalPrompt: m.ResetForceApprovalPrompt() return nil case authrequest.FieldLoggedIn: m.ResetLoggedIn() return nil case authrequest.FieldClaimsUserID: m.ResetClaimsUserID() return nil case authrequest.FieldClaimsUsername: m.ResetClaimsUsername() return nil case authrequest.FieldClaimsEmail: m.ResetClaimsEmail() return nil case authrequest.FieldClaimsEmailVerified: m.ResetClaimsEmailVerified() return nil case authrequest.FieldClaimsGroups: m.ResetClaimsGroups() return nil case authrequest.FieldClaimsPreferredUsername: m.ResetClaimsPreferredUsername() return nil case authrequest.FieldConnectorID: m.ResetConnectorID() return nil case authrequest.FieldConnectorData: m.ResetConnectorData() return nil case authrequest.FieldExpiry: m.ResetExpiry() return nil case authrequest.FieldCodeChallenge: m.ResetCodeChallenge() return nil case authrequest.FieldCodeChallengeMethod: m.ResetCodeChallengeMethod() return nil case authrequest.FieldHmacKey: m.ResetHmacKey() return nil case authrequest.FieldMfaValidated: m.ResetMfaValidated() return nil case authrequest.FieldPrompt: m.ResetPrompt() return nil case authrequest.FieldMaxAge: m.ResetMaxAge() return nil case authrequest.FieldAuthTime: m.ResetAuthTime() return nil } return fmt.Errorf("unknown AuthRequest field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *AuthRequestMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *AuthRequestMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *AuthRequestMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *AuthRequestMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *AuthRequestMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *AuthRequestMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *AuthRequestMutation) ClearEdge(name string) error { return fmt.Errorf("unknown AuthRequest unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *AuthRequestMutation) ResetEdge(name string) error { return fmt.Errorf("unknown AuthRequest edge %s", name) } // AuthSessionMutation represents an operation that mutates the AuthSession nodes in the graph. type AuthSessionMutation struct { config op Op typ string id *string user_id *string connector_id *string nonce *string client_states *[]byte created_at *time.Time last_activity *time.Time ip_address *string user_agent *string absolute_expiry *time.Time idle_expiry *time.Time clearedFields map[string]struct{} done bool oldValue func(context.Context) (*AuthSession, error) predicates []predicate.AuthSession } var _ ent.Mutation = (*AuthSessionMutation)(nil) // authsessionOption allows management of the mutation configuration using functional options. type authsessionOption func(*AuthSessionMutation) // newAuthSessionMutation creates new mutation for the AuthSession entity. func newAuthSessionMutation(c config, op Op, opts ...authsessionOption) *AuthSessionMutation { m := &AuthSessionMutation{ config: c, op: op, typ: TypeAuthSession, clearedFields: make(map[string]struct{}), } for _, opt := range opts { opt(m) } return m } // withAuthSessionID sets the ID field of the mutation. func withAuthSessionID(id string) authsessionOption { return func(m *AuthSessionMutation) { var ( err error once sync.Once value *AuthSession ) m.oldValue = func(ctx context.Context) (*AuthSession, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { value, err = m.Client().AuthSession.Get(ctx, id) } }) return value, err } m.id = &id } } // withAuthSession sets the old AuthSession of the mutation. func withAuthSession(node *AuthSession) authsessionOption { return func(m *AuthSessionMutation) { m.oldValue = func(context.Context) (*AuthSession, error) { return node, nil } m.id = &node.ID } } // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. func (m AuthSessionMutation) Client() *Client { client := &Client{config: m.config} client.init() return client } // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. func (m AuthSessionMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } tx := &Tx{config: m.config} tx.init() return tx, nil } // SetID sets the value of the id field. Note that this // operation is only accepted on creation of AuthSession entities. func (m *AuthSessionMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. func (m *AuthSessionMutation) ID() (id string, exists bool) { if m.id == nil { return } return *m.id, true } // IDs queries the database and returns the entity ids that match the mutation's predicate. // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. func (m *AuthSessionMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { return []string{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): return m.Client().AuthSession.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetUserID sets the "user_id" field. func (m *AuthSessionMutation) SetUserID(s string) { m.user_id = &s } // UserID returns the value of the "user_id" field in the mutation. func (m *AuthSessionMutation) UserID() (r string, exists bool) { v := m.user_id if v == nil { return } return *v, true } // OldUserID returns the old "user_id" field's value of the AuthSession entity. // If the AuthSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthSessionMutation) OldUserID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUserID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldUserID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldUserID: %w", err) } return oldValue.UserID, nil } // ResetUserID resets all changes to the "user_id" field. func (m *AuthSessionMutation) ResetUserID() { m.user_id = nil } // SetConnectorID sets the "connector_id" field. func (m *AuthSessionMutation) SetConnectorID(s string) { m.connector_id = &s } // ConnectorID returns the value of the "connector_id" field in the mutation. func (m *AuthSessionMutation) ConnectorID() (r string, exists bool) { v := m.connector_id if v == nil { return } return *v, true } // OldConnectorID returns the old "connector_id" field's value of the AuthSession entity. // If the AuthSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthSessionMutation) OldConnectorID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldConnectorID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldConnectorID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldConnectorID: %w", err) } return oldValue.ConnectorID, nil } // ResetConnectorID resets all changes to the "connector_id" field. func (m *AuthSessionMutation) ResetConnectorID() { m.connector_id = nil } // SetNonce sets the "nonce" field. func (m *AuthSessionMutation) SetNonce(s string) { m.nonce = &s } // Nonce returns the value of the "nonce" field in the mutation. func (m *AuthSessionMutation) Nonce() (r string, exists bool) { v := m.nonce if v == nil { return } return *v, true } // OldNonce returns the old "nonce" field's value of the AuthSession entity. // If the AuthSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthSessionMutation) OldNonce(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldNonce is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldNonce requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldNonce: %w", err) } return oldValue.Nonce, nil } // ResetNonce resets all changes to the "nonce" field. func (m *AuthSessionMutation) ResetNonce() { m.nonce = nil } // SetClientStates sets the "client_states" field. func (m *AuthSessionMutation) SetClientStates(b []byte) { m.client_states = &b } // ClientStates returns the value of the "client_states" field in the mutation. func (m *AuthSessionMutation) ClientStates() (r []byte, exists bool) { v := m.client_states if v == nil { return } return *v, true } // OldClientStates returns the old "client_states" field's value of the AuthSession entity. // If the AuthSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthSessionMutation) OldClientStates(ctx context.Context) (v []byte, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClientStates is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClientStates requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClientStates: %w", err) } return oldValue.ClientStates, nil } // ResetClientStates resets all changes to the "client_states" field. func (m *AuthSessionMutation) ResetClientStates() { m.client_states = nil } // SetCreatedAt sets the "created_at" field. func (m *AuthSessionMutation) SetCreatedAt(t time.Time) { m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. func (m *AuthSessionMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return } return *v, true } // OldCreatedAt returns the old "created_at" field's value of the AuthSession entity. // If the AuthSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthSessionMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldCreatedAt requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) } return oldValue.CreatedAt, nil } // ResetCreatedAt resets all changes to the "created_at" field. func (m *AuthSessionMutation) ResetCreatedAt() { m.created_at = nil } // SetLastActivity sets the "last_activity" field. func (m *AuthSessionMutation) SetLastActivity(t time.Time) { m.last_activity = &t } // LastActivity returns the value of the "last_activity" field in the mutation. func (m *AuthSessionMutation) LastActivity() (r time.Time, exists bool) { v := m.last_activity if v == nil { return } return *v, true } // OldLastActivity returns the old "last_activity" field's value of the AuthSession entity. // If the AuthSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthSessionMutation) OldLastActivity(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastActivity is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldLastActivity requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldLastActivity: %w", err) } return oldValue.LastActivity, nil } // ResetLastActivity resets all changes to the "last_activity" field. func (m *AuthSessionMutation) ResetLastActivity() { m.last_activity = nil } // SetIPAddress sets the "ip_address" field. func (m *AuthSessionMutation) SetIPAddress(s string) { m.ip_address = &s } // IPAddress returns the value of the "ip_address" field in the mutation. func (m *AuthSessionMutation) IPAddress() (r string, exists bool) { v := m.ip_address if v == nil { return } return *v, true } // OldIPAddress returns the old "ip_address" field's value of the AuthSession entity. // If the AuthSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthSessionMutation) OldIPAddress(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldIPAddress is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldIPAddress requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldIPAddress: %w", err) } return oldValue.IPAddress, nil } // ResetIPAddress resets all changes to the "ip_address" field. func (m *AuthSessionMutation) ResetIPAddress() { m.ip_address = nil } // SetUserAgent sets the "user_agent" field. func (m *AuthSessionMutation) SetUserAgent(s string) { m.user_agent = &s } // UserAgent returns the value of the "user_agent" field in the mutation. func (m *AuthSessionMutation) UserAgent() (r string, exists bool) { v := m.user_agent if v == nil { return } return *v, true } // OldUserAgent returns the old "user_agent" field's value of the AuthSession entity. // If the AuthSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthSessionMutation) OldUserAgent(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUserAgent is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldUserAgent requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldUserAgent: %w", err) } return oldValue.UserAgent, nil } // ResetUserAgent resets all changes to the "user_agent" field. func (m *AuthSessionMutation) ResetUserAgent() { m.user_agent = nil } // SetAbsoluteExpiry sets the "absolute_expiry" field. func (m *AuthSessionMutation) SetAbsoluteExpiry(t time.Time) { m.absolute_expiry = &t } // AbsoluteExpiry returns the value of the "absolute_expiry" field in the mutation. func (m *AuthSessionMutation) AbsoluteExpiry() (r time.Time, exists bool) { v := m.absolute_expiry if v == nil { return } return *v, true } // OldAbsoluteExpiry returns the old "absolute_expiry" field's value of the AuthSession entity. // If the AuthSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthSessionMutation) OldAbsoluteExpiry(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldAbsoluteExpiry is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldAbsoluteExpiry requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldAbsoluteExpiry: %w", err) } return oldValue.AbsoluteExpiry, nil } // ResetAbsoluteExpiry resets all changes to the "absolute_expiry" field. func (m *AuthSessionMutation) ResetAbsoluteExpiry() { m.absolute_expiry = nil } // SetIdleExpiry sets the "idle_expiry" field. func (m *AuthSessionMutation) SetIdleExpiry(t time.Time) { m.idle_expiry = &t } // IdleExpiry returns the value of the "idle_expiry" field in the mutation. func (m *AuthSessionMutation) IdleExpiry() (r time.Time, exists bool) { v := m.idle_expiry if v == nil { return } return *v, true } // OldIdleExpiry returns the old "idle_expiry" field's value of the AuthSession entity. // If the AuthSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *AuthSessionMutation) OldIdleExpiry(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldIdleExpiry is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldIdleExpiry requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldIdleExpiry: %w", err) } return oldValue.IdleExpiry, nil } // ResetIdleExpiry resets all changes to the "idle_expiry" field. func (m *AuthSessionMutation) ResetIdleExpiry() { m.idle_expiry = nil } // Where appends a list predicates to the AuthSessionMutation builder. func (m *AuthSessionMutation) Where(ps ...predicate.AuthSession) { m.predicates = append(m.predicates, ps...) } // WhereP appends storage-level predicates to the AuthSessionMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. func (m *AuthSessionMutation) WhereP(ps ...func(*sql.Selector)) { p := make([]predicate.AuthSession, len(ps)) for i := range ps { p[i] = ps[i] } m.Where(p...) } // Op returns the operation name. func (m *AuthSessionMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. func (m *AuthSessionMutation) SetOp(op Op) { m.op = op } // Type returns the node type of this mutation (AuthSession). func (m *AuthSessionMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *AuthSessionMutation) Fields() []string { fields := make([]string, 0, 10) if m.user_id != nil { fields = append(fields, authsession.FieldUserID) } if m.connector_id != nil { fields = append(fields, authsession.FieldConnectorID) } if m.nonce != nil { fields = append(fields, authsession.FieldNonce) } if m.client_states != nil { fields = append(fields, authsession.FieldClientStates) } if m.created_at != nil { fields = append(fields, authsession.FieldCreatedAt) } if m.last_activity != nil { fields = append(fields, authsession.FieldLastActivity) } if m.ip_address != nil { fields = append(fields, authsession.FieldIPAddress) } if m.user_agent != nil { fields = append(fields, authsession.FieldUserAgent) } if m.absolute_expiry != nil { fields = append(fields, authsession.FieldAbsoluteExpiry) } if m.idle_expiry != nil { fields = append(fields, authsession.FieldIdleExpiry) } return fields } // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. func (m *AuthSessionMutation) Field(name string) (ent.Value, bool) { switch name { case authsession.FieldUserID: return m.UserID() case authsession.FieldConnectorID: return m.ConnectorID() case authsession.FieldNonce: return m.Nonce() case authsession.FieldClientStates: return m.ClientStates() case authsession.FieldCreatedAt: return m.CreatedAt() case authsession.FieldLastActivity: return m.LastActivity() case authsession.FieldIPAddress: return m.IPAddress() case authsession.FieldUserAgent: return m.UserAgent() case authsession.FieldAbsoluteExpiry: return m.AbsoluteExpiry() case authsession.FieldIdleExpiry: return m.IdleExpiry() } return nil, false } // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. func (m *AuthSessionMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { case authsession.FieldUserID: return m.OldUserID(ctx) case authsession.FieldConnectorID: return m.OldConnectorID(ctx) case authsession.FieldNonce: return m.OldNonce(ctx) case authsession.FieldClientStates: return m.OldClientStates(ctx) case authsession.FieldCreatedAt: return m.OldCreatedAt(ctx) case authsession.FieldLastActivity: return m.OldLastActivity(ctx) case authsession.FieldIPAddress: return m.OldIPAddress(ctx) case authsession.FieldUserAgent: return m.OldUserAgent(ctx) case authsession.FieldAbsoluteExpiry: return m.OldAbsoluteExpiry(ctx) case authsession.FieldIdleExpiry: return m.OldIdleExpiry(ctx) } return nil, fmt.Errorf("unknown AuthSession field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *AuthSessionMutation) SetField(name string, value ent.Value) error { switch name { case authsession.FieldUserID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUserID(v) return nil case authsession.FieldConnectorID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetConnectorID(v) return nil case authsession.FieldNonce: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetNonce(v) return nil case authsession.FieldClientStates: v, ok := value.([]byte) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClientStates(v) return nil case authsession.FieldCreatedAt: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case authsession.FieldLastActivity: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetLastActivity(v) return nil case authsession.FieldIPAddress: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetIPAddress(v) return nil case authsession.FieldUserAgent: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUserAgent(v) return nil case authsession.FieldAbsoluteExpiry: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetAbsoluteExpiry(v) return nil case authsession.FieldIdleExpiry: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetIdleExpiry(v) return nil } return fmt.Errorf("unknown AuthSession field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *AuthSessionMutation) AddedFields() []string { return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *AuthSessionMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *AuthSessionMutation) AddField(name string, value ent.Value) error { switch name { } return fmt.Errorf("unknown AuthSession numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *AuthSessionMutation) ClearedFields() []string { return nil } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. func (m *AuthSessionMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *AuthSessionMutation) ClearField(name string) error { return fmt.Errorf("unknown AuthSession nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. func (m *AuthSessionMutation) ResetField(name string) error { switch name { case authsession.FieldUserID: m.ResetUserID() return nil case authsession.FieldConnectorID: m.ResetConnectorID() return nil case authsession.FieldNonce: m.ResetNonce() return nil case authsession.FieldClientStates: m.ResetClientStates() return nil case authsession.FieldCreatedAt: m.ResetCreatedAt() return nil case authsession.FieldLastActivity: m.ResetLastActivity() return nil case authsession.FieldIPAddress: m.ResetIPAddress() return nil case authsession.FieldUserAgent: m.ResetUserAgent() return nil case authsession.FieldAbsoluteExpiry: m.ResetAbsoluteExpiry() return nil case authsession.FieldIdleExpiry: m.ResetIdleExpiry() return nil } return fmt.Errorf("unknown AuthSession field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *AuthSessionMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *AuthSessionMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *AuthSessionMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *AuthSessionMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *AuthSessionMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *AuthSessionMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *AuthSessionMutation) ClearEdge(name string) error { return fmt.Errorf("unknown AuthSession unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *AuthSessionMutation) ResetEdge(name string) error { return fmt.Errorf("unknown AuthSession edge %s", name) } // ConnectorMutation represents an operation that mutates the Connector nodes in the graph. type ConnectorMutation struct { config op Op typ string id *string _type *string name *string resource_version *string _config *[]byte grant_types *[]string appendgrant_types []string clearedFields map[string]struct{} done bool oldValue func(context.Context) (*Connector, error) predicates []predicate.Connector } var _ ent.Mutation = (*ConnectorMutation)(nil) // connectorOption allows management of the mutation configuration using functional options. type connectorOption func(*ConnectorMutation) // newConnectorMutation creates new mutation for the Connector entity. func newConnectorMutation(c config, op Op, opts ...connectorOption) *ConnectorMutation { m := &ConnectorMutation{ config: c, op: op, typ: TypeConnector, clearedFields: make(map[string]struct{}), } for _, opt := range opts { opt(m) } return m } // withConnectorID sets the ID field of the mutation. func withConnectorID(id string) connectorOption { return func(m *ConnectorMutation) { var ( err error once sync.Once value *Connector ) m.oldValue = func(ctx context.Context) (*Connector, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { value, err = m.Client().Connector.Get(ctx, id) } }) return value, err } m.id = &id } } // withConnector sets the old Connector of the mutation. func withConnector(node *Connector) connectorOption { return func(m *ConnectorMutation) { m.oldValue = func(context.Context) (*Connector, error) { return node, nil } m.id = &node.ID } } // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. func (m ConnectorMutation) Client() *Client { client := &Client{config: m.config} client.init() return client } // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. func (m ConnectorMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } tx := &Tx{config: m.config} tx.init() return tx, nil } // SetID sets the value of the id field. Note that this // operation is only accepted on creation of Connector entities. func (m *ConnectorMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. func (m *ConnectorMutation) ID() (id string, exists bool) { if m.id == nil { return } return *m.id, true } // IDs queries the database and returns the entity ids that match the mutation's predicate. // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. func (m *ConnectorMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { return []string{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): return m.Client().Connector.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetType sets the "type" field. func (m *ConnectorMutation) SetType(s string) { m._type = &s } // GetType returns the value of the "type" field in the mutation. func (m *ConnectorMutation) GetType() (r string, exists bool) { v := m._type if v == nil { return } return *v, true } // OldType returns the old "type" field's value of the Connector entity. // If the Connector object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *ConnectorMutation) OldType(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldType is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldType requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldType: %w", err) } return oldValue.Type, nil } // ResetType resets all changes to the "type" field. func (m *ConnectorMutation) ResetType() { m._type = nil } // SetName sets the "name" field. func (m *ConnectorMutation) SetName(s string) { m.name = &s } // Name returns the value of the "name" field in the mutation. func (m *ConnectorMutation) Name() (r string, exists bool) { v := m.name if v == nil { return } return *v, true } // OldName returns the old "name" field's value of the Connector entity. // If the Connector object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *ConnectorMutation) OldName(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldName is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldName requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldName: %w", err) } return oldValue.Name, nil } // ResetName resets all changes to the "name" field. func (m *ConnectorMutation) ResetName() { m.name = nil } // SetResourceVersion sets the "resource_version" field. func (m *ConnectorMutation) SetResourceVersion(s string) { m.resource_version = &s } // ResourceVersion returns the value of the "resource_version" field in the mutation. func (m *ConnectorMutation) ResourceVersion() (r string, exists bool) { v := m.resource_version if v == nil { return } return *v, true } // OldResourceVersion returns the old "resource_version" field's value of the Connector entity. // If the Connector object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *ConnectorMutation) OldResourceVersion(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldResourceVersion is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldResourceVersion requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldResourceVersion: %w", err) } return oldValue.ResourceVersion, nil } // ResetResourceVersion resets all changes to the "resource_version" field. func (m *ConnectorMutation) ResetResourceVersion() { m.resource_version = nil } // SetConfig sets the "config" field. func (m *ConnectorMutation) SetConfig(b []byte) { m._config = &b } // Config returns the value of the "config" field in the mutation. func (m *ConnectorMutation) Config() (r []byte, exists bool) { v := m._config if v == nil { return } return *v, true } // OldConfig returns the old "config" field's value of the Connector entity. // If the Connector object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *ConnectorMutation) OldConfig(ctx context.Context) (v []byte, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldConfig is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldConfig requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldConfig: %w", err) } return oldValue.Config, nil } // ResetConfig resets all changes to the "config" field. func (m *ConnectorMutation) ResetConfig() { m._config = nil } // SetGrantTypes sets the "grant_types" field. func (m *ConnectorMutation) SetGrantTypes(s []string) { m.grant_types = &s m.appendgrant_types = nil } // GrantTypes returns the value of the "grant_types" field in the mutation. func (m *ConnectorMutation) GrantTypes() (r []string, exists bool) { v := m.grant_types if v == nil { return } return *v, true } // OldGrantTypes returns the old "grant_types" field's value of the Connector entity. // If the Connector object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *ConnectorMutation) OldGrantTypes(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldGrantTypes is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldGrantTypes requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldGrantTypes: %w", err) } return oldValue.GrantTypes, nil } // AppendGrantTypes adds s to the "grant_types" field. func (m *ConnectorMutation) AppendGrantTypes(s []string) { m.appendgrant_types = append(m.appendgrant_types, s...) } // AppendedGrantTypes returns the list of values that were appended to the "grant_types" field in this mutation. func (m *ConnectorMutation) AppendedGrantTypes() ([]string, bool) { if len(m.appendgrant_types) == 0 { return nil, false } return m.appendgrant_types, true } // ClearGrantTypes clears the value of the "grant_types" field. func (m *ConnectorMutation) ClearGrantTypes() { m.grant_types = nil m.appendgrant_types = nil m.clearedFields[connector.FieldGrantTypes] = struct{}{} } // GrantTypesCleared returns if the "grant_types" field was cleared in this mutation. func (m *ConnectorMutation) GrantTypesCleared() bool { _, ok := m.clearedFields[connector.FieldGrantTypes] return ok } // ResetGrantTypes resets all changes to the "grant_types" field. func (m *ConnectorMutation) ResetGrantTypes() { m.grant_types = nil m.appendgrant_types = nil delete(m.clearedFields, connector.FieldGrantTypes) } // Where appends a list predicates to the ConnectorMutation builder. func (m *ConnectorMutation) Where(ps ...predicate.Connector) { m.predicates = append(m.predicates, ps...) } // WhereP appends storage-level predicates to the ConnectorMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. func (m *ConnectorMutation) WhereP(ps ...func(*sql.Selector)) { p := make([]predicate.Connector, len(ps)) for i := range ps { p[i] = ps[i] } m.Where(p...) } // Op returns the operation name. func (m *ConnectorMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. func (m *ConnectorMutation) SetOp(op Op) { m.op = op } // Type returns the node type of this mutation (Connector). func (m *ConnectorMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *ConnectorMutation) Fields() []string { fields := make([]string, 0, 5) if m._type != nil { fields = append(fields, connector.FieldType) } if m.name != nil { fields = append(fields, connector.FieldName) } if m.resource_version != nil { fields = append(fields, connector.FieldResourceVersion) } if m._config != nil { fields = append(fields, connector.FieldConfig) } if m.grant_types != nil { fields = append(fields, connector.FieldGrantTypes) } return fields } // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. func (m *ConnectorMutation) Field(name string) (ent.Value, bool) { switch name { case connector.FieldType: return m.GetType() case connector.FieldName: return m.Name() case connector.FieldResourceVersion: return m.ResourceVersion() case connector.FieldConfig: return m.Config() case connector.FieldGrantTypes: return m.GrantTypes() } return nil, false } // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. func (m *ConnectorMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { case connector.FieldType: return m.OldType(ctx) case connector.FieldName: return m.OldName(ctx) case connector.FieldResourceVersion: return m.OldResourceVersion(ctx) case connector.FieldConfig: return m.OldConfig(ctx) case connector.FieldGrantTypes: return m.OldGrantTypes(ctx) } return nil, fmt.Errorf("unknown Connector field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *ConnectorMutation) SetField(name string, value ent.Value) error { switch name { case connector.FieldType: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetType(v) return nil case connector.FieldName: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetName(v) return nil case connector.FieldResourceVersion: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetResourceVersion(v) return nil case connector.FieldConfig: v, ok := value.([]byte) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetConfig(v) return nil case connector.FieldGrantTypes: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetGrantTypes(v) return nil } return fmt.Errorf("unknown Connector field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *ConnectorMutation) AddedFields() []string { return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *ConnectorMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *ConnectorMutation) AddField(name string, value ent.Value) error { switch name { } return fmt.Errorf("unknown Connector numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *ConnectorMutation) ClearedFields() []string { var fields []string if m.FieldCleared(connector.FieldGrantTypes) { fields = append(fields, connector.FieldGrantTypes) } return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. func (m *ConnectorMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *ConnectorMutation) ClearField(name string) error { switch name { case connector.FieldGrantTypes: m.ClearGrantTypes() return nil } return fmt.Errorf("unknown Connector nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. func (m *ConnectorMutation) ResetField(name string) error { switch name { case connector.FieldType: m.ResetType() return nil case connector.FieldName: m.ResetName() return nil case connector.FieldResourceVersion: m.ResetResourceVersion() return nil case connector.FieldConfig: m.ResetConfig() return nil case connector.FieldGrantTypes: m.ResetGrantTypes() return nil } return fmt.Errorf("unknown Connector field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *ConnectorMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *ConnectorMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *ConnectorMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *ConnectorMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *ConnectorMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *ConnectorMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *ConnectorMutation) ClearEdge(name string) error { return fmt.Errorf("unknown Connector unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *ConnectorMutation) ResetEdge(name string) error { return fmt.Errorf("unknown Connector edge %s", name) } // DeviceRequestMutation represents an operation that mutates the DeviceRequest nodes in the graph. type DeviceRequestMutation struct { config op Op typ string id *int user_code *string device_code *string client_id *string client_secret *string scopes *[]string appendscopes []string expiry *time.Time clearedFields map[string]struct{} done bool oldValue func(context.Context) (*DeviceRequest, error) predicates []predicate.DeviceRequest } var _ ent.Mutation = (*DeviceRequestMutation)(nil) // devicerequestOption allows management of the mutation configuration using functional options. type devicerequestOption func(*DeviceRequestMutation) // newDeviceRequestMutation creates new mutation for the DeviceRequest entity. func newDeviceRequestMutation(c config, op Op, opts ...devicerequestOption) *DeviceRequestMutation { m := &DeviceRequestMutation{ config: c, op: op, typ: TypeDeviceRequest, clearedFields: make(map[string]struct{}), } for _, opt := range opts { opt(m) } return m } // withDeviceRequestID sets the ID field of the mutation. func withDeviceRequestID(id int) devicerequestOption { return func(m *DeviceRequestMutation) { var ( err error once sync.Once value *DeviceRequest ) m.oldValue = func(ctx context.Context) (*DeviceRequest, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { value, err = m.Client().DeviceRequest.Get(ctx, id) } }) return value, err } m.id = &id } } // withDeviceRequest sets the old DeviceRequest of the mutation. func withDeviceRequest(node *DeviceRequest) devicerequestOption { return func(m *DeviceRequestMutation) { m.oldValue = func(context.Context) (*DeviceRequest, error) { return node, nil } m.id = &node.ID } } // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. func (m DeviceRequestMutation) Client() *Client { client := &Client{config: m.config} client.init() return client } // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. func (m DeviceRequestMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } tx := &Tx{config: m.config} tx.init() return tx, nil } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. func (m *DeviceRequestMutation) ID() (id int, exists bool) { if m.id == nil { return } return *m.id, true } // IDs queries the database and returns the entity ids that match the mutation's predicate. // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. func (m *DeviceRequestMutation) IDs(ctx context.Context) ([]int, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { return []int{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): return m.Client().DeviceRequest.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetUserCode sets the "user_code" field. func (m *DeviceRequestMutation) SetUserCode(s string) { m.user_code = &s } // UserCode returns the value of the "user_code" field in the mutation. func (m *DeviceRequestMutation) UserCode() (r string, exists bool) { v := m.user_code if v == nil { return } return *v, true } // OldUserCode returns the old "user_code" field's value of the DeviceRequest entity. // If the DeviceRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceRequestMutation) OldUserCode(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUserCode is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldUserCode requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldUserCode: %w", err) } return oldValue.UserCode, nil } // ResetUserCode resets all changes to the "user_code" field. func (m *DeviceRequestMutation) ResetUserCode() { m.user_code = nil } // SetDeviceCode sets the "device_code" field. func (m *DeviceRequestMutation) SetDeviceCode(s string) { m.device_code = &s } // DeviceCode returns the value of the "device_code" field in the mutation. func (m *DeviceRequestMutation) DeviceCode() (r string, exists bool) { v := m.device_code if v == nil { return } return *v, true } // OldDeviceCode returns the old "device_code" field's value of the DeviceRequest entity. // If the DeviceRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceRequestMutation) OldDeviceCode(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldDeviceCode is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldDeviceCode requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldDeviceCode: %w", err) } return oldValue.DeviceCode, nil } // ResetDeviceCode resets all changes to the "device_code" field. func (m *DeviceRequestMutation) ResetDeviceCode() { m.device_code = nil } // SetClientID sets the "client_id" field. func (m *DeviceRequestMutation) SetClientID(s string) { m.client_id = &s } // ClientID returns the value of the "client_id" field in the mutation. func (m *DeviceRequestMutation) ClientID() (r string, exists bool) { v := m.client_id if v == nil { return } return *v, true } // OldClientID returns the old "client_id" field's value of the DeviceRequest entity. // If the DeviceRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceRequestMutation) OldClientID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClientID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClientID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClientID: %w", err) } return oldValue.ClientID, nil } // ResetClientID resets all changes to the "client_id" field. func (m *DeviceRequestMutation) ResetClientID() { m.client_id = nil } // SetClientSecret sets the "client_secret" field. func (m *DeviceRequestMutation) SetClientSecret(s string) { m.client_secret = &s } // ClientSecret returns the value of the "client_secret" field in the mutation. func (m *DeviceRequestMutation) ClientSecret() (r string, exists bool) { v := m.client_secret if v == nil { return } return *v, true } // OldClientSecret returns the old "client_secret" field's value of the DeviceRequest entity. // If the DeviceRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceRequestMutation) OldClientSecret(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClientSecret is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClientSecret requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClientSecret: %w", err) } return oldValue.ClientSecret, nil } // ResetClientSecret resets all changes to the "client_secret" field. func (m *DeviceRequestMutation) ResetClientSecret() { m.client_secret = nil } // SetScopes sets the "scopes" field. func (m *DeviceRequestMutation) SetScopes(s []string) { m.scopes = &s m.appendscopes = nil } // Scopes returns the value of the "scopes" field in the mutation. func (m *DeviceRequestMutation) Scopes() (r []string, exists bool) { v := m.scopes if v == nil { return } return *v, true } // OldScopes returns the old "scopes" field's value of the DeviceRequest entity. // If the DeviceRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceRequestMutation) OldScopes(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldScopes is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldScopes requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldScopes: %w", err) } return oldValue.Scopes, nil } // AppendScopes adds s to the "scopes" field. func (m *DeviceRequestMutation) AppendScopes(s []string) { m.appendscopes = append(m.appendscopes, s...) } // AppendedScopes returns the list of values that were appended to the "scopes" field in this mutation. func (m *DeviceRequestMutation) AppendedScopes() ([]string, bool) { if len(m.appendscopes) == 0 { return nil, false } return m.appendscopes, true } // ClearScopes clears the value of the "scopes" field. func (m *DeviceRequestMutation) ClearScopes() { m.scopes = nil m.appendscopes = nil m.clearedFields[devicerequest.FieldScopes] = struct{}{} } // ScopesCleared returns if the "scopes" field was cleared in this mutation. func (m *DeviceRequestMutation) ScopesCleared() bool { _, ok := m.clearedFields[devicerequest.FieldScopes] return ok } // ResetScopes resets all changes to the "scopes" field. func (m *DeviceRequestMutation) ResetScopes() { m.scopes = nil m.appendscopes = nil delete(m.clearedFields, devicerequest.FieldScopes) } // SetExpiry sets the "expiry" field. func (m *DeviceRequestMutation) SetExpiry(t time.Time) { m.expiry = &t } // Expiry returns the value of the "expiry" field in the mutation. func (m *DeviceRequestMutation) Expiry() (r time.Time, exists bool) { v := m.expiry if v == nil { return } return *v, true } // OldExpiry returns the old "expiry" field's value of the DeviceRequest entity. // If the DeviceRequest object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceRequestMutation) OldExpiry(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldExpiry is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldExpiry requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldExpiry: %w", err) } return oldValue.Expiry, nil } // ResetExpiry resets all changes to the "expiry" field. func (m *DeviceRequestMutation) ResetExpiry() { m.expiry = nil } // Where appends a list predicates to the DeviceRequestMutation builder. func (m *DeviceRequestMutation) Where(ps ...predicate.DeviceRequest) { m.predicates = append(m.predicates, ps...) } // WhereP appends storage-level predicates to the DeviceRequestMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. func (m *DeviceRequestMutation) WhereP(ps ...func(*sql.Selector)) { p := make([]predicate.DeviceRequest, len(ps)) for i := range ps { p[i] = ps[i] } m.Where(p...) } // Op returns the operation name. func (m *DeviceRequestMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. func (m *DeviceRequestMutation) SetOp(op Op) { m.op = op } // Type returns the node type of this mutation (DeviceRequest). func (m *DeviceRequestMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *DeviceRequestMutation) Fields() []string { fields := make([]string, 0, 6) if m.user_code != nil { fields = append(fields, devicerequest.FieldUserCode) } if m.device_code != nil { fields = append(fields, devicerequest.FieldDeviceCode) } if m.client_id != nil { fields = append(fields, devicerequest.FieldClientID) } if m.client_secret != nil { fields = append(fields, devicerequest.FieldClientSecret) } if m.scopes != nil { fields = append(fields, devicerequest.FieldScopes) } if m.expiry != nil { fields = append(fields, devicerequest.FieldExpiry) } return fields } // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. func (m *DeviceRequestMutation) Field(name string) (ent.Value, bool) { switch name { case devicerequest.FieldUserCode: return m.UserCode() case devicerequest.FieldDeviceCode: return m.DeviceCode() case devicerequest.FieldClientID: return m.ClientID() case devicerequest.FieldClientSecret: return m.ClientSecret() case devicerequest.FieldScopes: return m.Scopes() case devicerequest.FieldExpiry: return m.Expiry() } return nil, false } // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. func (m *DeviceRequestMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { case devicerequest.FieldUserCode: return m.OldUserCode(ctx) case devicerequest.FieldDeviceCode: return m.OldDeviceCode(ctx) case devicerequest.FieldClientID: return m.OldClientID(ctx) case devicerequest.FieldClientSecret: return m.OldClientSecret(ctx) case devicerequest.FieldScopes: return m.OldScopes(ctx) case devicerequest.FieldExpiry: return m.OldExpiry(ctx) } return nil, fmt.Errorf("unknown DeviceRequest field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *DeviceRequestMutation) SetField(name string, value ent.Value) error { switch name { case devicerequest.FieldUserCode: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUserCode(v) return nil case devicerequest.FieldDeviceCode: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetDeviceCode(v) return nil case devicerequest.FieldClientID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClientID(v) return nil case devicerequest.FieldClientSecret: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClientSecret(v) return nil case devicerequest.FieldScopes: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetScopes(v) return nil case devicerequest.FieldExpiry: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetExpiry(v) return nil } return fmt.Errorf("unknown DeviceRequest field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *DeviceRequestMutation) AddedFields() []string { return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *DeviceRequestMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *DeviceRequestMutation) AddField(name string, value ent.Value) error { switch name { } return fmt.Errorf("unknown DeviceRequest numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *DeviceRequestMutation) ClearedFields() []string { var fields []string if m.FieldCleared(devicerequest.FieldScopes) { fields = append(fields, devicerequest.FieldScopes) } return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. func (m *DeviceRequestMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *DeviceRequestMutation) ClearField(name string) error { switch name { case devicerequest.FieldScopes: m.ClearScopes() return nil } return fmt.Errorf("unknown DeviceRequest nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. func (m *DeviceRequestMutation) ResetField(name string) error { switch name { case devicerequest.FieldUserCode: m.ResetUserCode() return nil case devicerequest.FieldDeviceCode: m.ResetDeviceCode() return nil case devicerequest.FieldClientID: m.ResetClientID() return nil case devicerequest.FieldClientSecret: m.ResetClientSecret() return nil case devicerequest.FieldScopes: m.ResetScopes() return nil case devicerequest.FieldExpiry: m.ResetExpiry() return nil } return fmt.Errorf("unknown DeviceRequest field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *DeviceRequestMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *DeviceRequestMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *DeviceRequestMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *DeviceRequestMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *DeviceRequestMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *DeviceRequestMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *DeviceRequestMutation) ClearEdge(name string) error { return fmt.Errorf("unknown DeviceRequest unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *DeviceRequestMutation) ResetEdge(name string) error { return fmt.Errorf("unknown DeviceRequest edge %s", name) } // DeviceTokenMutation represents an operation that mutates the DeviceToken nodes in the graph. type DeviceTokenMutation struct { config op Op typ string id *int device_code *string status *string token *[]byte expiry *time.Time last_request *time.Time poll_interval *int addpoll_interval *int code_challenge *string code_challenge_method *string clearedFields map[string]struct{} done bool oldValue func(context.Context) (*DeviceToken, error) predicates []predicate.DeviceToken } var _ ent.Mutation = (*DeviceTokenMutation)(nil) // devicetokenOption allows management of the mutation configuration using functional options. type devicetokenOption func(*DeviceTokenMutation) // newDeviceTokenMutation creates new mutation for the DeviceToken entity. func newDeviceTokenMutation(c config, op Op, opts ...devicetokenOption) *DeviceTokenMutation { m := &DeviceTokenMutation{ config: c, op: op, typ: TypeDeviceToken, clearedFields: make(map[string]struct{}), } for _, opt := range opts { opt(m) } return m } // withDeviceTokenID sets the ID field of the mutation. func withDeviceTokenID(id int) devicetokenOption { return func(m *DeviceTokenMutation) { var ( err error once sync.Once value *DeviceToken ) m.oldValue = func(ctx context.Context) (*DeviceToken, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { value, err = m.Client().DeviceToken.Get(ctx, id) } }) return value, err } m.id = &id } } // withDeviceToken sets the old DeviceToken of the mutation. func withDeviceToken(node *DeviceToken) devicetokenOption { return func(m *DeviceTokenMutation) { m.oldValue = func(context.Context) (*DeviceToken, error) { return node, nil } m.id = &node.ID } } // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. func (m DeviceTokenMutation) Client() *Client { client := &Client{config: m.config} client.init() return client } // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. func (m DeviceTokenMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } tx := &Tx{config: m.config} tx.init() return tx, nil } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. func (m *DeviceTokenMutation) ID() (id int, exists bool) { if m.id == nil { return } return *m.id, true } // IDs queries the database and returns the entity ids that match the mutation's predicate. // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. func (m *DeviceTokenMutation) IDs(ctx context.Context) ([]int, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { return []int{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): return m.Client().DeviceToken.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetDeviceCode sets the "device_code" field. func (m *DeviceTokenMutation) SetDeviceCode(s string) { m.device_code = &s } // DeviceCode returns the value of the "device_code" field in the mutation. func (m *DeviceTokenMutation) DeviceCode() (r string, exists bool) { v := m.device_code if v == nil { return } return *v, true } // OldDeviceCode returns the old "device_code" field's value of the DeviceToken entity. // If the DeviceToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceTokenMutation) OldDeviceCode(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldDeviceCode is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldDeviceCode requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldDeviceCode: %w", err) } return oldValue.DeviceCode, nil } // ResetDeviceCode resets all changes to the "device_code" field. func (m *DeviceTokenMutation) ResetDeviceCode() { m.device_code = nil } // SetStatus sets the "status" field. func (m *DeviceTokenMutation) SetStatus(s string) { m.status = &s } // Status returns the value of the "status" field in the mutation. func (m *DeviceTokenMutation) Status() (r string, exists bool) { v := m.status if v == nil { return } return *v, true } // OldStatus returns the old "status" field's value of the DeviceToken entity. // If the DeviceToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceTokenMutation) OldStatus(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldStatus is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldStatus requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldStatus: %w", err) } return oldValue.Status, nil } // ResetStatus resets all changes to the "status" field. func (m *DeviceTokenMutation) ResetStatus() { m.status = nil } // SetToken sets the "token" field. func (m *DeviceTokenMutation) SetToken(b []byte) { m.token = &b } // Token returns the value of the "token" field in the mutation. func (m *DeviceTokenMutation) Token() (r []byte, exists bool) { v := m.token if v == nil { return } return *v, true } // OldToken returns the old "token" field's value of the DeviceToken entity. // If the DeviceToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceTokenMutation) OldToken(ctx context.Context) (v *[]byte, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldToken is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldToken requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldToken: %w", err) } return oldValue.Token, nil } // ClearToken clears the value of the "token" field. func (m *DeviceTokenMutation) ClearToken() { m.token = nil m.clearedFields[devicetoken.FieldToken] = struct{}{} } // TokenCleared returns if the "token" field was cleared in this mutation. func (m *DeviceTokenMutation) TokenCleared() bool { _, ok := m.clearedFields[devicetoken.FieldToken] return ok } // ResetToken resets all changes to the "token" field. func (m *DeviceTokenMutation) ResetToken() { m.token = nil delete(m.clearedFields, devicetoken.FieldToken) } // SetExpiry sets the "expiry" field. func (m *DeviceTokenMutation) SetExpiry(t time.Time) { m.expiry = &t } // Expiry returns the value of the "expiry" field in the mutation. func (m *DeviceTokenMutation) Expiry() (r time.Time, exists bool) { v := m.expiry if v == nil { return } return *v, true } // OldExpiry returns the old "expiry" field's value of the DeviceToken entity. // If the DeviceToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceTokenMutation) OldExpiry(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldExpiry is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldExpiry requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldExpiry: %w", err) } return oldValue.Expiry, nil } // ResetExpiry resets all changes to the "expiry" field. func (m *DeviceTokenMutation) ResetExpiry() { m.expiry = nil } // SetLastRequest sets the "last_request" field. func (m *DeviceTokenMutation) SetLastRequest(t time.Time) { m.last_request = &t } // LastRequest returns the value of the "last_request" field in the mutation. func (m *DeviceTokenMutation) LastRequest() (r time.Time, exists bool) { v := m.last_request if v == nil { return } return *v, true } // OldLastRequest returns the old "last_request" field's value of the DeviceToken entity. // If the DeviceToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceTokenMutation) OldLastRequest(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastRequest is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldLastRequest requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldLastRequest: %w", err) } return oldValue.LastRequest, nil } // ResetLastRequest resets all changes to the "last_request" field. func (m *DeviceTokenMutation) ResetLastRequest() { m.last_request = nil } // SetPollInterval sets the "poll_interval" field. func (m *DeviceTokenMutation) SetPollInterval(i int) { m.poll_interval = &i m.addpoll_interval = nil } // PollInterval returns the value of the "poll_interval" field in the mutation. func (m *DeviceTokenMutation) PollInterval() (r int, exists bool) { v := m.poll_interval if v == nil { return } return *v, true } // OldPollInterval returns the old "poll_interval" field's value of the DeviceToken entity. // If the DeviceToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceTokenMutation) OldPollInterval(ctx context.Context) (v int, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldPollInterval is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldPollInterval requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldPollInterval: %w", err) } return oldValue.PollInterval, nil } // AddPollInterval adds i to the "poll_interval" field. func (m *DeviceTokenMutation) AddPollInterval(i int) { if m.addpoll_interval != nil { *m.addpoll_interval += i } else { m.addpoll_interval = &i } } // AddedPollInterval returns the value that was added to the "poll_interval" field in this mutation. func (m *DeviceTokenMutation) AddedPollInterval() (r int, exists bool) { v := m.addpoll_interval if v == nil { return } return *v, true } // ResetPollInterval resets all changes to the "poll_interval" field. func (m *DeviceTokenMutation) ResetPollInterval() { m.poll_interval = nil m.addpoll_interval = nil } // SetCodeChallenge sets the "code_challenge" field. func (m *DeviceTokenMutation) SetCodeChallenge(s string) { m.code_challenge = &s } // CodeChallenge returns the value of the "code_challenge" field in the mutation. func (m *DeviceTokenMutation) CodeChallenge() (r string, exists bool) { v := m.code_challenge if v == nil { return } return *v, true } // OldCodeChallenge returns the old "code_challenge" field's value of the DeviceToken entity. // If the DeviceToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceTokenMutation) OldCodeChallenge(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCodeChallenge is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldCodeChallenge requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldCodeChallenge: %w", err) } return oldValue.CodeChallenge, nil } // ResetCodeChallenge resets all changes to the "code_challenge" field. func (m *DeviceTokenMutation) ResetCodeChallenge() { m.code_challenge = nil } // SetCodeChallengeMethod sets the "code_challenge_method" field. func (m *DeviceTokenMutation) SetCodeChallengeMethod(s string) { m.code_challenge_method = &s } // CodeChallengeMethod returns the value of the "code_challenge_method" field in the mutation. func (m *DeviceTokenMutation) CodeChallengeMethod() (r string, exists bool) { v := m.code_challenge_method if v == nil { return } return *v, true } // OldCodeChallengeMethod returns the old "code_challenge_method" field's value of the DeviceToken entity. // If the DeviceToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *DeviceTokenMutation) OldCodeChallengeMethod(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCodeChallengeMethod is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldCodeChallengeMethod requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldCodeChallengeMethod: %w", err) } return oldValue.CodeChallengeMethod, nil } // ResetCodeChallengeMethod resets all changes to the "code_challenge_method" field. func (m *DeviceTokenMutation) ResetCodeChallengeMethod() { m.code_challenge_method = nil } // Where appends a list predicates to the DeviceTokenMutation builder. func (m *DeviceTokenMutation) Where(ps ...predicate.DeviceToken) { m.predicates = append(m.predicates, ps...) } // WhereP appends storage-level predicates to the DeviceTokenMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. func (m *DeviceTokenMutation) WhereP(ps ...func(*sql.Selector)) { p := make([]predicate.DeviceToken, len(ps)) for i := range ps { p[i] = ps[i] } m.Where(p...) } // Op returns the operation name. func (m *DeviceTokenMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. func (m *DeviceTokenMutation) SetOp(op Op) { m.op = op } // Type returns the node type of this mutation (DeviceToken). func (m *DeviceTokenMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *DeviceTokenMutation) Fields() []string { fields := make([]string, 0, 8) if m.device_code != nil { fields = append(fields, devicetoken.FieldDeviceCode) } if m.status != nil { fields = append(fields, devicetoken.FieldStatus) } if m.token != nil { fields = append(fields, devicetoken.FieldToken) } if m.expiry != nil { fields = append(fields, devicetoken.FieldExpiry) } if m.last_request != nil { fields = append(fields, devicetoken.FieldLastRequest) } if m.poll_interval != nil { fields = append(fields, devicetoken.FieldPollInterval) } if m.code_challenge != nil { fields = append(fields, devicetoken.FieldCodeChallenge) } if m.code_challenge_method != nil { fields = append(fields, devicetoken.FieldCodeChallengeMethod) } return fields } // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. func (m *DeviceTokenMutation) Field(name string) (ent.Value, bool) { switch name { case devicetoken.FieldDeviceCode: return m.DeviceCode() case devicetoken.FieldStatus: return m.Status() case devicetoken.FieldToken: return m.Token() case devicetoken.FieldExpiry: return m.Expiry() case devicetoken.FieldLastRequest: return m.LastRequest() case devicetoken.FieldPollInterval: return m.PollInterval() case devicetoken.FieldCodeChallenge: return m.CodeChallenge() case devicetoken.FieldCodeChallengeMethod: return m.CodeChallengeMethod() } return nil, false } // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. func (m *DeviceTokenMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { case devicetoken.FieldDeviceCode: return m.OldDeviceCode(ctx) case devicetoken.FieldStatus: return m.OldStatus(ctx) case devicetoken.FieldToken: return m.OldToken(ctx) case devicetoken.FieldExpiry: return m.OldExpiry(ctx) case devicetoken.FieldLastRequest: return m.OldLastRequest(ctx) case devicetoken.FieldPollInterval: return m.OldPollInterval(ctx) case devicetoken.FieldCodeChallenge: return m.OldCodeChallenge(ctx) case devicetoken.FieldCodeChallengeMethod: return m.OldCodeChallengeMethod(ctx) } return nil, fmt.Errorf("unknown DeviceToken field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *DeviceTokenMutation) SetField(name string, value ent.Value) error { switch name { case devicetoken.FieldDeviceCode: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetDeviceCode(v) return nil case devicetoken.FieldStatus: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetStatus(v) return nil case devicetoken.FieldToken: v, ok := value.([]byte) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetToken(v) return nil case devicetoken.FieldExpiry: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetExpiry(v) return nil case devicetoken.FieldLastRequest: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetLastRequest(v) return nil case devicetoken.FieldPollInterval: v, ok := value.(int) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetPollInterval(v) return nil case devicetoken.FieldCodeChallenge: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCodeChallenge(v) return nil case devicetoken.FieldCodeChallengeMethod: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCodeChallengeMethod(v) return nil } return fmt.Errorf("unknown DeviceToken field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *DeviceTokenMutation) AddedFields() []string { var fields []string if m.addpoll_interval != nil { fields = append(fields, devicetoken.FieldPollInterval) } return fields } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *DeviceTokenMutation) AddedField(name string) (ent.Value, bool) { switch name { case devicetoken.FieldPollInterval: return m.AddedPollInterval() } return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *DeviceTokenMutation) AddField(name string, value ent.Value) error { switch name { case devicetoken.FieldPollInterval: v, ok := value.(int) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.AddPollInterval(v) return nil } return fmt.Errorf("unknown DeviceToken numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *DeviceTokenMutation) ClearedFields() []string { var fields []string if m.FieldCleared(devicetoken.FieldToken) { fields = append(fields, devicetoken.FieldToken) } return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. func (m *DeviceTokenMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *DeviceTokenMutation) ClearField(name string) error { switch name { case devicetoken.FieldToken: m.ClearToken() return nil } return fmt.Errorf("unknown DeviceToken nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. func (m *DeviceTokenMutation) ResetField(name string) error { switch name { case devicetoken.FieldDeviceCode: m.ResetDeviceCode() return nil case devicetoken.FieldStatus: m.ResetStatus() return nil case devicetoken.FieldToken: m.ResetToken() return nil case devicetoken.FieldExpiry: m.ResetExpiry() return nil case devicetoken.FieldLastRequest: m.ResetLastRequest() return nil case devicetoken.FieldPollInterval: m.ResetPollInterval() return nil case devicetoken.FieldCodeChallenge: m.ResetCodeChallenge() return nil case devicetoken.FieldCodeChallengeMethod: m.ResetCodeChallengeMethod() return nil } return fmt.Errorf("unknown DeviceToken field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *DeviceTokenMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *DeviceTokenMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *DeviceTokenMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *DeviceTokenMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *DeviceTokenMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *DeviceTokenMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *DeviceTokenMutation) ClearEdge(name string) error { return fmt.Errorf("unknown DeviceToken unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *DeviceTokenMutation) ResetEdge(name string) error { return fmt.Errorf("unknown DeviceToken edge %s", name) } // KeysMutation represents an operation that mutates the Keys nodes in the graph. type KeysMutation struct { config op Op typ string id *string verification_keys *[]storage.VerificationKey appendverification_keys []storage.VerificationKey signing_key *jose.JSONWebKey signing_key_pub *jose.JSONWebKey next_rotation *time.Time clearedFields map[string]struct{} done bool oldValue func(context.Context) (*Keys, error) predicates []predicate.Keys } var _ ent.Mutation = (*KeysMutation)(nil) // keysOption allows management of the mutation configuration using functional options. type keysOption func(*KeysMutation) // newKeysMutation creates new mutation for the Keys entity. func newKeysMutation(c config, op Op, opts ...keysOption) *KeysMutation { m := &KeysMutation{ config: c, op: op, typ: TypeKeys, clearedFields: make(map[string]struct{}), } for _, opt := range opts { opt(m) } return m } // withKeysID sets the ID field of the mutation. func withKeysID(id string) keysOption { return func(m *KeysMutation) { var ( err error once sync.Once value *Keys ) m.oldValue = func(ctx context.Context) (*Keys, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { value, err = m.Client().Keys.Get(ctx, id) } }) return value, err } m.id = &id } } // withKeys sets the old Keys of the mutation. func withKeys(node *Keys) keysOption { return func(m *KeysMutation) { m.oldValue = func(context.Context) (*Keys, error) { return node, nil } m.id = &node.ID } } // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. func (m KeysMutation) Client() *Client { client := &Client{config: m.config} client.init() return client } // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. func (m KeysMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } tx := &Tx{config: m.config} tx.init() return tx, nil } // SetID sets the value of the id field. Note that this // operation is only accepted on creation of Keys entities. func (m *KeysMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. func (m *KeysMutation) ID() (id string, exists bool) { if m.id == nil { return } return *m.id, true } // IDs queries the database and returns the entity ids that match the mutation's predicate. // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. func (m *KeysMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { return []string{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): return m.Client().Keys.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetVerificationKeys sets the "verification_keys" field. func (m *KeysMutation) SetVerificationKeys(sk []storage.VerificationKey) { m.verification_keys = &sk m.appendverification_keys = nil } // VerificationKeys returns the value of the "verification_keys" field in the mutation. func (m *KeysMutation) VerificationKeys() (r []storage.VerificationKey, exists bool) { v := m.verification_keys if v == nil { return } return *v, true } // OldVerificationKeys returns the old "verification_keys" field's value of the Keys entity. // If the Keys object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *KeysMutation) OldVerificationKeys(ctx context.Context) (v []storage.VerificationKey, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldVerificationKeys is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldVerificationKeys requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldVerificationKeys: %w", err) } return oldValue.VerificationKeys, nil } // AppendVerificationKeys adds sk to the "verification_keys" field. func (m *KeysMutation) AppendVerificationKeys(sk []storage.VerificationKey) { m.appendverification_keys = append(m.appendverification_keys, sk...) } // AppendedVerificationKeys returns the list of values that were appended to the "verification_keys" field in this mutation. func (m *KeysMutation) AppendedVerificationKeys() ([]storage.VerificationKey, bool) { if len(m.appendverification_keys) == 0 { return nil, false } return m.appendverification_keys, true } // ResetVerificationKeys resets all changes to the "verification_keys" field. func (m *KeysMutation) ResetVerificationKeys() { m.verification_keys = nil m.appendverification_keys = nil } // SetSigningKey sets the "signing_key" field. func (m *KeysMutation) SetSigningKey(jwk jose.JSONWebKey) { m.signing_key = &jwk } // SigningKey returns the value of the "signing_key" field in the mutation. func (m *KeysMutation) SigningKey() (r jose.JSONWebKey, exists bool) { v := m.signing_key if v == nil { return } return *v, true } // OldSigningKey returns the old "signing_key" field's value of the Keys entity. // If the Keys object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *KeysMutation) OldSigningKey(ctx context.Context) (v jose.JSONWebKey, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldSigningKey is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldSigningKey requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldSigningKey: %w", err) } return oldValue.SigningKey, nil } // ResetSigningKey resets all changes to the "signing_key" field. func (m *KeysMutation) ResetSigningKey() { m.signing_key = nil } // SetSigningKeyPub sets the "signing_key_pub" field. func (m *KeysMutation) SetSigningKeyPub(jwk jose.JSONWebKey) { m.signing_key_pub = &jwk } // SigningKeyPub returns the value of the "signing_key_pub" field in the mutation. func (m *KeysMutation) SigningKeyPub() (r jose.JSONWebKey, exists bool) { v := m.signing_key_pub if v == nil { return } return *v, true } // OldSigningKeyPub returns the old "signing_key_pub" field's value of the Keys entity. // If the Keys object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *KeysMutation) OldSigningKeyPub(ctx context.Context) (v jose.JSONWebKey, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldSigningKeyPub is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldSigningKeyPub requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldSigningKeyPub: %w", err) } return oldValue.SigningKeyPub, nil } // ResetSigningKeyPub resets all changes to the "signing_key_pub" field. func (m *KeysMutation) ResetSigningKeyPub() { m.signing_key_pub = nil } // SetNextRotation sets the "next_rotation" field. func (m *KeysMutation) SetNextRotation(t time.Time) { m.next_rotation = &t } // NextRotation returns the value of the "next_rotation" field in the mutation. func (m *KeysMutation) NextRotation() (r time.Time, exists bool) { v := m.next_rotation if v == nil { return } return *v, true } // OldNextRotation returns the old "next_rotation" field's value of the Keys entity. // If the Keys object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *KeysMutation) OldNextRotation(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldNextRotation is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldNextRotation requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldNextRotation: %w", err) } return oldValue.NextRotation, nil } // ResetNextRotation resets all changes to the "next_rotation" field. func (m *KeysMutation) ResetNextRotation() { m.next_rotation = nil } // Where appends a list predicates to the KeysMutation builder. func (m *KeysMutation) Where(ps ...predicate.Keys) { m.predicates = append(m.predicates, ps...) } // WhereP appends storage-level predicates to the KeysMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. func (m *KeysMutation) WhereP(ps ...func(*sql.Selector)) { p := make([]predicate.Keys, len(ps)) for i := range ps { p[i] = ps[i] } m.Where(p...) } // Op returns the operation name. func (m *KeysMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. func (m *KeysMutation) SetOp(op Op) { m.op = op } // Type returns the node type of this mutation (Keys). func (m *KeysMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *KeysMutation) Fields() []string { fields := make([]string, 0, 4) if m.verification_keys != nil { fields = append(fields, keys.FieldVerificationKeys) } if m.signing_key != nil { fields = append(fields, keys.FieldSigningKey) } if m.signing_key_pub != nil { fields = append(fields, keys.FieldSigningKeyPub) } if m.next_rotation != nil { fields = append(fields, keys.FieldNextRotation) } return fields } // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. func (m *KeysMutation) Field(name string) (ent.Value, bool) { switch name { case keys.FieldVerificationKeys: return m.VerificationKeys() case keys.FieldSigningKey: return m.SigningKey() case keys.FieldSigningKeyPub: return m.SigningKeyPub() case keys.FieldNextRotation: return m.NextRotation() } return nil, false } // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. func (m *KeysMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { case keys.FieldVerificationKeys: return m.OldVerificationKeys(ctx) case keys.FieldSigningKey: return m.OldSigningKey(ctx) case keys.FieldSigningKeyPub: return m.OldSigningKeyPub(ctx) case keys.FieldNextRotation: return m.OldNextRotation(ctx) } return nil, fmt.Errorf("unknown Keys field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *KeysMutation) SetField(name string, value ent.Value) error { switch name { case keys.FieldVerificationKeys: v, ok := value.([]storage.VerificationKey) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetVerificationKeys(v) return nil case keys.FieldSigningKey: v, ok := value.(jose.JSONWebKey) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetSigningKey(v) return nil case keys.FieldSigningKeyPub: v, ok := value.(jose.JSONWebKey) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetSigningKeyPub(v) return nil case keys.FieldNextRotation: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetNextRotation(v) return nil } return fmt.Errorf("unknown Keys field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *KeysMutation) AddedFields() []string { return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *KeysMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *KeysMutation) AddField(name string, value ent.Value) error { switch name { } return fmt.Errorf("unknown Keys numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *KeysMutation) ClearedFields() []string { return nil } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. func (m *KeysMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *KeysMutation) ClearField(name string) error { return fmt.Errorf("unknown Keys nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. func (m *KeysMutation) ResetField(name string) error { switch name { case keys.FieldVerificationKeys: m.ResetVerificationKeys() return nil case keys.FieldSigningKey: m.ResetSigningKey() return nil case keys.FieldSigningKeyPub: m.ResetSigningKeyPub() return nil case keys.FieldNextRotation: m.ResetNextRotation() return nil } return fmt.Errorf("unknown Keys field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *KeysMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *KeysMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *KeysMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *KeysMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *KeysMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *KeysMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *KeysMutation) ClearEdge(name string) error { return fmt.Errorf("unknown Keys unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *KeysMutation) ResetEdge(name string) error { return fmt.Errorf("unknown Keys edge %s", name) } // OAuth2ClientMutation represents an operation that mutates the OAuth2Client nodes in the graph. type OAuth2ClientMutation struct { config op Op typ string id *string secret *string redirect_uris *[]string appendredirect_uris []string trusted_peers *[]string appendtrusted_peers []string public *bool name *string logo_url *string allowed_connectors *[]string appendallowed_connectors []string mfa_chain *[]string appendmfa_chain []string clearedFields map[string]struct{} done bool oldValue func(context.Context) (*OAuth2Client, error) predicates []predicate.OAuth2Client } var _ ent.Mutation = (*OAuth2ClientMutation)(nil) // oauth2clientOption allows management of the mutation configuration using functional options. type oauth2clientOption func(*OAuth2ClientMutation) // newOAuth2ClientMutation creates new mutation for the OAuth2Client entity. func newOAuth2ClientMutation(c config, op Op, opts ...oauth2clientOption) *OAuth2ClientMutation { m := &OAuth2ClientMutation{ config: c, op: op, typ: TypeOAuth2Client, clearedFields: make(map[string]struct{}), } for _, opt := range opts { opt(m) } return m } // withOAuth2ClientID sets the ID field of the mutation. func withOAuth2ClientID(id string) oauth2clientOption { return func(m *OAuth2ClientMutation) { var ( err error once sync.Once value *OAuth2Client ) m.oldValue = func(ctx context.Context) (*OAuth2Client, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { value, err = m.Client().OAuth2Client.Get(ctx, id) } }) return value, err } m.id = &id } } // withOAuth2Client sets the old OAuth2Client of the mutation. func withOAuth2Client(node *OAuth2Client) oauth2clientOption { return func(m *OAuth2ClientMutation) { m.oldValue = func(context.Context) (*OAuth2Client, error) { return node, nil } m.id = &node.ID } } // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. func (m OAuth2ClientMutation) Client() *Client { client := &Client{config: m.config} client.init() return client } // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. func (m OAuth2ClientMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } tx := &Tx{config: m.config} tx.init() return tx, nil } // SetID sets the value of the id field. Note that this // operation is only accepted on creation of OAuth2Client entities. func (m *OAuth2ClientMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. func (m *OAuth2ClientMutation) ID() (id string, exists bool) { if m.id == nil { return } return *m.id, true } // IDs queries the database and returns the entity ids that match the mutation's predicate. // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. func (m *OAuth2ClientMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { return []string{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): return m.Client().OAuth2Client.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetSecret sets the "secret" field. func (m *OAuth2ClientMutation) SetSecret(s string) { m.secret = &s } // Secret returns the value of the "secret" field in the mutation. func (m *OAuth2ClientMutation) Secret() (r string, exists bool) { v := m.secret if v == nil { return } return *v, true } // OldSecret returns the old "secret" field's value of the OAuth2Client entity. // If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *OAuth2ClientMutation) OldSecret(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldSecret is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldSecret requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldSecret: %w", err) } return oldValue.Secret, nil } // ResetSecret resets all changes to the "secret" field. func (m *OAuth2ClientMutation) ResetSecret() { m.secret = nil } // SetRedirectUris sets the "redirect_uris" field. func (m *OAuth2ClientMutation) SetRedirectUris(s []string) { m.redirect_uris = &s m.appendredirect_uris = nil } // RedirectUris returns the value of the "redirect_uris" field in the mutation. func (m *OAuth2ClientMutation) RedirectUris() (r []string, exists bool) { v := m.redirect_uris if v == nil { return } return *v, true } // OldRedirectUris returns the old "redirect_uris" field's value of the OAuth2Client entity. // If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *OAuth2ClientMutation) OldRedirectUris(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldRedirectUris is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldRedirectUris requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldRedirectUris: %w", err) } return oldValue.RedirectUris, nil } // AppendRedirectUris adds s to the "redirect_uris" field. func (m *OAuth2ClientMutation) AppendRedirectUris(s []string) { m.appendredirect_uris = append(m.appendredirect_uris, s...) } // AppendedRedirectUris returns the list of values that were appended to the "redirect_uris" field in this mutation. func (m *OAuth2ClientMutation) AppendedRedirectUris() ([]string, bool) { if len(m.appendredirect_uris) == 0 { return nil, false } return m.appendredirect_uris, true } // ClearRedirectUris clears the value of the "redirect_uris" field. func (m *OAuth2ClientMutation) ClearRedirectUris() { m.redirect_uris = nil m.appendredirect_uris = nil m.clearedFields[oauth2client.FieldRedirectUris] = struct{}{} } // RedirectUrisCleared returns if the "redirect_uris" field was cleared in this mutation. func (m *OAuth2ClientMutation) RedirectUrisCleared() bool { _, ok := m.clearedFields[oauth2client.FieldRedirectUris] return ok } // ResetRedirectUris resets all changes to the "redirect_uris" field. func (m *OAuth2ClientMutation) ResetRedirectUris() { m.redirect_uris = nil m.appendredirect_uris = nil delete(m.clearedFields, oauth2client.FieldRedirectUris) } // SetTrustedPeers sets the "trusted_peers" field. func (m *OAuth2ClientMutation) SetTrustedPeers(s []string) { m.trusted_peers = &s m.appendtrusted_peers = nil } // TrustedPeers returns the value of the "trusted_peers" field in the mutation. func (m *OAuth2ClientMutation) TrustedPeers() (r []string, exists bool) { v := m.trusted_peers if v == nil { return } return *v, true } // OldTrustedPeers returns the old "trusted_peers" field's value of the OAuth2Client entity. // If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *OAuth2ClientMutation) OldTrustedPeers(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldTrustedPeers is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldTrustedPeers requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldTrustedPeers: %w", err) } return oldValue.TrustedPeers, nil } // AppendTrustedPeers adds s to the "trusted_peers" field. func (m *OAuth2ClientMutation) AppendTrustedPeers(s []string) { m.appendtrusted_peers = append(m.appendtrusted_peers, s...) } // AppendedTrustedPeers returns the list of values that were appended to the "trusted_peers" field in this mutation. func (m *OAuth2ClientMutation) AppendedTrustedPeers() ([]string, bool) { if len(m.appendtrusted_peers) == 0 { return nil, false } return m.appendtrusted_peers, true } // ClearTrustedPeers clears the value of the "trusted_peers" field. func (m *OAuth2ClientMutation) ClearTrustedPeers() { m.trusted_peers = nil m.appendtrusted_peers = nil m.clearedFields[oauth2client.FieldTrustedPeers] = struct{}{} } // TrustedPeersCleared returns if the "trusted_peers" field was cleared in this mutation. func (m *OAuth2ClientMutation) TrustedPeersCleared() bool { _, ok := m.clearedFields[oauth2client.FieldTrustedPeers] return ok } // ResetTrustedPeers resets all changes to the "trusted_peers" field. func (m *OAuth2ClientMutation) ResetTrustedPeers() { m.trusted_peers = nil m.appendtrusted_peers = nil delete(m.clearedFields, oauth2client.FieldTrustedPeers) } // SetPublic sets the "public" field. func (m *OAuth2ClientMutation) SetPublic(b bool) { m.public = &b } // Public returns the value of the "public" field in the mutation. func (m *OAuth2ClientMutation) Public() (r bool, exists bool) { v := m.public if v == nil { return } return *v, true } // OldPublic returns the old "public" field's value of the OAuth2Client entity. // If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *OAuth2ClientMutation) OldPublic(ctx context.Context) (v bool, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldPublic is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldPublic requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldPublic: %w", err) } return oldValue.Public, nil } // ResetPublic resets all changes to the "public" field. func (m *OAuth2ClientMutation) ResetPublic() { m.public = nil } // SetName sets the "name" field. func (m *OAuth2ClientMutation) SetName(s string) { m.name = &s } // Name returns the value of the "name" field in the mutation. func (m *OAuth2ClientMutation) Name() (r string, exists bool) { v := m.name if v == nil { return } return *v, true } // OldName returns the old "name" field's value of the OAuth2Client entity. // If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *OAuth2ClientMutation) OldName(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldName is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldName requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldName: %w", err) } return oldValue.Name, nil } // ResetName resets all changes to the "name" field. func (m *OAuth2ClientMutation) ResetName() { m.name = nil } // SetLogoURL sets the "logo_url" field. func (m *OAuth2ClientMutation) SetLogoURL(s string) { m.logo_url = &s } // LogoURL returns the value of the "logo_url" field in the mutation. func (m *OAuth2ClientMutation) LogoURL() (r string, exists bool) { v := m.logo_url if v == nil { return } return *v, true } // OldLogoURL returns the old "logo_url" field's value of the OAuth2Client entity. // If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *OAuth2ClientMutation) OldLogoURL(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLogoURL is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldLogoURL requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldLogoURL: %w", err) } return oldValue.LogoURL, nil } // ResetLogoURL resets all changes to the "logo_url" field. func (m *OAuth2ClientMutation) ResetLogoURL() { m.logo_url = nil } // SetAllowedConnectors sets the "allowed_connectors" field. func (m *OAuth2ClientMutation) SetAllowedConnectors(s []string) { m.allowed_connectors = &s m.appendallowed_connectors = nil } // AllowedConnectors returns the value of the "allowed_connectors" field in the mutation. func (m *OAuth2ClientMutation) AllowedConnectors() (r []string, exists bool) { v := m.allowed_connectors if v == nil { return } return *v, true } // OldAllowedConnectors returns the old "allowed_connectors" field's value of the OAuth2Client entity. // If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *OAuth2ClientMutation) OldAllowedConnectors(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldAllowedConnectors is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldAllowedConnectors requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldAllowedConnectors: %w", err) } return oldValue.AllowedConnectors, nil } // AppendAllowedConnectors adds s to the "allowed_connectors" field. func (m *OAuth2ClientMutation) AppendAllowedConnectors(s []string) { m.appendallowed_connectors = append(m.appendallowed_connectors, s...) } // AppendedAllowedConnectors returns the list of values that were appended to the "allowed_connectors" field in this mutation. func (m *OAuth2ClientMutation) AppendedAllowedConnectors() ([]string, bool) { if len(m.appendallowed_connectors) == 0 { return nil, false } return m.appendallowed_connectors, true } // ClearAllowedConnectors clears the value of the "allowed_connectors" field. func (m *OAuth2ClientMutation) ClearAllowedConnectors() { m.allowed_connectors = nil m.appendallowed_connectors = nil m.clearedFields[oauth2client.FieldAllowedConnectors] = struct{}{} } // AllowedConnectorsCleared returns if the "allowed_connectors" field was cleared in this mutation. func (m *OAuth2ClientMutation) AllowedConnectorsCleared() bool { _, ok := m.clearedFields[oauth2client.FieldAllowedConnectors] return ok } // ResetAllowedConnectors resets all changes to the "allowed_connectors" field. func (m *OAuth2ClientMutation) ResetAllowedConnectors() { m.allowed_connectors = nil m.appendallowed_connectors = nil delete(m.clearedFields, oauth2client.FieldAllowedConnectors) } // SetMfaChain sets the "mfa_chain" field. func (m *OAuth2ClientMutation) SetMfaChain(s []string) { m.mfa_chain = &s m.appendmfa_chain = nil } // MfaChain returns the value of the "mfa_chain" field in the mutation. func (m *OAuth2ClientMutation) MfaChain() (r []string, exists bool) { v := m.mfa_chain if v == nil { return } return *v, true } // OldMfaChain returns the old "mfa_chain" field's value of the OAuth2Client entity. // If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *OAuth2ClientMutation) OldMfaChain(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldMfaChain is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldMfaChain requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldMfaChain: %w", err) } return oldValue.MfaChain, nil } // AppendMfaChain adds s to the "mfa_chain" field. func (m *OAuth2ClientMutation) AppendMfaChain(s []string) { m.appendmfa_chain = append(m.appendmfa_chain, s...) } // AppendedMfaChain returns the list of values that were appended to the "mfa_chain" field in this mutation. func (m *OAuth2ClientMutation) AppendedMfaChain() ([]string, bool) { if len(m.appendmfa_chain) == 0 { return nil, false } return m.appendmfa_chain, true } // ClearMfaChain clears the value of the "mfa_chain" field. func (m *OAuth2ClientMutation) ClearMfaChain() { m.mfa_chain = nil m.appendmfa_chain = nil m.clearedFields[oauth2client.FieldMfaChain] = struct{}{} } // MfaChainCleared returns if the "mfa_chain" field was cleared in this mutation. func (m *OAuth2ClientMutation) MfaChainCleared() bool { _, ok := m.clearedFields[oauth2client.FieldMfaChain] return ok } // ResetMfaChain resets all changes to the "mfa_chain" field. func (m *OAuth2ClientMutation) ResetMfaChain() { m.mfa_chain = nil m.appendmfa_chain = nil delete(m.clearedFields, oauth2client.FieldMfaChain) } // Where appends a list predicates to the OAuth2ClientMutation builder. func (m *OAuth2ClientMutation) Where(ps ...predicate.OAuth2Client) { m.predicates = append(m.predicates, ps...) } // WhereP appends storage-level predicates to the OAuth2ClientMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. func (m *OAuth2ClientMutation) WhereP(ps ...func(*sql.Selector)) { p := make([]predicate.OAuth2Client, len(ps)) for i := range ps { p[i] = ps[i] } m.Where(p...) } // Op returns the operation name. func (m *OAuth2ClientMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. func (m *OAuth2ClientMutation) SetOp(op Op) { m.op = op } // Type returns the node type of this mutation (OAuth2Client). func (m *OAuth2ClientMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *OAuth2ClientMutation) Fields() []string { fields := make([]string, 0, 8) if m.secret != nil { fields = append(fields, oauth2client.FieldSecret) } if m.redirect_uris != nil { fields = append(fields, oauth2client.FieldRedirectUris) } if m.trusted_peers != nil { fields = append(fields, oauth2client.FieldTrustedPeers) } if m.public != nil { fields = append(fields, oauth2client.FieldPublic) } if m.name != nil { fields = append(fields, oauth2client.FieldName) } if m.logo_url != nil { fields = append(fields, oauth2client.FieldLogoURL) } if m.allowed_connectors != nil { fields = append(fields, oauth2client.FieldAllowedConnectors) } if m.mfa_chain != nil { fields = append(fields, oauth2client.FieldMfaChain) } return fields } // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. func (m *OAuth2ClientMutation) Field(name string) (ent.Value, bool) { switch name { case oauth2client.FieldSecret: return m.Secret() case oauth2client.FieldRedirectUris: return m.RedirectUris() case oauth2client.FieldTrustedPeers: return m.TrustedPeers() case oauth2client.FieldPublic: return m.Public() case oauth2client.FieldName: return m.Name() case oauth2client.FieldLogoURL: return m.LogoURL() case oauth2client.FieldAllowedConnectors: return m.AllowedConnectors() case oauth2client.FieldMfaChain: return m.MfaChain() } return nil, false } // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. func (m *OAuth2ClientMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { case oauth2client.FieldSecret: return m.OldSecret(ctx) case oauth2client.FieldRedirectUris: return m.OldRedirectUris(ctx) case oauth2client.FieldTrustedPeers: return m.OldTrustedPeers(ctx) case oauth2client.FieldPublic: return m.OldPublic(ctx) case oauth2client.FieldName: return m.OldName(ctx) case oauth2client.FieldLogoURL: return m.OldLogoURL(ctx) case oauth2client.FieldAllowedConnectors: return m.OldAllowedConnectors(ctx) case oauth2client.FieldMfaChain: return m.OldMfaChain(ctx) } return nil, fmt.Errorf("unknown OAuth2Client field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *OAuth2ClientMutation) SetField(name string, value ent.Value) error { switch name { case oauth2client.FieldSecret: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetSecret(v) return nil case oauth2client.FieldRedirectUris: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetRedirectUris(v) return nil case oauth2client.FieldTrustedPeers: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetTrustedPeers(v) return nil case oauth2client.FieldPublic: v, ok := value.(bool) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetPublic(v) return nil case oauth2client.FieldName: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetName(v) return nil case oauth2client.FieldLogoURL: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetLogoURL(v) return nil case oauth2client.FieldAllowedConnectors: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetAllowedConnectors(v) return nil case oauth2client.FieldMfaChain: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetMfaChain(v) return nil } return fmt.Errorf("unknown OAuth2Client field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *OAuth2ClientMutation) AddedFields() []string { return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *OAuth2ClientMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *OAuth2ClientMutation) AddField(name string, value ent.Value) error { switch name { } return fmt.Errorf("unknown OAuth2Client numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *OAuth2ClientMutation) ClearedFields() []string { var fields []string if m.FieldCleared(oauth2client.FieldRedirectUris) { fields = append(fields, oauth2client.FieldRedirectUris) } if m.FieldCleared(oauth2client.FieldTrustedPeers) { fields = append(fields, oauth2client.FieldTrustedPeers) } if m.FieldCleared(oauth2client.FieldAllowedConnectors) { fields = append(fields, oauth2client.FieldAllowedConnectors) } if m.FieldCleared(oauth2client.FieldMfaChain) { fields = append(fields, oauth2client.FieldMfaChain) } return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. func (m *OAuth2ClientMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *OAuth2ClientMutation) ClearField(name string) error { switch name { case oauth2client.FieldRedirectUris: m.ClearRedirectUris() return nil case oauth2client.FieldTrustedPeers: m.ClearTrustedPeers() return nil case oauth2client.FieldAllowedConnectors: m.ClearAllowedConnectors() return nil case oauth2client.FieldMfaChain: m.ClearMfaChain() return nil } return fmt.Errorf("unknown OAuth2Client nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. func (m *OAuth2ClientMutation) ResetField(name string) error { switch name { case oauth2client.FieldSecret: m.ResetSecret() return nil case oauth2client.FieldRedirectUris: m.ResetRedirectUris() return nil case oauth2client.FieldTrustedPeers: m.ResetTrustedPeers() return nil case oauth2client.FieldPublic: m.ResetPublic() return nil case oauth2client.FieldName: m.ResetName() return nil case oauth2client.FieldLogoURL: m.ResetLogoURL() return nil case oauth2client.FieldAllowedConnectors: m.ResetAllowedConnectors() return nil case oauth2client.FieldMfaChain: m.ResetMfaChain() return nil } return fmt.Errorf("unknown OAuth2Client field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *OAuth2ClientMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *OAuth2ClientMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *OAuth2ClientMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *OAuth2ClientMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *OAuth2ClientMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *OAuth2ClientMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *OAuth2ClientMutation) ClearEdge(name string) error { return fmt.Errorf("unknown OAuth2Client unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *OAuth2ClientMutation) ResetEdge(name string) error { return fmt.Errorf("unknown OAuth2Client edge %s", name) } // OfflineSessionMutation represents an operation that mutates the OfflineSession nodes in the graph. type OfflineSessionMutation struct { config op Op typ string id *string user_id *string conn_id *string refresh *[]byte connector_data *[]byte clearedFields map[string]struct{} done bool oldValue func(context.Context) (*OfflineSession, error) predicates []predicate.OfflineSession } var _ ent.Mutation = (*OfflineSessionMutation)(nil) // offlinesessionOption allows management of the mutation configuration using functional options. type offlinesessionOption func(*OfflineSessionMutation) // newOfflineSessionMutation creates new mutation for the OfflineSession entity. func newOfflineSessionMutation(c config, op Op, opts ...offlinesessionOption) *OfflineSessionMutation { m := &OfflineSessionMutation{ config: c, op: op, typ: TypeOfflineSession, clearedFields: make(map[string]struct{}), } for _, opt := range opts { opt(m) } return m } // withOfflineSessionID sets the ID field of the mutation. func withOfflineSessionID(id string) offlinesessionOption { return func(m *OfflineSessionMutation) { var ( err error once sync.Once value *OfflineSession ) m.oldValue = func(ctx context.Context) (*OfflineSession, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { value, err = m.Client().OfflineSession.Get(ctx, id) } }) return value, err } m.id = &id } } // withOfflineSession sets the old OfflineSession of the mutation. func withOfflineSession(node *OfflineSession) offlinesessionOption { return func(m *OfflineSessionMutation) { m.oldValue = func(context.Context) (*OfflineSession, error) { return node, nil } m.id = &node.ID } } // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. func (m OfflineSessionMutation) Client() *Client { client := &Client{config: m.config} client.init() return client } // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. func (m OfflineSessionMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } tx := &Tx{config: m.config} tx.init() return tx, nil } // SetID sets the value of the id field. Note that this // operation is only accepted on creation of OfflineSession entities. func (m *OfflineSessionMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. func (m *OfflineSessionMutation) ID() (id string, exists bool) { if m.id == nil { return } return *m.id, true } // IDs queries the database and returns the entity ids that match the mutation's predicate. // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. func (m *OfflineSessionMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { return []string{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): return m.Client().OfflineSession.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetUserID sets the "user_id" field. func (m *OfflineSessionMutation) SetUserID(s string) { m.user_id = &s } // UserID returns the value of the "user_id" field in the mutation. func (m *OfflineSessionMutation) UserID() (r string, exists bool) { v := m.user_id if v == nil { return } return *v, true } // OldUserID returns the old "user_id" field's value of the OfflineSession entity. // If the OfflineSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *OfflineSessionMutation) OldUserID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUserID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldUserID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldUserID: %w", err) } return oldValue.UserID, nil } // ResetUserID resets all changes to the "user_id" field. func (m *OfflineSessionMutation) ResetUserID() { m.user_id = nil } // SetConnID sets the "conn_id" field. func (m *OfflineSessionMutation) SetConnID(s string) { m.conn_id = &s } // ConnID returns the value of the "conn_id" field in the mutation. func (m *OfflineSessionMutation) ConnID() (r string, exists bool) { v := m.conn_id if v == nil { return } return *v, true } // OldConnID returns the old "conn_id" field's value of the OfflineSession entity. // If the OfflineSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *OfflineSessionMutation) OldConnID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldConnID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldConnID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldConnID: %w", err) } return oldValue.ConnID, nil } // ResetConnID resets all changes to the "conn_id" field. func (m *OfflineSessionMutation) ResetConnID() { m.conn_id = nil } // SetRefresh sets the "refresh" field. func (m *OfflineSessionMutation) SetRefresh(b []byte) { m.refresh = &b } // Refresh returns the value of the "refresh" field in the mutation. func (m *OfflineSessionMutation) Refresh() (r []byte, exists bool) { v := m.refresh if v == nil { return } return *v, true } // OldRefresh returns the old "refresh" field's value of the OfflineSession entity. // If the OfflineSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *OfflineSessionMutation) OldRefresh(ctx context.Context) (v []byte, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldRefresh is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldRefresh requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldRefresh: %w", err) } return oldValue.Refresh, nil } // ResetRefresh resets all changes to the "refresh" field. func (m *OfflineSessionMutation) ResetRefresh() { m.refresh = nil } // SetConnectorData sets the "connector_data" field. func (m *OfflineSessionMutation) SetConnectorData(b []byte) { m.connector_data = &b } // ConnectorData returns the value of the "connector_data" field in the mutation. func (m *OfflineSessionMutation) ConnectorData() (r []byte, exists bool) { v := m.connector_data if v == nil { return } return *v, true } // OldConnectorData returns the old "connector_data" field's value of the OfflineSession entity. // If the OfflineSession object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *OfflineSessionMutation) OldConnectorData(ctx context.Context) (v *[]byte, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldConnectorData is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldConnectorData requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldConnectorData: %w", err) } return oldValue.ConnectorData, nil } // ClearConnectorData clears the value of the "connector_data" field. func (m *OfflineSessionMutation) ClearConnectorData() { m.connector_data = nil m.clearedFields[offlinesession.FieldConnectorData] = struct{}{} } // ConnectorDataCleared returns if the "connector_data" field was cleared in this mutation. func (m *OfflineSessionMutation) ConnectorDataCleared() bool { _, ok := m.clearedFields[offlinesession.FieldConnectorData] return ok } // ResetConnectorData resets all changes to the "connector_data" field. func (m *OfflineSessionMutation) ResetConnectorData() { m.connector_data = nil delete(m.clearedFields, offlinesession.FieldConnectorData) } // Where appends a list predicates to the OfflineSessionMutation builder. func (m *OfflineSessionMutation) Where(ps ...predicate.OfflineSession) { m.predicates = append(m.predicates, ps...) } // WhereP appends storage-level predicates to the OfflineSessionMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. func (m *OfflineSessionMutation) WhereP(ps ...func(*sql.Selector)) { p := make([]predicate.OfflineSession, len(ps)) for i := range ps { p[i] = ps[i] } m.Where(p...) } // Op returns the operation name. func (m *OfflineSessionMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. func (m *OfflineSessionMutation) SetOp(op Op) { m.op = op } // Type returns the node type of this mutation (OfflineSession). func (m *OfflineSessionMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *OfflineSessionMutation) Fields() []string { fields := make([]string, 0, 4) if m.user_id != nil { fields = append(fields, offlinesession.FieldUserID) } if m.conn_id != nil { fields = append(fields, offlinesession.FieldConnID) } if m.refresh != nil { fields = append(fields, offlinesession.FieldRefresh) } if m.connector_data != nil { fields = append(fields, offlinesession.FieldConnectorData) } return fields } // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. func (m *OfflineSessionMutation) Field(name string) (ent.Value, bool) { switch name { case offlinesession.FieldUserID: return m.UserID() case offlinesession.FieldConnID: return m.ConnID() case offlinesession.FieldRefresh: return m.Refresh() case offlinesession.FieldConnectorData: return m.ConnectorData() } return nil, false } // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. func (m *OfflineSessionMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { case offlinesession.FieldUserID: return m.OldUserID(ctx) case offlinesession.FieldConnID: return m.OldConnID(ctx) case offlinesession.FieldRefresh: return m.OldRefresh(ctx) case offlinesession.FieldConnectorData: return m.OldConnectorData(ctx) } return nil, fmt.Errorf("unknown OfflineSession field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *OfflineSessionMutation) SetField(name string, value ent.Value) error { switch name { case offlinesession.FieldUserID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUserID(v) return nil case offlinesession.FieldConnID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetConnID(v) return nil case offlinesession.FieldRefresh: v, ok := value.([]byte) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetRefresh(v) return nil case offlinesession.FieldConnectorData: v, ok := value.([]byte) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetConnectorData(v) return nil } return fmt.Errorf("unknown OfflineSession field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *OfflineSessionMutation) AddedFields() []string { return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *OfflineSessionMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *OfflineSessionMutation) AddField(name string, value ent.Value) error { switch name { } return fmt.Errorf("unknown OfflineSession numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *OfflineSessionMutation) ClearedFields() []string { var fields []string if m.FieldCleared(offlinesession.FieldConnectorData) { fields = append(fields, offlinesession.FieldConnectorData) } return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. func (m *OfflineSessionMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *OfflineSessionMutation) ClearField(name string) error { switch name { case offlinesession.FieldConnectorData: m.ClearConnectorData() return nil } return fmt.Errorf("unknown OfflineSession nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. func (m *OfflineSessionMutation) ResetField(name string) error { switch name { case offlinesession.FieldUserID: m.ResetUserID() return nil case offlinesession.FieldConnID: m.ResetConnID() return nil case offlinesession.FieldRefresh: m.ResetRefresh() return nil case offlinesession.FieldConnectorData: m.ResetConnectorData() return nil } return fmt.Errorf("unknown OfflineSession field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *OfflineSessionMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *OfflineSessionMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *OfflineSessionMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *OfflineSessionMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *OfflineSessionMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *OfflineSessionMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *OfflineSessionMutation) ClearEdge(name string) error { return fmt.Errorf("unknown OfflineSession unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *OfflineSessionMutation) ResetEdge(name string) error { return fmt.Errorf("unknown OfflineSession edge %s", name) } // PasswordMutation represents an operation that mutates the Password nodes in the graph. type PasswordMutation struct { config op Op typ string id *int email *string hash *[]byte username *string name *string preferred_username *string email_verified *bool user_id *string groups *[]string appendgroups []string clearedFields map[string]struct{} done bool oldValue func(context.Context) (*Password, error) predicates []predicate.Password } var _ ent.Mutation = (*PasswordMutation)(nil) // passwordOption allows management of the mutation configuration using functional options. type passwordOption func(*PasswordMutation) // newPasswordMutation creates new mutation for the Password entity. func newPasswordMutation(c config, op Op, opts ...passwordOption) *PasswordMutation { m := &PasswordMutation{ config: c, op: op, typ: TypePassword, clearedFields: make(map[string]struct{}), } for _, opt := range opts { opt(m) } return m } // withPasswordID sets the ID field of the mutation. func withPasswordID(id int) passwordOption { return func(m *PasswordMutation) { var ( err error once sync.Once value *Password ) m.oldValue = func(ctx context.Context) (*Password, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { value, err = m.Client().Password.Get(ctx, id) } }) return value, err } m.id = &id } } // withPassword sets the old Password of the mutation. func withPassword(node *Password) passwordOption { return func(m *PasswordMutation) { m.oldValue = func(context.Context) (*Password, error) { return node, nil } m.id = &node.ID } } // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. func (m PasswordMutation) Client() *Client { client := &Client{config: m.config} client.init() return client } // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. func (m PasswordMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } tx := &Tx{config: m.config} tx.init() return tx, nil } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. func (m *PasswordMutation) ID() (id int, exists bool) { if m.id == nil { return } return *m.id, true } // IDs queries the database and returns the entity ids that match the mutation's predicate. // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. func (m *PasswordMutation) IDs(ctx context.Context) ([]int, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { return []int{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): return m.Client().Password.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetEmail sets the "email" field. func (m *PasswordMutation) SetEmail(s string) { m.email = &s } // Email returns the value of the "email" field in the mutation. func (m *PasswordMutation) Email() (r string, exists bool) { v := m.email if v == nil { return } return *v, true } // OldEmail returns the old "email" field's value of the Password entity. // If the Password object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *PasswordMutation) OldEmail(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldEmail is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldEmail requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldEmail: %w", err) } return oldValue.Email, nil } // ResetEmail resets all changes to the "email" field. func (m *PasswordMutation) ResetEmail() { m.email = nil } // SetHash sets the "hash" field. func (m *PasswordMutation) SetHash(b []byte) { m.hash = &b } // Hash returns the value of the "hash" field in the mutation. func (m *PasswordMutation) Hash() (r []byte, exists bool) { v := m.hash if v == nil { return } return *v, true } // OldHash returns the old "hash" field's value of the Password entity. // If the Password object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *PasswordMutation) OldHash(ctx context.Context) (v []byte, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldHash is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldHash requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldHash: %w", err) } return oldValue.Hash, nil } // ResetHash resets all changes to the "hash" field. func (m *PasswordMutation) ResetHash() { m.hash = nil } // SetUsername sets the "username" field. func (m *PasswordMutation) SetUsername(s string) { m.username = &s } // Username returns the value of the "username" field in the mutation. func (m *PasswordMutation) Username() (r string, exists bool) { v := m.username if v == nil { return } return *v, true } // OldUsername returns the old "username" field's value of the Password entity. // If the Password object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *PasswordMutation) OldUsername(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUsername is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldUsername requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldUsername: %w", err) } return oldValue.Username, nil } // ResetUsername resets all changes to the "username" field. func (m *PasswordMutation) ResetUsername() { m.username = nil } // SetName sets the "name" field. func (m *PasswordMutation) SetName(s string) { m.name = &s } // Name returns the value of the "name" field in the mutation. func (m *PasswordMutation) Name() (r string, exists bool) { v := m.name if v == nil { return } return *v, true } // OldName returns the old "name" field's value of the Password entity. // If the Password object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *PasswordMutation) OldName(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldName is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldName requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldName: %w", err) } return oldValue.Name, nil } // ResetName resets all changes to the "name" field. func (m *PasswordMutation) ResetName() { m.name = nil } // SetPreferredUsername sets the "preferred_username" field. func (m *PasswordMutation) SetPreferredUsername(s string) { m.preferred_username = &s } // PreferredUsername returns the value of the "preferred_username" field in the mutation. func (m *PasswordMutation) PreferredUsername() (r string, exists bool) { v := m.preferred_username if v == nil { return } return *v, true } // OldPreferredUsername returns the old "preferred_username" field's value of the Password entity. // If the Password object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *PasswordMutation) OldPreferredUsername(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldPreferredUsername is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldPreferredUsername requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldPreferredUsername: %w", err) } return oldValue.PreferredUsername, nil } // ResetPreferredUsername resets all changes to the "preferred_username" field. func (m *PasswordMutation) ResetPreferredUsername() { m.preferred_username = nil } // SetEmailVerified sets the "email_verified" field. func (m *PasswordMutation) SetEmailVerified(b bool) { m.email_verified = &b } // EmailVerified returns the value of the "email_verified" field in the mutation. func (m *PasswordMutation) EmailVerified() (r bool, exists bool) { v := m.email_verified if v == nil { return } return *v, true } // OldEmailVerified returns the old "email_verified" field's value of the Password entity. // If the Password object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *PasswordMutation) OldEmailVerified(ctx context.Context) (v *bool, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldEmailVerified is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldEmailVerified requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldEmailVerified: %w", err) } return oldValue.EmailVerified, nil } // ClearEmailVerified clears the value of the "email_verified" field. func (m *PasswordMutation) ClearEmailVerified() { m.email_verified = nil m.clearedFields[password.FieldEmailVerified] = struct{}{} } // EmailVerifiedCleared returns if the "email_verified" field was cleared in this mutation. func (m *PasswordMutation) EmailVerifiedCleared() bool { _, ok := m.clearedFields[password.FieldEmailVerified] return ok } // ResetEmailVerified resets all changes to the "email_verified" field. func (m *PasswordMutation) ResetEmailVerified() { m.email_verified = nil delete(m.clearedFields, password.FieldEmailVerified) } // SetUserID sets the "user_id" field. func (m *PasswordMutation) SetUserID(s string) { m.user_id = &s } // UserID returns the value of the "user_id" field in the mutation. func (m *PasswordMutation) UserID() (r string, exists bool) { v := m.user_id if v == nil { return } return *v, true } // OldUserID returns the old "user_id" field's value of the Password entity. // If the Password object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *PasswordMutation) OldUserID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUserID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldUserID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldUserID: %w", err) } return oldValue.UserID, nil } // ResetUserID resets all changes to the "user_id" field. func (m *PasswordMutation) ResetUserID() { m.user_id = nil } // SetGroups sets the "groups" field. func (m *PasswordMutation) SetGroups(s []string) { m.groups = &s m.appendgroups = nil } // Groups returns the value of the "groups" field in the mutation. func (m *PasswordMutation) Groups() (r []string, exists bool) { v := m.groups if v == nil { return } return *v, true } // OldGroups returns the old "groups" field's value of the Password entity. // If the Password object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *PasswordMutation) OldGroups(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldGroups is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldGroups requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldGroups: %w", err) } return oldValue.Groups, nil } // AppendGroups adds s to the "groups" field. func (m *PasswordMutation) AppendGroups(s []string) { m.appendgroups = append(m.appendgroups, s...) } // AppendedGroups returns the list of values that were appended to the "groups" field in this mutation. func (m *PasswordMutation) AppendedGroups() ([]string, bool) { if len(m.appendgroups) == 0 { return nil, false } return m.appendgroups, true } // ClearGroups clears the value of the "groups" field. func (m *PasswordMutation) ClearGroups() { m.groups = nil m.appendgroups = nil m.clearedFields[password.FieldGroups] = struct{}{} } // GroupsCleared returns if the "groups" field was cleared in this mutation. func (m *PasswordMutation) GroupsCleared() bool { _, ok := m.clearedFields[password.FieldGroups] return ok } // ResetGroups resets all changes to the "groups" field. func (m *PasswordMutation) ResetGroups() { m.groups = nil m.appendgroups = nil delete(m.clearedFields, password.FieldGroups) } // Where appends a list predicates to the PasswordMutation builder. func (m *PasswordMutation) Where(ps ...predicate.Password) { m.predicates = append(m.predicates, ps...) } // WhereP appends storage-level predicates to the PasswordMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. func (m *PasswordMutation) WhereP(ps ...func(*sql.Selector)) { p := make([]predicate.Password, len(ps)) for i := range ps { p[i] = ps[i] } m.Where(p...) } // Op returns the operation name. func (m *PasswordMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. func (m *PasswordMutation) SetOp(op Op) { m.op = op } // Type returns the node type of this mutation (Password). func (m *PasswordMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *PasswordMutation) Fields() []string { fields := make([]string, 0, 8) if m.email != nil { fields = append(fields, password.FieldEmail) } if m.hash != nil { fields = append(fields, password.FieldHash) } if m.username != nil { fields = append(fields, password.FieldUsername) } if m.name != nil { fields = append(fields, password.FieldName) } if m.preferred_username != nil { fields = append(fields, password.FieldPreferredUsername) } if m.email_verified != nil { fields = append(fields, password.FieldEmailVerified) } if m.user_id != nil { fields = append(fields, password.FieldUserID) } if m.groups != nil { fields = append(fields, password.FieldGroups) } return fields } // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. func (m *PasswordMutation) Field(name string) (ent.Value, bool) { switch name { case password.FieldEmail: return m.Email() case password.FieldHash: return m.Hash() case password.FieldUsername: return m.Username() case password.FieldName: return m.Name() case password.FieldPreferredUsername: return m.PreferredUsername() case password.FieldEmailVerified: return m.EmailVerified() case password.FieldUserID: return m.UserID() case password.FieldGroups: return m.Groups() } return nil, false } // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. func (m *PasswordMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { case password.FieldEmail: return m.OldEmail(ctx) case password.FieldHash: return m.OldHash(ctx) case password.FieldUsername: return m.OldUsername(ctx) case password.FieldName: return m.OldName(ctx) case password.FieldPreferredUsername: return m.OldPreferredUsername(ctx) case password.FieldEmailVerified: return m.OldEmailVerified(ctx) case password.FieldUserID: return m.OldUserID(ctx) case password.FieldGroups: return m.OldGroups(ctx) } return nil, fmt.Errorf("unknown Password field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *PasswordMutation) SetField(name string, value ent.Value) error { switch name { case password.FieldEmail: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetEmail(v) return nil case password.FieldHash: v, ok := value.([]byte) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetHash(v) return nil case password.FieldUsername: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUsername(v) return nil case password.FieldName: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetName(v) return nil case password.FieldPreferredUsername: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetPreferredUsername(v) return nil case password.FieldEmailVerified: v, ok := value.(bool) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetEmailVerified(v) return nil case password.FieldUserID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUserID(v) return nil case password.FieldGroups: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetGroups(v) return nil } return fmt.Errorf("unknown Password field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *PasswordMutation) AddedFields() []string { return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *PasswordMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *PasswordMutation) AddField(name string, value ent.Value) error { switch name { } return fmt.Errorf("unknown Password numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *PasswordMutation) ClearedFields() []string { var fields []string if m.FieldCleared(password.FieldEmailVerified) { fields = append(fields, password.FieldEmailVerified) } if m.FieldCleared(password.FieldGroups) { fields = append(fields, password.FieldGroups) } return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. func (m *PasswordMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *PasswordMutation) ClearField(name string) error { switch name { case password.FieldEmailVerified: m.ClearEmailVerified() return nil case password.FieldGroups: m.ClearGroups() return nil } return fmt.Errorf("unknown Password nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. func (m *PasswordMutation) ResetField(name string) error { switch name { case password.FieldEmail: m.ResetEmail() return nil case password.FieldHash: m.ResetHash() return nil case password.FieldUsername: m.ResetUsername() return nil case password.FieldName: m.ResetName() return nil case password.FieldPreferredUsername: m.ResetPreferredUsername() return nil case password.FieldEmailVerified: m.ResetEmailVerified() return nil case password.FieldUserID: m.ResetUserID() return nil case password.FieldGroups: m.ResetGroups() return nil } return fmt.Errorf("unknown Password field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *PasswordMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *PasswordMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *PasswordMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *PasswordMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *PasswordMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *PasswordMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *PasswordMutation) ClearEdge(name string) error { return fmt.Errorf("unknown Password unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *PasswordMutation) ResetEdge(name string) error { return fmt.Errorf("unknown Password edge %s", name) } // RefreshTokenMutation represents an operation that mutates the RefreshToken nodes in the graph. type RefreshTokenMutation struct { config op Op typ string id *string client_id *string scopes *[]string appendscopes []string nonce *string claims_user_id *string claims_username *string claims_email *string claims_email_verified *bool claims_groups *[]string appendclaims_groups []string claims_preferred_username *string connector_id *string connector_data *[]byte token *string obsolete_token *string created_at *time.Time last_used *time.Time clearedFields map[string]struct{} done bool oldValue func(context.Context) (*RefreshToken, error) predicates []predicate.RefreshToken } var _ ent.Mutation = (*RefreshTokenMutation)(nil) // refreshtokenOption allows management of the mutation configuration using functional options. type refreshtokenOption func(*RefreshTokenMutation) // newRefreshTokenMutation creates new mutation for the RefreshToken entity. func newRefreshTokenMutation(c config, op Op, opts ...refreshtokenOption) *RefreshTokenMutation { m := &RefreshTokenMutation{ config: c, op: op, typ: TypeRefreshToken, clearedFields: make(map[string]struct{}), } for _, opt := range opts { opt(m) } return m } // withRefreshTokenID sets the ID field of the mutation. func withRefreshTokenID(id string) refreshtokenOption { return func(m *RefreshTokenMutation) { var ( err error once sync.Once value *RefreshToken ) m.oldValue = func(ctx context.Context) (*RefreshToken, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { value, err = m.Client().RefreshToken.Get(ctx, id) } }) return value, err } m.id = &id } } // withRefreshToken sets the old RefreshToken of the mutation. func withRefreshToken(node *RefreshToken) refreshtokenOption { return func(m *RefreshTokenMutation) { m.oldValue = func(context.Context) (*RefreshToken, error) { return node, nil } m.id = &node.ID } } // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. func (m RefreshTokenMutation) Client() *Client { client := &Client{config: m.config} client.init() return client } // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. func (m RefreshTokenMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } tx := &Tx{config: m.config} tx.init() return tx, nil } // SetID sets the value of the id field. Note that this // operation is only accepted on creation of RefreshToken entities. func (m *RefreshTokenMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. func (m *RefreshTokenMutation) ID() (id string, exists bool) { if m.id == nil { return } return *m.id, true } // IDs queries the database and returns the entity ids that match the mutation's predicate. // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. func (m *RefreshTokenMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { return []string{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): return m.Client().RefreshToken.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetClientID sets the "client_id" field. func (m *RefreshTokenMutation) SetClientID(s string) { m.client_id = &s } // ClientID returns the value of the "client_id" field in the mutation. func (m *RefreshTokenMutation) ClientID() (r string, exists bool) { v := m.client_id if v == nil { return } return *v, true } // OldClientID returns the old "client_id" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldClientID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClientID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClientID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClientID: %w", err) } return oldValue.ClientID, nil } // ResetClientID resets all changes to the "client_id" field. func (m *RefreshTokenMutation) ResetClientID() { m.client_id = nil } // SetScopes sets the "scopes" field. func (m *RefreshTokenMutation) SetScopes(s []string) { m.scopes = &s m.appendscopes = nil } // Scopes returns the value of the "scopes" field in the mutation. func (m *RefreshTokenMutation) Scopes() (r []string, exists bool) { v := m.scopes if v == nil { return } return *v, true } // OldScopes returns the old "scopes" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldScopes(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldScopes is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldScopes requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldScopes: %w", err) } return oldValue.Scopes, nil } // AppendScopes adds s to the "scopes" field. func (m *RefreshTokenMutation) AppendScopes(s []string) { m.appendscopes = append(m.appendscopes, s...) } // AppendedScopes returns the list of values that were appended to the "scopes" field in this mutation. func (m *RefreshTokenMutation) AppendedScopes() ([]string, bool) { if len(m.appendscopes) == 0 { return nil, false } return m.appendscopes, true } // ClearScopes clears the value of the "scopes" field. func (m *RefreshTokenMutation) ClearScopes() { m.scopes = nil m.appendscopes = nil m.clearedFields[refreshtoken.FieldScopes] = struct{}{} } // ScopesCleared returns if the "scopes" field was cleared in this mutation. func (m *RefreshTokenMutation) ScopesCleared() bool { _, ok := m.clearedFields[refreshtoken.FieldScopes] return ok } // ResetScopes resets all changes to the "scopes" field. func (m *RefreshTokenMutation) ResetScopes() { m.scopes = nil m.appendscopes = nil delete(m.clearedFields, refreshtoken.FieldScopes) } // SetNonce sets the "nonce" field. func (m *RefreshTokenMutation) SetNonce(s string) { m.nonce = &s } // Nonce returns the value of the "nonce" field in the mutation. func (m *RefreshTokenMutation) Nonce() (r string, exists bool) { v := m.nonce if v == nil { return } return *v, true } // OldNonce returns the old "nonce" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldNonce(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldNonce is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldNonce requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldNonce: %w", err) } return oldValue.Nonce, nil } // ResetNonce resets all changes to the "nonce" field. func (m *RefreshTokenMutation) ResetNonce() { m.nonce = nil } // SetClaimsUserID sets the "claims_user_id" field. func (m *RefreshTokenMutation) SetClaimsUserID(s string) { m.claims_user_id = &s } // ClaimsUserID returns the value of the "claims_user_id" field in the mutation. func (m *RefreshTokenMutation) ClaimsUserID() (r string, exists bool) { v := m.claims_user_id if v == nil { return } return *v, true } // OldClaimsUserID returns the old "claims_user_id" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldClaimsUserID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsUserID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsUserID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsUserID: %w", err) } return oldValue.ClaimsUserID, nil } // ResetClaimsUserID resets all changes to the "claims_user_id" field. func (m *RefreshTokenMutation) ResetClaimsUserID() { m.claims_user_id = nil } // SetClaimsUsername sets the "claims_username" field. func (m *RefreshTokenMutation) SetClaimsUsername(s string) { m.claims_username = &s } // ClaimsUsername returns the value of the "claims_username" field in the mutation. func (m *RefreshTokenMutation) ClaimsUsername() (r string, exists bool) { v := m.claims_username if v == nil { return } return *v, true } // OldClaimsUsername returns the old "claims_username" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldClaimsUsername(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsUsername is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsUsername requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsUsername: %w", err) } return oldValue.ClaimsUsername, nil } // ResetClaimsUsername resets all changes to the "claims_username" field. func (m *RefreshTokenMutation) ResetClaimsUsername() { m.claims_username = nil } // SetClaimsEmail sets the "claims_email" field. func (m *RefreshTokenMutation) SetClaimsEmail(s string) { m.claims_email = &s } // ClaimsEmail returns the value of the "claims_email" field in the mutation. func (m *RefreshTokenMutation) ClaimsEmail() (r string, exists bool) { v := m.claims_email if v == nil { return } return *v, true } // OldClaimsEmail returns the old "claims_email" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldClaimsEmail(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsEmail is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsEmail requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsEmail: %w", err) } return oldValue.ClaimsEmail, nil } // ResetClaimsEmail resets all changes to the "claims_email" field. func (m *RefreshTokenMutation) ResetClaimsEmail() { m.claims_email = nil } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (m *RefreshTokenMutation) SetClaimsEmailVerified(b bool) { m.claims_email_verified = &b } // ClaimsEmailVerified returns the value of the "claims_email_verified" field in the mutation. func (m *RefreshTokenMutation) ClaimsEmailVerified() (r bool, exists bool) { v := m.claims_email_verified if v == nil { return } return *v, true } // OldClaimsEmailVerified returns the old "claims_email_verified" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldClaimsEmailVerified(ctx context.Context) (v bool, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsEmailVerified is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsEmailVerified requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsEmailVerified: %w", err) } return oldValue.ClaimsEmailVerified, nil } // ResetClaimsEmailVerified resets all changes to the "claims_email_verified" field. func (m *RefreshTokenMutation) ResetClaimsEmailVerified() { m.claims_email_verified = nil } // SetClaimsGroups sets the "claims_groups" field. func (m *RefreshTokenMutation) SetClaimsGroups(s []string) { m.claims_groups = &s m.appendclaims_groups = nil } // ClaimsGroups returns the value of the "claims_groups" field in the mutation. func (m *RefreshTokenMutation) ClaimsGroups() (r []string, exists bool) { v := m.claims_groups if v == nil { return } return *v, true } // OldClaimsGroups returns the old "claims_groups" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldClaimsGroups(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsGroups is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsGroups requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsGroups: %w", err) } return oldValue.ClaimsGroups, nil } // AppendClaimsGroups adds s to the "claims_groups" field. func (m *RefreshTokenMutation) AppendClaimsGroups(s []string) { m.appendclaims_groups = append(m.appendclaims_groups, s...) } // AppendedClaimsGroups returns the list of values that were appended to the "claims_groups" field in this mutation. func (m *RefreshTokenMutation) AppendedClaimsGroups() ([]string, bool) { if len(m.appendclaims_groups) == 0 { return nil, false } return m.appendclaims_groups, true } // ClearClaimsGroups clears the value of the "claims_groups" field. func (m *RefreshTokenMutation) ClearClaimsGroups() { m.claims_groups = nil m.appendclaims_groups = nil m.clearedFields[refreshtoken.FieldClaimsGroups] = struct{}{} } // ClaimsGroupsCleared returns if the "claims_groups" field was cleared in this mutation. func (m *RefreshTokenMutation) ClaimsGroupsCleared() bool { _, ok := m.clearedFields[refreshtoken.FieldClaimsGroups] return ok } // ResetClaimsGroups resets all changes to the "claims_groups" field. func (m *RefreshTokenMutation) ResetClaimsGroups() { m.claims_groups = nil m.appendclaims_groups = nil delete(m.clearedFields, refreshtoken.FieldClaimsGroups) } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (m *RefreshTokenMutation) SetClaimsPreferredUsername(s string) { m.claims_preferred_username = &s } // ClaimsPreferredUsername returns the value of the "claims_preferred_username" field in the mutation. func (m *RefreshTokenMutation) ClaimsPreferredUsername() (r string, exists bool) { v := m.claims_preferred_username if v == nil { return } return *v, true } // OldClaimsPreferredUsername returns the old "claims_preferred_username" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldClaimsPreferredUsername(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsPreferredUsername is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsPreferredUsername requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsPreferredUsername: %w", err) } return oldValue.ClaimsPreferredUsername, nil } // ResetClaimsPreferredUsername resets all changes to the "claims_preferred_username" field. func (m *RefreshTokenMutation) ResetClaimsPreferredUsername() { m.claims_preferred_username = nil } // SetConnectorID sets the "connector_id" field. func (m *RefreshTokenMutation) SetConnectorID(s string) { m.connector_id = &s } // ConnectorID returns the value of the "connector_id" field in the mutation. func (m *RefreshTokenMutation) ConnectorID() (r string, exists bool) { v := m.connector_id if v == nil { return } return *v, true } // OldConnectorID returns the old "connector_id" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldConnectorID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldConnectorID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldConnectorID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldConnectorID: %w", err) } return oldValue.ConnectorID, nil } // ResetConnectorID resets all changes to the "connector_id" field. func (m *RefreshTokenMutation) ResetConnectorID() { m.connector_id = nil } // SetConnectorData sets the "connector_data" field. func (m *RefreshTokenMutation) SetConnectorData(b []byte) { m.connector_data = &b } // ConnectorData returns the value of the "connector_data" field in the mutation. func (m *RefreshTokenMutation) ConnectorData() (r []byte, exists bool) { v := m.connector_data if v == nil { return } return *v, true } // OldConnectorData returns the old "connector_data" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldConnectorData(ctx context.Context) (v *[]byte, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldConnectorData is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldConnectorData requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldConnectorData: %w", err) } return oldValue.ConnectorData, nil } // ClearConnectorData clears the value of the "connector_data" field. func (m *RefreshTokenMutation) ClearConnectorData() { m.connector_data = nil m.clearedFields[refreshtoken.FieldConnectorData] = struct{}{} } // ConnectorDataCleared returns if the "connector_data" field was cleared in this mutation. func (m *RefreshTokenMutation) ConnectorDataCleared() bool { _, ok := m.clearedFields[refreshtoken.FieldConnectorData] return ok } // ResetConnectorData resets all changes to the "connector_data" field. func (m *RefreshTokenMutation) ResetConnectorData() { m.connector_data = nil delete(m.clearedFields, refreshtoken.FieldConnectorData) } // SetToken sets the "token" field. func (m *RefreshTokenMutation) SetToken(s string) { m.token = &s } // Token returns the value of the "token" field in the mutation. func (m *RefreshTokenMutation) Token() (r string, exists bool) { v := m.token if v == nil { return } return *v, true } // OldToken returns the old "token" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldToken(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldToken is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldToken requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldToken: %w", err) } return oldValue.Token, nil } // ResetToken resets all changes to the "token" field. func (m *RefreshTokenMutation) ResetToken() { m.token = nil } // SetObsoleteToken sets the "obsolete_token" field. func (m *RefreshTokenMutation) SetObsoleteToken(s string) { m.obsolete_token = &s } // ObsoleteToken returns the value of the "obsolete_token" field in the mutation. func (m *RefreshTokenMutation) ObsoleteToken() (r string, exists bool) { v := m.obsolete_token if v == nil { return } return *v, true } // OldObsoleteToken returns the old "obsolete_token" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldObsoleteToken(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldObsoleteToken is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldObsoleteToken requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldObsoleteToken: %w", err) } return oldValue.ObsoleteToken, nil } // ResetObsoleteToken resets all changes to the "obsolete_token" field. func (m *RefreshTokenMutation) ResetObsoleteToken() { m.obsolete_token = nil } // SetCreatedAt sets the "created_at" field. func (m *RefreshTokenMutation) SetCreatedAt(t time.Time) { m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. func (m *RefreshTokenMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return } return *v, true } // OldCreatedAt returns the old "created_at" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldCreatedAt requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) } return oldValue.CreatedAt, nil } // ResetCreatedAt resets all changes to the "created_at" field. func (m *RefreshTokenMutation) ResetCreatedAt() { m.created_at = nil } // SetLastUsed sets the "last_used" field. func (m *RefreshTokenMutation) SetLastUsed(t time.Time) { m.last_used = &t } // LastUsed returns the value of the "last_used" field in the mutation. func (m *RefreshTokenMutation) LastUsed() (r time.Time, exists bool) { v := m.last_used if v == nil { return } return *v, true } // OldLastUsed returns the old "last_used" field's value of the RefreshToken entity. // If the RefreshToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *RefreshTokenMutation) OldLastUsed(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastUsed is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldLastUsed requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldLastUsed: %w", err) } return oldValue.LastUsed, nil } // ResetLastUsed resets all changes to the "last_used" field. func (m *RefreshTokenMutation) ResetLastUsed() { m.last_used = nil } // Where appends a list predicates to the RefreshTokenMutation builder. func (m *RefreshTokenMutation) Where(ps ...predicate.RefreshToken) { m.predicates = append(m.predicates, ps...) } // WhereP appends storage-level predicates to the RefreshTokenMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. func (m *RefreshTokenMutation) WhereP(ps ...func(*sql.Selector)) { p := make([]predicate.RefreshToken, len(ps)) for i := range ps { p[i] = ps[i] } m.Where(p...) } // Op returns the operation name. func (m *RefreshTokenMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. func (m *RefreshTokenMutation) SetOp(op Op) { m.op = op } // Type returns the node type of this mutation (RefreshToken). func (m *RefreshTokenMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *RefreshTokenMutation) Fields() []string { fields := make([]string, 0, 15) if m.client_id != nil { fields = append(fields, refreshtoken.FieldClientID) } if m.scopes != nil { fields = append(fields, refreshtoken.FieldScopes) } if m.nonce != nil { fields = append(fields, refreshtoken.FieldNonce) } if m.claims_user_id != nil { fields = append(fields, refreshtoken.FieldClaimsUserID) } if m.claims_username != nil { fields = append(fields, refreshtoken.FieldClaimsUsername) } if m.claims_email != nil { fields = append(fields, refreshtoken.FieldClaimsEmail) } if m.claims_email_verified != nil { fields = append(fields, refreshtoken.FieldClaimsEmailVerified) } if m.claims_groups != nil { fields = append(fields, refreshtoken.FieldClaimsGroups) } if m.claims_preferred_username != nil { fields = append(fields, refreshtoken.FieldClaimsPreferredUsername) } if m.connector_id != nil { fields = append(fields, refreshtoken.FieldConnectorID) } if m.connector_data != nil { fields = append(fields, refreshtoken.FieldConnectorData) } if m.token != nil { fields = append(fields, refreshtoken.FieldToken) } if m.obsolete_token != nil { fields = append(fields, refreshtoken.FieldObsoleteToken) } if m.created_at != nil { fields = append(fields, refreshtoken.FieldCreatedAt) } if m.last_used != nil { fields = append(fields, refreshtoken.FieldLastUsed) } return fields } // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. func (m *RefreshTokenMutation) Field(name string) (ent.Value, bool) { switch name { case refreshtoken.FieldClientID: return m.ClientID() case refreshtoken.FieldScopes: return m.Scopes() case refreshtoken.FieldNonce: return m.Nonce() case refreshtoken.FieldClaimsUserID: return m.ClaimsUserID() case refreshtoken.FieldClaimsUsername: return m.ClaimsUsername() case refreshtoken.FieldClaimsEmail: return m.ClaimsEmail() case refreshtoken.FieldClaimsEmailVerified: return m.ClaimsEmailVerified() case refreshtoken.FieldClaimsGroups: return m.ClaimsGroups() case refreshtoken.FieldClaimsPreferredUsername: return m.ClaimsPreferredUsername() case refreshtoken.FieldConnectorID: return m.ConnectorID() case refreshtoken.FieldConnectorData: return m.ConnectorData() case refreshtoken.FieldToken: return m.Token() case refreshtoken.FieldObsoleteToken: return m.ObsoleteToken() case refreshtoken.FieldCreatedAt: return m.CreatedAt() case refreshtoken.FieldLastUsed: return m.LastUsed() } return nil, false } // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. func (m *RefreshTokenMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { case refreshtoken.FieldClientID: return m.OldClientID(ctx) case refreshtoken.FieldScopes: return m.OldScopes(ctx) case refreshtoken.FieldNonce: return m.OldNonce(ctx) case refreshtoken.FieldClaimsUserID: return m.OldClaimsUserID(ctx) case refreshtoken.FieldClaimsUsername: return m.OldClaimsUsername(ctx) case refreshtoken.FieldClaimsEmail: return m.OldClaimsEmail(ctx) case refreshtoken.FieldClaimsEmailVerified: return m.OldClaimsEmailVerified(ctx) case refreshtoken.FieldClaimsGroups: return m.OldClaimsGroups(ctx) case refreshtoken.FieldClaimsPreferredUsername: return m.OldClaimsPreferredUsername(ctx) case refreshtoken.FieldConnectorID: return m.OldConnectorID(ctx) case refreshtoken.FieldConnectorData: return m.OldConnectorData(ctx) case refreshtoken.FieldToken: return m.OldToken(ctx) case refreshtoken.FieldObsoleteToken: return m.OldObsoleteToken(ctx) case refreshtoken.FieldCreatedAt: return m.OldCreatedAt(ctx) case refreshtoken.FieldLastUsed: return m.OldLastUsed(ctx) } return nil, fmt.Errorf("unknown RefreshToken field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *RefreshTokenMutation) SetField(name string, value ent.Value) error { switch name { case refreshtoken.FieldClientID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClientID(v) return nil case refreshtoken.FieldScopes: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetScopes(v) return nil case refreshtoken.FieldNonce: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetNonce(v) return nil case refreshtoken.FieldClaimsUserID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsUserID(v) return nil case refreshtoken.FieldClaimsUsername: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsUsername(v) return nil case refreshtoken.FieldClaimsEmail: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsEmail(v) return nil case refreshtoken.FieldClaimsEmailVerified: v, ok := value.(bool) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsEmailVerified(v) return nil case refreshtoken.FieldClaimsGroups: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsGroups(v) return nil case refreshtoken.FieldClaimsPreferredUsername: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsPreferredUsername(v) return nil case refreshtoken.FieldConnectorID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetConnectorID(v) return nil case refreshtoken.FieldConnectorData: v, ok := value.([]byte) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetConnectorData(v) return nil case refreshtoken.FieldToken: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetToken(v) return nil case refreshtoken.FieldObsoleteToken: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetObsoleteToken(v) return nil case refreshtoken.FieldCreatedAt: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case refreshtoken.FieldLastUsed: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetLastUsed(v) return nil } return fmt.Errorf("unknown RefreshToken field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *RefreshTokenMutation) AddedFields() []string { return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *RefreshTokenMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *RefreshTokenMutation) AddField(name string, value ent.Value) error { switch name { } return fmt.Errorf("unknown RefreshToken numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *RefreshTokenMutation) ClearedFields() []string { var fields []string if m.FieldCleared(refreshtoken.FieldScopes) { fields = append(fields, refreshtoken.FieldScopes) } if m.FieldCleared(refreshtoken.FieldClaimsGroups) { fields = append(fields, refreshtoken.FieldClaimsGroups) } if m.FieldCleared(refreshtoken.FieldConnectorData) { fields = append(fields, refreshtoken.FieldConnectorData) } return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. func (m *RefreshTokenMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *RefreshTokenMutation) ClearField(name string) error { switch name { case refreshtoken.FieldScopes: m.ClearScopes() return nil case refreshtoken.FieldClaimsGroups: m.ClearClaimsGroups() return nil case refreshtoken.FieldConnectorData: m.ClearConnectorData() return nil } return fmt.Errorf("unknown RefreshToken nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. func (m *RefreshTokenMutation) ResetField(name string) error { switch name { case refreshtoken.FieldClientID: m.ResetClientID() return nil case refreshtoken.FieldScopes: m.ResetScopes() return nil case refreshtoken.FieldNonce: m.ResetNonce() return nil case refreshtoken.FieldClaimsUserID: m.ResetClaimsUserID() return nil case refreshtoken.FieldClaimsUsername: m.ResetClaimsUsername() return nil case refreshtoken.FieldClaimsEmail: m.ResetClaimsEmail() return nil case refreshtoken.FieldClaimsEmailVerified: m.ResetClaimsEmailVerified() return nil case refreshtoken.FieldClaimsGroups: m.ResetClaimsGroups() return nil case refreshtoken.FieldClaimsPreferredUsername: m.ResetClaimsPreferredUsername() return nil case refreshtoken.FieldConnectorID: m.ResetConnectorID() return nil case refreshtoken.FieldConnectorData: m.ResetConnectorData() return nil case refreshtoken.FieldToken: m.ResetToken() return nil case refreshtoken.FieldObsoleteToken: m.ResetObsoleteToken() return nil case refreshtoken.FieldCreatedAt: m.ResetCreatedAt() return nil case refreshtoken.FieldLastUsed: m.ResetLastUsed() return nil } return fmt.Errorf("unknown RefreshToken field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *RefreshTokenMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *RefreshTokenMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *RefreshTokenMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *RefreshTokenMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *RefreshTokenMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *RefreshTokenMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *RefreshTokenMutation) ClearEdge(name string) error { return fmt.Errorf("unknown RefreshToken unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *RefreshTokenMutation) ResetEdge(name string) error { return fmt.Errorf("unknown RefreshToken edge %s", name) } // UserIdentityMutation represents an operation that mutates the UserIdentity nodes in the graph. type UserIdentityMutation struct { config op Op typ string id *string user_id *string connector_id *string claims_user_id *string claims_username *string claims_preferred_username *string claims_email *string claims_email_verified *bool claims_groups *[]string appendclaims_groups []string consents *[]byte mfa_secrets *[]byte created_at *time.Time last_login *time.Time blocked_until *time.Time clearedFields map[string]struct{} done bool oldValue func(context.Context) (*UserIdentity, error) predicates []predicate.UserIdentity } var _ ent.Mutation = (*UserIdentityMutation)(nil) // useridentityOption allows management of the mutation configuration using functional options. type useridentityOption func(*UserIdentityMutation) // newUserIdentityMutation creates new mutation for the UserIdentity entity. func newUserIdentityMutation(c config, op Op, opts ...useridentityOption) *UserIdentityMutation { m := &UserIdentityMutation{ config: c, op: op, typ: TypeUserIdentity, clearedFields: make(map[string]struct{}), } for _, opt := range opts { opt(m) } return m } // withUserIdentityID sets the ID field of the mutation. func withUserIdentityID(id string) useridentityOption { return func(m *UserIdentityMutation) { var ( err error once sync.Once value *UserIdentity ) m.oldValue = func(ctx context.Context) (*UserIdentity, error) { once.Do(func() { if m.done { err = errors.New("querying old values post mutation is not allowed") } else { value, err = m.Client().UserIdentity.Get(ctx, id) } }) return value, err } m.id = &id } } // withUserIdentity sets the old UserIdentity of the mutation. func withUserIdentity(node *UserIdentity) useridentityOption { return func(m *UserIdentityMutation) { m.oldValue = func(context.Context) (*UserIdentity, error) { return node, nil } m.id = &node.ID } } // Client returns a new `ent.Client` from the mutation. If the mutation was // executed in a transaction (ent.Tx), a transactional client is returned. func (m UserIdentityMutation) Client() *Client { client := &Client{config: m.config} client.init() return client } // Tx returns an `ent.Tx` for mutations that were executed in transactions; // it returns an error otherwise. func (m UserIdentityMutation) Tx() (*Tx, error) { if _, ok := m.driver.(*txDriver); !ok { return nil, errors.New("db: mutation is not running in a transaction") } tx := &Tx{config: m.config} tx.init() return tx, nil } // SetID sets the value of the id field. Note that this // operation is only accepted on creation of UserIdentity entities. func (m *UserIdentityMutation) SetID(id string) { m.id = &id } // ID returns the ID value in the mutation. Note that the ID is only available // if it was provided to the builder or after it was returned from the database. func (m *UserIdentityMutation) ID() (id string, exists bool) { if m.id == nil { return } return *m.id, true } // IDs queries the database and returns the entity ids that match the mutation's predicate. // That means, if the mutation is applied within a transaction with an isolation level such // as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated // or updated by the mutation. func (m *UserIdentityMutation) IDs(ctx context.Context) ([]string, error) { switch { case m.op.Is(OpUpdateOne | OpDeleteOne): id, exists := m.ID() if exists { return []string{id}, nil } fallthrough case m.op.Is(OpUpdate | OpDelete): return m.Client().UserIdentity.Query().Where(m.predicates...).IDs(ctx) default: return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) } } // SetUserID sets the "user_id" field. func (m *UserIdentityMutation) SetUserID(s string) { m.user_id = &s } // UserID returns the value of the "user_id" field in the mutation. func (m *UserIdentityMutation) UserID() (r string, exists bool) { v := m.user_id if v == nil { return } return *v, true } // OldUserID returns the old "user_id" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldUserID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldUserID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldUserID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldUserID: %w", err) } return oldValue.UserID, nil } // ResetUserID resets all changes to the "user_id" field. func (m *UserIdentityMutation) ResetUserID() { m.user_id = nil } // SetConnectorID sets the "connector_id" field. func (m *UserIdentityMutation) SetConnectorID(s string) { m.connector_id = &s } // ConnectorID returns the value of the "connector_id" field in the mutation. func (m *UserIdentityMutation) ConnectorID() (r string, exists bool) { v := m.connector_id if v == nil { return } return *v, true } // OldConnectorID returns the old "connector_id" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldConnectorID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldConnectorID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldConnectorID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldConnectorID: %w", err) } return oldValue.ConnectorID, nil } // ResetConnectorID resets all changes to the "connector_id" field. func (m *UserIdentityMutation) ResetConnectorID() { m.connector_id = nil } // SetClaimsUserID sets the "claims_user_id" field. func (m *UserIdentityMutation) SetClaimsUserID(s string) { m.claims_user_id = &s } // ClaimsUserID returns the value of the "claims_user_id" field in the mutation. func (m *UserIdentityMutation) ClaimsUserID() (r string, exists bool) { v := m.claims_user_id if v == nil { return } return *v, true } // OldClaimsUserID returns the old "claims_user_id" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldClaimsUserID(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsUserID is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsUserID requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsUserID: %w", err) } return oldValue.ClaimsUserID, nil } // ResetClaimsUserID resets all changes to the "claims_user_id" field. func (m *UserIdentityMutation) ResetClaimsUserID() { m.claims_user_id = nil } // SetClaimsUsername sets the "claims_username" field. func (m *UserIdentityMutation) SetClaimsUsername(s string) { m.claims_username = &s } // ClaimsUsername returns the value of the "claims_username" field in the mutation. func (m *UserIdentityMutation) ClaimsUsername() (r string, exists bool) { v := m.claims_username if v == nil { return } return *v, true } // OldClaimsUsername returns the old "claims_username" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldClaimsUsername(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsUsername is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsUsername requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsUsername: %w", err) } return oldValue.ClaimsUsername, nil } // ResetClaimsUsername resets all changes to the "claims_username" field. func (m *UserIdentityMutation) ResetClaimsUsername() { m.claims_username = nil } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (m *UserIdentityMutation) SetClaimsPreferredUsername(s string) { m.claims_preferred_username = &s } // ClaimsPreferredUsername returns the value of the "claims_preferred_username" field in the mutation. func (m *UserIdentityMutation) ClaimsPreferredUsername() (r string, exists bool) { v := m.claims_preferred_username if v == nil { return } return *v, true } // OldClaimsPreferredUsername returns the old "claims_preferred_username" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldClaimsPreferredUsername(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsPreferredUsername is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsPreferredUsername requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsPreferredUsername: %w", err) } return oldValue.ClaimsPreferredUsername, nil } // ResetClaimsPreferredUsername resets all changes to the "claims_preferred_username" field. func (m *UserIdentityMutation) ResetClaimsPreferredUsername() { m.claims_preferred_username = nil } // SetClaimsEmail sets the "claims_email" field. func (m *UserIdentityMutation) SetClaimsEmail(s string) { m.claims_email = &s } // ClaimsEmail returns the value of the "claims_email" field in the mutation. func (m *UserIdentityMutation) ClaimsEmail() (r string, exists bool) { v := m.claims_email if v == nil { return } return *v, true } // OldClaimsEmail returns the old "claims_email" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldClaimsEmail(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsEmail is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsEmail requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsEmail: %w", err) } return oldValue.ClaimsEmail, nil } // ResetClaimsEmail resets all changes to the "claims_email" field. func (m *UserIdentityMutation) ResetClaimsEmail() { m.claims_email = nil } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (m *UserIdentityMutation) SetClaimsEmailVerified(b bool) { m.claims_email_verified = &b } // ClaimsEmailVerified returns the value of the "claims_email_verified" field in the mutation. func (m *UserIdentityMutation) ClaimsEmailVerified() (r bool, exists bool) { v := m.claims_email_verified if v == nil { return } return *v, true } // OldClaimsEmailVerified returns the old "claims_email_verified" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldClaimsEmailVerified(ctx context.Context) (v bool, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsEmailVerified is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsEmailVerified requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsEmailVerified: %w", err) } return oldValue.ClaimsEmailVerified, nil } // ResetClaimsEmailVerified resets all changes to the "claims_email_verified" field. func (m *UserIdentityMutation) ResetClaimsEmailVerified() { m.claims_email_verified = nil } // SetClaimsGroups sets the "claims_groups" field. func (m *UserIdentityMutation) SetClaimsGroups(s []string) { m.claims_groups = &s m.appendclaims_groups = nil } // ClaimsGroups returns the value of the "claims_groups" field in the mutation. func (m *UserIdentityMutation) ClaimsGroups() (r []string, exists bool) { v := m.claims_groups if v == nil { return } return *v, true } // OldClaimsGroups returns the old "claims_groups" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldClaimsGroups(ctx context.Context) (v []string, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldClaimsGroups is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldClaimsGroups requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldClaimsGroups: %w", err) } return oldValue.ClaimsGroups, nil } // AppendClaimsGroups adds s to the "claims_groups" field. func (m *UserIdentityMutation) AppendClaimsGroups(s []string) { m.appendclaims_groups = append(m.appendclaims_groups, s...) } // AppendedClaimsGroups returns the list of values that were appended to the "claims_groups" field in this mutation. func (m *UserIdentityMutation) AppendedClaimsGroups() ([]string, bool) { if len(m.appendclaims_groups) == 0 { return nil, false } return m.appendclaims_groups, true } // ClearClaimsGroups clears the value of the "claims_groups" field. func (m *UserIdentityMutation) ClearClaimsGroups() { m.claims_groups = nil m.appendclaims_groups = nil m.clearedFields[useridentity.FieldClaimsGroups] = struct{}{} } // ClaimsGroupsCleared returns if the "claims_groups" field was cleared in this mutation. func (m *UserIdentityMutation) ClaimsGroupsCleared() bool { _, ok := m.clearedFields[useridentity.FieldClaimsGroups] return ok } // ResetClaimsGroups resets all changes to the "claims_groups" field. func (m *UserIdentityMutation) ResetClaimsGroups() { m.claims_groups = nil m.appendclaims_groups = nil delete(m.clearedFields, useridentity.FieldClaimsGroups) } // SetConsents sets the "consents" field. func (m *UserIdentityMutation) SetConsents(b []byte) { m.consents = &b } // Consents returns the value of the "consents" field in the mutation. func (m *UserIdentityMutation) Consents() (r []byte, exists bool) { v := m.consents if v == nil { return } return *v, true } // OldConsents returns the old "consents" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldConsents(ctx context.Context) (v []byte, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldConsents is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldConsents requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldConsents: %w", err) } return oldValue.Consents, nil } // ResetConsents resets all changes to the "consents" field. func (m *UserIdentityMutation) ResetConsents() { m.consents = nil } // SetMfaSecrets sets the "mfa_secrets" field. func (m *UserIdentityMutation) SetMfaSecrets(b []byte) { m.mfa_secrets = &b } // MfaSecrets returns the value of the "mfa_secrets" field in the mutation. func (m *UserIdentityMutation) MfaSecrets() (r []byte, exists bool) { v := m.mfa_secrets if v == nil { return } return *v, true } // OldMfaSecrets returns the old "mfa_secrets" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldMfaSecrets(ctx context.Context) (v *[]byte, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldMfaSecrets is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldMfaSecrets requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldMfaSecrets: %w", err) } return oldValue.MfaSecrets, nil } // ClearMfaSecrets clears the value of the "mfa_secrets" field. func (m *UserIdentityMutation) ClearMfaSecrets() { m.mfa_secrets = nil m.clearedFields[useridentity.FieldMfaSecrets] = struct{}{} } // MfaSecretsCleared returns if the "mfa_secrets" field was cleared in this mutation. func (m *UserIdentityMutation) MfaSecretsCleared() bool { _, ok := m.clearedFields[useridentity.FieldMfaSecrets] return ok } // ResetMfaSecrets resets all changes to the "mfa_secrets" field. func (m *UserIdentityMutation) ResetMfaSecrets() { m.mfa_secrets = nil delete(m.clearedFields, useridentity.FieldMfaSecrets) } // SetCreatedAt sets the "created_at" field. func (m *UserIdentityMutation) SetCreatedAt(t time.Time) { m.created_at = &t } // CreatedAt returns the value of the "created_at" field in the mutation. func (m *UserIdentityMutation) CreatedAt() (r time.Time, exists bool) { v := m.created_at if v == nil { return } return *v, true } // OldCreatedAt returns the old "created_at" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldCreatedAt requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) } return oldValue.CreatedAt, nil } // ResetCreatedAt resets all changes to the "created_at" field. func (m *UserIdentityMutation) ResetCreatedAt() { m.created_at = nil } // SetLastLogin sets the "last_login" field. func (m *UserIdentityMutation) SetLastLogin(t time.Time) { m.last_login = &t } // LastLogin returns the value of the "last_login" field in the mutation. func (m *UserIdentityMutation) LastLogin() (r time.Time, exists bool) { v := m.last_login if v == nil { return } return *v, true } // OldLastLogin returns the old "last_login" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldLastLogin(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldLastLogin is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldLastLogin requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldLastLogin: %w", err) } return oldValue.LastLogin, nil } // ResetLastLogin resets all changes to the "last_login" field. func (m *UserIdentityMutation) ResetLastLogin() { m.last_login = nil } // SetBlockedUntil sets the "blocked_until" field. func (m *UserIdentityMutation) SetBlockedUntil(t time.Time) { m.blocked_until = &t } // BlockedUntil returns the value of the "blocked_until" field in the mutation. func (m *UserIdentityMutation) BlockedUntil() (r time.Time, exists bool) { v := m.blocked_until if v == nil { return } return *v, true } // OldBlockedUntil returns the old "blocked_until" field's value of the UserIdentity entity. // If the UserIdentity object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. func (m *UserIdentityMutation) OldBlockedUntil(ctx context.Context) (v time.Time, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldBlockedUntil is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { return v, errors.New("OldBlockedUntil requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { return v, fmt.Errorf("querying old value for OldBlockedUntil: %w", err) } return oldValue.BlockedUntil, nil } // ResetBlockedUntil resets all changes to the "blocked_until" field. func (m *UserIdentityMutation) ResetBlockedUntil() { m.blocked_until = nil } // Where appends a list predicates to the UserIdentityMutation builder. func (m *UserIdentityMutation) Where(ps ...predicate.UserIdentity) { m.predicates = append(m.predicates, ps...) } // WhereP appends storage-level predicates to the UserIdentityMutation builder. Using this method, // users can use type-assertion to append predicates that do not depend on any generated package. func (m *UserIdentityMutation) WhereP(ps ...func(*sql.Selector)) { p := make([]predicate.UserIdentity, len(ps)) for i := range ps { p[i] = ps[i] } m.Where(p...) } // Op returns the operation name. func (m *UserIdentityMutation) Op() Op { return m.op } // SetOp allows setting the mutation operation. func (m *UserIdentityMutation) SetOp(op Op) { m.op = op } // Type returns the node type of this mutation (UserIdentity). func (m *UserIdentityMutation) Type() string { return m.typ } // Fields returns all fields that were changed during this mutation. Note that in // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *UserIdentityMutation) Fields() []string { fields := make([]string, 0, 13) if m.user_id != nil { fields = append(fields, useridentity.FieldUserID) } if m.connector_id != nil { fields = append(fields, useridentity.FieldConnectorID) } if m.claims_user_id != nil { fields = append(fields, useridentity.FieldClaimsUserID) } if m.claims_username != nil { fields = append(fields, useridentity.FieldClaimsUsername) } if m.claims_preferred_username != nil { fields = append(fields, useridentity.FieldClaimsPreferredUsername) } if m.claims_email != nil { fields = append(fields, useridentity.FieldClaimsEmail) } if m.claims_email_verified != nil { fields = append(fields, useridentity.FieldClaimsEmailVerified) } if m.claims_groups != nil { fields = append(fields, useridentity.FieldClaimsGroups) } if m.consents != nil { fields = append(fields, useridentity.FieldConsents) } if m.mfa_secrets != nil { fields = append(fields, useridentity.FieldMfaSecrets) } if m.created_at != nil { fields = append(fields, useridentity.FieldCreatedAt) } if m.last_login != nil { fields = append(fields, useridentity.FieldLastLogin) } if m.blocked_until != nil { fields = append(fields, useridentity.FieldBlockedUntil) } return fields } // Field returns the value of a field with the given name. The second boolean // return value indicates that this field was not set, or was not defined in the // schema. func (m *UserIdentityMutation) Field(name string) (ent.Value, bool) { switch name { case useridentity.FieldUserID: return m.UserID() case useridentity.FieldConnectorID: return m.ConnectorID() case useridentity.FieldClaimsUserID: return m.ClaimsUserID() case useridentity.FieldClaimsUsername: return m.ClaimsUsername() case useridentity.FieldClaimsPreferredUsername: return m.ClaimsPreferredUsername() case useridentity.FieldClaimsEmail: return m.ClaimsEmail() case useridentity.FieldClaimsEmailVerified: return m.ClaimsEmailVerified() case useridentity.FieldClaimsGroups: return m.ClaimsGroups() case useridentity.FieldConsents: return m.Consents() case useridentity.FieldMfaSecrets: return m.MfaSecrets() case useridentity.FieldCreatedAt: return m.CreatedAt() case useridentity.FieldLastLogin: return m.LastLogin() case useridentity.FieldBlockedUntil: return m.BlockedUntil() } return nil, false } // OldField returns the old value of the field from the database. An error is // returned if the mutation operation is not UpdateOne, or the query to the // database failed. func (m *UserIdentityMutation) OldField(ctx context.Context, name string) (ent.Value, error) { switch name { case useridentity.FieldUserID: return m.OldUserID(ctx) case useridentity.FieldConnectorID: return m.OldConnectorID(ctx) case useridentity.FieldClaimsUserID: return m.OldClaimsUserID(ctx) case useridentity.FieldClaimsUsername: return m.OldClaimsUsername(ctx) case useridentity.FieldClaimsPreferredUsername: return m.OldClaimsPreferredUsername(ctx) case useridentity.FieldClaimsEmail: return m.OldClaimsEmail(ctx) case useridentity.FieldClaimsEmailVerified: return m.OldClaimsEmailVerified(ctx) case useridentity.FieldClaimsGroups: return m.OldClaimsGroups(ctx) case useridentity.FieldConsents: return m.OldConsents(ctx) case useridentity.FieldMfaSecrets: return m.OldMfaSecrets(ctx) case useridentity.FieldCreatedAt: return m.OldCreatedAt(ctx) case useridentity.FieldLastLogin: return m.OldLastLogin(ctx) case useridentity.FieldBlockedUntil: return m.OldBlockedUntil(ctx) } return nil, fmt.Errorf("unknown UserIdentity field %s", name) } // SetField sets the value of a field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *UserIdentityMutation) SetField(name string, value ent.Value) error { switch name { case useridentity.FieldUserID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetUserID(v) return nil case useridentity.FieldConnectorID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetConnectorID(v) return nil case useridentity.FieldClaimsUserID: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsUserID(v) return nil case useridentity.FieldClaimsUsername: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsUsername(v) return nil case useridentity.FieldClaimsPreferredUsername: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsPreferredUsername(v) return nil case useridentity.FieldClaimsEmail: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsEmail(v) return nil case useridentity.FieldClaimsEmailVerified: v, ok := value.(bool) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsEmailVerified(v) return nil case useridentity.FieldClaimsGroups: v, ok := value.([]string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetClaimsGroups(v) return nil case useridentity.FieldConsents: v, ok := value.([]byte) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetConsents(v) return nil case useridentity.FieldMfaSecrets: v, ok := value.([]byte) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetMfaSecrets(v) return nil case useridentity.FieldCreatedAt: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetCreatedAt(v) return nil case useridentity.FieldLastLogin: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetLastLogin(v) return nil case useridentity.FieldBlockedUntil: v, ok := value.(time.Time) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } m.SetBlockedUntil(v) return nil } return fmt.Errorf("unknown UserIdentity field %s", name) } // AddedFields returns all numeric fields that were incremented/decremented during // this mutation. func (m *UserIdentityMutation) AddedFields() []string { return nil } // AddedField returns the numeric value that was incremented/decremented on a field // with the given name. The second boolean return value indicates that this field // was not set, or was not defined in the schema. func (m *UserIdentityMutation) AddedField(name string) (ent.Value, bool) { return nil, false } // AddField adds the value to the field with the given name. It returns an error if // the field is not defined in the schema, or if the type mismatched the field // type. func (m *UserIdentityMutation) AddField(name string, value ent.Value) error { switch name { } return fmt.Errorf("unknown UserIdentity numeric field %s", name) } // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *UserIdentityMutation) ClearedFields() []string { var fields []string if m.FieldCleared(useridentity.FieldClaimsGroups) { fields = append(fields, useridentity.FieldClaimsGroups) } if m.FieldCleared(useridentity.FieldMfaSecrets) { fields = append(fields, useridentity.FieldMfaSecrets) } return fields } // FieldCleared returns a boolean indicating if a field with the given name was // cleared in this mutation. func (m *UserIdentityMutation) FieldCleared(name string) bool { _, ok := m.clearedFields[name] return ok } // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *UserIdentityMutation) ClearField(name string) error { switch name { case useridentity.FieldClaimsGroups: m.ClearClaimsGroups() return nil case useridentity.FieldMfaSecrets: m.ClearMfaSecrets() return nil } return fmt.Errorf("unknown UserIdentity nullable field %s", name) } // ResetField resets all changes in the mutation for the field with the given name. // It returns an error if the field is not defined in the schema. func (m *UserIdentityMutation) ResetField(name string) error { switch name { case useridentity.FieldUserID: m.ResetUserID() return nil case useridentity.FieldConnectorID: m.ResetConnectorID() return nil case useridentity.FieldClaimsUserID: m.ResetClaimsUserID() return nil case useridentity.FieldClaimsUsername: m.ResetClaimsUsername() return nil case useridentity.FieldClaimsPreferredUsername: m.ResetClaimsPreferredUsername() return nil case useridentity.FieldClaimsEmail: m.ResetClaimsEmail() return nil case useridentity.FieldClaimsEmailVerified: m.ResetClaimsEmailVerified() return nil case useridentity.FieldClaimsGroups: m.ResetClaimsGroups() return nil case useridentity.FieldConsents: m.ResetConsents() return nil case useridentity.FieldMfaSecrets: m.ResetMfaSecrets() return nil case useridentity.FieldCreatedAt: m.ResetCreatedAt() return nil case useridentity.FieldLastLogin: m.ResetLastLogin() return nil case useridentity.FieldBlockedUntil: m.ResetBlockedUntil() return nil } return fmt.Errorf("unknown UserIdentity field %s", name) } // AddedEdges returns all edge names that were set/added in this mutation. func (m *UserIdentityMutation) AddedEdges() []string { edges := make([]string, 0, 0) return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *UserIdentityMutation) AddedIDs(name string) []ent.Value { return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *UserIdentityMutation) RemovedEdges() []string { edges := make([]string, 0, 0) return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *UserIdentityMutation) RemovedIDs(name string) []ent.Value { return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *UserIdentityMutation) ClearedEdges() []string { edges := make([]string, 0, 0) return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *UserIdentityMutation) EdgeCleared(name string) bool { return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *UserIdentityMutation) ClearEdge(name string) error { return fmt.Errorf("unknown UserIdentity unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *UserIdentityMutation) ResetEdge(name string) error { return fmt.Errorf("unknown UserIdentity edge %s", name) } ================================================ FILE: storage/ent/db/oauth2client/oauth2client.go ================================================ // Code generated by ent, DO NOT EDIT. package oauth2client import ( "entgo.io/ent/dialect/sql" ) const ( // Label holds the string label denoting the oauth2client type in the database. Label = "oauth2client" // FieldID holds the string denoting the id field in the database. FieldID = "id" // FieldSecret holds the string denoting the secret field in the database. FieldSecret = "secret" // FieldRedirectUris holds the string denoting the redirect_uris field in the database. FieldRedirectUris = "redirect_uris" // FieldTrustedPeers holds the string denoting the trusted_peers field in the database. FieldTrustedPeers = "trusted_peers" // FieldPublic holds the string denoting the public field in the database. FieldPublic = "public" // FieldName holds the string denoting the name field in the database. FieldName = "name" // FieldLogoURL holds the string denoting the logo_url field in the database. FieldLogoURL = "logo_url" // FieldAllowedConnectors holds the string denoting the allowed_connectors field in the database. FieldAllowedConnectors = "allowed_connectors" // FieldMfaChain holds the string denoting the mfa_chain field in the database. FieldMfaChain = "mfa_chain" // Table holds the table name of the oauth2client in the database. Table = "oauth2clients" ) // Columns holds all SQL columns for oauth2client fields. var Columns = []string{ FieldID, FieldSecret, FieldRedirectUris, FieldTrustedPeers, FieldPublic, FieldName, FieldLogoURL, FieldAllowedConnectors, FieldMfaChain, } // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { if column == Columns[i] { return true } } return false } var ( // SecretValidator is a validator for the "secret" field. It is called by the builders before save. SecretValidator func(string) error // NameValidator is a validator for the "name" field. It is called by the builders before save. NameValidator func(string) error // LogoURLValidator is a validator for the "logo_url" field. It is called by the builders before save. LogoURLValidator func(string) error // IDValidator is a validator for the "id" field. It is called by the builders before save. IDValidator func(string) error ) // OrderOption defines the ordering options for the OAuth2Client queries. type OrderOption func(*sql.Selector) // ByID orders the results by the id field. func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } // BySecret orders the results by the secret field. func BySecret(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldSecret, opts...).ToFunc() } // ByPublic orders the results by the public field. func ByPublic(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldPublic, opts...).ToFunc() } // ByName orders the results by the name field. func ByName(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldName, opts...).ToFunc() } // ByLogoURL orders the results by the logo_url field. func ByLogoURL(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldLogoURL, opts...).ToFunc() } ================================================ FILE: storage/ent/db/oauth2client/where.go ================================================ // Code generated by ent, DO NOT EDIT. package oauth2client import ( "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ID filters vertices based on their ID field. func ID(id string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. func IDEQ(id string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. func IDNEQ(id string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. func IDIn(ids ...string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. func IDNotIn(ids ...string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. func IDGT(id string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. func IDGTE(id string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. func IDLT(id string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. func IDLTE(id string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldLTE(FieldID, id)) } // IDEqualFold applies the EqualFold predicate on the ID field. func IDEqualFold(id string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEqualFold(FieldID, id)) } // IDContainsFold applies the ContainsFold predicate on the ID field. func IDContainsFold(id string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldContainsFold(FieldID, id)) } // Secret applies equality check predicate on the "secret" field. It's identical to SecretEQ. func Secret(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEQ(FieldSecret, v)) } // Public applies equality check predicate on the "public" field. It's identical to PublicEQ. func Public(v bool) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEQ(FieldPublic, v)) } // Name applies equality check predicate on the "name" field. It's identical to NameEQ. func Name(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEQ(FieldName, v)) } // LogoURL applies equality check predicate on the "logo_url" field. It's identical to LogoURLEQ. func LogoURL(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEQ(FieldLogoURL, v)) } // SecretEQ applies the EQ predicate on the "secret" field. func SecretEQ(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEQ(FieldSecret, v)) } // SecretNEQ applies the NEQ predicate on the "secret" field. func SecretNEQ(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNEQ(FieldSecret, v)) } // SecretIn applies the In predicate on the "secret" field. func SecretIn(vs ...string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldIn(FieldSecret, vs...)) } // SecretNotIn applies the NotIn predicate on the "secret" field. func SecretNotIn(vs ...string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNotIn(FieldSecret, vs...)) } // SecretGT applies the GT predicate on the "secret" field. func SecretGT(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldGT(FieldSecret, v)) } // SecretGTE applies the GTE predicate on the "secret" field. func SecretGTE(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldGTE(FieldSecret, v)) } // SecretLT applies the LT predicate on the "secret" field. func SecretLT(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldLT(FieldSecret, v)) } // SecretLTE applies the LTE predicate on the "secret" field. func SecretLTE(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldLTE(FieldSecret, v)) } // SecretContains applies the Contains predicate on the "secret" field. func SecretContains(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldContains(FieldSecret, v)) } // SecretHasPrefix applies the HasPrefix predicate on the "secret" field. func SecretHasPrefix(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldHasPrefix(FieldSecret, v)) } // SecretHasSuffix applies the HasSuffix predicate on the "secret" field. func SecretHasSuffix(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldHasSuffix(FieldSecret, v)) } // SecretEqualFold applies the EqualFold predicate on the "secret" field. func SecretEqualFold(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEqualFold(FieldSecret, v)) } // SecretContainsFold applies the ContainsFold predicate on the "secret" field. func SecretContainsFold(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldContainsFold(FieldSecret, v)) } // RedirectUrisIsNil applies the IsNil predicate on the "redirect_uris" field. func RedirectUrisIsNil() predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldIsNull(FieldRedirectUris)) } // RedirectUrisNotNil applies the NotNil predicate on the "redirect_uris" field. func RedirectUrisNotNil() predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNotNull(FieldRedirectUris)) } // TrustedPeersIsNil applies the IsNil predicate on the "trusted_peers" field. func TrustedPeersIsNil() predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldIsNull(FieldTrustedPeers)) } // TrustedPeersNotNil applies the NotNil predicate on the "trusted_peers" field. func TrustedPeersNotNil() predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNotNull(FieldTrustedPeers)) } // PublicEQ applies the EQ predicate on the "public" field. func PublicEQ(v bool) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEQ(FieldPublic, v)) } // PublicNEQ applies the NEQ predicate on the "public" field. func PublicNEQ(v bool) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNEQ(FieldPublic, v)) } // NameEQ applies the EQ predicate on the "name" field. func NameEQ(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEQ(FieldName, v)) } // NameNEQ applies the NEQ predicate on the "name" field. func NameNEQ(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNEQ(FieldName, v)) } // NameIn applies the In predicate on the "name" field. func NameIn(vs ...string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldIn(FieldName, vs...)) } // NameNotIn applies the NotIn predicate on the "name" field. func NameNotIn(vs ...string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNotIn(FieldName, vs...)) } // NameGT applies the GT predicate on the "name" field. func NameGT(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldGT(FieldName, v)) } // NameGTE applies the GTE predicate on the "name" field. func NameGTE(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldGTE(FieldName, v)) } // NameLT applies the LT predicate on the "name" field. func NameLT(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldLT(FieldName, v)) } // NameLTE applies the LTE predicate on the "name" field. func NameLTE(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldLTE(FieldName, v)) } // NameContains applies the Contains predicate on the "name" field. func NameContains(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldContains(FieldName, v)) } // NameHasPrefix applies the HasPrefix predicate on the "name" field. func NameHasPrefix(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldHasPrefix(FieldName, v)) } // NameHasSuffix applies the HasSuffix predicate on the "name" field. func NameHasSuffix(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldHasSuffix(FieldName, v)) } // NameEqualFold applies the EqualFold predicate on the "name" field. func NameEqualFold(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEqualFold(FieldName, v)) } // NameContainsFold applies the ContainsFold predicate on the "name" field. func NameContainsFold(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldContainsFold(FieldName, v)) } // LogoURLEQ applies the EQ predicate on the "logo_url" field. func LogoURLEQ(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEQ(FieldLogoURL, v)) } // LogoURLNEQ applies the NEQ predicate on the "logo_url" field. func LogoURLNEQ(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNEQ(FieldLogoURL, v)) } // LogoURLIn applies the In predicate on the "logo_url" field. func LogoURLIn(vs ...string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldIn(FieldLogoURL, vs...)) } // LogoURLNotIn applies the NotIn predicate on the "logo_url" field. func LogoURLNotIn(vs ...string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNotIn(FieldLogoURL, vs...)) } // LogoURLGT applies the GT predicate on the "logo_url" field. func LogoURLGT(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldGT(FieldLogoURL, v)) } // LogoURLGTE applies the GTE predicate on the "logo_url" field. func LogoURLGTE(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldGTE(FieldLogoURL, v)) } // LogoURLLT applies the LT predicate on the "logo_url" field. func LogoURLLT(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldLT(FieldLogoURL, v)) } // LogoURLLTE applies the LTE predicate on the "logo_url" field. func LogoURLLTE(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldLTE(FieldLogoURL, v)) } // LogoURLContains applies the Contains predicate on the "logo_url" field. func LogoURLContains(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldContains(FieldLogoURL, v)) } // LogoURLHasPrefix applies the HasPrefix predicate on the "logo_url" field. func LogoURLHasPrefix(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldHasPrefix(FieldLogoURL, v)) } // LogoURLHasSuffix applies the HasSuffix predicate on the "logo_url" field. func LogoURLHasSuffix(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldHasSuffix(FieldLogoURL, v)) } // LogoURLEqualFold applies the EqualFold predicate on the "logo_url" field. func LogoURLEqualFold(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldEqualFold(FieldLogoURL, v)) } // LogoURLContainsFold applies the ContainsFold predicate on the "logo_url" field. func LogoURLContainsFold(v string) predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldContainsFold(FieldLogoURL, v)) } // AllowedConnectorsIsNil applies the IsNil predicate on the "allowed_connectors" field. func AllowedConnectorsIsNil() predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldIsNull(FieldAllowedConnectors)) } // AllowedConnectorsNotNil applies the NotNil predicate on the "allowed_connectors" field. func AllowedConnectorsNotNil() predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNotNull(FieldAllowedConnectors)) } // MfaChainIsNil applies the IsNil predicate on the "mfa_chain" field. func MfaChainIsNil() predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldIsNull(FieldMfaChain)) } // MfaChainNotNil applies the NotNil predicate on the "mfa_chain" field. func MfaChainNotNil() predicate.OAuth2Client { return predicate.OAuth2Client(sql.FieldNotNull(FieldMfaChain)) } // And groups predicates with the AND operator between them. func And(predicates ...predicate.OAuth2Client) predicate.OAuth2Client { return predicate.OAuth2Client(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.OAuth2Client) predicate.OAuth2Client { return predicate.OAuth2Client(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.OAuth2Client) predicate.OAuth2Client { return predicate.OAuth2Client(sql.NotPredicates(p)) } ================================================ FILE: storage/ent/db/oauth2client.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "encoding/json" "fmt" "strings" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/oauth2client" ) // OAuth2Client is the model entity for the OAuth2Client schema. type OAuth2Client struct { config `json:"-"` // ID of the ent. ID string `json:"id,omitempty"` // Secret holds the value of the "secret" field. Secret string `json:"secret,omitempty"` // RedirectUris holds the value of the "redirect_uris" field. RedirectUris []string `json:"redirect_uris,omitempty"` // TrustedPeers holds the value of the "trusted_peers" field. TrustedPeers []string `json:"trusted_peers,omitempty"` // Public holds the value of the "public" field. Public bool `json:"public,omitempty"` // Name holds the value of the "name" field. Name string `json:"name,omitempty"` // LogoURL holds the value of the "logo_url" field. LogoURL string `json:"logo_url,omitempty"` // AllowedConnectors holds the value of the "allowed_connectors" field. AllowedConnectors []string `json:"allowed_connectors,omitempty"` // MfaChain holds the value of the "mfa_chain" field. MfaChain []string `json:"mfa_chain,omitempty"` selectValues sql.SelectValues } // scanValues returns the types for scanning values from sql.Rows. func (*OAuth2Client) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { case oauth2client.FieldRedirectUris, oauth2client.FieldTrustedPeers, oauth2client.FieldAllowedConnectors, oauth2client.FieldMfaChain: values[i] = new([]byte) case oauth2client.FieldPublic: values[i] = new(sql.NullBool) case oauth2client.FieldID, oauth2client.FieldSecret, oauth2client.FieldName, oauth2client.FieldLogoURL: values[i] = new(sql.NullString) default: values[i] = new(sql.UnknownType) } } return values, nil } // assignValues assigns the values that were returned from sql.Rows (after scanning) // to the OAuth2Client fields. func (_m *OAuth2Client) assignValues(columns []string, values []any) error { if m, n := len(values), len(columns); m < n { return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) } for i := range columns { switch columns[i] { case oauth2client.FieldID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field id", values[i]) } else if value.Valid { _m.ID = value.String } case oauth2client.FieldSecret: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field secret", values[i]) } else if value.Valid { _m.Secret = value.String } case oauth2client.FieldRedirectUris: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field redirect_uris", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.RedirectUris); err != nil { return fmt.Errorf("unmarshal field redirect_uris: %w", err) } } case oauth2client.FieldTrustedPeers: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field trusted_peers", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.TrustedPeers); err != nil { return fmt.Errorf("unmarshal field trusted_peers: %w", err) } } case oauth2client.FieldPublic: if value, ok := values[i].(*sql.NullBool); !ok { return fmt.Errorf("unexpected type %T for field public", values[i]) } else if value.Valid { _m.Public = value.Bool } case oauth2client.FieldName: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field name", values[i]) } else if value.Valid { _m.Name = value.String } case oauth2client.FieldLogoURL: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field logo_url", values[i]) } else if value.Valid { _m.LogoURL = value.String } case oauth2client.FieldAllowedConnectors: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field allowed_connectors", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.AllowedConnectors); err != nil { return fmt.Errorf("unmarshal field allowed_connectors: %w", err) } } case oauth2client.FieldMfaChain: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field mfa_chain", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.MfaChain); err != nil { return fmt.Errorf("unmarshal field mfa_chain: %w", err) } } default: _m.selectValues.Set(columns[i], values[i]) } } return nil } // Value returns the ent.Value that was dynamically selected and assigned to the OAuth2Client. // This includes values selected through modifiers, order, etc. func (_m *OAuth2Client) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } // Update returns a builder for updating this OAuth2Client. // Note that you need to call OAuth2Client.Unwrap() before calling this method if this OAuth2Client // was returned from a transaction, and the transaction was committed or rolled back. func (_m *OAuth2Client) Update() *OAuth2ClientUpdateOne { return NewOAuth2ClientClient(_m.config).UpdateOne(_m) } // Unwrap unwraps the OAuth2Client entity that was returned from a transaction after it was closed, // so that all future queries will be executed through the driver which created the transaction. func (_m *OAuth2Client) Unwrap() *OAuth2Client { _tx, ok := _m.config.driver.(*txDriver) if !ok { panic("db: OAuth2Client is not a transactional entity") } _m.config.driver = _tx.drv return _m } // String implements the fmt.Stringer. func (_m *OAuth2Client) String() string { var builder strings.Builder builder.WriteString("OAuth2Client(") builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) builder.WriteString("secret=") builder.WriteString(_m.Secret) builder.WriteString(", ") builder.WriteString("redirect_uris=") builder.WriteString(fmt.Sprintf("%v", _m.RedirectUris)) builder.WriteString(", ") builder.WriteString("trusted_peers=") builder.WriteString(fmt.Sprintf("%v", _m.TrustedPeers)) builder.WriteString(", ") builder.WriteString("public=") builder.WriteString(fmt.Sprintf("%v", _m.Public)) builder.WriteString(", ") builder.WriteString("name=") builder.WriteString(_m.Name) builder.WriteString(", ") builder.WriteString("logo_url=") builder.WriteString(_m.LogoURL) builder.WriteString(", ") builder.WriteString("allowed_connectors=") builder.WriteString(fmt.Sprintf("%v", _m.AllowedConnectors)) builder.WriteString(", ") builder.WriteString("mfa_chain=") builder.WriteString(fmt.Sprintf("%v", _m.MfaChain)) builder.WriteByte(')') return builder.String() } // OAuth2Clients is a parsable slice of OAuth2Client. type OAuth2Clients []*OAuth2Client ================================================ FILE: storage/ent/db/oauth2client_create.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/oauth2client" ) // OAuth2ClientCreate is the builder for creating a OAuth2Client entity. type OAuth2ClientCreate struct { config mutation *OAuth2ClientMutation hooks []Hook } // SetSecret sets the "secret" field. func (_c *OAuth2ClientCreate) SetSecret(v string) *OAuth2ClientCreate { _c.mutation.SetSecret(v) return _c } // SetRedirectUris sets the "redirect_uris" field. func (_c *OAuth2ClientCreate) SetRedirectUris(v []string) *OAuth2ClientCreate { _c.mutation.SetRedirectUris(v) return _c } // SetTrustedPeers sets the "trusted_peers" field. func (_c *OAuth2ClientCreate) SetTrustedPeers(v []string) *OAuth2ClientCreate { _c.mutation.SetTrustedPeers(v) return _c } // SetPublic sets the "public" field. func (_c *OAuth2ClientCreate) SetPublic(v bool) *OAuth2ClientCreate { _c.mutation.SetPublic(v) return _c } // SetName sets the "name" field. func (_c *OAuth2ClientCreate) SetName(v string) *OAuth2ClientCreate { _c.mutation.SetName(v) return _c } // SetLogoURL sets the "logo_url" field. func (_c *OAuth2ClientCreate) SetLogoURL(v string) *OAuth2ClientCreate { _c.mutation.SetLogoURL(v) return _c } // SetAllowedConnectors sets the "allowed_connectors" field. func (_c *OAuth2ClientCreate) SetAllowedConnectors(v []string) *OAuth2ClientCreate { _c.mutation.SetAllowedConnectors(v) return _c } // SetMfaChain sets the "mfa_chain" field. func (_c *OAuth2ClientCreate) SetMfaChain(v []string) *OAuth2ClientCreate { _c.mutation.SetMfaChain(v) return _c } // SetID sets the "id" field. func (_c *OAuth2ClientCreate) SetID(v string) *OAuth2ClientCreate { _c.mutation.SetID(v) return _c } // Mutation returns the OAuth2ClientMutation object of the builder. func (_c *OAuth2ClientCreate) Mutation() *OAuth2ClientMutation { return _c.mutation } // Save creates the OAuth2Client in the database. func (_c *OAuth2ClientCreate) Save(ctx context.Context) (*OAuth2Client, error) { return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) } // SaveX calls Save and panics if Save returns an error. func (_c *OAuth2ClientCreate) SaveX(ctx context.Context) *OAuth2Client { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *OAuth2ClientCreate) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *OAuth2ClientCreate) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_c *OAuth2ClientCreate) check() error { if _, ok := _c.mutation.Secret(); !ok { return &ValidationError{Name: "secret", err: errors.New(`db: missing required field "OAuth2Client.secret"`)} } if v, ok := _c.mutation.Secret(); ok { if err := oauth2client.SecretValidator(v); err != nil { return &ValidationError{Name: "secret", err: fmt.Errorf(`db: validator failed for field "OAuth2Client.secret": %w`, err)} } } if _, ok := _c.mutation.Public(); !ok { return &ValidationError{Name: "public", err: errors.New(`db: missing required field "OAuth2Client.public"`)} } if _, ok := _c.mutation.Name(); !ok { return &ValidationError{Name: "name", err: errors.New(`db: missing required field "OAuth2Client.name"`)} } if v, ok := _c.mutation.Name(); ok { if err := oauth2client.NameValidator(v); err != nil { return &ValidationError{Name: "name", err: fmt.Errorf(`db: validator failed for field "OAuth2Client.name": %w`, err)} } } if _, ok := _c.mutation.LogoURL(); !ok { return &ValidationError{Name: "logo_url", err: errors.New(`db: missing required field "OAuth2Client.logo_url"`)} } if v, ok := _c.mutation.LogoURL(); ok { if err := oauth2client.LogoURLValidator(v); err != nil { return &ValidationError{Name: "logo_url", err: fmt.Errorf(`db: validator failed for field "OAuth2Client.logo_url": %w`, err)} } } if v, ok := _c.mutation.ID(); ok { if err := oauth2client.IDValidator(v); err != nil { return &ValidationError{Name: "id", err: fmt.Errorf(`db: validator failed for field "OAuth2Client.id": %w`, err)} } } return nil } func (_c *OAuth2ClientCreate) sqlSave(ctx context.Context) (*OAuth2Client, error) { if err := _c.check(); err != nil { return nil, err } _node, _spec := _c.createSpec() if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } if _spec.ID.Value != nil { if id, ok := _spec.ID.Value.(string); ok { _node.ID = id } else { return nil, fmt.Errorf("unexpected OAuth2Client.ID type: %T", _spec.ID.Value) } } _c.mutation.id = &_node.ID _c.mutation.done = true return _node, nil } func (_c *OAuth2ClientCreate) createSpec() (*OAuth2Client, *sqlgraph.CreateSpec) { var ( _node = &OAuth2Client{config: _c.config} _spec = sqlgraph.NewCreateSpec(oauth2client.Table, sqlgraph.NewFieldSpec(oauth2client.FieldID, field.TypeString)) ) if id, ok := _c.mutation.ID(); ok { _node.ID = id _spec.ID.Value = id } if value, ok := _c.mutation.Secret(); ok { _spec.SetField(oauth2client.FieldSecret, field.TypeString, value) _node.Secret = value } if value, ok := _c.mutation.RedirectUris(); ok { _spec.SetField(oauth2client.FieldRedirectUris, field.TypeJSON, value) _node.RedirectUris = value } if value, ok := _c.mutation.TrustedPeers(); ok { _spec.SetField(oauth2client.FieldTrustedPeers, field.TypeJSON, value) _node.TrustedPeers = value } if value, ok := _c.mutation.Public(); ok { _spec.SetField(oauth2client.FieldPublic, field.TypeBool, value) _node.Public = value } if value, ok := _c.mutation.Name(); ok { _spec.SetField(oauth2client.FieldName, field.TypeString, value) _node.Name = value } if value, ok := _c.mutation.LogoURL(); ok { _spec.SetField(oauth2client.FieldLogoURL, field.TypeString, value) _node.LogoURL = value } if value, ok := _c.mutation.AllowedConnectors(); ok { _spec.SetField(oauth2client.FieldAllowedConnectors, field.TypeJSON, value) _node.AllowedConnectors = value } if value, ok := _c.mutation.MfaChain(); ok { _spec.SetField(oauth2client.FieldMfaChain, field.TypeJSON, value) _node.MfaChain = value } return _node, _spec } // OAuth2ClientCreateBulk is the builder for creating many OAuth2Client entities in bulk. type OAuth2ClientCreateBulk struct { config err error builders []*OAuth2ClientCreate } // Save creates the OAuth2Client entities in the database. func (_c *OAuth2ClientCreateBulk) Save(ctx context.Context) ([]*OAuth2Client, error) { if _c.err != nil { return nil, _c.err } specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) nodes := make([]*OAuth2Client, len(_c.builders)) mutators := make([]Mutator, len(_c.builders)) for i := range _c.builders { func(i int, root context.Context) { builder := _c.builders[i] var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*OAuth2ClientMutation) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } if err := builder.check(); err != nil { return nil, err } builder.mutation = mutation var err error nodes[i], specs[i] = builder.createSpec() if i < len(mutators)-1 { _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) } else { spec := &sqlgraph.BatchCreateSpec{Nodes: specs} // Invoke the actual operation on the latest mutation in the chain. if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } } } if err != nil { return nil, err } mutation.id = &nodes[i].ID mutation.done = true return nodes[i], nil }) for i := len(builder.hooks) - 1; i >= 0; i-- { mut = builder.hooks[i](mut) } mutators[i] = mut }(i, ctx) } if len(mutators) > 0 { if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { return nil, err } } return nodes, nil } // SaveX is like Save, but panics if an error occurs. func (_c *OAuth2ClientCreateBulk) SaveX(ctx context.Context) []*OAuth2Client { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *OAuth2ClientCreateBulk) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *OAuth2ClientCreateBulk) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/oauth2client_delete.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/oauth2client" "github.com/dexidp/dex/storage/ent/db/predicate" ) // OAuth2ClientDelete is the builder for deleting a OAuth2Client entity. type OAuth2ClientDelete struct { config hooks []Hook mutation *OAuth2ClientMutation } // Where appends a list predicates to the OAuth2ClientDelete builder. func (_d *OAuth2ClientDelete) Where(ps ...predicate.OAuth2Client) *OAuth2ClientDelete { _d.mutation.Where(ps...) return _d } // Exec executes the deletion query and returns how many vertices were deleted. func (_d *OAuth2ClientDelete) Exec(ctx context.Context) (int, error) { return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) } // ExecX is like Exec, but panics if an error occurs. func (_d *OAuth2ClientDelete) ExecX(ctx context.Context) int { n, err := _d.Exec(ctx) if err != nil { panic(err) } return n } func (_d *OAuth2ClientDelete) sqlExec(ctx context.Context) (int, error) { _spec := sqlgraph.NewDeleteSpec(oauth2client.Table, sqlgraph.NewFieldSpec(oauth2client.FieldID, field.TypeString)) if ps := _d.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) if err != nil && sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } _d.mutation.done = true return affected, err } // OAuth2ClientDeleteOne is the builder for deleting a single OAuth2Client entity. type OAuth2ClientDeleteOne struct { _d *OAuth2ClientDelete } // Where appends a list predicates to the OAuth2ClientDelete builder. func (_d *OAuth2ClientDeleteOne) Where(ps ...predicate.OAuth2Client) *OAuth2ClientDeleteOne { _d._d.mutation.Where(ps...) return _d } // Exec executes the deletion query. func (_d *OAuth2ClientDeleteOne) Exec(ctx context.Context) error { n, err := _d._d.Exec(ctx) switch { case err != nil: return err case n == 0: return &NotFoundError{oauth2client.Label} default: return nil } } // ExecX is like Exec, but panics if an error occurs. func (_d *OAuth2ClientDeleteOne) ExecX(ctx context.Context) { if err := _d.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/oauth2client_query.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "fmt" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/oauth2client" "github.com/dexidp/dex/storage/ent/db/predicate" ) // OAuth2ClientQuery is the builder for querying OAuth2Client entities. type OAuth2ClientQuery struct { config ctx *QueryContext order []oauth2client.OrderOption inters []Interceptor predicates []predicate.OAuth2Client // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) } // Where adds a new predicate for the OAuth2ClientQuery builder. func (_q *OAuth2ClientQuery) Where(ps ...predicate.OAuth2Client) *OAuth2ClientQuery { _q.predicates = append(_q.predicates, ps...) return _q } // Limit the number of records to be returned by this query. func (_q *OAuth2ClientQuery) Limit(limit int) *OAuth2ClientQuery { _q.ctx.Limit = &limit return _q } // Offset to start from. func (_q *OAuth2ClientQuery) Offset(offset int) *OAuth2ClientQuery { _q.ctx.Offset = &offset return _q } // Unique configures the query builder to filter duplicate records on query. // By default, unique is set to true, and can be disabled using this method. func (_q *OAuth2ClientQuery) Unique(unique bool) *OAuth2ClientQuery { _q.ctx.Unique = &unique return _q } // Order specifies how the records should be ordered. func (_q *OAuth2ClientQuery) Order(o ...oauth2client.OrderOption) *OAuth2ClientQuery { _q.order = append(_q.order, o...) return _q } // First returns the first OAuth2Client entity from the query. // Returns a *NotFoundError when no OAuth2Client was found. func (_q *OAuth2ClientQuery) First(ctx context.Context) (*OAuth2Client, error) { nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) if err != nil { return nil, err } if len(nodes) == 0 { return nil, &NotFoundError{oauth2client.Label} } return nodes[0], nil } // FirstX is like First, but panics if an error occurs. func (_q *OAuth2ClientQuery) FirstX(ctx context.Context) *OAuth2Client { node, err := _q.First(ctx) if err != nil && !IsNotFound(err) { panic(err) } return node } // FirstID returns the first OAuth2Client ID from the query. // Returns a *NotFoundError when no OAuth2Client ID was found. func (_q *OAuth2ClientQuery) FirstID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { return } if len(ids) == 0 { err = &NotFoundError{oauth2client.Label} return } return ids[0], nil } // FirstIDX is like FirstID, but panics if an error occurs. func (_q *OAuth2ClientQuery) FirstIDX(ctx context.Context) string { id, err := _q.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) } return id } // Only returns a single OAuth2Client entity found by the query, ensuring it only returns one. // Returns a *NotSingularError when more than one OAuth2Client entity is found. // Returns a *NotFoundError when no OAuth2Client entities are found. func (_q *OAuth2ClientQuery) Only(ctx context.Context) (*OAuth2Client, error) { nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) if err != nil { return nil, err } switch len(nodes) { case 1: return nodes[0], nil case 0: return nil, &NotFoundError{oauth2client.Label} default: return nil, &NotSingularError{oauth2client.Label} } } // OnlyX is like Only, but panics if an error occurs. func (_q *OAuth2ClientQuery) OnlyX(ctx context.Context) *OAuth2Client { node, err := _q.Only(ctx) if err != nil { panic(err) } return node } // OnlyID is like Only, but returns the only OAuth2Client ID in the query. // Returns a *NotSingularError when more than one OAuth2Client ID is found. // Returns a *NotFoundError when no entities are found. func (_q *OAuth2ClientQuery) OnlyID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { return } switch len(ids) { case 1: id = ids[0] case 0: err = &NotFoundError{oauth2client.Label} default: err = &NotSingularError{oauth2client.Label} } return } // OnlyIDX is like OnlyID, but panics if an error occurs. func (_q *OAuth2ClientQuery) OnlyIDX(ctx context.Context) string { id, err := _q.OnlyID(ctx) if err != nil { panic(err) } return id } // All executes the query and returns a list of OAuth2Clients. func (_q *OAuth2ClientQuery) All(ctx context.Context) ([]*OAuth2Client, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) if err := _q.prepareQuery(ctx); err != nil { return nil, err } qr := querierAll[[]*OAuth2Client, *OAuth2ClientQuery]() return withInterceptors[[]*OAuth2Client](ctx, _q, qr, _q.inters) } // AllX is like All, but panics if an error occurs. func (_q *OAuth2ClientQuery) AllX(ctx context.Context) []*OAuth2Client { nodes, err := _q.All(ctx) if err != nil { panic(err) } return nodes } // IDs executes the query and returns a list of OAuth2Client IDs. func (_q *OAuth2ClientQuery) IDs(ctx context.Context) (ids []string, err error) { if _q.ctx.Unique == nil && _q.path != nil { _q.Unique(true) } ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) if err = _q.Select(oauth2client.FieldID).Scan(ctx, &ids); err != nil { return nil, err } return ids, nil } // IDsX is like IDs, but panics if an error occurs. func (_q *OAuth2ClientQuery) IDsX(ctx context.Context) []string { ids, err := _q.IDs(ctx) if err != nil { panic(err) } return ids } // Count returns the count of the given query. func (_q *OAuth2ClientQuery) Count(ctx context.Context) (int, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) if err := _q.prepareQuery(ctx); err != nil { return 0, err } return withInterceptors[int](ctx, _q, querierCount[*OAuth2ClientQuery](), _q.inters) } // CountX is like Count, but panics if an error occurs. func (_q *OAuth2ClientQuery) CountX(ctx context.Context) int { count, err := _q.Count(ctx) if err != nil { panic(err) } return count } // Exist returns true if the query has elements in the graph. func (_q *OAuth2ClientQuery) Exist(ctx context.Context) (bool, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) switch _, err := _q.FirstID(ctx); { case IsNotFound(err): return false, nil case err != nil: return false, fmt.Errorf("db: check existence: %w", err) default: return true, nil } } // ExistX is like Exist, but panics if an error occurs. func (_q *OAuth2ClientQuery) ExistX(ctx context.Context) bool { exist, err := _q.Exist(ctx) if err != nil { panic(err) } return exist } // Clone returns a duplicate of the OAuth2ClientQuery builder, including all associated steps. It can be // used to prepare common query builders and use them differently after the clone is made. func (_q *OAuth2ClientQuery) Clone() *OAuth2ClientQuery { if _q == nil { return nil } return &OAuth2ClientQuery{ config: _q.config, ctx: _q.ctx.Clone(), order: append([]oauth2client.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.OAuth2Client{}, _q.predicates...), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // // Example: // // var v []struct { // Secret string `json:"secret,omitempty"` // Count int `json:"count,omitempty"` // } // // client.OAuth2Client.Query(). // GroupBy(oauth2client.FieldSecret). // Aggregate(db.Count()). // Scan(ctx, &v) func (_q *OAuth2ClientQuery) GroupBy(field string, fields ...string) *OAuth2ClientGroupBy { _q.ctx.Fields = append([]string{field}, fields...) grbuild := &OAuth2ClientGroupBy{build: _q} grbuild.flds = &_q.ctx.Fields grbuild.label = oauth2client.Label grbuild.scan = grbuild.Scan return grbuild } // Select allows the selection one or more fields/columns for the given query, // instead of selecting all fields in the entity. // // Example: // // var v []struct { // Secret string `json:"secret,omitempty"` // } // // client.OAuth2Client.Query(). // Select(oauth2client.FieldSecret). // Scan(ctx, &v) func (_q *OAuth2ClientQuery) Select(fields ...string) *OAuth2ClientSelect { _q.ctx.Fields = append(_q.ctx.Fields, fields...) sbuild := &OAuth2ClientSelect{OAuth2ClientQuery: _q} sbuild.label = oauth2client.Label sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan return sbuild } // Aggregate returns a OAuth2ClientSelect configured with the given aggregations. func (_q *OAuth2ClientQuery) Aggregate(fns ...AggregateFunc) *OAuth2ClientSelect { return _q.Select().Aggregate(fns...) } func (_q *OAuth2ClientQuery) prepareQuery(ctx context.Context) error { for _, inter := range _q.inters { if inter == nil { return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") } if trv, ok := inter.(Traverser); ok { if err := trv.Traverse(ctx, _q); err != nil { return err } } } for _, f := range _q.ctx.Fields { if !oauth2client.ValidColumn(f) { return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } } if _q.path != nil { prev, err := _q.path(ctx) if err != nil { return err } _q.sql = prev } return nil } func (_q *OAuth2ClientQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*OAuth2Client, error) { var ( nodes = []*OAuth2Client{} _spec = _q.querySpec() ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*OAuth2Client).scanValues(nil, columns) } _spec.Assign = func(columns []string, values []any) error { node := &OAuth2Client{config: _q.config} nodes = append(nodes, node) return node.assignValues(columns, values) } for i := range hooks { hooks[i](ctx, _spec) } if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { return nil, err } if len(nodes) == 0 { return nodes, nil } return nodes, nil } func (_q *OAuth2ClientQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields if len(_q.ctx.Fields) > 0 { _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique } return sqlgraph.CountNodes(ctx, _q.driver, _spec) } func (_q *OAuth2ClientQuery) querySpec() *sqlgraph.QuerySpec { _spec := sqlgraph.NewQuerySpec(oauth2client.Table, oauth2client.Columns, sqlgraph.NewFieldSpec(oauth2client.FieldID, field.TypeString)) _spec.From = _q.sql if unique := _q.ctx.Unique; unique != nil { _spec.Unique = *unique } else if _q.path != nil { _spec.Unique = true } if fields := _q.ctx.Fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, oauth2client.FieldID) for i := range fields { if fields[i] != oauth2client.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } } if ps := _q.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if limit := _q.ctx.Limit; limit != nil { _spec.Limit = *limit } if offset := _q.ctx.Offset; offset != nil { _spec.Offset = *offset } if ps := _q.order; len(ps) > 0 { _spec.Order = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } return _spec } func (_q *OAuth2ClientQuery) sqlQuery(ctx context.Context) *sql.Selector { builder := sql.Dialect(_q.driver.Dialect()) t1 := builder.Table(oauth2client.Table) columns := _q.ctx.Fields if len(columns) == 0 { columns = oauth2client.Columns } selector := builder.Select(t1.Columns(columns...)...).From(t1) if _q.sql != nil { selector = _q.sql selector.Select(selector.Columns(columns...)...) } if _q.ctx.Unique != nil && *_q.ctx.Unique { selector.Distinct() } for _, p := range _q.predicates { p(selector) } for _, p := range _q.order { p(selector) } if offset := _q.ctx.Offset; offset != nil { // limit is mandatory for offset clause. We start // with default value, and override it below if needed. selector.Offset(*offset).Limit(math.MaxInt32) } if limit := _q.ctx.Limit; limit != nil { selector.Limit(*limit) } return selector } // OAuth2ClientGroupBy is the group-by builder for OAuth2Client entities. type OAuth2ClientGroupBy struct { selector build *OAuth2ClientQuery } // Aggregate adds the given aggregation functions to the group-by query. func (_g *OAuth2ClientGroupBy) Aggregate(fns ...AggregateFunc) *OAuth2ClientGroupBy { _g.fns = append(_g.fns, fns...) return _g } // Scan applies the selector query and scans the result into the given value. func (_g *OAuth2ClientGroupBy) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) if err := _g.build.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*OAuth2ClientQuery, *OAuth2ClientGroupBy](ctx, _g.build, _g, _g.build.inters, v) } func (_g *OAuth2ClientGroupBy) sqlScan(ctx context.Context, root *OAuth2ClientQuery, v any) error { selector := root.sqlQuery(ctx).Select() aggregation := make([]string, 0, len(_g.fns)) for _, fn := range _g.fns { aggregation = append(aggregation, fn(selector)) } if len(selector.SelectedColumns()) == 0 { columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) for _, f := range *_g.flds { columns = append(columns, selector.C(f)) } columns = append(columns, aggregation...) selector.Select(columns...) } selector.GroupBy(selector.Columns(*_g.flds...)...) if err := selector.Err(); err != nil { return err } rows := &sql.Rows{} query, args := selector.Query() if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } // OAuth2ClientSelect is the builder for selecting fields of OAuth2Client entities. type OAuth2ClientSelect struct { *OAuth2ClientQuery selector } // Aggregate adds the given aggregation functions to the selector query. func (_s *OAuth2ClientSelect) Aggregate(fns ...AggregateFunc) *OAuth2ClientSelect { _s.fns = append(_s.fns, fns...) return _s } // Scan applies the selector query and scans the result into the given value. func (_s *OAuth2ClientSelect) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) if err := _s.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*OAuth2ClientQuery, *OAuth2ClientSelect](ctx, _s.OAuth2ClientQuery, _s, _s.inters, v) } func (_s *OAuth2ClientSelect) sqlScan(ctx context.Context, root *OAuth2ClientQuery, v any) error { selector := root.sqlQuery(ctx) aggregation := make([]string, 0, len(_s.fns)) for _, fn := range _s.fns { aggregation = append(aggregation, fn(selector)) } switch n := len(*_s.selector.flds); { case n == 0 && len(aggregation) > 0: selector.Select(aggregation...) case n != 0 && len(aggregation) > 0: selector.AppendSelect(aggregation...) } rows := &sql.Rows{} query, args := selector.Query() if err := _s.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } ================================================ FILE: storage/ent/db/oauth2client_update.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/oauth2client" "github.com/dexidp/dex/storage/ent/db/predicate" ) // OAuth2ClientUpdate is the builder for updating OAuth2Client entities. type OAuth2ClientUpdate struct { config hooks []Hook mutation *OAuth2ClientMutation } // Where appends a list predicates to the OAuth2ClientUpdate builder. func (_u *OAuth2ClientUpdate) Where(ps ...predicate.OAuth2Client) *OAuth2ClientUpdate { _u.mutation.Where(ps...) return _u } // SetSecret sets the "secret" field. func (_u *OAuth2ClientUpdate) SetSecret(v string) *OAuth2ClientUpdate { _u.mutation.SetSecret(v) return _u } // SetNillableSecret sets the "secret" field if the given value is not nil. func (_u *OAuth2ClientUpdate) SetNillableSecret(v *string) *OAuth2ClientUpdate { if v != nil { _u.SetSecret(*v) } return _u } // SetRedirectUris sets the "redirect_uris" field. func (_u *OAuth2ClientUpdate) SetRedirectUris(v []string) *OAuth2ClientUpdate { _u.mutation.SetRedirectUris(v) return _u } // AppendRedirectUris appends value to the "redirect_uris" field. func (_u *OAuth2ClientUpdate) AppendRedirectUris(v []string) *OAuth2ClientUpdate { _u.mutation.AppendRedirectUris(v) return _u } // ClearRedirectUris clears the value of the "redirect_uris" field. func (_u *OAuth2ClientUpdate) ClearRedirectUris() *OAuth2ClientUpdate { _u.mutation.ClearRedirectUris() return _u } // SetTrustedPeers sets the "trusted_peers" field. func (_u *OAuth2ClientUpdate) SetTrustedPeers(v []string) *OAuth2ClientUpdate { _u.mutation.SetTrustedPeers(v) return _u } // AppendTrustedPeers appends value to the "trusted_peers" field. func (_u *OAuth2ClientUpdate) AppendTrustedPeers(v []string) *OAuth2ClientUpdate { _u.mutation.AppendTrustedPeers(v) return _u } // ClearTrustedPeers clears the value of the "trusted_peers" field. func (_u *OAuth2ClientUpdate) ClearTrustedPeers() *OAuth2ClientUpdate { _u.mutation.ClearTrustedPeers() return _u } // SetPublic sets the "public" field. func (_u *OAuth2ClientUpdate) SetPublic(v bool) *OAuth2ClientUpdate { _u.mutation.SetPublic(v) return _u } // SetNillablePublic sets the "public" field if the given value is not nil. func (_u *OAuth2ClientUpdate) SetNillablePublic(v *bool) *OAuth2ClientUpdate { if v != nil { _u.SetPublic(*v) } return _u } // SetName sets the "name" field. func (_u *OAuth2ClientUpdate) SetName(v string) *OAuth2ClientUpdate { _u.mutation.SetName(v) return _u } // SetNillableName sets the "name" field if the given value is not nil. func (_u *OAuth2ClientUpdate) SetNillableName(v *string) *OAuth2ClientUpdate { if v != nil { _u.SetName(*v) } return _u } // SetLogoURL sets the "logo_url" field. func (_u *OAuth2ClientUpdate) SetLogoURL(v string) *OAuth2ClientUpdate { _u.mutation.SetLogoURL(v) return _u } // SetNillableLogoURL sets the "logo_url" field if the given value is not nil. func (_u *OAuth2ClientUpdate) SetNillableLogoURL(v *string) *OAuth2ClientUpdate { if v != nil { _u.SetLogoURL(*v) } return _u } // SetAllowedConnectors sets the "allowed_connectors" field. func (_u *OAuth2ClientUpdate) SetAllowedConnectors(v []string) *OAuth2ClientUpdate { _u.mutation.SetAllowedConnectors(v) return _u } // AppendAllowedConnectors appends value to the "allowed_connectors" field. func (_u *OAuth2ClientUpdate) AppendAllowedConnectors(v []string) *OAuth2ClientUpdate { _u.mutation.AppendAllowedConnectors(v) return _u } // ClearAllowedConnectors clears the value of the "allowed_connectors" field. func (_u *OAuth2ClientUpdate) ClearAllowedConnectors() *OAuth2ClientUpdate { _u.mutation.ClearAllowedConnectors() return _u } // SetMfaChain sets the "mfa_chain" field. func (_u *OAuth2ClientUpdate) SetMfaChain(v []string) *OAuth2ClientUpdate { _u.mutation.SetMfaChain(v) return _u } // AppendMfaChain appends value to the "mfa_chain" field. func (_u *OAuth2ClientUpdate) AppendMfaChain(v []string) *OAuth2ClientUpdate { _u.mutation.AppendMfaChain(v) return _u } // ClearMfaChain clears the value of the "mfa_chain" field. func (_u *OAuth2ClientUpdate) ClearMfaChain() *OAuth2ClientUpdate { _u.mutation.ClearMfaChain() return _u } // Mutation returns the OAuth2ClientMutation object of the builder. func (_u *OAuth2ClientUpdate) Mutation() *OAuth2ClientMutation { return _u.mutation } // Save executes the query and returns the number of nodes affected by the update operation. func (_u *OAuth2ClientUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *OAuth2ClientUpdate) SaveX(ctx context.Context) int { affected, err := _u.Save(ctx) if err != nil { panic(err) } return affected } // Exec executes the query. func (_u *OAuth2ClientUpdate) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *OAuth2ClientUpdate) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *OAuth2ClientUpdate) check() error { if v, ok := _u.mutation.Secret(); ok { if err := oauth2client.SecretValidator(v); err != nil { return &ValidationError{Name: "secret", err: fmt.Errorf(`db: validator failed for field "OAuth2Client.secret": %w`, err)} } } if v, ok := _u.mutation.Name(); ok { if err := oauth2client.NameValidator(v); err != nil { return &ValidationError{Name: "name", err: fmt.Errorf(`db: validator failed for field "OAuth2Client.name": %w`, err)} } } if v, ok := _u.mutation.LogoURL(); ok { if err := oauth2client.LogoURLValidator(v); err != nil { return &ValidationError{Name: "logo_url", err: fmt.Errorf(`db: validator failed for field "OAuth2Client.logo_url": %w`, err)} } } return nil } func (_u *OAuth2ClientUpdate) sqlSave(ctx context.Context) (_node int, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(oauth2client.Table, oauth2client.Columns, sqlgraph.NewFieldSpec(oauth2client.FieldID, field.TypeString)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.Secret(); ok { _spec.SetField(oauth2client.FieldSecret, field.TypeString, value) } if value, ok := _u.mutation.RedirectUris(); ok { _spec.SetField(oauth2client.FieldRedirectUris, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedRedirectUris(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, oauth2client.FieldRedirectUris, value) }) } if _u.mutation.RedirectUrisCleared() { _spec.ClearField(oauth2client.FieldRedirectUris, field.TypeJSON) } if value, ok := _u.mutation.TrustedPeers(); ok { _spec.SetField(oauth2client.FieldTrustedPeers, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedTrustedPeers(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, oauth2client.FieldTrustedPeers, value) }) } if _u.mutation.TrustedPeersCleared() { _spec.ClearField(oauth2client.FieldTrustedPeers, field.TypeJSON) } if value, ok := _u.mutation.Public(); ok { _spec.SetField(oauth2client.FieldPublic, field.TypeBool, value) } if value, ok := _u.mutation.Name(); ok { _spec.SetField(oauth2client.FieldName, field.TypeString, value) } if value, ok := _u.mutation.LogoURL(); ok { _spec.SetField(oauth2client.FieldLogoURL, field.TypeString, value) } if value, ok := _u.mutation.AllowedConnectors(); ok { _spec.SetField(oauth2client.FieldAllowedConnectors, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedAllowedConnectors(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, oauth2client.FieldAllowedConnectors, value) }) } if _u.mutation.AllowedConnectorsCleared() { _spec.ClearField(oauth2client.FieldAllowedConnectors, field.TypeJSON) } if value, ok := _u.mutation.MfaChain(); ok { _spec.SetField(oauth2client.FieldMfaChain, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedMfaChain(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, oauth2client.FieldMfaChain, value) }) } if _u.mutation.MfaChainCleared() { _spec.ClearField(oauth2client.FieldMfaChain, field.TypeJSON) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{oauth2client.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return 0, err } _u.mutation.done = true return _node, nil } // OAuth2ClientUpdateOne is the builder for updating a single OAuth2Client entity. type OAuth2ClientUpdateOne struct { config fields []string hooks []Hook mutation *OAuth2ClientMutation } // SetSecret sets the "secret" field. func (_u *OAuth2ClientUpdateOne) SetSecret(v string) *OAuth2ClientUpdateOne { _u.mutation.SetSecret(v) return _u } // SetNillableSecret sets the "secret" field if the given value is not nil. func (_u *OAuth2ClientUpdateOne) SetNillableSecret(v *string) *OAuth2ClientUpdateOne { if v != nil { _u.SetSecret(*v) } return _u } // SetRedirectUris sets the "redirect_uris" field. func (_u *OAuth2ClientUpdateOne) SetRedirectUris(v []string) *OAuth2ClientUpdateOne { _u.mutation.SetRedirectUris(v) return _u } // AppendRedirectUris appends value to the "redirect_uris" field. func (_u *OAuth2ClientUpdateOne) AppendRedirectUris(v []string) *OAuth2ClientUpdateOne { _u.mutation.AppendRedirectUris(v) return _u } // ClearRedirectUris clears the value of the "redirect_uris" field. func (_u *OAuth2ClientUpdateOne) ClearRedirectUris() *OAuth2ClientUpdateOne { _u.mutation.ClearRedirectUris() return _u } // SetTrustedPeers sets the "trusted_peers" field. func (_u *OAuth2ClientUpdateOne) SetTrustedPeers(v []string) *OAuth2ClientUpdateOne { _u.mutation.SetTrustedPeers(v) return _u } // AppendTrustedPeers appends value to the "trusted_peers" field. func (_u *OAuth2ClientUpdateOne) AppendTrustedPeers(v []string) *OAuth2ClientUpdateOne { _u.mutation.AppendTrustedPeers(v) return _u } // ClearTrustedPeers clears the value of the "trusted_peers" field. func (_u *OAuth2ClientUpdateOne) ClearTrustedPeers() *OAuth2ClientUpdateOne { _u.mutation.ClearTrustedPeers() return _u } // SetPublic sets the "public" field. func (_u *OAuth2ClientUpdateOne) SetPublic(v bool) *OAuth2ClientUpdateOne { _u.mutation.SetPublic(v) return _u } // SetNillablePublic sets the "public" field if the given value is not nil. func (_u *OAuth2ClientUpdateOne) SetNillablePublic(v *bool) *OAuth2ClientUpdateOne { if v != nil { _u.SetPublic(*v) } return _u } // SetName sets the "name" field. func (_u *OAuth2ClientUpdateOne) SetName(v string) *OAuth2ClientUpdateOne { _u.mutation.SetName(v) return _u } // SetNillableName sets the "name" field if the given value is not nil. func (_u *OAuth2ClientUpdateOne) SetNillableName(v *string) *OAuth2ClientUpdateOne { if v != nil { _u.SetName(*v) } return _u } // SetLogoURL sets the "logo_url" field. func (_u *OAuth2ClientUpdateOne) SetLogoURL(v string) *OAuth2ClientUpdateOne { _u.mutation.SetLogoURL(v) return _u } // SetNillableLogoURL sets the "logo_url" field if the given value is not nil. func (_u *OAuth2ClientUpdateOne) SetNillableLogoURL(v *string) *OAuth2ClientUpdateOne { if v != nil { _u.SetLogoURL(*v) } return _u } // SetAllowedConnectors sets the "allowed_connectors" field. func (_u *OAuth2ClientUpdateOne) SetAllowedConnectors(v []string) *OAuth2ClientUpdateOne { _u.mutation.SetAllowedConnectors(v) return _u } // AppendAllowedConnectors appends value to the "allowed_connectors" field. func (_u *OAuth2ClientUpdateOne) AppendAllowedConnectors(v []string) *OAuth2ClientUpdateOne { _u.mutation.AppendAllowedConnectors(v) return _u } // ClearAllowedConnectors clears the value of the "allowed_connectors" field. func (_u *OAuth2ClientUpdateOne) ClearAllowedConnectors() *OAuth2ClientUpdateOne { _u.mutation.ClearAllowedConnectors() return _u } // SetMfaChain sets the "mfa_chain" field. func (_u *OAuth2ClientUpdateOne) SetMfaChain(v []string) *OAuth2ClientUpdateOne { _u.mutation.SetMfaChain(v) return _u } // AppendMfaChain appends value to the "mfa_chain" field. func (_u *OAuth2ClientUpdateOne) AppendMfaChain(v []string) *OAuth2ClientUpdateOne { _u.mutation.AppendMfaChain(v) return _u } // ClearMfaChain clears the value of the "mfa_chain" field. func (_u *OAuth2ClientUpdateOne) ClearMfaChain() *OAuth2ClientUpdateOne { _u.mutation.ClearMfaChain() return _u } // Mutation returns the OAuth2ClientMutation object of the builder. func (_u *OAuth2ClientUpdateOne) Mutation() *OAuth2ClientMutation { return _u.mutation } // Where appends a list predicates to the OAuth2ClientUpdate builder. func (_u *OAuth2ClientUpdateOne) Where(ps ...predicate.OAuth2Client) *OAuth2ClientUpdateOne { _u.mutation.Where(ps...) return _u } // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (_u *OAuth2ClientUpdateOne) Select(field string, fields ...string) *OAuth2ClientUpdateOne { _u.fields = append([]string{field}, fields...) return _u } // Save executes the query and returns the updated OAuth2Client entity. func (_u *OAuth2ClientUpdateOne) Save(ctx context.Context) (*OAuth2Client, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *OAuth2ClientUpdateOne) SaveX(ctx context.Context) *OAuth2Client { node, err := _u.Save(ctx) if err != nil { panic(err) } return node } // Exec executes the query on the entity. func (_u *OAuth2ClientUpdateOne) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *OAuth2ClientUpdateOne) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *OAuth2ClientUpdateOne) check() error { if v, ok := _u.mutation.Secret(); ok { if err := oauth2client.SecretValidator(v); err != nil { return &ValidationError{Name: "secret", err: fmt.Errorf(`db: validator failed for field "OAuth2Client.secret": %w`, err)} } } if v, ok := _u.mutation.Name(); ok { if err := oauth2client.NameValidator(v); err != nil { return &ValidationError{Name: "name", err: fmt.Errorf(`db: validator failed for field "OAuth2Client.name": %w`, err)} } } if v, ok := _u.mutation.LogoURL(); ok { if err := oauth2client.LogoURLValidator(v); err != nil { return &ValidationError{Name: "logo_url", err: fmt.Errorf(`db: validator failed for field "OAuth2Client.logo_url": %w`, err)} } } return nil } func (_u *OAuth2ClientUpdateOne) sqlSave(ctx context.Context) (_node *OAuth2Client, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(oauth2client.Table, oauth2client.Columns, sqlgraph.NewFieldSpec(oauth2client.FieldID, field.TypeString)) id, ok := _u.mutation.ID() if !ok { return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "OAuth2Client.id" for update`)} } _spec.Node.ID.Value = id if fields := _u.fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, oauth2client.FieldID) for _, f := range fields { if !oauth2client.ValidColumn(f) { return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } if f != oauth2client.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, f) } } } if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.Secret(); ok { _spec.SetField(oauth2client.FieldSecret, field.TypeString, value) } if value, ok := _u.mutation.RedirectUris(); ok { _spec.SetField(oauth2client.FieldRedirectUris, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedRedirectUris(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, oauth2client.FieldRedirectUris, value) }) } if _u.mutation.RedirectUrisCleared() { _spec.ClearField(oauth2client.FieldRedirectUris, field.TypeJSON) } if value, ok := _u.mutation.TrustedPeers(); ok { _spec.SetField(oauth2client.FieldTrustedPeers, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedTrustedPeers(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, oauth2client.FieldTrustedPeers, value) }) } if _u.mutation.TrustedPeersCleared() { _spec.ClearField(oauth2client.FieldTrustedPeers, field.TypeJSON) } if value, ok := _u.mutation.Public(); ok { _spec.SetField(oauth2client.FieldPublic, field.TypeBool, value) } if value, ok := _u.mutation.Name(); ok { _spec.SetField(oauth2client.FieldName, field.TypeString, value) } if value, ok := _u.mutation.LogoURL(); ok { _spec.SetField(oauth2client.FieldLogoURL, field.TypeString, value) } if value, ok := _u.mutation.AllowedConnectors(); ok { _spec.SetField(oauth2client.FieldAllowedConnectors, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedAllowedConnectors(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, oauth2client.FieldAllowedConnectors, value) }) } if _u.mutation.AllowedConnectorsCleared() { _spec.ClearField(oauth2client.FieldAllowedConnectors, field.TypeJSON) } if value, ok := _u.mutation.MfaChain(); ok { _spec.SetField(oauth2client.FieldMfaChain, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedMfaChain(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, oauth2client.FieldMfaChain, value) }) } if _u.mutation.MfaChainCleared() { _spec.ClearField(oauth2client.FieldMfaChain, field.TypeJSON) } _node = &OAuth2Client{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{oauth2client.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } _u.mutation.done = true return _node, nil } ================================================ FILE: storage/ent/db/offlinesession/offlinesession.go ================================================ // Code generated by ent, DO NOT EDIT. package offlinesession import ( "entgo.io/ent/dialect/sql" ) const ( // Label holds the string label denoting the offlinesession type in the database. Label = "offline_session" // FieldID holds the string denoting the id field in the database. FieldID = "id" // FieldUserID holds the string denoting the user_id field in the database. FieldUserID = "user_id" // FieldConnID holds the string denoting the conn_id field in the database. FieldConnID = "conn_id" // FieldRefresh holds the string denoting the refresh field in the database. FieldRefresh = "refresh" // FieldConnectorData holds the string denoting the connector_data field in the database. FieldConnectorData = "connector_data" // Table holds the table name of the offlinesession in the database. Table = "offline_sessions" ) // Columns holds all SQL columns for offlinesession fields. var Columns = []string{ FieldID, FieldUserID, FieldConnID, FieldRefresh, FieldConnectorData, } // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { if column == Columns[i] { return true } } return false } var ( // UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. UserIDValidator func(string) error // ConnIDValidator is a validator for the "conn_id" field. It is called by the builders before save. ConnIDValidator func(string) error // IDValidator is a validator for the "id" field. It is called by the builders before save. IDValidator func(string) error ) // OrderOption defines the ordering options for the OfflineSession queries. type OrderOption func(*sql.Selector) // ByID orders the results by the id field. func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } // ByUserID orders the results by the user_id field. func ByUserID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldUserID, opts...).ToFunc() } // ByConnID orders the results by the conn_id field. func ByConnID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldConnID, opts...).ToFunc() } ================================================ FILE: storage/ent/db/offlinesession/where.go ================================================ // Code generated by ent, DO NOT EDIT. package offlinesession import ( "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ID filters vertices based on their ID field. func ID(id string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. func IDEQ(id string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. func IDNEQ(id string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. func IDIn(ids ...string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. func IDNotIn(ids ...string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. func IDGT(id string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. func IDGTE(id string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. func IDLT(id string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. func IDLTE(id string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldLTE(FieldID, id)) } // IDEqualFold applies the EqualFold predicate on the ID field. func IDEqualFold(id string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEqualFold(FieldID, id)) } // IDContainsFold applies the ContainsFold predicate on the ID field. func IDContainsFold(id string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldContainsFold(FieldID, id)) } // UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. func UserID(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEQ(FieldUserID, v)) } // ConnID applies equality check predicate on the "conn_id" field. It's identical to ConnIDEQ. func ConnID(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEQ(FieldConnID, v)) } // Refresh applies equality check predicate on the "refresh" field. It's identical to RefreshEQ. func Refresh(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEQ(FieldRefresh, v)) } // ConnectorData applies equality check predicate on the "connector_data" field. It's identical to ConnectorDataEQ. func ConnectorData(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEQ(FieldConnectorData, v)) } // UserIDEQ applies the EQ predicate on the "user_id" field. func UserIDEQ(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEQ(FieldUserID, v)) } // UserIDNEQ applies the NEQ predicate on the "user_id" field. func UserIDNEQ(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldNEQ(FieldUserID, v)) } // UserIDIn applies the In predicate on the "user_id" field. func UserIDIn(vs ...string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldIn(FieldUserID, vs...)) } // UserIDNotIn applies the NotIn predicate on the "user_id" field. func UserIDNotIn(vs ...string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldNotIn(FieldUserID, vs...)) } // UserIDGT applies the GT predicate on the "user_id" field. func UserIDGT(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldGT(FieldUserID, v)) } // UserIDGTE applies the GTE predicate on the "user_id" field. func UserIDGTE(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldGTE(FieldUserID, v)) } // UserIDLT applies the LT predicate on the "user_id" field. func UserIDLT(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldLT(FieldUserID, v)) } // UserIDLTE applies the LTE predicate on the "user_id" field. func UserIDLTE(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldLTE(FieldUserID, v)) } // UserIDContains applies the Contains predicate on the "user_id" field. func UserIDContains(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldContains(FieldUserID, v)) } // UserIDHasPrefix applies the HasPrefix predicate on the "user_id" field. func UserIDHasPrefix(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldHasPrefix(FieldUserID, v)) } // UserIDHasSuffix applies the HasSuffix predicate on the "user_id" field. func UserIDHasSuffix(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldHasSuffix(FieldUserID, v)) } // UserIDEqualFold applies the EqualFold predicate on the "user_id" field. func UserIDEqualFold(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEqualFold(FieldUserID, v)) } // UserIDContainsFold applies the ContainsFold predicate on the "user_id" field. func UserIDContainsFold(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldContainsFold(FieldUserID, v)) } // ConnIDEQ applies the EQ predicate on the "conn_id" field. func ConnIDEQ(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEQ(FieldConnID, v)) } // ConnIDNEQ applies the NEQ predicate on the "conn_id" field. func ConnIDNEQ(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldNEQ(FieldConnID, v)) } // ConnIDIn applies the In predicate on the "conn_id" field. func ConnIDIn(vs ...string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldIn(FieldConnID, vs...)) } // ConnIDNotIn applies the NotIn predicate on the "conn_id" field. func ConnIDNotIn(vs ...string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldNotIn(FieldConnID, vs...)) } // ConnIDGT applies the GT predicate on the "conn_id" field. func ConnIDGT(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldGT(FieldConnID, v)) } // ConnIDGTE applies the GTE predicate on the "conn_id" field. func ConnIDGTE(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldGTE(FieldConnID, v)) } // ConnIDLT applies the LT predicate on the "conn_id" field. func ConnIDLT(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldLT(FieldConnID, v)) } // ConnIDLTE applies the LTE predicate on the "conn_id" field. func ConnIDLTE(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldLTE(FieldConnID, v)) } // ConnIDContains applies the Contains predicate on the "conn_id" field. func ConnIDContains(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldContains(FieldConnID, v)) } // ConnIDHasPrefix applies the HasPrefix predicate on the "conn_id" field. func ConnIDHasPrefix(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldHasPrefix(FieldConnID, v)) } // ConnIDHasSuffix applies the HasSuffix predicate on the "conn_id" field. func ConnIDHasSuffix(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldHasSuffix(FieldConnID, v)) } // ConnIDEqualFold applies the EqualFold predicate on the "conn_id" field. func ConnIDEqualFold(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEqualFold(FieldConnID, v)) } // ConnIDContainsFold applies the ContainsFold predicate on the "conn_id" field. func ConnIDContainsFold(v string) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldContainsFold(FieldConnID, v)) } // RefreshEQ applies the EQ predicate on the "refresh" field. func RefreshEQ(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEQ(FieldRefresh, v)) } // RefreshNEQ applies the NEQ predicate on the "refresh" field. func RefreshNEQ(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldNEQ(FieldRefresh, v)) } // RefreshIn applies the In predicate on the "refresh" field. func RefreshIn(vs ...[]byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldIn(FieldRefresh, vs...)) } // RefreshNotIn applies the NotIn predicate on the "refresh" field. func RefreshNotIn(vs ...[]byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldNotIn(FieldRefresh, vs...)) } // RefreshGT applies the GT predicate on the "refresh" field. func RefreshGT(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldGT(FieldRefresh, v)) } // RefreshGTE applies the GTE predicate on the "refresh" field. func RefreshGTE(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldGTE(FieldRefresh, v)) } // RefreshLT applies the LT predicate on the "refresh" field. func RefreshLT(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldLT(FieldRefresh, v)) } // RefreshLTE applies the LTE predicate on the "refresh" field. func RefreshLTE(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldLTE(FieldRefresh, v)) } // ConnectorDataEQ applies the EQ predicate on the "connector_data" field. func ConnectorDataEQ(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldEQ(FieldConnectorData, v)) } // ConnectorDataNEQ applies the NEQ predicate on the "connector_data" field. func ConnectorDataNEQ(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldNEQ(FieldConnectorData, v)) } // ConnectorDataIn applies the In predicate on the "connector_data" field. func ConnectorDataIn(vs ...[]byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldIn(FieldConnectorData, vs...)) } // ConnectorDataNotIn applies the NotIn predicate on the "connector_data" field. func ConnectorDataNotIn(vs ...[]byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldNotIn(FieldConnectorData, vs...)) } // ConnectorDataGT applies the GT predicate on the "connector_data" field. func ConnectorDataGT(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldGT(FieldConnectorData, v)) } // ConnectorDataGTE applies the GTE predicate on the "connector_data" field. func ConnectorDataGTE(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldGTE(FieldConnectorData, v)) } // ConnectorDataLT applies the LT predicate on the "connector_data" field. func ConnectorDataLT(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldLT(FieldConnectorData, v)) } // ConnectorDataLTE applies the LTE predicate on the "connector_data" field. func ConnectorDataLTE(v []byte) predicate.OfflineSession { return predicate.OfflineSession(sql.FieldLTE(FieldConnectorData, v)) } // ConnectorDataIsNil applies the IsNil predicate on the "connector_data" field. func ConnectorDataIsNil() predicate.OfflineSession { return predicate.OfflineSession(sql.FieldIsNull(FieldConnectorData)) } // ConnectorDataNotNil applies the NotNil predicate on the "connector_data" field. func ConnectorDataNotNil() predicate.OfflineSession { return predicate.OfflineSession(sql.FieldNotNull(FieldConnectorData)) } // And groups predicates with the AND operator between them. func And(predicates ...predicate.OfflineSession) predicate.OfflineSession { return predicate.OfflineSession(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.OfflineSession) predicate.OfflineSession { return predicate.OfflineSession(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.OfflineSession) predicate.OfflineSession { return predicate.OfflineSession(sql.NotPredicates(p)) } ================================================ FILE: storage/ent/db/offlinesession.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "fmt" "strings" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/offlinesession" ) // OfflineSession is the model entity for the OfflineSession schema. type OfflineSession struct { config `json:"-"` // ID of the ent. ID string `json:"id,omitempty"` // UserID holds the value of the "user_id" field. UserID string `json:"user_id,omitempty"` // ConnID holds the value of the "conn_id" field. ConnID string `json:"conn_id,omitempty"` // Refresh holds the value of the "refresh" field. Refresh []byte `json:"refresh,omitempty"` // ConnectorData holds the value of the "connector_data" field. ConnectorData *[]byte `json:"connector_data,omitempty"` selectValues sql.SelectValues } // scanValues returns the types for scanning values from sql.Rows. func (*OfflineSession) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { case offlinesession.FieldRefresh, offlinesession.FieldConnectorData: values[i] = new([]byte) case offlinesession.FieldID, offlinesession.FieldUserID, offlinesession.FieldConnID: values[i] = new(sql.NullString) default: values[i] = new(sql.UnknownType) } } return values, nil } // assignValues assigns the values that were returned from sql.Rows (after scanning) // to the OfflineSession fields. func (_m *OfflineSession) assignValues(columns []string, values []any) error { if m, n := len(values), len(columns); m < n { return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) } for i := range columns { switch columns[i] { case offlinesession.FieldID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field id", values[i]) } else if value.Valid { _m.ID = value.String } case offlinesession.FieldUserID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field user_id", values[i]) } else if value.Valid { _m.UserID = value.String } case offlinesession.FieldConnID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field conn_id", values[i]) } else if value.Valid { _m.ConnID = value.String } case offlinesession.FieldRefresh: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field refresh", values[i]) } else if value != nil { _m.Refresh = *value } case offlinesession.FieldConnectorData: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field connector_data", values[i]) } else if value != nil { _m.ConnectorData = value } default: _m.selectValues.Set(columns[i], values[i]) } } return nil } // Value returns the ent.Value that was dynamically selected and assigned to the OfflineSession. // This includes values selected through modifiers, order, etc. func (_m *OfflineSession) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } // Update returns a builder for updating this OfflineSession. // Note that you need to call OfflineSession.Unwrap() before calling this method if this OfflineSession // was returned from a transaction, and the transaction was committed or rolled back. func (_m *OfflineSession) Update() *OfflineSessionUpdateOne { return NewOfflineSessionClient(_m.config).UpdateOne(_m) } // Unwrap unwraps the OfflineSession entity that was returned from a transaction after it was closed, // so that all future queries will be executed through the driver which created the transaction. func (_m *OfflineSession) Unwrap() *OfflineSession { _tx, ok := _m.config.driver.(*txDriver) if !ok { panic("db: OfflineSession is not a transactional entity") } _m.config.driver = _tx.drv return _m } // String implements the fmt.Stringer. func (_m *OfflineSession) String() string { var builder strings.Builder builder.WriteString("OfflineSession(") builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) builder.WriteString("user_id=") builder.WriteString(_m.UserID) builder.WriteString(", ") builder.WriteString("conn_id=") builder.WriteString(_m.ConnID) builder.WriteString(", ") builder.WriteString("refresh=") builder.WriteString(fmt.Sprintf("%v", _m.Refresh)) builder.WriteString(", ") if v := _m.ConnectorData; v != nil { builder.WriteString("connector_data=") builder.WriteString(fmt.Sprintf("%v", *v)) } builder.WriteByte(')') return builder.String() } // OfflineSessions is a parsable slice of OfflineSession. type OfflineSessions []*OfflineSession ================================================ FILE: storage/ent/db/offlinesession_create.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/offlinesession" ) // OfflineSessionCreate is the builder for creating a OfflineSession entity. type OfflineSessionCreate struct { config mutation *OfflineSessionMutation hooks []Hook } // SetUserID sets the "user_id" field. func (_c *OfflineSessionCreate) SetUserID(v string) *OfflineSessionCreate { _c.mutation.SetUserID(v) return _c } // SetConnID sets the "conn_id" field. func (_c *OfflineSessionCreate) SetConnID(v string) *OfflineSessionCreate { _c.mutation.SetConnID(v) return _c } // SetRefresh sets the "refresh" field. func (_c *OfflineSessionCreate) SetRefresh(v []byte) *OfflineSessionCreate { _c.mutation.SetRefresh(v) return _c } // SetConnectorData sets the "connector_data" field. func (_c *OfflineSessionCreate) SetConnectorData(v []byte) *OfflineSessionCreate { _c.mutation.SetConnectorData(v) return _c } // SetID sets the "id" field. func (_c *OfflineSessionCreate) SetID(v string) *OfflineSessionCreate { _c.mutation.SetID(v) return _c } // Mutation returns the OfflineSessionMutation object of the builder. func (_c *OfflineSessionCreate) Mutation() *OfflineSessionMutation { return _c.mutation } // Save creates the OfflineSession in the database. func (_c *OfflineSessionCreate) Save(ctx context.Context) (*OfflineSession, error) { return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) } // SaveX calls Save and panics if Save returns an error. func (_c *OfflineSessionCreate) SaveX(ctx context.Context) *OfflineSession { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *OfflineSessionCreate) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *OfflineSessionCreate) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_c *OfflineSessionCreate) check() error { if _, ok := _c.mutation.UserID(); !ok { return &ValidationError{Name: "user_id", err: errors.New(`db: missing required field "OfflineSession.user_id"`)} } if v, ok := _c.mutation.UserID(); ok { if err := offlinesession.UserIDValidator(v); err != nil { return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "OfflineSession.user_id": %w`, err)} } } if _, ok := _c.mutation.ConnID(); !ok { return &ValidationError{Name: "conn_id", err: errors.New(`db: missing required field "OfflineSession.conn_id"`)} } if v, ok := _c.mutation.ConnID(); ok { if err := offlinesession.ConnIDValidator(v); err != nil { return &ValidationError{Name: "conn_id", err: fmt.Errorf(`db: validator failed for field "OfflineSession.conn_id": %w`, err)} } } if _, ok := _c.mutation.Refresh(); !ok { return &ValidationError{Name: "refresh", err: errors.New(`db: missing required field "OfflineSession.refresh"`)} } if v, ok := _c.mutation.ID(); ok { if err := offlinesession.IDValidator(v); err != nil { return &ValidationError{Name: "id", err: fmt.Errorf(`db: validator failed for field "OfflineSession.id": %w`, err)} } } return nil } func (_c *OfflineSessionCreate) sqlSave(ctx context.Context) (*OfflineSession, error) { if err := _c.check(); err != nil { return nil, err } _node, _spec := _c.createSpec() if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } if _spec.ID.Value != nil { if id, ok := _spec.ID.Value.(string); ok { _node.ID = id } else { return nil, fmt.Errorf("unexpected OfflineSession.ID type: %T", _spec.ID.Value) } } _c.mutation.id = &_node.ID _c.mutation.done = true return _node, nil } func (_c *OfflineSessionCreate) createSpec() (*OfflineSession, *sqlgraph.CreateSpec) { var ( _node = &OfflineSession{config: _c.config} _spec = sqlgraph.NewCreateSpec(offlinesession.Table, sqlgraph.NewFieldSpec(offlinesession.FieldID, field.TypeString)) ) if id, ok := _c.mutation.ID(); ok { _node.ID = id _spec.ID.Value = id } if value, ok := _c.mutation.UserID(); ok { _spec.SetField(offlinesession.FieldUserID, field.TypeString, value) _node.UserID = value } if value, ok := _c.mutation.ConnID(); ok { _spec.SetField(offlinesession.FieldConnID, field.TypeString, value) _node.ConnID = value } if value, ok := _c.mutation.Refresh(); ok { _spec.SetField(offlinesession.FieldRefresh, field.TypeBytes, value) _node.Refresh = value } if value, ok := _c.mutation.ConnectorData(); ok { _spec.SetField(offlinesession.FieldConnectorData, field.TypeBytes, value) _node.ConnectorData = &value } return _node, _spec } // OfflineSessionCreateBulk is the builder for creating many OfflineSession entities in bulk. type OfflineSessionCreateBulk struct { config err error builders []*OfflineSessionCreate } // Save creates the OfflineSession entities in the database. func (_c *OfflineSessionCreateBulk) Save(ctx context.Context) ([]*OfflineSession, error) { if _c.err != nil { return nil, _c.err } specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) nodes := make([]*OfflineSession, len(_c.builders)) mutators := make([]Mutator, len(_c.builders)) for i := range _c.builders { func(i int, root context.Context) { builder := _c.builders[i] var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*OfflineSessionMutation) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } if err := builder.check(); err != nil { return nil, err } builder.mutation = mutation var err error nodes[i], specs[i] = builder.createSpec() if i < len(mutators)-1 { _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) } else { spec := &sqlgraph.BatchCreateSpec{Nodes: specs} // Invoke the actual operation on the latest mutation in the chain. if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } } } if err != nil { return nil, err } mutation.id = &nodes[i].ID mutation.done = true return nodes[i], nil }) for i := len(builder.hooks) - 1; i >= 0; i-- { mut = builder.hooks[i](mut) } mutators[i] = mut }(i, ctx) } if len(mutators) > 0 { if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { return nil, err } } return nodes, nil } // SaveX is like Save, but panics if an error occurs. func (_c *OfflineSessionCreateBulk) SaveX(ctx context.Context) []*OfflineSession { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *OfflineSessionCreateBulk) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *OfflineSessionCreateBulk) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/offlinesession_delete.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/offlinesession" "github.com/dexidp/dex/storage/ent/db/predicate" ) // OfflineSessionDelete is the builder for deleting a OfflineSession entity. type OfflineSessionDelete struct { config hooks []Hook mutation *OfflineSessionMutation } // Where appends a list predicates to the OfflineSessionDelete builder. func (_d *OfflineSessionDelete) Where(ps ...predicate.OfflineSession) *OfflineSessionDelete { _d.mutation.Where(ps...) return _d } // Exec executes the deletion query and returns how many vertices were deleted. func (_d *OfflineSessionDelete) Exec(ctx context.Context) (int, error) { return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) } // ExecX is like Exec, but panics if an error occurs. func (_d *OfflineSessionDelete) ExecX(ctx context.Context) int { n, err := _d.Exec(ctx) if err != nil { panic(err) } return n } func (_d *OfflineSessionDelete) sqlExec(ctx context.Context) (int, error) { _spec := sqlgraph.NewDeleteSpec(offlinesession.Table, sqlgraph.NewFieldSpec(offlinesession.FieldID, field.TypeString)) if ps := _d.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) if err != nil && sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } _d.mutation.done = true return affected, err } // OfflineSessionDeleteOne is the builder for deleting a single OfflineSession entity. type OfflineSessionDeleteOne struct { _d *OfflineSessionDelete } // Where appends a list predicates to the OfflineSessionDelete builder. func (_d *OfflineSessionDeleteOne) Where(ps ...predicate.OfflineSession) *OfflineSessionDeleteOne { _d._d.mutation.Where(ps...) return _d } // Exec executes the deletion query. func (_d *OfflineSessionDeleteOne) Exec(ctx context.Context) error { n, err := _d._d.Exec(ctx) switch { case err != nil: return err case n == 0: return &NotFoundError{offlinesession.Label} default: return nil } } // ExecX is like Exec, but panics if an error occurs. func (_d *OfflineSessionDeleteOne) ExecX(ctx context.Context) { if err := _d.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/offlinesession_query.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "fmt" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/offlinesession" "github.com/dexidp/dex/storage/ent/db/predicate" ) // OfflineSessionQuery is the builder for querying OfflineSession entities. type OfflineSessionQuery struct { config ctx *QueryContext order []offlinesession.OrderOption inters []Interceptor predicates []predicate.OfflineSession // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) } // Where adds a new predicate for the OfflineSessionQuery builder. func (_q *OfflineSessionQuery) Where(ps ...predicate.OfflineSession) *OfflineSessionQuery { _q.predicates = append(_q.predicates, ps...) return _q } // Limit the number of records to be returned by this query. func (_q *OfflineSessionQuery) Limit(limit int) *OfflineSessionQuery { _q.ctx.Limit = &limit return _q } // Offset to start from. func (_q *OfflineSessionQuery) Offset(offset int) *OfflineSessionQuery { _q.ctx.Offset = &offset return _q } // Unique configures the query builder to filter duplicate records on query. // By default, unique is set to true, and can be disabled using this method. func (_q *OfflineSessionQuery) Unique(unique bool) *OfflineSessionQuery { _q.ctx.Unique = &unique return _q } // Order specifies how the records should be ordered. func (_q *OfflineSessionQuery) Order(o ...offlinesession.OrderOption) *OfflineSessionQuery { _q.order = append(_q.order, o...) return _q } // First returns the first OfflineSession entity from the query. // Returns a *NotFoundError when no OfflineSession was found. func (_q *OfflineSessionQuery) First(ctx context.Context) (*OfflineSession, error) { nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) if err != nil { return nil, err } if len(nodes) == 0 { return nil, &NotFoundError{offlinesession.Label} } return nodes[0], nil } // FirstX is like First, but panics if an error occurs. func (_q *OfflineSessionQuery) FirstX(ctx context.Context) *OfflineSession { node, err := _q.First(ctx) if err != nil && !IsNotFound(err) { panic(err) } return node } // FirstID returns the first OfflineSession ID from the query. // Returns a *NotFoundError when no OfflineSession ID was found. func (_q *OfflineSessionQuery) FirstID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { return } if len(ids) == 0 { err = &NotFoundError{offlinesession.Label} return } return ids[0], nil } // FirstIDX is like FirstID, but panics if an error occurs. func (_q *OfflineSessionQuery) FirstIDX(ctx context.Context) string { id, err := _q.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) } return id } // Only returns a single OfflineSession entity found by the query, ensuring it only returns one. // Returns a *NotSingularError when more than one OfflineSession entity is found. // Returns a *NotFoundError when no OfflineSession entities are found. func (_q *OfflineSessionQuery) Only(ctx context.Context) (*OfflineSession, error) { nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) if err != nil { return nil, err } switch len(nodes) { case 1: return nodes[0], nil case 0: return nil, &NotFoundError{offlinesession.Label} default: return nil, &NotSingularError{offlinesession.Label} } } // OnlyX is like Only, but panics if an error occurs. func (_q *OfflineSessionQuery) OnlyX(ctx context.Context) *OfflineSession { node, err := _q.Only(ctx) if err != nil { panic(err) } return node } // OnlyID is like Only, but returns the only OfflineSession ID in the query. // Returns a *NotSingularError when more than one OfflineSession ID is found. // Returns a *NotFoundError when no entities are found. func (_q *OfflineSessionQuery) OnlyID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { return } switch len(ids) { case 1: id = ids[0] case 0: err = &NotFoundError{offlinesession.Label} default: err = &NotSingularError{offlinesession.Label} } return } // OnlyIDX is like OnlyID, but panics if an error occurs. func (_q *OfflineSessionQuery) OnlyIDX(ctx context.Context) string { id, err := _q.OnlyID(ctx) if err != nil { panic(err) } return id } // All executes the query and returns a list of OfflineSessions. func (_q *OfflineSessionQuery) All(ctx context.Context) ([]*OfflineSession, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) if err := _q.prepareQuery(ctx); err != nil { return nil, err } qr := querierAll[[]*OfflineSession, *OfflineSessionQuery]() return withInterceptors[[]*OfflineSession](ctx, _q, qr, _q.inters) } // AllX is like All, but panics if an error occurs. func (_q *OfflineSessionQuery) AllX(ctx context.Context) []*OfflineSession { nodes, err := _q.All(ctx) if err != nil { panic(err) } return nodes } // IDs executes the query and returns a list of OfflineSession IDs. func (_q *OfflineSessionQuery) IDs(ctx context.Context) (ids []string, err error) { if _q.ctx.Unique == nil && _q.path != nil { _q.Unique(true) } ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) if err = _q.Select(offlinesession.FieldID).Scan(ctx, &ids); err != nil { return nil, err } return ids, nil } // IDsX is like IDs, but panics if an error occurs. func (_q *OfflineSessionQuery) IDsX(ctx context.Context) []string { ids, err := _q.IDs(ctx) if err != nil { panic(err) } return ids } // Count returns the count of the given query. func (_q *OfflineSessionQuery) Count(ctx context.Context) (int, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) if err := _q.prepareQuery(ctx); err != nil { return 0, err } return withInterceptors[int](ctx, _q, querierCount[*OfflineSessionQuery](), _q.inters) } // CountX is like Count, but panics if an error occurs. func (_q *OfflineSessionQuery) CountX(ctx context.Context) int { count, err := _q.Count(ctx) if err != nil { panic(err) } return count } // Exist returns true if the query has elements in the graph. func (_q *OfflineSessionQuery) Exist(ctx context.Context) (bool, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) switch _, err := _q.FirstID(ctx); { case IsNotFound(err): return false, nil case err != nil: return false, fmt.Errorf("db: check existence: %w", err) default: return true, nil } } // ExistX is like Exist, but panics if an error occurs. func (_q *OfflineSessionQuery) ExistX(ctx context.Context) bool { exist, err := _q.Exist(ctx) if err != nil { panic(err) } return exist } // Clone returns a duplicate of the OfflineSessionQuery builder, including all associated steps. It can be // used to prepare common query builders and use them differently after the clone is made. func (_q *OfflineSessionQuery) Clone() *OfflineSessionQuery { if _q == nil { return nil } return &OfflineSessionQuery{ config: _q.config, ctx: _q.ctx.Clone(), order: append([]offlinesession.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.OfflineSession{}, _q.predicates...), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // // Example: // // var v []struct { // UserID string `json:"user_id,omitempty"` // Count int `json:"count,omitempty"` // } // // client.OfflineSession.Query(). // GroupBy(offlinesession.FieldUserID). // Aggregate(db.Count()). // Scan(ctx, &v) func (_q *OfflineSessionQuery) GroupBy(field string, fields ...string) *OfflineSessionGroupBy { _q.ctx.Fields = append([]string{field}, fields...) grbuild := &OfflineSessionGroupBy{build: _q} grbuild.flds = &_q.ctx.Fields grbuild.label = offlinesession.Label grbuild.scan = grbuild.Scan return grbuild } // Select allows the selection one or more fields/columns for the given query, // instead of selecting all fields in the entity. // // Example: // // var v []struct { // UserID string `json:"user_id,omitempty"` // } // // client.OfflineSession.Query(). // Select(offlinesession.FieldUserID). // Scan(ctx, &v) func (_q *OfflineSessionQuery) Select(fields ...string) *OfflineSessionSelect { _q.ctx.Fields = append(_q.ctx.Fields, fields...) sbuild := &OfflineSessionSelect{OfflineSessionQuery: _q} sbuild.label = offlinesession.Label sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan return sbuild } // Aggregate returns a OfflineSessionSelect configured with the given aggregations. func (_q *OfflineSessionQuery) Aggregate(fns ...AggregateFunc) *OfflineSessionSelect { return _q.Select().Aggregate(fns...) } func (_q *OfflineSessionQuery) prepareQuery(ctx context.Context) error { for _, inter := range _q.inters { if inter == nil { return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") } if trv, ok := inter.(Traverser); ok { if err := trv.Traverse(ctx, _q); err != nil { return err } } } for _, f := range _q.ctx.Fields { if !offlinesession.ValidColumn(f) { return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } } if _q.path != nil { prev, err := _q.path(ctx) if err != nil { return err } _q.sql = prev } return nil } func (_q *OfflineSessionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*OfflineSession, error) { var ( nodes = []*OfflineSession{} _spec = _q.querySpec() ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*OfflineSession).scanValues(nil, columns) } _spec.Assign = func(columns []string, values []any) error { node := &OfflineSession{config: _q.config} nodes = append(nodes, node) return node.assignValues(columns, values) } for i := range hooks { hooks[i](ctx, _spec) } if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { return nil, err } if len(nodes) == 0 { return nodes, nil } return nodes, nil } func (_q *OfflineSessionQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields if len(_q.ctx.Fields) > 0 { _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique } return sqlgraph.CountNodes(ctx, _q.driver, _spec) } func (_q *OfflineSessionQuery) querySpec() *sqlgraph.QuerySpec { _spec := sqlgraph.NewQuerySpec(offlinesession.Table, offlinesession.Columns, sqlgraph.NewFieldSpec(offlinesession.FieldID, field.TypeString)) _spec.From = _q.sql if unique := _q.ctx.Unique; unique != nil { _spec.Unique = *unique } else if _q.path != nil { _spec.Unique = true } if fields := _q.ctx.Fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, offlinesession.FieldID) for i := range fields { if fields[i] != offlinesession.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } } if ps := _q.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if limit := _q.ctx.Limit; limit != nil { _spec.Limit = *limit } if offset := _q.ctx.Offset; offset != nil { _spec.Offset = *offset } if ps := _q.order; len(ps) > 0 { _spec.Order = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } return _spec } func (_q *OfflineSessionQuery) sqlQuery(ctx context.Context) *sql.Selector { builder := sql.Dialect(_q.driver.Dialect()) t1 := builder.Table(offlinesession.Table) columns := _q.ctx.Fields if len(columns) == 0 { columns = offlinesession.Columns } selector := builder.Select(t1.Columns(columns...)...).From(t1) if _q.sql != nil { selector = _q.sql selector.Select(selector.Columns(columns...)...) } if _q.ctx.Unique != nil && *_q.ctx.Unique { selector.Distinct() } for _, p := range _q.predicates { p(selector) } for _, p := range _q.order { p(selector) } if offset := _q.ctx.Offset; offset != nil { // limit is mandatory for offset clause. We start // with default value, and override it below if needed. selector.Offset(*offset).Limit(math.MaxInt32) } if limit := _q.ctx.Limit; limit != nil { selector.Limit(*limit) } return selector } // OfflineSessionGroupBy is the group-by builder for OfflineSession entities. type OfflineSessionGroupBy struct { selector build *OfflineSessionQuery } // Aggregate adds the given aggregation functions to the group-by query. func (_g *OfflineSessionGroupBy) Aggregate(fns ...AggregateFunc) *OfflineSessionGroupBy { _g.fns = append(_g.fns, fns...) return _g } // Scan applies the selector query and scans the result into the given value. func (_g *OfflineSessionGroupBy) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) if err := _g.build.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*OfflineSessionQuery, *OfflineSessionGroupBy](ctx, _g.build, _g, _g.build.inters, v) } func (_g *OfflineSessionGroupBy) sqlScan(ctx context.Context, root *OfflineSessionQuery, v any) error { selector := root.sqlQuery(ctx).Select() aggregation := make([]string, 0, len(_g.fns)) for _, fn := range _g.fns { aggregation = append(aggregation, fn(selector)) } if len(selector.SelectedColumns()) == 0 { columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) for _, f := range *_g.flds { columns = append(columns, selector.C(f)) } columns = append(columns, aggregation...) selector.Select(columns...) } selector.GroupBy(selector.Columns(*_g.flds...)...) if err := selector.Err(); err != nil { return err } rows := &sql.Rows{} query, args := selector.Query() if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } // OfflineSessionSelect is the builder for selecting fields of OfflineSession entities. type OfflineSessionSelect struct { *OfflineSessionQuery selector } // Aggregate adds the given aggregation functions to the selector query. func (_s *OfflineSessionSelect) Aggregate(fns ...AggregateFunc) *OfflineSessionSelect { _s.fns = append(_s.fns, fns...) return _s } // Scan applies the selector query and scans the result into the given value. func (_s *OfflineSessionSelect) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) if err := _s.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*OfflineSessionQuery, *OfflineSessionSelect](ctx, _s.OfflineSessionQuery, _s, _s.inters, v) } func (_s *OfflineSessionSelect) sqlScan(ctx context.Context, root *OfflineSessionQuery, v any) error { selector := root.sqlQuery(ctx) aggregation := make([]string, 0, len(_s.fns)) for _, fn := range _s.fns { aggregation = append(aggregation, fn(selector)) } switch n := len(*_s.selector.flds); { case n == 0 && len(aggregation) > 0: selector.Select(aggregation...) case n != 0 && len(aggregation) > 0: selector.AppendSelect(aggregation...) } rows := &sql.Rows{} query, args := selector.Query() if err := _s.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } ================================================ FILE: storage/ent/db/offlinesession_update.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/offlinesession" "github.com/dexidp/dex/storage/ent/db/predicate" ) // OfflineSessionUpdate is the builder for updating OfflineSession entities. type OfflineSessionUpdate struct { config hooks []Hook mutation *OfflineSessionMutation } // Where appends a list predicates to the OfflineSessionUpdate builder. func (_u *OfflineSessionUpdate) Where(ps ...predicate.OfflineSession) *OfflineSessionUpdate { _u.mutation.Where(ps...) return _u } // SetUserID sets the "user_id" field. func (_u *OfflineSessionUpdate) SetUserID(v string) *OfflineSessionUpdate { _u.mutation.SetUserID(v) return _u } // SetNillableUserID sets the "user_id" field if the given value is not nil. func (_u *OfflineSessionUpdate) SetNillableUserID(v *string) *OfflineSessionUpdate { if v != nil { _u.SetUserID(*v) } return _u } // SetConnID sets the "conn_id" field. func (_u *OfflineSessionUpdate) SetConnID(v string) *OfflineSessionUpdate { _u.mutation.SetConnID(v) return _u } // SetNillableConnID sets the "conn_id" field if the given value is not nil. func (_u *OfflineSessionUpdate) SetNillableConnID(v *string) *OfflineSessionUpdate { if v != nil { _u.SetConnID(*v) } return _u } // SetRefresh sets the "refresh" field. func (_u *OfflineSessionUpdate) SetRefresh(v []byte) *OfflineSessionUpdate { _u.mutation.SetRefresh(v) return _u } // SetConnectorData sets the "connector_data" field. func (_u *OfflineSessionUpdate) SetConnectorData(v []byte) *OfflineSessionUpdate { _u.mutation.SetConnectorData(v) return _u } // ClearConnectorData clears the value of the "connector_data" field. func (_u *OfflineSessionUpdate) ClearConnectorData() *OfflineSessionUpdate { _u.mutation.ClearConnectorData() return _u } // Mutation returns the OfflineSessionMutation object of the builder. func (_u *OfflineSessionUpdate) Mutation() *OfflineSessionMutation { return _u.mutation } // Save executes the query and returns the number of nodes affected by the update operation. func (_u *OfflineSessionUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *OfflineSessionUpdate) SaveX(ctx context.Context) int { affected, err := _u.Save(ctx) if err != nil { panic(err) } return affected } // Exec executes the query. func (_u *OfflineSessionUpdate) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *OfflineSessionUpdate) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *OfflineSessionUpdate) check() error { if v, ok := _u.mutation.UserID(); ok { if err := offlinesession.UserIDValidator(v); err != nil { return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "OfflineSession.user_id": %w`, err)} } } if v, ok := _u.mutation.ConnID(); ok { if err := offlinesession.ConnIDValidator(v); err != nil { return &ValidationError{Name: "conn_id", err: fmt.Errorf(`db: validator failed for field "OfflineSession.conn_id": %w`, err)} } } return nil } func (_u *OfflineSessionUpdate) sqlSave(ctx context.Context) (_node int, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(offlinesession.Table, offlinesession.Columns, sqlgraph.NewFieldSpec(offlinesession.FieldID, field.TypeString)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.UserID(); ok { _spec.SetField(offlinesession.FieldUserID, field.TypeString, value) } if value, ok := _u.mutation.ConnID(); ok { _spec.SetField(offlinesession.FieldConnID, field.TypeString, value) } if value, ok := _u.mutation.Refresh(); ok { _spec.SetField(offlinesession.FieldRefresh, field.TypeBytes, value) } if value, ok := _u.mutation.ConnectorData(); ok { _spec.SetField(offlinesession.FieldConnectorData, field.TypeBytes, value) } if _u.mutation.ConnectorDataCleared() { _spec.ClearField(offlinesession.FieldConnectorData, field.TypeBytes) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{offlinesession.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return 0, err } _u.mutation.done = true return _node, nil } // OfflineSessionUpdateOne is the builder for updating a single OfflineSession entity. type OfflineSessionUpdateOne struct { config fields []string hooks []Hook mutation *OfflineSessionMutation } // SetUserID sets the "user_id" field. func (_u *OfflineSessionUpdateOne) SetUserID(v string) *OfflineSessionUpdateOne { _u.mutation.SetUserID(v) return _u } // SetNillableUserID sets the "user_id" field if the given value is not nil. func (_u *OfflineSessionUpdateOne) SetNillableUserID(v *string) *OfflineSessionUpdateOne { if v != nil { _u.SetUserID(*v) } return _u } // SetConnID sets the "conn_id" field. func (_u *OfflineSessionUpdateOne) SetConnID(v string) *OfflineSessionUpdateOne { _u.mutation.SetConnID(v) return _u } // SetNillableConnID sets the "conn_id" field if the given value is not nil. func (_u *OfflineSessionUpdateOne) SetNillableConnID(v *string) *OfflineSessionUpdateOne { if v != nil { _u.SetConnID(*v) } return _u } // SetRefresh sets the "refresh" field. func (_u *OfflineSessionUpdateOne) SetRefresh(v []byte) *OfflineSessionUpdateOne { _u.mutation.SetRefresh(v) return _u } // SetConnectorData sets the "connector_data" field. func (_u *OfflineSessionUpdateOne) SetConnectorData(v []byte) *OfflineSessionUpdateOne { _u.mutation.SetConnectorData(v) return _u } // ClearConnectorData clears the value of the "connector_data" field. func (_u *OfflineSessionUpdateOne) ClearConnectorData() *OfflineSessionUpdateOne { _u.mutation.ClearConnectorData() return _u } // Mutation returns the OfflineSessionMutation object of the builder. func (_u *OfflineSessionUpdateOne) Mutation() *OfflineSessionMutation { return _u.mutation } // Where appends a list predicates to the OfflineSessionUpdate builder. func (_u *OfflineSessionUpdateOne) Where(ps ...predicate.OfflineSession) *OfflineSessionUpdateOne { _u.mutation.Where(ps...) return _u } // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (_u *OfflineSessionUpdateOne) Select(field string, fields ...string) *OfflineSessionUpdateOne { _u.fields = append([]string{field}, fields...) return _u } // Save executes the query and returns the updated OfflineSession entity. func (_u *OfflineSessionUpdateOne) Save(ctx context.Context) (*OfflineSession, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *OfflineSessionUpdateOne) SaveX(ctx context.Context) *OfflineSession { node, err := _u.Save(ctx) if err != nil { panic(err) } return node } // Exec executes the query on the entity. func (_u *OfflineSessionUpdateOne) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *OfflineSessionUpdateOne) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *OfflineSessionUpdateOne) check() error { if v, ok := _u.mutation.UserID(); ok { if err := offlinesession.UserIDValidator(v); err != nil { return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "OfflineSession.user_id": %w`, err)} } } if v, ok := _u.mutation.ConnID(); ok { if err := offlinesession.ConnIDValidator(v); err != nil { return &ValidationError{Name: "conn_id", err: fmt.Errorf(`db: validator failed for field "OfflineSession.conn_id": %w`, err)} } } return nil } func (_u *OfflineSessionUpdateOne) sqlSave(ctx context.Context) (_node *OfflineSession, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(offlinesession.Table, offlinesession.Columns, sqlgraph.NewFieldSpec(offlinesession.FieldID, field.TypeString)) id, ok := _u.mutation.ID() if !ok { return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "OfflineSession.id" for update`)} } _spec.Node.ID.Value = id if fields := _u.fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, offlinesession.FieldID) for _, f := range fields { if !offlinesession.ValidColumn(f) { return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } if f != offlinesession.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, f) } } } if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.UserID(); ok { _spec.SetField(offlinesession.FieldUserID, field.TypeString, value) } if value, ok := _u.mutation.ConnID(); ok { _spec.SetField(offlinesession.FieldConnID, field.TypeString, value) } if value, ok := _u.mutation.Refresh(); ok { _spec.SetField(offlinesession.FieldRefresh, field.TypeBytes, value) } if value, ok := _u.mutation.ConnectorData(); ok { _spec.SetField(offlinesession.FieldConnectorData, field.TypeBytes, value) } if _u.mutation.ConnectorDataCleared() { _spec.ClearField(offlinesession.FieldConnectorData, field.TypeBytes) } _node = &OfflineSession{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{offlinesession.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } _u.mutation.done = true return _node, nil } ================================================ FILE: storage/ent/db/password/password.go ================================================ // Code generated by ent, DO NOT EDIT. package password import ( "entgo.io/ent/dialect/sql" ) const ( // Label holds the string label denoting the password type in the database. Label = "password" // FieldID holds the string denoting the id field in the database. FieldID = "id" // FieldEmail holds the string denoting the email field in the database. FieldEmail = "email" // FieldHash holds the string denoting the hash field in the database. FieldHash = "hash" // FieldUsername holds the string denoting the username field in the database. FieldUsername = "username" // FieldName holds the string denoting the name field in the database. FieldName = "name" // FieldPreferredUsername holds the string denoting the preferred_username field in the database. FieldPreferredUsername = "preferred_username" // FieldEmailVerified holds the string denoting the email_verified field in the database. FieldEmailVerified = "email_verified" // FieldUserID holds the string denoting the user_id field in the database. FieldUserID = "user_id" // FieldGroups holds the string denoting the groups field in the database. FieldGroups = "groups" // Table holds the table name of the password in the database. Table = "passwords" ) // Columns holds all SQL columns for password fields. var Columns = []string{ FieldID, FieldEmail, FieldHash, FieldUsername, FieldName, FieldPreferredUsername, FieldEmailVerified, FieldUserID, FieldGroups, } // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { if column == Columns[i] { return true } } return false } var ( // EmailValidator is a validator for the "email" field. It is called by the builders before save. EmailValidator func(string) error // UsernameValidator is a validator for the "username" field. It is called by the builders before save. UsernameValidator func(string) error // DefaultName holds the default value on creation for the "name" field. DefaultName string // DefaultPreferredUsername holds the default value on creation for the "preferred_username" field. DefaultPreferredUsername string // UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. UserIDValidator func(string) error ) // OrderOption defines the ordering options for the Password queries. type OrderOption func(*sql.Selector) // ByID orders the results by the id field. func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } // ByEmail orders the results by the email field. func ByEmail(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldEmail, opts...).ToFunc() } // ByUsername orders the results by the username field. func ByUsername(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldUsername, opts...).ToFunc() } // ByName orders the results by the name field. func ByName(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldName, opts...).ToFunc() } // ByPreferredUsername orders the results by the preferred_username field. func ByPreferredUsername(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldPreferredUsername, opts...).ToFunc() } // ByEmailVerified orders the results by the email_verified field. func ByEmailVerified(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldEmailVerified, opts...).ToFunc() } // ByUserID orders the results by the user_id field. func ByUserID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldUserID, opts...).ToFunc() } ================================================ FILE: storage/ent/db/password/where.go ================================================ // Code generated by ent, DO NOT EDIT. package password import ( "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ID filters vertices based on their ID field. func ID(id int) predicate.Password { return predicate.Password(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. func IDEQ(id int) predicate.Password { return predicate.Password(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. func IDNEQ(id int) predicate.Password { return predicate.Password(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. func IDIn(ids ...int) predicate.Password { return predicate.Password(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. func IDNotIn(ids ...int) predicate.Password { return predicate.Password(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. func IDGT(id int) predicate.Password { return predicate.Password(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. func IDGTE(id int) predicate.Password { return predicate.Password(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. func IDLT(id int) predicate.Password { return predicate.Password(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. func IDLTE(id int) predicate.Password { return predicate.Password(sql.FieldLTE(FieldID, id)) } // Email applies equality check predicate on the "email" field. It's identical to EmailEQ. func Email(v string) predicate.Password { return predicate.Password(sql.FieldEQ(FieldEmail, v)) } // Hash applies equality check predicate on the "hash" field. It's identical to HashEQ. func Hash(v []byte) predicate.Password { return predicate.Password(sql.FieldEQ(FieldHash, v)) } // Username applies equality check predicate on the "username" field. It's identical to UsernameEQ. func Username(v string) predicate.Password { return predicate.Password(sql.FieldEQ(FieldUsername, v)) } // Name applies equality check predicate on the "name" field. It's identical to NameEQ. func Name(v string) predicate.Password { return predicate.Password(sql.FieldEQ(FieldName, v)) } // PreferredUsername applies equality check predicate on the "preferred_username" field. It's identical to PreferredUsernameEQ. func PreferredUsername(v string) predicate.Password { return predicate.Password(sql.FieldEQ(FieldPreferredUsername, v)) } // EmailVerified applies equality check predicate on the "email_verified" field. It's identical to EmailVerifiedEQ. func EmailVerified(v bool) predicate.Password { return predicate.Password(sql.FieldEQ(FieldEmailVerified, v)) } // UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. func UserID(v string) predicate.Password { return predicate.Password(sql.FieldEQ(FieldUserID, v)) } // EmailEQ applies the EQ predicate on the "email" field. func EmailEQ(v string) predicate.Password { return predicate.Password(sql.FieldEQ(FieldEmail, v)) } // EmailNEQ applies the NEQ predicate on the "email" field. func EmailNEQ(v string) predicate.Password { return predicate.Password(sql.FieldNEQ(FieldEmail, v)) } // EmailIn applies the In predicate on the "email" field. func EmailIn(vs ...string) predicate.Password { return predicate.Password(sql.FieldIn(FieldEmail, vs...)) } // EmailNotIn applies the NotIn predicate on the "email" field. func EmailNotIn(vs ...string) predicate.Password { return predicate.Password(sql.FieldNotIn(FieldEmail, vs...)) } // EmailGT applies the GT predicate on the "email" field. func EmailGT(v string) predicate.Password { return predicate.Password(sql.FieldGT(FieldEmail, v)) } // EmailGTE applies the GTE predicate on the "email" field. func EmailGTE(v string) predicate.Password { return predicate.Password(sql.FieldGTE(FieldEmail, v)) } // EmailLT applies the LT predicate on the "email" field. func EmailLT(v string) predicate.Password { return predicate.Password(sql.FieldLT(FieldEmail, v)) } // EmailLTE applies the LTE predicate on the "email" field. func EmailLTE(v string) predicate.Password { return predicate.Password(sql.FieldLTE(FieldEmail, v)) } // EmailContains applies the Contains predicate on the "email" field. func EmailContains(v string) predicate.Password { return predicate.Password(sql.FieldContains(FieldEmail, v)) } // EmailHasPrefix applies the HasPrefix predicate on the "email" field. func EmailHasPrefix(v string) predicate.Password { return predicate.Password(sql.FieldHasPrefix(FieldEmail, v)) } // EmailHasSuffix applies the HasSuffix predicate on the "email" field. func EmailHasSuffix(v string) predicate.Password { return predicate.Password(sql.FieldHasSuffix(FieldEmail, v)) } // EmailEqualFold applies the EqualFold predicate on the "email" field. func EmailEqualFold(v string) predicate.Password { return predicate.Password(sql.FieldEqualFold(FieldEmail, v)) } // EmailContainsFold applies the ContainsFold predicate on the "email" field. func EmailContainsFold(v string) predicate.Password { return predicate.Password(sql.FieldContainsFold(FieldEmail, v)) } // HashEQ applies the EQ predicate on the "hash" field. func HashEQ(v []byte) predicate.Password { return predicate.Password(sql.FieldEQ(FieldHash, v)) } // HashNEQ applies the NEQ predicate on the "hash" field. func HashNEQ(v []byte) predicate.Password { return predicate.Password(sql.FieldNEQ(FieldHash, v)) } // HashIn applies the In predicate on the "hash" field. func HashIn(vs ...[]byte) predicate.Password { return predicate.Password(sql.FieldIn(FieldHash, vs...)) } // HashNotIn applies the NotIn predicate on the "hash" field. func HashNotIn(vs ...[]byte) predicate.Password { return predicate.Password(sql.FieldNotIn(FieldHash, vs...)) } // HashGT applies the GT predicate on the "hash" field. func HashGT(v []byte) predicate.Password { return predicate.Password(sql.FieldGT(FieldHash, v)) } // HashGTE applies the GTE predicate on the "hash" field. func HashGTE(v []byte) predicate.Password { return predicate.Password(sql.FieldGTE(FieldHash, v)) } // HashLT applies the LT predicate on the "hash" field. func HashLT(v []byte) predicate.Password { return predicate.Password(sql.FieldLT(FieldHash, v)) } // HashLTE applies the LTE predicate on the "hash" field. func HashLTE(v []byte) predicate.Password { return predicate.Password(sql.FieldLTE(FieldHash, v)) } // UsernameEQ applies the EQ predicate on the "username" field. func UsernameEQ(v string) predicate.Password { return predicate.Password(sql.FieldEQ(FieldUsername, v)) } // UsernameNEQ applies the NEQ predicate on the "username" field. func UsernameNEQ(v string) predicate.Password { return predicate.Password(sql.FieldNEQ(FieldUsername, v)) } // UsernameIn applies the In predicate on the "username" field. func UsernameIn(vs ...string) predicate.Password { return predicate.Password(sql.FieldIn(FieldUsername, vs...)) } // UsernameNotIn applies the NotIn predicate on the "username" field. func UsernameNotIn(vs ...string) predicate.Password { return predicate.Password(sql.FieldNotIn(FieldUsername, vs...)) } // UsernameGT applies the GT predicate on the "username" field. func UsernameGT(v string) predicate.Password { return predicate.Password(sql.FieldGT(FieldUsername, v)) } // UsernameGTE applies the GTE predicate on the "username" field. func UsernameGTE(v string) predicate.Password { return predicate.Password(sql.FieldGTE(FieldUsername, v)) } // UsernameLT applies the LT predicate on the "username" field. func UsernameLT(v string) predicate.Password { return predicate.Password(sql.FieldLT(FieldUsername, v)) } // UsernameLTE applies the LTE predicate on the "username" field. func UsernameLTE(v string) predicate.Password { return predicate.Password(sql.FieldLTE(FieldUsername, v)) } // UsernameContains applies the Contains predicate on the "username" field. func UsernameContains(v string) predicate.Password { return predicate.Password(sql.FieldContains(FieldUsername, v)) } // UsernameHasPrefix applies the HasPrefix predicate on the "username" field. func UsernameHasPrefix(v string) predicate.Password { return predicate.Password(sql.FieldHasPrefix(FieldUsername, v)) } // UsernameHasSuffix applies the HasSuffix predicate on the "username" field. func UsernameHasSuffix(v string) predicate.Password { return predicate.Password(sql.FieldHasSuffix(FieldUsername, v)) } // UsernameEqualFold applies the EqualFold predicate on the "username" field. func UsernameEqualFold(v string) predicate.Password { return predicate.Password(sql.FieldEqualFold(FieldUsername, v)) } // UsernameContainsFold applies the ContainsFold predicate on the "username" field. func UsernameContainsFold(v string) predicate.Password { return predicate.Password(sql.FieldContainsFold(FieldUsername, v)) } // NameEQ applies the EQ predicate on the "name" field. func NameEQ(v string) predicate.Password { return predicate.Password(sql.FieldEQ(FieldName, v)) } // NameNEQ applies the NEQ predicate on the "name" field. func NameNEQ(v string) predicate.Password { return predicate.Password(sql.FieldNEQ(FieldName, v)) } // NameIn applies the In predicate on the "name" field. func NameIn(vs ...string) predicate.Password { return predicate.Password(sql.FieldIn(FieldName, vs...)) } // NameNotIn applies the NotIn predicate on the "name" field. func NameNotIn(vs ...string) predicate.Password { return predicate.Password(sql.FieldNotIn(FieldName, vs...)) } // NameGT applies the GT predicate on the "name" field. func NameGT(v string) predicate.Password { return predicate.Password(sql.FieldGT(FieldName, v)) } // NameGTE applies the GTE predicate on the "name" field. func NameGTE(v string) predicate.Password { return predicate.Password(sql.FieldGTE(FieldName, v)) } // NameLT applies the LT predicate on the "name" field. func NameLT(v string) predicate.Password { return predicate.Password(sql.FieldLT(FieldName, v)) } // NameLTE applies the LTE predicate on the "name" field. func NameLTE(v string) predicate.Password { return predicate.Password(sql.FieldLTE(FieldName, v)) } // NameContains applies the Contains predicate on the "name" field. func NameContains(v string) predicate.Password { return predicate.Password(sql.FieldContains(FieldName, v)) } // NameHasPrefix applies the HasPrefix predicate on the "name" field. func NameHasPrefix(v string) predicate.Password { return predicate.Password(sql.FieldHasPrefix(FieldName, v)) } // NameHasSuffix applies the HasSuffix predicate on the "name" field. func NameHasSuffix(v string) predicate.Password { return predicate.Password(sql.FieldHasSuffix(FieldName, v)) } // NameEqualFold applies the EqualFold predicate on the "name" field. func NameEqualFold(v string) predicate.Password { return predicate.Password(sql.FieldEqualFold(FieldName, v)) } // NameContainsFold applies the ContainsFold predicate on the "name" field. func NameContainsFold(v string) predicate.Password { return predicate.Password(sql.FieldContainsFold(FieldName, v)) } // PreferredUsernameEQ applies the EQ predicate on the "preferred_username" field. func PreferredUsernameEQ(v string) predicate.Password { return predicate.Password(sql.FieldEQ(FieldPreferredUsername, v)) } // PreferredUsernameNEQ applies the NEQ predicate on the "preferred_username" field. func PreferredUsernameNEQ(v string) predicate.Password { return predicate.Password(sql.FieldNEQ(FieldPreferredUsername, v)) } // PreferredUsernameIn applies the In predicate on the "preferred_username" field. func PreferredUsernameIn(vs ...string) predicate.Password { return predicate.Password(sql.FieldIn(FieldPreferredUsername, vs...)) } // PreferredUsernameNotIn applies the NotIn predicate on the "preferred_username" field. func PreferredUsernameNotIn(vs ...string) predicate.Password { return predicate.Password(sql.FieldNotIn(FieldPreferredUsername, vs...)) } // PreferredUsernameGT applies the GT predicate on the "preferred_username" field. func PreferredUsernameGT(v string) predicate.Password { return predicate.Password(sql.FieldGT(FieldPreferredUsername, v)) } // PreferredUsernameGTE applies the GTE predicate on the "preferred_username" field. func PreferredUsernameGTE(v string) predicate.Password { return predicate.Password(sql.FieldGTE(FieldPreferredUsername, v)) } // PreferredUsernameLT applies the LT predicate on the "preferred_username" field. func PreferredUsernameLT(v string) predicate.Password { return predicate.Password(sql.FieldLT(FieldPreferredUsername, v)) } // PreferredUsernameLTE applies the LTE predicate on the "preferred_username" field. func PreferredUsernameLTE(v string) predicate.Password { return predicate.Password(sql.FieldLTE(FieldPreferredUsername, v)) } // PreferredUsernameContains applies the Contains predicate on the "preferred_username" field. func PreferredUsernameContains(v string) predicate.Password { return predicate.Password(sql.FieldContains(FieldPreferredUsername, v)) } // PreferredUsernameHasPrefix applies the HasPrefix predicate on the "preferred_username" field. func PreferredUsernameHasPrefix(v string) predicate.Password { return predicate.Password(sql.FieldHasPrefix(FieldPreferredUsername, v)) } // PreferredUsernameHasSuffix applies the HasSuffix predicate on the "preferred_username" field. func PreferredUsernameHasSuffix(v string) predicate.Password { return predicate.Password(sql.FieldHasSuffix(FieldPreferredUsername, v)) } // PreferredUsernameEqualFold applies the EqualFold predicate on the "preferred_username" field. func PreferredUsernameEqualFold(v string) predicate.Password { return predicate.Password(sql.FieldEqualFold(FieldPreferredUsername, v)) } // PreferredUsernameContainsFold applies the ContainsFold predicate on the "preferred_username" field. func PreferredUsernameContainsFold(v string) predicate.Password { return predicate.Password(sql.FieldContainsFold(FieldPreferredUsername, v)) } // EmailVerifiedEQ applies the EQ predicate on the "email_verified" field. func EmailVerifiedEQ(v bool) predicate.Password { return predicate.Password(sql.FieldEQ(FieldEmailVerified, v)) } // EmailVerifiedNEQ applies the NEQ predicate on the "email_verified" field. func EmailVerifiedNEQ(v bool) predicate.Password { return predicate.Password(sql.FieldNEQ(FieldEmailVerified, v)) } // EmailVerifiedIsNil applies the IsNil predicate on the "email_verified" field. func EmailVerifiedIsNil() predicate.Password { return predicate.Password(sql.FieldIsNull(FieldEmailVerified)) } // EmailVerifiedNotNil applies the NotNil predicate on the "email_verified" field. func EmailVerifiedNotNil() predicate.Password { return predicate.Password(sql.FieldNotNull(FieldEmailVerified)) } // UserIDEQ applies the EQ predicate on the "user_id" field. func UserIDEQ(v string) predicate.Password { return predicate.Password(sql.FieldEQ(FieldUserID, v)) } // UserIDNEQ applies the NEQ predicate on the "user_id" field. func UserIDNEQ(v string) predicate.Password { return predicate.Password(sql.FieldNEQ(FieldUserID, v)) } // UserIDIn applies the In predicate on the "user_id" field. func UserIDIn(vs ...string) predicate.Password { return predicate.Password(sql.FieldIn(FieldUserID, vs...)) } // UserIDNotIn applies the NotIn predicate on the "user_id" field. func UserIDNotIn(vs ...string) predicate.Password { return predicate.Password(sql.FieldNotIn(FieldUserID, vs...)) } // UserIDGT applies the GT predicate on the "user_id" field. func UserIDGT(v string) predicate.Password { return predicate.Password(sql.FieldGT(FieldUserID, v)) } // UserIDGTE applies the GTE predicate on the "user_id" field. func UserIDGTE(v string) predicate.Password { return predicate.Password(sql.FieldGTE(FieldUserID, v)) } // UserIDLT applies the LT predicate on the "user_id" field. func UserIDLT(v string) predicate.Password { return predicate.Password(sql.FieldLT(FieldUserID, v)) } // UserIDLTE applies the LTE predicate on the "user_id" field. func UserIDLTE(v string) predicate.Password { return predicate.Password(sql.FieldLTE(FieldUserID, v)) } // UserIDContains applies the Contains predicate on the "user_id" field. func UserIDContains(v string) predicate.Password { return predicate.Password(sql.FieldContains(FieldUserID, v)) } // UserIDHasPrefix applies the HasPrefix predicate on the "user_id" field. func UserIDHasPrefix(v string) predicate.Password { return predicate.Password(sql.FieldHasPrefix(FieldUserID, v)) } // UserIDHasSuffix applies the HasSuffix predicate on the "user_id" field. func UserIDHasSuffix(v string) predicate.Password { return predicate.Password(sql.FieldHasSuffix(FieldUserID, v)) } // UserIDEqualFold applies the EqualFold predicate on the "user_id" field. func UserIDEqualFold(v string) predicate.Password { return predicate.Password(sql.FieldEqualFold(FieldUserID, v)) } // UserIDContainsFold applies the ContainsFold predicate on the "user_id" field. func UserIDContainsFold(v string) predicate.Password { return predicate.Password(sql.FieldContainsFold(FieldUserID, v)) } // GroupsIsNil applies the IsNil predicate on the "groups" field. func GroupsIsNil() predicate.Password { return predicate.Password(sql.FieldIsNull(FieldGroups)) } // GroupsNotNil applies the NotNil predicate on the "groups" field. func GroupsNotNil() predicate.Password { return predicate.Password(sql.FieldNotNull(FieldGroups)) } // And groups predicates with the AND operator between them. func And(predicates ...predicate.Password) predicate.Password { return predicate.Password(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.Password) predicate.Password { return predicate.Password(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.Password) predicate.Password { return predicate.Password(sql.NotPredicates(p)) } ================================================ FILE: storage/ent/db/password.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "encoding/json" "fmt" "strings" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/password" ) // Password is the model entity for the Password schema. type Password struct { config `json:"-"` // ID of the ent. ID int `json:"id,omitempty"` // Email holds the value of the "email" field. Email string `json:"email,omitempty"` // Hash holds the value of the "hash" field. Hash []byte `json:"hash,omitempty"` // Username holds the value of the "username" field. Username string `json:"username,omitempty"` // Name holds the value of the "name" field. Name string `json:"name,omitempty"` // PreferredUsername holds the value of the "preferred_username" field. PreferredUsername string `json:"preferred_username,omitempty"` // EmailVerified holds the value of the "email_verified" field. EmailVerified *bool `json:"email_verified,omitempty"` // UserID holds the value of the "user_id" field. UserID string `json:"user_id,omitempty"` // Groups holds the value of the "groups" field. Groups []string `json:"groups,omitempty"` selectValues sql.SelectValues } // scanValues returns the types for scanning values from sql.Rows. func (*Password) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { case password.FieldHash, password.FieldGroups: values[i] = new([]byte) case password.FieldEmailVerified: values[i] = new(sql.NullBool) case password.FieldID: values[i] = new(sql.NullInt64) case password.FieldEmail, password.FieldUsername, password.FieldName, password.FieldPreferredUsername, password.FieldUserID: values[i] = new(sql.NullString) default: values[i] = new(sql.UnknownType) } } return values, nil } // assignValues assigns the values that were returned from sql.Rows (after scanning) // to the Password fields. func (_m *Password) assignValues(columns []string, values []any) error { if m, n := len(values), len(columns); m < n { return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) } for i := range columns { switch columns[i] { case password.FieldID: value, ok := values[i].(*sql.NullInt64) if !ok { return fmt.Errorf("unexpected type %T for field id", value) } _m.ID = int(value.Int64) case password.FieldEmail: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field email", values[i]) } else if value.Valid { _m.Email = value.String } case password.FieldHash: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field hash", values[i]) } else if value != nil { _m.Hash = *value } case password.FieldUsername: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field username", values[i]) } else if value.Valid { _m.Username = value.String } case password.FieldName: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field name", values[i]) } else if value.Valid { _m.Name = value.String } case password.FieldPreferredUsername: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field preferred_username", values[i]) } else if value.Valid { _m.PreferredUsername = value.String } case password.FieldEmailVerified: if value, ok := values[i].(*sql.NullBool); !ok { return fmt.Errorf("unexpected type %T for field email_verified", values[i]) } else if value.Valid { _m.EmailVerified = new(bool) *_m.EmailVerified = value.Bool } case password.FieldUserID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field user_id", values[i]) } else if value.Valid { _m.UserID = value.String } case password.FieldGroups: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field groups", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.Groups); err != nil { return fmt.Errorf("unmarshal field groups: %w", err) } } default: _m.selectValues.Set(columns[i], values[i]) } } return nil } // Value returns the ent.Value that was dynamically selected and assigned to the Password. // This includes values selected through modifiers, order, etc. func (_m *Password) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } // Update returns a builder for updating this Password. // Note that you need to call Password.Unwrap() before calling this method if this Password // was returned from a transaction, and the transaction was committed or rolled back. func (_m *Password) Update() *PasswordUpdateOne { return NewPasswordClient(_m.config).UpdateOne(_m) } // Unwrap unwraps the Password entity that was returned from a transaction after it was closed, // so that all future queries will be executed through the driver which created the transaction. func (_m *Password) Unwrap() *Password { _tx, ok := _m.config.driver.(*txDriver) if !ok { panic("db: Password is not a transactional entity") } _m.config.driver = _tx.drv return _m } // String implements the fmt.Stringer. func (_m *Password) String() string { var builder strings.Builder builder.WriteString("Password(") builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) builder.WriteString("email=") builder.WriteString(_m.Email) builder.WriteString(", ") builder.WriteString("hash=") builder.WriteString(fmt.Sprintf("%v", _m.Hash)) builder.WriteString(", ") builder.WriteString("username=") builder.WriteString(_m.Username) builder.WriteString(", ") builder.WriteString("name=") builder.WriteString(_m.Name) builder.WriteString(", ") builder.WriteString("preferred_username=") builder.WriteString(_m.PreferredUsername) builder.WriteString(", ") if v := _m.EmailVerified; v != nil { builder.WriteString("email_verified=") builder.WriteString(fmt.Sprintf("%v", *v)) } builder.WriteString(", ") builder.WriteString("user_id=") builder.WriteString(_m.UserID) builder.WriteString(", ") builder.WriteString("groups=") builder.WriteString(fmt.Sprintf("%v", _m.Groups)) builder.WriteByte(')') return builder.String() } // Passwords is a parsable slice of Password. type Passwords []*Password ================================================ FILE: storage/ent/db/password_create.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/password" ) // PasswordCreate is the builder for creating a Password entity. type PasswordCreate struct { config mutation *PasswordMutation hooks []Hook } // SetEmail sets the "email" field. func (_c *PasswordCreate) SetEmail(v string) *PasswordCreate { _c.mutation.SetEmail(v) return _c } // SetHash sets the "hash" field. func (_c *PasswordCreate) SetHash(v []byte) *PasswordCreate { _c.mutation.SetHash(v) return _c } // SetUsername sets the "username" field. func (_c *PasswordCreate) SetUsername(v string) *PasswordCreate { _c.mutation.SetUsername(v) return _c } // SetName sets the "name" field. func (_c *PasswordCreate) SetName(v string) *PasswordCreate { _c.mutation.SetName(v) return _c } // SetNillableName sets the "name" field if the given value is not nil. func (_c *PasswordCreate) SetNillableName(v *string) *PasswordCreate { if v != nil { _c.SetName(*v) } return _c } // SetPreferredUsername sets the "preferred_username" field. func (_c *PasswordCreate) SetPreferredUsername(v string) *PasswordCreate { _c.mutation.SetPreferredUsername(v) return _c } // SetNillablePreferredUsername sets the "preferred_username" field if the given value is not nil. func (_c *PasswordCreate) SetNillablePreferredUsername(v *string) *PasswordCreate { if v != nil { _c.SetPreferredUsername(*v) } return _c } // SetEmailVerified sets the "email_verified" field. func (_c *PasswordCreate) SetEmailVerified(v bool) *PasswordCreate { _c.mutation.SetEmailVerified(v) return _c } // SetNillableEmailVerified sets the "email_verified" field if the given value is not nil. func (_c *PasswordCreate) SetNillableEmailVerified(v *bool) *PasswordCreate { if v != nil { _c.SetEmailVerified(*v) } return _c } // SetUserID sets the "user_id" field. func (_c *PasswordCreate) SetUserID(v string) *PasswordCreate { _c.mutation.SetUserID(v) return _c } // SetGroups sets the "groups" field. func (_c *PasswordCreate) SetGroups(v []string) *PasswordCreate { _c.mutation.SetGroups(v) return _c } // Mutation returns the PasswordMutation object of the builder. func (_c *PasswordCreate) Mutation() *PasswordMutation { return _c.mutation } // Save creates the Password in the database. func (_c *PasswordCreate) Save(ctx context.Context) (*Password, error) { _c.defaults() return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) } // SaveX calls Save and panics if Save returns an error. func (_c *PasswordCreate) SaveX(ctx context.Context) *Password { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *PasswordCreate) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *PasswordCreate) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } // defaults sets the default values of the builder before save. func (_c *PasswordCreate) defaults() { if _, ok := _c.mutation.Name(); !ok { v := password.DefaultName _c.mutation.SetName(v) } if _, ok := _c.mutation.PreferredUsername(); !ok { v := password.DefaultPreferredUsername _c.mutation.SetPreferredUsername(v) } } // check runs all checks and user-defined validators on the builder. func (_c *PasswordCreate) check() error { if _, ok := _c.mutation.Email(); !ok { return &ValidationError{Name: "email", err: errors.New(`db: missing required field "Password.email"`)} } if v, ok := _c.mutation.Email(); ok { if err := password.EmailValidator(v); err != nil { return &ValidationError{Name: "email", err: fmt.Errorf(`db: validator failed for field "Password.email": %w`, err)} } } if _, ok := _c.mutation.Hash(); !ok { return &ValidationError{Name: "hash", err: errors.New(`db: missing required field "Password.hash"`)} } if _, ok := _c.mutation.Username(); !ok { return &ValidationError{Name: "username", err: errors.New(`db: missing required field "Password.username"`)} } if v, ok := _c.mutation.Username(); ok { if err := password.UsernameValidator(v); err != nil { return &ValidationError{Name: "username", err: fmt.Errorf(`db: validator failed for field "Password.username": %w`, err)} } } if _, ok := _c.mutation.Name(); !ok { return &ValidationError{Name: "name", err: errors.New(`db: missing required field "Password.name"`)} } if _, ok := _c.mutation.PreferredUsername(); !ok { return &ValidationError{Name: "preferred_username", err: errors.New(`db: missing required field "Password.preferred_username"`)} } if _, ok := _c.mutation.UserID(); !ok { return &ValidationError{Name: "user_id", err: errors.New(`db: missing required field "Password.user_id"`)} } if v, ok := _c.mutation.UserID(); ok { if err := password.UserIDValidator(v); err != nil { return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "Password.user_id": %w`, err)} } } return nil } func (_c *PasswordCreate) sqlSave(ctx context.Context) (*Password, error) { if err := _c.check(); err != nil { return nil, err } _node, _spec := _c.createSpec() if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } id := _spec.ID.Value.(int64) _node.ID = int(id) _c.mutation.id = &_node.ID _c.mutation.done = true return _node, nil } func (_c *PasswordCreate) createSpec() (*Password, *sqlgraph.CreateSpec) { var ( _node = &Password{config: _c.config} _spec = sqlgraph.NewCreateSpec(password.Table, sqlgraph.NewFieldSpec(password.FieldID, field.TypeInt)) ) if value, ok := _c.mutation.Email(); ok { _spec.SetField(password.FieldEmail, field.TypeString, value) _node.Email = value } if value, ok := _c.mutation.Hash(); ok { _spec.SetField(password.FieldHash, field.TypeBytes, value) _node.Hash = value } if value, ok := _c.mutation.Username(); ok { _spec.SetField(password.FieldUsername, field.TypeString, value) _node.Username = value } if value, ok := _c.mutation.Name(); ok { _spec.SetField(password.FieldName, field.TypeString, value) _node.Name = value } if value, ok := _c.mutation.PreferredUsername(); ok { _spec.SetField(password.FieldPreferredUsername, field.TypeString, value) _node.PreferredUsername = value } if value, ok := _c.mutation.EmailVerified(); ok { _spec.SetField(password.FieldEmailVerified, field.TypeBool, value) _node.EmailVerified = &value } if value, ok := _c.mutation.UserID(); ok { _spec.SetField(password.FieldUserID, field.TypeString, value) _node.UserID = value } if value, ok := _c.mutation.Groups(); ok { _spec.SetField(password.FieldGroups, field.TypeJSON, value) _node.Groups = value } return _node, _spec } // PasswordCreateBulk is the builder for creating many Password entities in bulk. type PasswordCreateBulk struct { config err error builders []*PasswordCreate } // Save creates the Password entities in the database. func (_c *PasswordCreateBulk) Save(ctx context.Context) ([]*Password, error) { if _c.err != nil { return nil, _c.err } specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) nodes := make([]*Password, len(_c.builders)) mutators := make([]Mutator, len(_c.builders)) for i := range _c.builders { func(i int, root context.Context) { builder := _c.builders[i] builder.defaults() var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*PasswordMutation) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } if err := builder.check(); err != nil { return nil, err } builder.mutation = mutation var err error nodes[i], specs[i] = builder.createSpec() if i < len(mutators)-1 { _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) } else { spec := &sqlgraph.BatchCreateSpec{Nodes: specs} // Invoke the actual operation on the latest mutation in the chain. if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } } } if err != nil { return nil, err } mutation.id = &nodes[i].ID if specs[i].ID.Value != nil { id := specs[i].ID.Value.(int64) nodes[i].ID = int(id) } mutation.done = true return nodes[i], nil }) for i := len(builder.hooks) - 1; i >= 0; i-- { mut = builder.hooks[i](mut) } mutators[i] = mut }(i, ctx) } if len(mutators) > 0 { if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { return nil, err } } return nodes, nil } // SaveX is like Save, but panics if an error occurs. func (_c *PasswordCreateBulk) SaveX(ctx context.Context) []*Password { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *PasswordCreateBulk) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *PasswordCreateBulk) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/password_delete.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/password" "github.com/dexidp/dex/storage/ent/db/predicate" ) // PasswordDelete is the builder for deleting a Password entity. type PasswordDelete struct { config hooks []Hook mutation *PasswordMutation } // Where appends a list predicates to the PasswordDelete builder. func (_d *PasswordDelete) Where(ps ...predicate.Password) *PasswordDelete { _d.mutation.Where(ps...) return _d } // Exec executes the deletion query and returns how many vertices were deleted. func (_d *PasswordDelete) Exec(ctx context.Context) (int, error) { return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) } // ExecX is like Exec, but panics if an error occurs. func (_d *PasswordDelete) ExecX(ctx context.Context) int { n, err := _d.Exec(ctx) if err != nil { panic(err) } return n } func (_d *PasswordDelete) sqlExec(ctx context.Context) (int, error) { _spec := sqlgraph.NewDeleteSpec(password.Table, sqlgraph.NewFieldSpec(password.FieldID, field.TypeInt)) if ps := _d.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) if err != nil && sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } _d.mutation.done = true return affected, err } // PasswordDeleteOne is the builder for deleting a single Password entity. type PasswordDeleteOne struct { _d *PasswordDelete } // Where appends a list predicates to the PasswordDelete builder. func (_d *PasswordDeleteOne) Where(ps ...predicate.Password) *PasswordDeleteOne { _d._d.mutation.Where(ps...) return _d } // Exec executes the deletion query. func (_d *PasswordDeleteOne) Exec(ctx context.Context) error { n, err := _d._d.Exec(ctx) switch { case err != nil: return err case n == 0: return &NotFoundError{password.Label} default: return nil } } // ExecX is like Exec, but panics if an error occurs. func (_d *PasswordDeleteOne) ExecX(ctx context.Context) { if err := _d.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/password_query.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "fmt" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/password" "github.com/dexidp/dex/storage/ent/db/predicate" ) // PasswordQuery is the builder for querying Password entities. type PasswordQuery struct { config ctx *QueryContext order []password.OrderOption inters []Interceptor predicates []predicate.Password // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) } // Where adds a new predicate for the PasswordQuery builder. func (_q *PasswordQuery) Where(ps ...predicate.Password) *PasswordQuery { _q.predicates = append(_q.predicates, ps...) return _q } // Limit the number of records to be returned by this query. func (_q *PasswordQuery) Limit(limit int) *PasswordQuery { _q.ctx.Limit = &limit return _q } // Offset to start from. func (_q *PasswordQuery) Offset(offset int) *PasswordQuery { _q.ctx.Offset = &offset return _q } // Unique configures the query builder to filter duplicate records on query. // By default, unique is set to true, and can be disabled using this method. func (_q *PasswordQuery) Unique(unique bool) *PasswordQuery { _q.ctx.Unique = &unique return _q } // Order specifies how the records should be ordered. func (_q *PasswordQuery) Order(o ...password.OrderOption) *PasswordQuery { _q.order = append(_q.order, o...) return _q } // First returns the first Password entity from the query. // Returns a *NotFoundError when no Password was found. func (_q *PasswordQuery) First(ctx context.Context) (*Password, error) { nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) if err != nil { return nil, err } if len(nodes) == 0 { return nil, &NotFoundError{password.Label} } return nodes[0], nil } // FirstX is like First, but panics if an error occurs. func (_q *PasswordQuery) FirstX(ctx context.Context) *Password { node, err := _q.First(ctx) if err != nil && !IsNotFound(err) { panic(err) } return node } // FirstID returns the first Password ID from the query. // Returns a *NotFoundError when no Password ID was found. func (_q *PasswordQuery) FirstID(ctx context.Context) (id int, err error) { var ids []int if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { return } if len(ids) == 0 { err = &NotFoundError{password.Label} return } return ids[0], nil } // FirstIDX is like FirstID, but panics if an error occurs. func (_q *PasswordQuery) FirstIDX(ctx context.Context) int { id, err := _q.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) } return id } // Only returns a single Password entity found by the query, ensuring it only returns one. // Returns a *NotSingularError when more than one Password entity is found. // Returns a *NotFoundError when no Password entities are found. func (_q *PasswordQuery) Only(ctx context.Context) (*Password, error) { nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) if err != nil { return nil, err } switch len(nodes) { case 1: return nodes[0], nil case 0: return nil, &NotFoundError{password.Label} default: return nil, &NotSingularError{password.Label} } } // OnlyX is like Only, but panics if an error occurs. func (_q *PasswordQuery) OnlyX(ctx context.Context) *Password { node, err := _q.Only(ctx) if err != nil { panic(err) } return node } // OnlyID is like Only, but returns the only Password ID in the query. // Returns a *NotSingularError when more than one Password ID is found. // Returns a *NotFoundError when no entities are found. func (_q *PasswordQuery) OnlyID(ctx context.Context) (id int, err error) { var ids []int if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { return } switch len(ids) { case 1: id = ids[0] case 0: err = &NotFoundError{password.Label} default: err = &NotSingularError{password.Label} } return } // OnlyIDX is like OnlyID, but panics if an error occurs. func (_q *PasswordQuery) OnlyIDX(ctx context.Context) int { id, err := _q.OnlyID(ctx) if err != nil { panic(err) } return id } // All executes the query and returns a list of Passwords. func (_q *PasswordQuery) All(ctx context.Context) ([]*Password, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) if err := _q.prepareQuery(ctx); err != nil { return nil, err } qr := querierAll[[]*Password, *PasswordQuery]() return withInterceptors[[]*Password](ctx, _q, qr, _q.inters) } // AllX is like All, but panics if an error occurs. func (_q *PasswordQuery) AllX(ctx context.Context) []*Password { nodes, err := _q.All(ctx) if err != nil { panic(err) } return nodes } // IDs executes the query and returns a list of Password IDs. func (_q *PasswordQuery) IDs(ctx context.Context) (ids []int, err error) { if _q.ctx.Unique == nil && _q.path != nil { _q.Unique(true) } ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) if err = _q.Select(password.FieldID).Scan(ctx, &ids); err != nil { return nil, err } return ids, nil } // IDsX is like IDs, but panics if an error occurs. func (_q *PasswordQuery) IDsX(ctx context.Context) []int { ids, err := _q.IDs(ctx) if err != nil { panic(err) } return ids } // Count returns the count of the given query. func (_q *PasswordQuery) Count(ctx context.Context) (int, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) if err := _q.prepareQuery(ctx); err != nil { return 0, err } return withInterceptors[int](ctx, _q, querierCount[*PasswordQuery](), _q.inters) } // CountX is like Count, but panics if an error occurs. func (_q *PasswordQuery) CountX(ctx context.Context) int { count, err := _q.Count(ctx) if err != nil { panic(err) } return count } // Exist returns true if the query has elements in the graph. func (_q *PasswordQuery) Exist(ctx context.Context) (bool, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) switch _, err := _q.FirstID(ctx); { case IsNotFound(err): return false, nil case err != nil: return false, fmt.Errorf("db: check existence: %w", err) default: return true, nil } } // ExistX is like Exist, but panics if an error occurs. func (_q *PasswordQuery) ExistX(ctx context.Context) bool { exist, err := _q.Exist(ctx) if err != nil { panic(err) } return exist } // Clone returns a duplicate of the PasswordQuery builder, including all associated steps. It can be // used to prepare common query builders and use them differently after the clone is made. func (_q *PasswordQuery) Clone() *PasswordQuery { if _q == nil { return nil } return &PasswordQuery{ config: _q.config, ctx: _q.ctx.Clone(), order: append([]password.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.Password{}, _q.predicates...), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // // Example: // // var v []struct { // Email string `json:"email,omitempty"` // Count int `json:"count,omitempty"` // } // // client.Password.Query(). // GroupBy(password.FieldEmail). // Aggregate(db.Count()). // Scan(ctx, &v) func (_q *PasswordQuery) GroupBy(field string, fields ...string) *PasswordGroupBy { _q.ctx.Fields = append([]string{field}, fields...) grbuild := &PasswordGroupBy{build: _q} grbuild.flds = &_q.ctx.Fields grbuild.label = password.Label grbuild.scan = grbuild.Scan return grbuild } // Select allows the selection one or more fields/columns for the given query, // instead of selecting all fields in the entity. // // Example: // // var v []struct { // Email string `json:"email,omitempty"` // } // // client.Password.Query(). // Select(password.FieldEmail). // Scan(ctx, &v) func (_q *PasswordQuery) Select(fields ...string) *PasswordSelect { _q.ctx.Fields = append(_q.ctx.Fields, fields...) sbuild := &PasswordSelect{PasswordQuery: _q} sbuild.label = password.Label sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan return sbuild } // Aggregate returns a PasswordSelect configured with the given aggregations. func (_q *PasswordQuery) Aggregate(fns ...AggregateFunc) *PasswordSelect { return _q.Select().Aggregate(fns...) } func (_q *PasswordQuery) prepareQuery(ctx context.Context) error { for _, inter := range _q.inters { if inter == nil { return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") } if trv, ok := inter.(Traverser); ok { if err := trv.Traverse(ctx, _q); err != nil { return err } } } for _, f := range _q.ctx.Fields { if !password.ValidColumn(f) { return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } } if _q.path != nil { prev, err := _q.path(ctx) if err != nil { return err } _q.sql = prev } return nil } func (_q *PasswordQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Password, error) { var ( nodes = []*Password{} _spec = _q.querySpec() ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*Password).scanValues(nil, columns) } _spec.Assign = func(columns []string, values []any) error { node := &Password{config: _q.config} nodes = append(nodes, node) return node.assignValues(columns, values) } for i := range hooks { hooks[i](ctx, _spec) } if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { return nil, err } if len(nodes) == 0 { return nodes, nil } return nodes, nil } func (_q *PasswordQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields if len(_q.ctx.Fields) > 0 { _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique } return sqlgraph.CountNodes(ctx, _q.driver, _spec) } func (_q *PasswordQuery) querySpec() *sqlgraph.QuerySpec { _spec := sqlgraph.NewQuerySpec(password.Table, password.Columns, sqlgraph.NewFieldSpec(password.FieldID, field.TypeInt)) _spec.From = _q.sql if unique := _q.ctx.Unique; unique != nil { _spec.Unique = *unique } else if _q.path != nil { _spec.Unique = true } if fields := _q.ctx.Fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, password.FieldID) for i := range fields { if fields[i] != password.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } } if ps := _q.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if limit := _q.ctx.Limit; limit != nil { _spec.Limit = *limit } if offset := _q.ctx.Offset; offset != nil { _spec.Offset = *offset } if ps := _q.order; len(ps) > 0 { _spec.Order = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } return _spec } func (_q *PasswordQuery) sqlQuery(ctx context.Context) *sql.Selector { builder := sql.Dialect(_q.driver.Dialect()) t1 := builder.Table(password.Table) columns := _q.ctx.Fields if len(columns) == 0 { columns = password.Columns } selector := builder.Select(t1.Columns(columns...)...).From(t1) if _q.sql != nil { selector = _q.sql selector.Select(selector.Columns(columns...)...) } if _q.ctx.Unique != nil && *_q.ctx.Unique { selector.Distinct() } for _, p := range _q.predicates { p(selector) } for _, p := range _q.order { p(selector) } if offset := _q.ctx.Offset; offset != nil { // limit is mandatory for offset clause. We start // with default value, and override it below if needed. selector.Offset(*offset).Limit(math.MaxInt32) } if limit := _q.ctx.Limit; limit != nil { selector.Limit(*limit) } return selector } // PasswordGroupBy is the group-by builder for Password entities. type PasswordGroupBy struct { selector build *PasswordQuery } // Aggregate adds the given aggregation functions to the group-by query. func (_g *PasswordGroupBy) Aggregate(fns ...AggregateFunc) *PasswordGroupBy { _g.fns = append(_g.fns, fns...) return _g } // Scan applies the selector query and scans the result into the given value. func (_g *PasswordGroupBy) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) if err := _g.build.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*PasswordQuery, *PasswordGroupBy](ctx, _g.build, _g, _g.build.inters, v) } func (_g *PasswordGroupBy) sqlScan(ctx context.Context, root *PasswordQuery, v any) error { selector := root.sqlQuery(ctx).Select() aggregation := make([]string, 0, len(_g.fns)) for _, fn := range _g.fns { aggregation = append(aggregation, fn(selector)) } if len(selector.SelectedColumns()) == 0 { columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) for _, f := range *_g.flds { columns = append(columns, selector.C(f)) } columns = append(columns, aggregation...) selector.Select(columns...) } selector.GroupBy(selector.Columns(*_g.flds...)...) if err := selector.Err(); err != nil { return err } rows := &sql.Rows{} query, args := selector.Query() if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } // PasswordSelect is the builder for selecting fields of Password entities. type PasswordSelect struct { *PasswordQuery selector } // Aggregate adds the given aggregation functions to the selector query. func (_s *PasswordSelect) Aggregate(fns ...AggregateFunc) *PasswordSelect { _s.fns = append(_s.fns, fns...) return _s } // Scan applies the selector query and scans the result into the given value. func (_s *PasswordSelect) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) if err := _s.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*PasswordQuery, *PasswordSelect](ctx, _s.PasswordQuery, _s, _s.inters, v) } func (_s *PasswordSelect) sqlScan(ctx context.Context, root *PasswordQuery, v any) error { selector := root.sqlQuery(ctx) aggregation := make([]string, 0, len(_s.fns)) for _, fn := range _s.fns { aggregation = append(aggregation, fn(selector)) } switch n := len(*_s.selector.flds); { case n == 0 && len(aggregation) > 0: selector.Select(aggregation...) case n != 0 && len(aggregation) > 0: selector.AppendSelect(aggregation...) } rows := &sql.Rows{} query, args := selector.Query() if err := _s.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } ================================================ FILE: storage/ent/db/password_update.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/password" "github.com/dexidp/dex/storage/ent/db/predicate" ) // PasswordUpdate is the builder for updating Password entities. type PasswordUpdate struct { config hooks []Hook mutation *PasswordMutation } // Where appends a list predicates to the PasswordUpdate builder. func (_u *PasswordUpdate) Where(ps ...predicate.Password) *PasswordUpdate { _u.mutation.Where(ps...) return _u } // SetEmail sets the "email" field. func (_u *PasswordUpdate) SetEmail(v string) *PasswordUpdate { _u.mutation.SetEmail(v) return _u } // SetNillableEmail sets the "email" field if the given value is not nil. func (_u *PasswordUpdate) SetNillableEmail(v *string) *PasswordUpdate { if v != nil { _u.SetEmail(*v) } return _u } // SetHash sets the "hash" field. func (_u *PasswordUpdate) SetHash(v []byte) *PasswordUpdate { _u.mutation.SetHash(v) return _u } // SetUsername sets the "username" field. func (_u *PasswordUpdate) SetUsername(v string) *PasswordUpdate { _u.mutation.SetUsername(v) return _u } // SetNillableUsername sets the "username" field if the given value is not nil. func (_u *PasswordUpdate) SetNillableUsername(v *string) *PasswordUpdate { if v != nil { _u.SetUsername(*v) } return _u } // SetName sets the "name" field. func (_u *PasswordUpdate) SetName(v string) *PasswordUpdate { _u.mutation.SetName(v) return _u } // SetNillableName sets the "name" field if the given value is not nil. func (_u *PasswordUpdate) SetNillableName(v *string) *PasswordUpdate { if v != nil { _u.SetName(*v) } return _u } // SetPreferredUsername sets the "preferred_username" field. func (_u *PasswordUpdate) SetPreferredUsername(v string) *PasswordUpdate { _u.mutation.SetPreferredUsername(v) return _u } // SetNillablePreferredUsername sets the "preferred_username" field if the given value is not nil. func (_u *PasswordUpdate) SetNillablePreferredUsername(v *string) *PasswordUpdate { if v != nil { _u.SetPreferredUsername(*v) } return _u } // SetEmailVerified sets the "email_verified" field. func (_u *PasswordUpdate) SetEmailVerified(v bool) *PasswordUpdate { _u.mutation.SetEmailVerified(v) return _u } // SetNillableEmailVerified sets the "email_verified" field if the given value is not nil. func (_u *PasswordUpdate) SetNillableEmailVerified(v *bool) *PasswordUpdate { if v != nil { _u.SetEmailVerified(*v) } return _u } // ClearEmailVerified clears the value of the "email_verified" field. func (_u *PasswordUpdate) ClearEmailVerified() *PasswordUpdate { _u.mutation.ClearEmailVerified() return _u } // SetUserID sets the "user_id" field. func (_u *PasswordUpdate) SetUserID(v string) *PasswordUpdate { _u.mutation.SetUserID(v) return _u } // SetNillableUserID sets the "user_id" field if the given value is not nil. func (_u *PasswordUpdate) SetNillableUserID(v *string) *PasswordUpdate { if v != nil { _u.SetUserID(*v) } return _u } // SetGroups sets the "groups" field. func (_u *PasswordUpdate) SetGroups(v []string) *PasswordUpdate { _u.mutation.SetGroups(v) return _u } // AppendGroups appends value to the "groups" field. func (_u *PasswordUpdate) AppendGroups(v []string) *PasswordUpdate { _u.mutation.AppendGroups(v) return _u } // ClearGroups clears the value of the "groups" field. func (_u *PasswordUpdate) ClearGroups() *PasswordUpdate { _u.mutation.ClearGroups() return _u } // Mutation returns the PasswordMutation object of the builder. func (_u *PasswordUpdate) Mutation() *PasswordMutation { return _u.mutation } // Save executes the query and returns the number of nodes affected by the update operation. func (_u *PasswordUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *PasswordUpdate) SaveX(ctx context.Context) int { affected, err := _u.Save(ctx) if err != nil { panic(err) } return affected } // Exec executes the query. func (_u *PasswordUpdate) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *PasswordUpdate) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *PasswordUpdate) check() error { if v, ok := _u.mutation.Email(); ok { if err := password.EmailValidator(v); err != nil { return &ValidationError{Name: "email", err: fmt.Errorf(`db: validator failed for field "Password.email": %w`, err)} } } if v, ok := _u.mutation.Username(); ok { if err := password.UsernameValidator(v); err != nil { return &ValidationError{Name: "username", err: fmt.Errorf(`db: validator failed for field "Password.username": %w`, err)} } } if v, ok := _u.mutation.UserID(); ok { if err := password.UserIDValidator(v); err != nil { return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "Password.user_id": %w`, err)} } } return nil } func (_u *PasswordUpdate) sqlSave(ctx context.Context) (_node int, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(password.Table, password.Columns, sqlgraph.NewFieldSpec(password.FieldID, field.TypeInt)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.Email(); ok { _spec.SetField(password.FieldEmail, field.TypeString, value) } if value, ok := _u.mutation.Hash(); ok { _spec.SetField(password.FieldHash, field.TypeBytes, value) } if value, ok := _u.mutation.Username(); ok { _spec.SetField(password.FieldUsername, field.TypeString, value) } if value, ok := _u.mutation.Name(); ok { _spec.SetField(password.FieldName, field.TypeString, value) } if value, ok := _u.mutation.PreferredUsername(); ok { _spec.SetField(password.FieldPreferredUsername, field.TypeString, value) } if value, ok := _u.mutation.EmailVerified(); ok { _spec.SetField(password.FieldEmailVerified, field.TypeBool, value) } if _u.mutation.EmailVerifiedCleared() { _spec.ClearField(password.FieldEmailVerified, field.TypeBool) } if value, ok := _u.mutation.UserID(); ok { _spec.SetField(password.FieldUserID, field.TypeString, value) } if value, ok := _u.mutation.Groups(); ok { _spec.SetField(password.FieldGroups, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedGroups(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, password.FieldGroups, value) }) } if _u.mutation.GroupsCleared() { _spec.ClearField(password.FieldGroups, field.TypeJSON) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{password.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return 0, err } _u.mutation.done = true return _node, nil } // PasswordUpdateOne is the builder for updating a single Password entity. type PasswordUpdateOne struct { config fields []string hooks []Hook mutation *PasswordMutation } // SetEmail sets the "email" field. func (_u *PasswordUpdateOne) SetEmail(v string) *PasswordUpdateOne { _u.mutation.SetEmail(v) return _u } // SetNillableEmail sets the "email" field if the given value is not nil. func (_u *PasswordUpdateOne) SetNillableEmail(v *string) *PasswordUpdateOne { if v != nil { _u.SetEmail(*v) } return _u } // SetHash sets the "hash" field. func (_u *PasswordUpdateOne) SetHash(v []byte) *PasswordUpdateOne { _u.mutation.SetHash(v) return _u } // SetUsername sets the "username" field. func (_u *PasswordUpdateOne) SetUsername(v string) *PasswordUpdateOne { _u.mutation.SetUsername(v) return _u } // SetNillableUsername sets the "username" field if the given value is not nil. func (_u *PasswordUpdateOne) SetNillableUsername(v *string) *PasswordUpdateOne { if v != nil { _u.SetUsername(*v) } return _u } // SetName sets the "name" field. func (_u *PasswordUpdateOne) SetName(v string) *PasswordUpdateOne { _u.mutation.SetName(v) return _u } // SetNillableName sets the "name" field if the given value is not nil. func (_u *PasswordUpdateOne) SetNillableName(v *string) *PasswordUpdateOne { if v != nil { _u.SetName(*v) } return _u } // SetPreferredUsername sets the "preferred_username" field. func (_u *PasswordUpdateOne) SetPreferredUsername(v string) *PasswordUpdateOne { _u.mutation.SetPreferredUsername(v) return _u } // SetNillablePreferredUsername sets the "preferred_username" field if the given value is not nil. func (_u *PasswordUpdateOne) SetNillablePreferredUsername(v *string) *PasswordUpdateOne { if v != nil { _u.SetPreferredUsername(*v) } return _u } // SetEmailVerified sets the "email_verified" field. func (_u *PasswordUpdateOne) SetEmailVerified(v bool) *PasswordUpdateOne { _u.mutation.SetEmailVerified(v) return _u } // SetNillableEmailVerified sets the "email_verified" field if the given value is not nil. func (_u *PasswordUpdateOne) SetNillableEmailVerified(v *bool) *PasswordUpdateOne { if v != nil { _u.SetEmailVerified(*v) } return _u } // ClearEmailVerified clears the value of the "email_verified" field. func (_u *PasswordUpdateOne) ClearEmailVerified() *PasswordUpdateOne { _u.mutation.ClearEmailVerified() return _u } // SetUserID sets the "user_id" field. func (_u *PasswordUpdateOne) SetUserID(v string) *PasswordUpdateOne { _u.mutation.SetUserID(v) return _u } // SetNillableUserID sets the "user_id" field if the given value is not nil. func (_u *PasswordUpdateOne) SetNillableUserID(v *string) *PasswordUpdateOne { if v != nil { _u.SetUserID(*v) } return _u } // SetGroups sets the "groups" field. func (_u *PasswordUpdateOne) SetGroups(v []string) *PasswordUpdateOne { _u.mutation.SetGroups(v) return _u } // AppendGroups appends value to the "groups" field. func (_u *PasswordUpdateOne) AppendGroups(v []string) *PasswordUpdateOne { _u.mutation.AppendGroups(v) return _u } // ClearGroups clears the value of the "groups" field. func (_u *PasswordUpdateOne) ClearGroups() *PasswordUpdateOne { _u.mutation.ClearGroups() return _u } // Mutation returns the PasswordMutation object of the builder. func (_u *PasswordUpdateOne) Mutation() *PasswordMutation { return _u.mutation } // Where appends a list predicates to the PasswordUpdate builder. func (_u *PasswordUpdateOne) Where(ps ...predicate.Password) *PasswordUpdateOne { _u.mutation.Where(ps...) return _u } // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (_u *PasswordUpdateOne) Select(field string, fields ...string) *PasswordUpdateOne { _u.fields = append([]string{field}, fields...) return _u } // Save executes the query and returns the updated Password entity. func (_u *PasswordUpdateOne) Save(ctx context.Context) (*Password, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *PasswordUpdateOne) SaveX(ctx context.Context) *Password { node, err := _u.Save(ctx) if err != nil { panic(err) } return node } // Exec executes the query on the entity. func (_u *PasswordUpdateOne) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *PasswordUpdateOne) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *PasswordUpdateOne) check() error { if v, ok := _u.mutation.Email(); ok { if err := password.EmailValidator(v); err != nil { return &ValidationError{Name: "email", err: fmt.Errorf(`db: validator failed for field "Password.email": %w`, err)} } } if v, ok := _u.mutation.Username(); ok { if err := password.UsernameValidator(v); err != nil { return &ValidationError{Name: "username", err: fmt.Errorf(`db: validator failed for field "Password.username": %w`, err)} } } if v, ok := _u.mutation.UserID(); ok { if err := password.UserIDValidator(v); err != nil { return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "Password.user_id": %w`, err)} } } return nil } func (_u *PasswordUpdateOne) sqlSave(ctx context.Context) (_node *Password, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(password.Table, password.Columns, sqlgraph.NewFieldSpec(password.FieldID, field.TypeInt)) id, ok := _u.mutation.ID() if !ok { return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "Password.id" for update`)} } _spec.Node.ID.Value = id if fields := _u.fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, password.FieldID) for _, f := range fields { if !password.ValidColumn(f) { return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } if f != password.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, f) } } } if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.Email(); ok { _spec.SetField(password.FieldEmail, field.TypeString, value) } if value, ok := _u.mutation.Hash(); ok { _spec.SetField(password.FieldHash, field.TypeBytes, value) } if value, ok := _u.mutation.Username(); ok { _spec.SetField(password.FieldUsername, field.TypeString, value) } if value, ok := _u.mutation.Name(); ok { _spec.SetField(password.FieldName, field.TypeString, value) } if value, ok := _u.mutation.PreferredUsername(); ok { _spec.SetField(password.FieldPreferredUsername, field.TypeString, value) } if value, ok := _u.mutation.EmailVerified(); ok { _spec.SetField(password.FieldEmailVerified, field.TypeBool, value) } if _u.mutation.EmailVerifiedCleared() { _spec.ClearField(password.FieldEmailVerified, field.TypeBool) } if value, ok := _u.mutation.UserID(); ok { _spec.SetField(password.FieldUserID, field.TypeString, value) } if value, ok := _u.mutation.Groups(); ok { _spec.SetField(password.FieldGroups, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedGroups(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, password.FieldGroups, value) }) } if _u.mutation.GroupsCleared() { _spec.ClearField(password.FieldGroups, field.TypeJSON) } _node = &Password{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{password.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } _u.mutation.done = true return _node, nil } ================================================ FILE: storage/ent/db/predicate/predicate.go ================================================ // Code generated by ent, DO NOT EDIT. package predicate import ( "entgo.io/ent/dialect/sql" ) // AuthCode is the predicate function for authcode builders. type AuthCode func(*sql.Selector) // AuthRequest is the predicate function for authrequest builders. type AuthRequest func(*sql.Selector) // AuthSession is the predicate function for authsession builders. type AuthSession func(*sql.Selector) // Connector is the predicate function for connector builders. type Connector func(*sql.Selector) // DeviceRequest is the predicate function for devicerequest builders. type DeviceRequest func(*sql.Selector) // DeviceToken is the predicate function for devicetoken builders. type DeviceToken func(*sql.Selector) // Keys is the predicate function for keys builders. type Keys func(*sql.Selector) // OAuth2Client is the predicate function for oauth2client builders. type OAuth2Client func(*sql.Selector) // OfflineSession is the predicate function for offlinesession builders. type OfflineSession func(*sql.Selector) // Password is the predicate function for password builders. type Password func(*sql.Selector) // RefreshToken is the predicate function for refreshtoken builders. type RefreshToken func(*sql.Selector) // UserIdentity is the predicate function for useridentity builders. type UserIdentity func(*sql.Selector) ================================================ FILE: storage/ent/db/refreshtoken/refreshtoken.go ================================================ // Code generated by ent, DO NOT EDIT. package refreshtoken import ( "time" "entgo.io/ent/dialect/sql" ) const ( // Label holds the string label denoting the refreshtoken type in the database. Label = "refresh_token" // FieldID holds the string denoting the id field in the database. FieldID = "id" // FieldClientID holds the string denoting the client_id field in the database. FieldClientID = "client_id" // FieldScopes holds the string denoting the scopes field in the database. FieldScopes = "scopes" // FieldNonce holds the string denoting the nonce field in the database. FieldNonce = "nonce" // FieldClaimsUserID holds the string denoting the claims_user_id field in the database. FieldClaimsUserID = "claims_user_id" // FieldClaimsUsername holds the string denoting the claims_username field in the database. FieldClaimsUsername = "claims_username" // FieldClaimsEmail holds the string denoting the claims_email field in the database. FieldClaimsEmail = "claims_email" // FieldClaimsEmailVerified holds the string denoting the claims_email_verified field in the database. FieldClaimsEmailVerified = "claims_email_verified" // FieldClaimsGroups holds the string denoting the claims_groups field in the database. FieldClaimsGroups = "claims_groups" // FieldClaimsPreferredUsername holds the string denoting the claims_preferred_username field in the database. FieldClaimsPreferredUsername = "claims_preferred_username" // FieldConnectorID holds the string denoting the connector_id field in the database. FieldConnectorID = "connector_id" // FieldConnectorData holds the string denoting the connector_data field in the database. FieldConnectorData = "connector_data" // FieldToken holds the string denoting the token field in the database. FieldToken = "token" // FieldObsoleteToken holds the string denoting the obsolete_token field in the database. FieldObsoleteToken = "obsolete_token" // FieldCreatedAt holds the string denoting the created_at field in the database. FieldCreatedAt = "created_at" // FieldLastUsed holds the string denoting the last_used field in the database. FieldLastUsed = "last_used" // Table holds the table name of the refreshtoken in the database. Table = "refresh_tokens" ) // Columns holds all SQL columns for refreshtoken fields. var Columns = []string{ FieldID, FieldClientID, FieldScopes, FieldNonce, FieldClaimsUserID, FieldClaimsUsername, FieldClaimsEmail, FieldClaimsEmailVerified, FieldClaimsGroups, FieldClaimsPreferredUsername, FieldConnectorID, FieldConnectorData, FieldToken, FieldObsoleteToken, FieldCreatedAt, FieldLastUsed, } // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { if column == Columns[i] { return true } } return false } var ( // ClientIDValidator is a validator for the "client_id" field. It is called by the builders before save. ClientIDValidator func(string) error // NonceValidator is a validator for the "nonce" field. It is called by the builders before save. NonceValidator func(string) error // ClaimsUserIDValidator is a validator for the "claims_user_id" field. It is called by the builders before save. ClaimsUserIDValidator func(string) error // ClaimsUsernameValidator is a validator for the "claims_username" field. It is called by the builders before save. ClaimsUsernameValidator func(string) error // ClaimsEmailValidator is a validator for the "claims_email" field. It is called by the builders before save. ClaimsEmailValidator func(string) error // DefaultClaimsPreferredUsername holds the default value on creation for the "claims_preferred_username" field. DefaultClaimsPreferredUsername string // ConnectorIDValidator is a validator for the "connector_id" field. It is called by the builders before save. ConnectorIDValidator func(string) error // DefaultToken holds the default value on creation for the "token" field. DefaultToken string // DefaultObsoleteToken holds the default value on creation for the "obsolete_token" field. DefaultObsoleteToken string // DefaultCreatedAt holds the default value on creation for the "created_at" field. DefaultCreatedAt func() time.Time // DefaultLastUsed holds the default value on creation for the "last_used" field. DefaultLastUsed func() time.Time // IDValidator is a validator for the "id" field. It is called by the builders before save. IDValidator func(string) error ) // OrderOption defines the ordering options for the RefreshToken queries. type OrderOption func(*sql.Selector) // ByID orders the results by the id field. func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } // ByClientID orders the results by the client_id field. func ByClientID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClientID, opts...).ToFunc() } // ByNonce orders the results by the nonce field. func ByNonce(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldNonce, opts...).ToFunc() } // ByClaimsUserID orders the results by the claims_user_id field. func ByClaimsUserID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsUserID, opts...).ToFunc() } // ByClaimsUsername orders the results by the claims_username field. func ByClaimsUsername(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsUsername, opts...).ToFunc() } // ByClaimsEmail orders the results by the claims_email field. func ByClaimsEmail(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsEmail, opts...).ToFunc() } // ByClaimsEmailVerified orders the results by the claims_email_verified field. func ByClaimsEmailVerified(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsEmailVerified, opts...).ToFunc() } // ByClaimsPreferredUsername orders the results by the claims_preferred_username field. func ByClaimsPreferredUsername(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsPreferredUsername, opts...).ToFunc() } // ByConnectorID orders the results by the connector_id field. func ByConnectorID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldConnectorID, opts...).ToFunc() } // ByToken orders the results by the token field. func ByToken(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldToken, opts...).ToFunc() } // ByObsoleteToken orders the results by the obsolete_token field. func ByObsoleteToken(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldObsoleteToken, opts...).ToFunc() } // ByCreatedAt orders the results by the created_at field. func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() } // ByLastUsed orders the results by the last_used field. func ByLastUsed(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldLastUsed, opts...).ToFunc() } ================================================ FILE: storage/ent/db/refreshtoken/where.go ================================================ // Code generated by ent, DO NOT EDIT. package refreshtoken import ( "time" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ID filters vertices based on their ID field. func ID(id string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. func IDEQ(id string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. func IDNEQ(id string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. func IDIn(ids ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. func IDNotIn(ids ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. func IDGT(id string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. func IDGTE(id string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. func IDLT(id string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. func IDLTE(id string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldID, id)) } // IDEqualFold applies the EqualFold predicate on the ID field. func IDEqualFold(id string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEqualFold(FieldID, id)) } // IDContainsFold applies the ContainsFold predicate on the ID field. func IDContainsFold(id string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContainsFold(FieldID, id)) } // ClientID applies equality check predicate on the "client_id" field. It's identical to ClientIDEQ. func ClientID(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldClientID, v)) } // Nonce applies equality check predicate on the "nonce" field. It's identical to NonceEQ. func Nonce(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldNonce, v)) } // ClaimsUserID applies equality check predicate on the "claims_user_id" field. It's identical to ClaimsUserIDEQ. func ClaimsUserID(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldClaimsUserID, v)) } // ClaimsUsername applies equality check predicate on the "claims_username" field. It's identical to ClaimsUsernameEQ. func ClaimsUsername(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldClaimsUsername, v)) } // ClaimsEmail applies equality check predicate on the "claims_email" field. It's identical to ClaimsEmailEQ. func ClaimsEmail(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldClaimsEmail, v)) } // ClaimsEmailVerified applies equality check predicate on the "claims_email_verified" field. It's identical to ClaimsEmailVerifiedEQ. func ClaimsEmailVerified(v bool) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldClaimsEmailVerified, v)) } // ClaimsPreferredUsername applies equality check predicate on the "claims_preferred_username" field. It's identical to ClaimsPreferredUsernameEQ. func ClaimsPreferredUsername(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldClaimsPreferredUsername, v)) } // ConnectorID applies equality check predicate on the "connector_id" field. It's identical to ConnectorIDEQ. func ConnectorID(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldConnectorID, v)) } // ConnectorData applies equality check predicate on the "connector_data" field. It's identical to ConnectorDataEQ. func ConnectorData(v []byte) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldConnectorData, v)) } // Token applies equality check predicate on the "token" field. It's identical to TokenEQ. func Token(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldToken, v)) } // ObsoleteToken applies equality check predicate on the "obsolete_token" field. It's identical to ObsoleteTokenEQ. func ObsoleteToken(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldObsoleteToken, v)) } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. func CreatedAt(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldCreatedAt, v)) } // LastUsed applies equality check predicate on the "last_used" field. It's identical to LastUsedEQ. func LastUsed(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldLastUsed, v)) } // ClientIDEQ applies the EQ predicate on the "client_id" field. func ClientIDEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldClientID, v)) } // ClientIDNEQ applies the NEQ predicate on the "client_id" field. func ClientIDNEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldClientID, v)) } // ClientIDIn applies the In predicate on the "client_id" field. func ClientIDIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldClientID, vs...)) } // ClientIDNotIn applies the NotIn predicate on the "client_id" field. func ClientIDNotIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldClientID, vs...)) } // ClientIDGT applies the GT predicate on the "client_id" field. func ClientIDGT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldClientID, v)) } // ClientIDGTE applies the GTE predicate on the "client_id" field. func ClientIDGTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldClientID, v)) } // ClientIDLT applies the LT predicate on the "client_id" field. func ClientIDLT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldClientID, v)) } // ClientIDLTE applies the LTE predicate on the "client_id" field. func ClientIDLTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldClientID, v)) } // ClientIDContains applies the Contains predicate on the "client_id" field. func ClientIDContains(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContains(FieldClientID, v)) } // ClientIDHasPrefix applies the HasPrefix predicate on the "client_id" field. func ClientIDHasPrefix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasPrefix(FieldClientID, v)) } // ClientIDHasSuffix applies the HasSuffix predicate on the "client_id" field. func ClientIDHasSuffix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasSuffix(FieldClientID, v)) } // ClientIDEqualFold applies the EqualFold predicate on the "client_id" field. func ClientIDEqualFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEqualFold(FieldClientID, v)) } // ClientIDContainsFold applies the ContainsFold predicate on the "client_id" field. func ClientIDContainsFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContainsFold(FieldClientID, v)) } // ScopesIsNil applies the IsNil predicate on the "scopes" field. func ScopesIsNil() predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIsNull(FieldScopes)) } // ScopesNotNil applies the NotNil predicate on the "scopes" field. func ScopesNotNil() predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotNull(FieldScopes)) } // NonceEQ applies the EQ predicate on the "nonce" field. func NonceEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldNonce, v)) } // NonceNEQ applies the NEQ predicate on the "nonce" field. func NonceNEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldNonce, v)) } // NonceIn applies the In predicate on the "nonce" field. func NonceIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldNonce, vs...)) } // NonceNotIn applies the NotIn predicate on the "nonce" field. func NonceNotIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldNonce, vs...)) } // NonceGT applies the GT predicate on the "nonce" field. func NonceGT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldNonce, v)) } // NonceGTE applies the GTE predicate on the "nonce" field. func NonceGTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldNonce, v)) } // NonceLT applies the LT predicate on the "nonce" field. func NonceLT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldNonce, v)) } // NonceLTE applies the LTE predicate on the "nonce" field. func NonceLTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldNonce, v)) } // NonceContains applies the Contains predicate on the "nonce" field. func NonceContains(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContains(FieldNonce, v)) } // NonceHasPrefix applies the HasPrefix predicate on the "nonce" field. func NonceHasPrefix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasPrefix(FieldNonce, v)) } // NonceHasSuffix applies the HasSuffix predicate on the "nonce" field. func NonceHasSuffix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasSuffix(FieldNonce, v)) } // NonceEqualFold applies the EqualFold predicate on the "nonce" field. func NonceEqualFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEqualFold(FieldNonce, v)) } // NonceContainsFold applies the ContainsFold predicate on the "nonce" field. func NonceContainsFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContainsFold(FieldNonce, v)) } // ClaimsUserIDEQ applies the EQ predicate on the "claims_user_id" field. func ClaimsUserIDEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldClaimsUserID, v)) } // ClaimsUserIDNEQ applies the NEQ predicate on the "claims_user_id" field. func ClaimsUserIDNEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldClaimsUserID, v)) } // ClaimsUserIDIn applies the In predicate on the "claims_user_id" field. func ClaimsUserIDIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldClaimsUserID, vs...)) } // ClaimsUserIDNotIn applies the NotIn predicate on the "claims_user_id" field. func ClaimsUserIDNotIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldClaimsUserID, vs...)) } // ClaimsUserIDGT applies the GT predicate on the "claims_user_id" field. func ClaimsUserIDGT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldClaimsUserID, v)) } // ClaimsUserIDGTE applies the GTE predicate on the "claims_user_id" field. func ClaimsUserIDGTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldClaimsUserID, v)) } // ClaimsUserIDLT applies the LT predicate on the "claims_user_id" field. func ClaimsUserIDLT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldClaimsUserID, v)) } // ClaimsUserIDLTE applies the LTE predicate on the "claims_user_id" field. func ClaimsUserIDLTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldClaimsUserID, v)) } // ClaimsUserIDContains applies the Contains predicate on the "claims_user_id" field. func ClaimsUserIDContains(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContains(FieldClaimsUserID, v)) } // ClaimsUserIDHasPrefix applies the HasPrefix predicate on the "claims_user_id" field. func ClaimsUserIDHasPrefix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasPrefix(FieldClaimsUserID, v)) } // ClaimsUserIDHasSuffix applies the HasSuffix predicate on the "claims_user_id" field. func ClaimsUserIDHasSuffix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasSuffix(FieldClaimsUserID, v)) } // ClaimsUserIDEqualFold applies the EqualFold predicate on the "claims_user_id" field. func ClaimsUserIDEqualFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEqualFold(FieldClaimsUserID, v)) } // ClaimsUserIDContainsFold applies the ContainsFold predicate on the "claims_user_id" field. func ClaimsUserIDContainsFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContainsFold(FieldClaimsUserID, v)) } // ClaimsUsernameEQ applies the EQ predicate on the "claims_username" field. func ClaimsUsernameEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldClaimsUsername, v)) } // ClaimsUsernameNEQ applies the NEQ predicate on the "claims_username" field. func ClaimsUsernameNEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldClaimsUsername, v)) } // ClaimsUsernameIn applies the In predicate on the "claims_username" field. func ClaimsUsernameIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldClaimsUsername, vs...)) } // ClaimsUsernameNotIn applies the NotIn predicate on the "claims_username" field. func ClaimsUsernameNotIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldClaimsUsername, vs...)) } // ClaimsUsernameGT applies the GT predicate on the "claims_username" field. func ClaimsUsernameGT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldClaimsUsername, v)) } // ClaimsUsernameGTE applies the GTE predicate on the "claims_username" field. func ClaimsUsernameGTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldClaimsUsername, v)) } // ClaimsUsernameLT applies the LT predicate on the "claims_username" field. func ClaimsUsernameLT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldClaimsUsername, v)) } // ClaimsUsernameLTE applies the LTE predicate on the "claims_username" field. func ClaimsUsernameLTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldClaimsUsername, v)) } // ClaimsUsernameContains applies the Contains predicate on the "claims_username" field. func ClaimsUsernameContains(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContains(FieldClaimsUsername, v)) } // ClaimsUsernameHasPrefix applies the HasPrefix predicate on the "claims_username" field. func ClaimsUsernameHasPrefix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasPrefix(FieldClaimsUsername, v)) } // ClaimsUsernameHasSuffix applies the HasSuffix predicate on the "claims_username" field. func ClaimsUsernameHasSuffix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasSuffix(FieldClaimsUsername, v)) } // ClaimsUsernameEqualFold applies the EqualFold predicate on the "claims_username" field. func ClaimsUsernameEqualFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEqualFold(FieldClaimsUsername, v)) } // ClaimsUsernameContainsFold applies the ContainsFold predicate on the "claims_username" field. func ClaimsUsernameContainsFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContainsFold(FieldClaimsUsername, v)) } // ClaimsEmailEQ applies the EQ predicate on the "claims_email" field. func ClaimsEmailEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldClaimsEmail, v)) } // ClaimsEmailNEQ applies the NEQ predicate on the "claims_email" field. func ClaimsEmailNEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldClaimsEmail, v)) } // ClaimsEmailIn applies the In predicate on the "claims_email" field. func ClaimsEmailIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldClaimsEmail, vs...)) } // ClaimsEmailNotIn applies the NotIn predicate on the "claims_email" field. func ClaimsEmailNotIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldClaimsEmail, vs...)) } // ClaimsEmailGT applies the GT predicate on the "claims_email" field. func ClaimsEmailGT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldClaimsEmail, v)) } // ClaimsEmailGTE applies the GTE predicate on the "claims_email" field. func ClaimsEmailGTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldClaimsEmail, v)) } // ClaimsEmailLT applies the LT predicate on the "claims_email" field. func ClaimsEmailLT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldClaimsEmail, v)) } // ClaimsEmailLTE applies the LTE predicate on the "claims_email" field. func ClaimsEmailLTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldClaimsEmail, v)) } // ClaimsEmailContains applies the Contains predicate on the "claims_email" field. func ClaimsEmailContains(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContains(FieldClaimsEmail, v)) } // ClaimsEmailHasPrefix applies the HasPrefix predicate on the "claims_email" field. func ClaimsEmailHasPrefix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasPrefix(FieldClaimsEmail, v)) } // ClaimsEmailHasSuffix applies the HasSuffix predicate on the "claims_email" field. func ClaimsEmailHasSuffix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasSuffix(FieldClaimsEmail, v)) } // ClaimsEmailEqualFold applies the EqualFold predicate on the "claims_email" field. func ClaimsEmailEqualFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEqualFold(FieldClaimsEmail, v)) } // ClaimsEmailContainsFold applies the ContainsFold predicate on the "claims_email" field. func ClaimsEmailContainsFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContainsFold(FieldClaimsEmail, v)) } // ClaimsEmailVerifiedEQ applies the EQ predicate on the "claims_email_verified" field. func ClaimsEmailVerifiedEQ(v bool) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldClaimsEmailVerified, v)) } // ClaimsEmailVerifiedNEQ applies the NEQ predicate on the "claims_email_verified" field. func ClaimsEmailVerifiedNEQ(v bool) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldClaimsEmailVerified, v)) } // ClaimsGroupsIsNil applies the IsNil predicate on the "claims_groups" field. func ClaimsGroupsIsNil() predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIsNull(FieldClaimsGroups)) } // ClaimsGroupsNotNil applies the NotNil predicate on the "claims_groups" field. func ClaimsGroupsNotNil() predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotNull(FieldClaimsGroups)) } // ClaimsPreferredUsernameEQ applies the EQ predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameNEQ applies the NEQ predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameNEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameIn applies the In predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldClaimsPreferredUsername, vs...)) } // ClaimsPreferredUsernameNotIn applies the NotIn predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameNotIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldClaimsPreferredUsername, vs...)) } // ClaimsPreferredUsernameGT applies the GT predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameGT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameGTE applies the GTE predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameGTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameLT applies the LT predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameLT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameLTE applies the LTE predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameLTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameContains applies the Contains predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameContains(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContains(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameHasPrefix applies the HasPrefix predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameHasPrefix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasPrefix(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameHasSuffix applies the HasSuffix predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameHasSuffix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasSuffix(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameEqualFold applies the EqualFold predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameEqualFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEqualFold(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameContainsFold applies the ContainsFold predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameContainsFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContainsFold(FieldClaimsPreferredUsername, v)) } // ConnectorIDEQ applies the EQ predicate on the "connector_id" field. func ConnectorIDEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldConnectorID, v)) } // ConnectorIDNEQ applies the NEQ predicate on the "connector_id" field. func ConnectorIDNEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldConnectorID, v)) } // ConnectorIDIn applies the In predicate on the "connector_id" field. func ConnectorIDIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldConnectorID, vs...)) } // ConnectorIDNotIn applies the NotIn predicate on the "connector_id" field. func ConnectorIDNotIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldConnectorID, vs...)) } // ConnectorIDGT applies the GT predicate on the "connector_id" field. func ConnectorIDGT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldConnectorID, v)) } // ConnectorIDGTE applies the GTE predicate on the "connector_id" field. func ConnectorIDGTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldConnectorID, v)) } // ConnectorIDLT applies the LT predicate on the "connector_id" field. func ConnectorIDLT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldConnectorID, v)) } // ConnectorIDLTE applies the LTE predicate on the "connector_id" field. func ConnectorIDLTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldConnectorID, v)) } // ConnectorIDContains applies the Contains predicate on the "connector_id" field. func ConnectorIDContains(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContains(FieldConnectorID, v)) } // ConnectorIDHasPrefix applies the HasPrefix predicate on the "connector_id" field. func ConnectorIDHasPrefix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasPrefix(FieldConnectorID, v)) } // ConnectorIDHasSuffix applies the HasSuffix predicate on the "connector_id" field. func ConnectorIDHasSuffix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasSuffix(FieldConnectorID, v)) } // ConnectorIDEqualFold applies the EqualFold predicate on the "connector_id" field. func ConnectorIDEqualFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEqualFold(FieldConnectorID, v)) } // ConnectorIDContainsFold applies the ContainsFold predicate on the "connector_id" field. func ConnectorIDContainsFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContainsFold(FieldConnectorID, v)) } // ConnectorDataEQ applies the EQ predicate on the "connector_data" field. func ConnectorDataEQ(v []byte) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldConnectorData, v)) } // ConnectorDataNEQ applies the NEQ predicate on the "connector_data" field. func ConnectorDataNEQ(v []byte) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldConnectorData, v)) } // ConnectorDataIn applies the In predicate on the "connector_data" field. func ConnectorDataIn(vs ...[]byte) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldConnectorData, vs...)) } // ConnectorDataNotIn applies the NotIn predicate on the "connector_data" field. func ConnectorDataNotIn(vs ...[]byte) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldConnectorData, vs...)) } // ConnectorDataGT applies the GT predicate on the "connector_data" field. func ConnectorDataGT(v []byte) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldConnectorData, v)) } // ConnectorDataGTE applies the GTE predicate on the "connector_data" field. func ConnectorDataGTE(v []byte) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldConnectorData, v)) } // ConnectorDataLT applies the LT predicate on the "connector_data" field. func ConnectorDataLT(v []byte) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldConnectorData, v)) } // ConnectorDataLTE applies the LTE predicate on the "connector_data" field. func ConnectorDataLTE(v []byte) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldConnectorData, v)) } // ConnectorDataIsNil applies the IsNil predicate on the "connector_data" field. func ConnectorDataIsNil() predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIsNull(FieldConnectorData)) } // ConnectorDataNotNil applies the NotNil predicate on the "connector_data" field. func ConnectorDataNotNil() predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotNull(FieldConnectorData)) } // TokenEQ applies the EQ predicate on the "token" field. func TokenEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldToken, v)) } // TokenNEQ applies the NEQ predicate on the "token" field. func TokenNEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldToken, v)) } // TokenIn applies the In predicate on the "token" field. func TokenIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldToken, vs...)) } // TokenNotIn applies the NotIn predicate on the "token" field. func TokenNotIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldToken, vs...)) } // TokenGT applies the GT predicate on the "token" field. func TokenGT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldToken, v)) } // TokenGTE applies the GTE predicate on the "token" field. func TokenGTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldToken, v)) } // TokenLT applies the LT predicate on the "token" field. func TokenLT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldToken, v)) } // TokenLTE applies the LTE predicate on the "token" field. func TokenLTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldToken, v)) } // TokenContains applies the Contains predicate on the "token" field. func TokenContains(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContains(FieldToken, v)) } // TokenHasPrefix applies the HasPrefix predicate on the "token" field. func TokenHasPrefix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasPrefix(FieldToken, v)) } // TokenHasSuffix applies the HasSuffix predicate on the "token" field. func TokenHasSuffix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasSuffix(FieldToken, v)) } // TokenEqualFold applies the EqualFold predicate on the "token" field. func TokenEqualFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEqualFold(FieldToken, v)) } // TokenContainsFold applies the ContainsFold predicate on the "token" field. func TokenContainsFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContainsFold(FieldToken, v)) } // ObsoleteTokenEQ applies the EQ predicate on the "obsolete_token" field. func ObsoleteTokenEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldObsoleteToken, v)) } // ObsoleteTokenNEQ applies the NEQ predicate on the "obsolete_token" field. func ObsoleteTokenNEQ(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldObsoleteToken, v)) } // ObsoleteTokenIn applies the In predicate on the "obsolete_token" field. func ObsoleteTokenIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldObsoleteToken, vs...)) } // ObsoleteTokenNotIn applies the NotIn predicate on the "obsolete_token" field. func ObsoleteTokenNotIn(vs ...string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldObsoleteToken, vs...)) } // ObsoleteTokenGT applies the GT predicate on the "obsolete_token" field. func ObsoleteTokenGT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldObsoleteToken, v)) } // ObsoleteTokenGTE applies the GTE predicate on the "obsolete_token" field. func ObsoleteTokenGTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldObsoleteToken, v)) } // ObsoleteTokenLT applies the LT predicate on the "obsolete_token" field. func ObsoleteTokenLT(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldObsoleteToken, v)) } // ObsoleteTokenLTE applies the LTE predicate on the "obsolete_token" field. func ObsoleteTokenLTE(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldObsoleteToken, v)) } // ObsoleteTokenContains applies the Contains predicate on the "obsolete_token" field. func ObsoleteTokenContains(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContains(FieldObsoleteToken, v)) } // ObsoleteTokenHasPrefix applies the HasPrefix predicate on the "obsolete_token" field. func ObsoleteTokenHasPrefix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasPrefix(FieldObsoleteToken, v)) } // ObsoleteTokenHasSuffix applies the HasSuffix predicate on the "obsolete_token" field. func ObsoleteTokenHasSuffix(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldHasSuffix(FieldObsoleteToken, v)) } // ObsoleteTokenEqualFold applies the EqualFold predicate on the "obsolete_token" field. func ObsoleteTokenEqualFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEqualFold(FieldObsoleteToken, v)) } // ObsoleteTokenContainsFold applies the ContainsFold predicate on the "obsolete_token" field. func ObsoleteTokenContainsFold(v string) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldContainsFold(FieldObsoleteToken, v)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. func CreatedAtNEQ(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. func CreatedAtIn(vs ...time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. func CreatedAtNotIn(vs ...time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. func CreatedAtGT(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. func CreatedAtGTE(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. func CreatedAtLT(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. func CreatedAtLTE(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldCreatedAt, v)) } // LastUsedEQ applies the EQ predicate on the "last_used" field. func LastUsedEQ(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldEQ(FieldLastUsed, v)) } // LastUsedNEQ applies the NEQ predicate on the "last_used" field. func LastUsedNEQ(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNEQ(FieldLastUsed, v)) } // LastUsedIn applies the In predicate on the "last_used" field. func LastUsedIn(vs ...time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldIn(FieldLastUsed, vs...)) } // LastUsedNotIn applies the NotIn predicate on the "last_used" field. func LastUsedNotIn(vs ...time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldNotIn(FieldLastUsed, vs...)) } // LastUsedGT applies the GT predicate on the "last_used" field. func LastUsedGT(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGT(FieldLastUsed, v)) } // LastUsedGTE applies the GTE predicate on the "last_used" field. func LastUsedGTE(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldGTE(FieldLastUsed, v)) } // LastUsedLT applies the LT predicate on the "last_used" field. func LastUsedLT(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLT(FieldLastUsed, v)) } // LastUsedLTE applies the LTE predicate on the "last_used" field. func LastUsedLTE(v time.Time) predicate.RefreshToken { return predicate.RefreshToken(sql.FieldLTE(FieldLastUsed, v)) } // And groups predicates with the AND operator between them. func And(predicates ...predicate.RefreshToken) predicate.RefreshToken { return predicate.RefreshToken(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.RefreshToken) predicate.RefreshToken { return predicate.RefreshToken(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.RefreshToken) predicate.RefreshToken { return predicate.RefreshToken(sql.NotPredicates(p)) } ================================================ FILE: storage/ent/db/refreshtoken.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "encoding/json" "fmt" "strings" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/refreshtoken" ) // RefreshToken is the model entity for the RefreshToken schema. type RefreshToken struct { config `json:"-"` // ID of the ent. ID string `json:"id,omitempty"` // ClientID holds the value of the "client_id" field. ClientID string `json:"client_id,omitempty"` // Scopes holds the value of the "scopes" field. Scopes []string `json:"scopes,omitempty"` // Nonce holds the value of the "nonce" field. Nonce string `json:"nonce,omitempty"` // ClaimsUserID holds the value of the "claims_user_id" field. ClaimsUserID string `json:"claims_user_id,omitempty"` // ClaimsUsername holds the value of the "claims_username" field. ClaimsUsername string `json:"claims_username,omitempty"` // ClaimsEmail holds the value of the "claims_email" field. ClaimsEmail string `json:"claims_email,omitempty"` // ClaimsEmailVerified holds the value of the "claims_email_verified" field. ClaimsEmailVerified bool `json:"claims_email_verified,omitempty"` // ClaimsGroups holds the value of the "claims_groups" field. ClaimsGroups []string `json:"claims_groups,omitempty"` // ClaimsPreferredUsername holds the value of the "claims_preferred_username" field. ClaimsPreferredUsername string `json:"claims_preferred_username,omitempty"` // ConnectorID holds the value of the "connector_id" field. ConnectorID string `json:"connector_id,omitempty"` // ConnectorData holds the value of the "connector_data" field. ConnectorData *[]byte `json:"connector_data,omitempty"` // Token holds the value of the "token" field. Token string `json:"token,omitempty"` // ObsoleteToken holds the value of the "obsolete_token" field. ObsoleteToken string `json:"obsolete_token,omitempty"` // CreatedAt holds the value of the "created_at" field. CreatedAt time.Time `json:"created_at,omitempty"` // LastUsed holds the value of the "last_used" field. LastUsed time.Time `json:"last_used,omitempty"` selectValues sql.SelectValues } // scanValues returns the types for scanning values from sql.Rows. func (*RefreshToken) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { case refreshtoken.FieldScopes, refreshtoken.FieldClaimsGroups, refreshtoken.FieldConnectorData: values[i] = new([]byte) case refreshtoken.FieldClaimsEmailVerified: values[i] = new(sql.NullBool) case refreshtoken.FieldID, refreshtoken.FieldClientID, refreshtoken.FieldNonce, refreshtoken.FieldClaimsUserID, refreshtoken.FieldClaimsUsername, refreshtoken.FieldClaimsEmail, refreshtoken.FieldClaimsPreferredUsername, refreshtoken.FieldConnectorID, refreshtoken.FieldToken, refreshtoken.FieldObsoleteToken: values[i] = new(sql.NullString) case refreshtoken.FieldCreatedAt, refreshtoken.FieldLastUsed: values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } } return values, nil } // assignValues assigns the values that were returned from sql.Rows (after scanning) // to the RefreshToken fields. func (_m *RefreshToken) assignValues(columns []string, values []any) error { if m, n := len(values), len(columns); m < n { return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) } for i := range columns { switch columns[i] { case refreshtoken.FieldID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field id", values[i]) } else if value.Valid { _m.ID = value.String } case refreshtoken.FieldClientID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field client_id", values[i]) } else if value.Valid { _m.ClientID = value.String } case refreshtoken.FieldScopes: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field scopes", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.Scopes); err != nil { return fmt.Errorf("unmarshal field scopes: %w", err) } } case refreshtoken.FieldNonce: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field nonce", values[i]) } else if value.Valid { _m.Nonce = value.String } case refreshtoken.FieldClaimsUserID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_user_id", values[i]) } else if value.Valid { _m.ClaimsUserID = value.String } case refreshtoken.FieldClaimsUsername: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_username", values[i]) } else if value.Valid { _m.ClaimsUsername = value.String } case refreshtoken.FieldClaimsEmail: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_email", values[i]) } else if value.Valid { _m.ClaimsEmail = value.String } case refreshtoken.FieldClaimsEmailVerified: if value, ok := values[i].(*sql.NullBool); !ok { return fmt.Errorf("unexpected type %T for field claims_email_verified", values[i]) } else if value.Valid { _m.ClaimsEmailVerified = value.Bool } case refreshtoken.FieldClaimsGroups: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field claims_groups", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.ClaimsGroups); err != nil { return fmt.Errorf("unmarshal field claims_groups: %w", err) } } case refreshtoken.FieldClaimsPreferredUsername: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_preferred_username", values[i]) } else if value.Valid { _m.ClaimsPreferredUsername = value.String } case refreshtoken.FieldConnectorID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field connector_id", values[i]) } else if value.Valid { _m.ConnectorID = value.String } case refreshtoken.FieldConnectorData: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field connector_data", values[i]) } else if value != nil { _m.ConnectorData = value } case refreshtoken.FieldToken: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field token", values[i]) } else if value.Valid { _m.Token = value.String } case refreshtoken.FieldObsoleteToken: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field obsolete_token", values[i]) } else if value.Valid { _m.ObsoleteToken = value.String } case refreshtoken.FieldCreatedAt: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { _m.CreatedAt = value.Time } case refreshtoken.FieldLastUsed: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field last_used", values[i]) } else if value.Valid { _m.LastUsed = value.Time } default: _m.selectValues.Set(columns[i], values[i]) } } return nil } // Value returns the ent.Value that was dynamically selected and assigned to the RefreshToken. // This includes values selected through modifiers, order, etc. func (_m *RefreshToken) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } // Update returns a builder for updating this RefreshToken. // Note that you need to call RefreshToken.Unwrap() before calling this method if this RefreshToken // was returned from a transaction, and the transaction was committed or rolled back. func (_m *RefreshToken) Update() *RefreshTokenUpdateOne { return NewRefreshTokenClient(_m.config).UpdateOne(_m) } // Unwrap unwraps the RefreshToken entity that was returned from a transaction after it was closed, // so that all future queries will be executed through the driver which created the transaction. func (_m *RefreshToken) Unwrap() *RefreshToken { _tx, ok := _m.config.driver.(*txDriver) if !ok { panic("db: RefreshToken is not a transactional entity") } _m.config.driver = _tx.drv return _m } // String implements the fmt.Stringer. func (_m *RefreshToken) String() string { var builder strings.Builder builder.WriteString("RefreshToken(") builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) builder.WriteString("client_id=") builder.WriteString(_m.ClientID) builder.WriteString(", ") builder.WriteString("scopes=") builder.WriteString(fmt.Sprintf("%v", _m.Scopes)) builder.WriteString(", ") builder.WriteString("nonce=") builder.WriteString(_m.Nonce) builder.WriteString(", ") builder.WriteString("claims_user_id=") builder.WriteString(_m.ClaimsUserID) builder.WriteString(", ") builder.WriteString("claims_username=") builder.WriteString(_m.ClaimsUsername) builder.WriteString(", ") builder.WriteString("claims_email=") builder.WriteString(_m.ClaimsEmail) builder.WriteString(", ") builder.WriteString("claims_email_verified=") builder.WriteString(fmt.Sprintf("%v", _m.ClaimsEmailVerified)) builder.WriteString(", ") builder.WriteString("claims_groups=") builder.WriteString(fmt.Sprintf("%v", _m.ClaimsGroups)) builder.WriteString(", ") builder.WriteString("claims_preferred_username=") builder.WriteString(_m.ClaimsPreferredUsername) builder.WriteString(", ") builder.WriteString("connector_id=") builder.WriteString(_m.ConnectorID) builder.WriteString(", ") if v := _m.ConnectorData; v != nil { builder.WriteString("connector_data=") builder.WriteString(fmt.Sprintf("%v", *v)) } builder.WriteString(", ") builder.WriteString("token=") builder.WriteString(_m.Token) builder.WriteString(", ") builder.WriteString("obsolete_token=") builder.WriteString(_m.ObsoleteToken) builder.WriteString(", ") builder.WriteString("created_at=") builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("last_used=") builder.WriteString(_m.LastUsed.Format(time.ANSIC)) builder.WriteByte(')') return builder.String() } // RefreshTokens is a parsable slice of RefreshToken. type RefreshTokens []*RefreshToken ================================================ FILE: storage/ent/db/refreshtoken_create.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/refreshtoken" ) // RefreshTokenCreate is the builder for creating a RefreshToken entity. type RefreshTokenCreate struct { config mutation *RefreshTokenMutation hooks []Hook } // SetClientID sets the "client_id" field. func (_c *RefreshTokenCreate) SetClientID(v string) *RefreshTokenCreate { _c.mutation.SetClientID(v) return _c } // SetScopes sets the "scopes" field. func (_c *RefreshTokenCreate) SetScopes(v []string) *RefreshTokenCreate { _c.mutation.SetScopes(v) return _c } // SetNonce sets the "nonce" field. func (_c *RefreshTokenCreate) SetNonce(v string) *RefreshTokenCreate { _c.mutation.SetNonce(v) return _c } // SetClaimsUserID sets the "claims_user_id" field. func (_c *RefreshTokenCreate) SetClaimsUserID(v string) *RefreshTokenCreate { _c.mutation.SetClaimsUserID(v) return _c } // SetClaimsUsername sets the "claims_username" field. func (_c *RefreshTokenCreate) SetClaimsUsername(v string) *RefreshTokenCreate { _c.mutation.SetClaimsUsername(v) return _c } // SetClaimsEmail sets the "claims_email" field. func (_c *RefreshTokenCreate) SetClaimsEmail(v string) *RefreshTokenCreate { _c.mutation.SetClaimsEmail(v) return _c } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (_c *RefreshTokenCreate) SetClaimsEmailVerified(v bool) *RefreshTokenCreate { _c.mutation.SetClaimsEmailVerified(v) return _c } // SetClaimsGroups sets the "claims_groups" field. func (_c *RefreshTokenCreate) SetClaimsGroups(v []string) *RefreshTokenCreate { _c.mutation.SetClaimsGroups(v) return _c } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (_c *RefreshTokenCreate) SetClaimsPreferredUsername(v string) *RefreshTokenCreate { _c.mutation.SetClaimsPreferredUsername(v) return _c } // SetNillableClaimsPreferredUsername sets the "claims_preferred_username" field if the given value is not nil. func (_c *RefreshTokenCreate) SetNillableClaimsPreferredUsername(v *string) *RefreshTokenCreate { if v != nil { _c.SetClaimsPreferredUsername(*v) } return _c } // SetConnectorID sets the "connector_id" field. func (_c *RefreshTokenCreate) SetConnectorID(v string) *RefreshTokenCreate { _c.mutation.SetConnectorID(v) return _c } // SetConnectorData sets the "connector_data" field. func (_c *RefreshTokenCreate) SetConnectorData(v []byte) *RefreshTokenCreate { _c.mutation.SetConnectorData(v) return _c } // SetToken sets the "token" field. func (_c *RefreshTokenCreate) SetToken(v string) *RefreshTokenCreate { _c.mutation.SetToken(v) return _c } // SetNillableToken sets the "token" field if the given value is not nil. func (_c *RefreshTokenCreate) SetNillableToken(v *string) *RefreshTokenCreate { if v != nil { _c.SetToken(*v) } return _c } // SetObsoleteToken sets the "obsolete_token" field. func (_c *RefreshTokenCreate) SetObsoleteToken(v string) *RefreshTokenCreate { _c.mutation.SetObsoleteToken(v) return _c } // SetNillableObsoleteToken sets the "obsolete_token" field if the given value is not nil. func (_c *RefreshTokenCreate) SetNillableObsoleteToken(v *string) *RefreshTokenCreate { if v != nil { _c.SetObsoleteToken(*v) } return _c } // SetCreatedAt sets the "created_at" field. func (_c *RefreshTokenCreate) SetCreatedAt(v time.Time) *RefreshTokenCreate { _c.mutation.SetCreatedAt(v) return _c } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. func (_c *RefreshTokenCreate) SetNillableCreatedAt(v *time.Time) *RefreshTokenCreate { if v != nil { _c.SetCreatedAt(*v) } return _c } // SetLastUsed sets the "last_used" field. func (_c *RefreshTokenCreate) SetLastUsed(v time.Time) *RefreshTokenCreate { _c.mutation.SetLastUsed(v) return _c } // SetNillableLastUsed sets the "last_used" field if the given value is not nil. func (_c *RefreshTokenCreate) SetNillableLastUsed(v *time.Time) *RefreshTokenCreate { if v != nil { _c.SetLastUsed(*v) } return _c } // SetID sets the "id" field. func (_c *RefreshTokenCreate) SetID(v string) *RefreshTokenCreate { _c.mutation.SetID(v) return _c } // Mutation returns the RefreshTokenMutation object of the builder. func (_c *RefreshTokenCreate) Mutation() *RefreshTokenMutation { return _c.mutation } // Save creates the RefreshToken in the database. func (_c *RefreshTokenCreate) Save(ctx context.Context) (*RefreshToken, error) { _c.defaults() return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) } // SaveX calls Save and panics if Save returns an error. func (_c *RefreshTokenCreate) SaveX(ctx context.Context) *RefreshToken { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *RefreshTokenCreate) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *RefreshTokenCreate) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } // defaults sets the default values of the builder before save. func (_c *RefreshTokenCreate) defaults() { if _, ok := _c.mutation.ClaimsPreferredUsername(); !ok { v := refreshtoken.DefaultClaimsPreferredUsername _c.mutation.SetClaimsPreferredUsername(v) } if _, ok := _c.mutation.Token(); !ok { v := refreshtoken.DefaultToken _c.mutation.SetToken(v) } if _, ok := _c.mutation.ObsoleteToken(); !ok { v := refreshtoken.DefaultObsoleteToken _c.mutation.SetObsoleteToken(v) } if _, ok := _c.mutation.CreatedAt(); !ok { v := refreshtoken.DefaultCreatedAt() _c.mutation.SetCreatedAt(v) } if _, ok := _c.mutation.LastUsed(); !ok { v := refreshtoken.DefaultLastUsed() _c.mutation.SetLastUsed(v) } } // check runs all checks and user-defined validators on the builder. func (_c *RefreshTokenCreate) check() error { if _, ok := _c.mutation.ClientID(); !ok { return &ValidationError{Name: "client_id", err: errors.New(`db: missing required field "RefreshToken.client_id"`)} } if v, ok := _c.mutation.ClientID(); ok { if err := refreshtoken.ClientIDValidator(v); err != nil { return &ValidationError{Name: "client_id", err: fmt.Errorf(`db: validator failed for field "RefreshToken.client_id": %w`, err)} } } if _, ok := _c.mutation.Nonce(); !ok { return &ValidationError{Name: "nonce", err: errors.New(`db: missing required field "RefreshToken.nonce"`)} } if v, ok := _c.mutation.Nonce(); ok { if err := refreshtoken.NonceValidator(v); err != nil { return &ValidationError{Name: "nonce", err: fmt.Errorf(`db: validator failed for field "RefreshToken.nonce": %w`, err)} } } if _, ok := _c.mutation.ClaimsUserID(); !ok { return &ValidationError{Name: "claims_user_id", err: errors.New(`db: missing required field "RefreshToken.claims_user_id"`)} } if v, ok := _c.mutation.ClaimsUserID(); ok { if err := refreshtoken.ClaimsUserIDValidator(v); err != nil { return &ValidationError{Name: "claims_user_id", err: fmt.Errorf(`db: validator failed for field "RefreshToken.claims_user_id": %w`, err)} } } if _, ok := _c.mutation.ClaimsUsername(); !ok { return &ValidationError{Name: "claims_username", err: errors.New(`db: missing required field "RefreshToken.claims_username"`)} } if v, ok := _c.mutation.ClaimsUsername(); ok { if err := refreshtoken.ClaimsUsernameValidator(v); err != nil { return &ValidationError{Name: "claims_username", err: fmt.Errorf(`db: validator failed for field "RefreshToken.claims_username": %w`, err)} } } if _, ok := _c.mutation.ClaimsEmail(); !ok { return &ValidationError{Name: "claims_email", err: errors.New(`db: missing required field "RefreshToken.claims_email"`)} } if v, ok := _c.mutation.ClaimsEmail(); ok { if err := refreshtoken.ClaimsEmailValidator(v); err != nil { return &ValidationError{Name: "claims_email", err: fmt.Errorf(`db: validator failed for field "RefreshToken.claims_email": %w`, err)} } } if _, ok := _c.mutation.ClaimsEmailVerified(); !ok { return &ValidationError{Name: "claims_email_verified", err: errors.New(`db: missing required field "RefreshToken.claims_email_verified"`)} } if _, ok := _c.mutation.ClaimsPreferredUsername(); !ok { return &ValidationError{Name: "claims_preferred_username", err: errors.New(`db: missing required field "RefreshToken.claims_preferred_username"`)} } if _, ok := _c.mutation.ConnectorID(); !ok { return &ValidationError{Name: "connector_id", err: errors.New(`db: missing required field "RefreshToken.connector_id"`)} } if v, ok := _c.mutation.ConnectorID(); ok { if err := refreshtoken.ConnectorIDValidator(v); err != nil { return &ValidationError{Name: "connector_id", err: fmt.Errorf(`db: validator failed for field "RefreshToken.connector_id": %w`, err)} } } if _, ok := _c.mutation.Token(); !ok { return &ValidationError{Name: "token", err: errors.New(`db: missing required field "RefreshToken.token"`)} } if _, ok := _c.mutation.ObsoleteToken(); !ok { return &ValidationError{Name: "obsolete_token", err: errors.New(`db: missing required field "RefreshToken.obsolete_token"`)} } if _, ok := _c.mutation.CreatedAt(); !ok { return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "RefreshToken.created_at"`)} } if _, ok := _c.mutation.LastUsed(); !ok { return &ValidationError{Name: "last_used", err: errors.New(`db: missing required field "RefreshToken.last_used"`)} } if v, ok := _c.mutation.ID(); ok { if err := refreshtoken.IDValidator(v); err != nil { return &ValidationError{Name: "id", err: fmt.Errorf(`db: validator failed for field "RefreshToken.id": %w`, err)} } } return nil } func (_c *RefreshTokenCreate) sqlSave(ctx context.Context) (*RefreshToken, error) { if err := _c.check(); err != nil { return nil, err } _node, _spec := _c.createSpec() if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } if _spec.ID.Value != nil { if id, ok := _spec.ID.Value.(string); ok { _node.ID = id } else { return nil, fmt.Errorf("unexpected RefreshToken.ID type: %T", _spec.ID.Value) } } _c.mutation.id = &_node.ID _c.mutation.done = true return _node, nil } func (_c *RefreshTokenCreate) createSpec() (*RefreshToken, *sqlgraph.CreateSpec) { var ( _node = &RefreshToken{config: _c.config} _spec = sqlgraph.NewCreateSpec(refreshtoken.Table, sqlgraph.NewFieldSpec(refreshtoken.FieldID, field.TypeString)) ) if id, ok := _c.mutation.ID(); ok { _node.ID = id _spec.ID.Value = id } if value, ok := _c.mutation.ClientID(); ok { _spec.SetField(refreshtoken.FieldClientID, field.TypeString, value) _node.ClientID = value } if value, ok := _c.mutation.Scopes(); ok { _spec.SetField(refreshtoken.FieldScopes, field.TypeJSON, value) _node.Scopes = value } if value, ok := _c.mutation.Nonce(); ok { _spec.SetField(refreshtoken.FieldNonce, field.TypeString, value) _node.Nonce = value } if value, ok := _c.mutation.ClaimsUserID(); ok { _spec.SetField(refreshtoken.FieldClaimsUserID, field.TypeString, value) _node.ClaimsUserID = value } if value, ok := _c.mutation.ClaimsUsername(); ok { _spec.SetField(refreshtoken.FieldClaimsUsername, field.TypeString, value) _node.ClaimsUsername = value } if value, ok := _c.mutation.ClaimsEmail(); ok { _spec.SetField(refreshtoken.FieldClaimsEmail, field.TypeString, value) _node.ClaimsEmail = value } if value, ok := _c.mutation.ClaimsEmailVerified(); ok { _spec.SetField(refreshtoken.FieldClaimsEmailVerified, field.TypeBool, value) _node.ClaimsEmailVerified = value } if value, ok := _c.mutation.ClaimsGroups(); ok { _spec.SetField(refreshtoken.FieldClaimsGroups, field.TypeJSON, value) _node.ClaimsGroups = value } if value, ok := _c.mutation.ClaimsPreferredUsername(); ok { _spec.SetField(refreshtoken.FieldClaimsPreferredUsername, field.TypeString, value) _node.ClaimsPreferredUsername = value } if value, ok := _c.mutation.ConnectorID(); ok { _spec.SetField(refreshtoken.FieldConnectorID, field.TypeString, value) _node.ConnectorID = value } if value, ok := _c.mutation.ConnectorData(); ok { _spec.SetField(refreshtoken.FieldConnectorData, field.TypeBytes, value) _node.ConnectorData = &value } if value, ok := _c.mutation.Token(); ok { _spec.SetField(refreshtoken.FieldToken, field.TypeString, value) _node.Token = value } if value, ok := _c.mutation.ObsoleteToken(); ok { _spec.SetField(refreshtoken.FieldObsoleteToken, field.TypeString, value) _node.ObsoleteToken = value } if value, ok := _c.mutation.CreatedAt(); ok { _spec.SetField(refreshtoken.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := _c.mutation.LastUsed(); ok { _spec.SetField(refreshtoken.FieldLastUsed, field.TypeTime, value) _node.LastUsed = value } return _node, _spec } // RefreshTokenCreateBulk is the builder for creating many RefreshToken entities in bulk. type RefreshTokenCreateBulk struct { config err error builders []*RefreshTokenCreate } // Save creates the RefreshToken entities in the database. func (_c *RefreshTokenCreateBulk) Save(ctx context.Context) ([]*RefreshToken, error) { if _c.err != nil { return nil, _c.err } specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) nodes := make([]*RefreshToken, len(_c.builders)) mutators := make([]Mutator, len(_c.builders)) for i := range _c.builders { func(i int, root context.Context) { builder := _c.builders[i] builder.defaults() var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*RefreshTokenMutation) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } if err := builder.check(); err != nil { return nil, err } builder.mutation = mutation var err error nodes[i], specs[i] = builder.createSpec() if i < len(mutators)-1 { _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) } else { spec := &sqlgraph.BatchCreateSpec{Nodes: specs} // Invoke the actual operation on the latest mutation in the chain. if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } } } if err != nil { return nil, err } mutation.id = &nodes[i].ID mutation.done = true return nodes[i], nil }) for i := len(builder.hooks) - 1; i >= 0; i-- { mut = builder.hooks[i](mut) } mutators[i] = mut }(i, ctx) } if len(mutators) > 0 { if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { return nil, err } } return nodes, nil } // SaveX is like Save, but panics if an error occurs. func (_c *RefreshTokenCreateBulk) SaveX(ctx context.Context) []*RefreshToken { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *RefreshTokenCreateBulk) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *RefreshTokenCreateBulk) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/refreshtoken_delete.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/predicate" "github.com/dexidp/dex/storage/ent/db/refreshtoken" ) // RefreshTokenDelete is the builder for deleting a RefreshToken entity. type RefreshTokenDelete struct { config hooks []Hook mutation *RefreshTokenMutation } // Where appends a list predicates to the RefreshTokenDelete builder. func (_d *RefreshTokenDelete) Where(ps ...predicate.RefreshToken) *RefreshTokenDelete { _d.mutation.Where(ps...) return _d } // Exec executes the deletion query and returns how many vertices were deleted. func (_d *RefreshTokenDelete) Exec(ctx context.Context) (int, error) { return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) } // ExecX is like Exec, but panics if an error occurs. func (_d *RefreshTokenDelete) ExecX(ctx context.Context) int { n, err := _d.Exec(ctx) if err != nil { panic(err) } return n } func (_d *RefreshTokenDelete) sqlExec(ctx context.Context) (int, error) { _spec := sqlgraph.NewDeleteSpec(refreshtoken.Table, sqlgraph.NewFieldSpec(refreshtoken.FieldID, field.TypeString)) if ps := _d.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) if err != nil && sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } _d.mutation.done = true return affected, err } // RefreshTokenDeleteOne is the builder for deleting a single RefreshToken entity. type RefreshTokenDeleteOne struct { _d *RefreshTokenDelete } // Where appends a list predicates to the RefreshTokenDelete builder. func (_d *RefreshTokenDeleteOne) Where(ps ...predicate.RefreshToken) *RefreshTokenDeleteOne { _d._d.mutation.Where(ps...) return _d } // Exec executes the deletion query. func (_d *RefreshTokenDeleteOne) Exec(ctx context.Context) error { n, err := _d._d.Exec(ctx) switch { case err != nil: return err case n == 0: return &NotFoundError{refreshtoken.Label} default: return nil } } // ExecX is like Exec, but panics if an error occurs. func (_d *RefreshTokenDeleteOne) ExecX(ctx context.Context) { if err := _d.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/refreshtoken_query.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "fmt" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/predicate" "github.com/dexidp/dex/storage/ent/db/refreshtoken" ) // RefreshTokenQuery is the builder for querying RefreshToken entities. type RefreshTokenQuery struct { config ctx *QueryContext order []refreshtoken.OrderOption inters []Interceptor predicates []predicate.RefreshToken // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) } // Where adds a new predicate for the RefreshTokenQuery builder. func (_q *RefreshTokenQuery) Where(ps ...predicate.RefreshToken) *RefreshTokenQuery { _q.predicates = append(_q.predicates, ps...) return _q } // Limit the number of records to be returned by this query. func (_q *RefreshTokenQuery) Limit(limit int) *RefreshTokenQuery { _q.ctx.Limit = &limit return _q } // Offset to start from. func (_q *RefreshTokenQuery) Offset(offset int) *RefreshTokenQuery { _q.ctx.Offset = &offset return _q } // Unique configures the query builder to filter duplicate records on query. // By default, unique is set to true, and can be disabled using this method. func (_q *RefreshTokenQuery) Unique(unique bool) *RefreshTokenQuery { _q.ctx.Unique = &unique return _q } // Order specifies how the records should be ordered. func (_q *RefreshTokenQuery) Order(o ...refreshtoken.OrderOption) *RefreshTokenQuery { _q.order = append(_q.order, o...) return _q } // First returns the first RefreshToken entity from the query. // Returns a *NotFoundError when no RefreshToken was found. func (_q *RefreshTokenQuery) First(ctx context.Context) (*RefreshToken, error) { nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) if err != nil { return nil, err } if len(nodes) == 0 { return nil, &NotFoundError{refreshtoken.Label} } return nodes[0], nil } // FirstX is like First, but panics if an error occurs. func (_q *RefreshTokenQuery) FirstX(ctx context.Context) *RefreshToken { node, err := _q.First(ctx) if err != nil && !IsNotFound(err) { panic(err) } return node } // FirstID returns the first RefreshToken ID from the query. // Returns a *NotFoundError when no RefreshToken ID was found. func (_q *RefreshTokenQuery) FirstID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { return } if len(ids) == 0 { err = &NotFoundError{refreshtoken.Label} return } return ids[0], nil } // FirstIDX is like FirstID, but panics if an error occurs. func (_q *RefreshTokenQuery) FirstIDX(ctx context.Context) string { id, err := _q.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) } return id } // Only returns a single RefreshToken entity found by the query, ensuring it only returns one. // Returns a *NotSingularError when more than one RefreshToken entity is found. // Returns a *NotFoundError when no RefreshToken entities are found. func (_q *RefreshTokenQuery) Only(ctx context.Context) (*RefreshToken, error) { nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) if err != nil { return nil, err } switch len(nodes) { case 1: return nodes[0], nil case 0: return nil, &NotFoundError{refreshtoken.Label} default: return nil, &NotSingularError{refreshtoken.Label} } } // OnlyX is like Only, but panics if an error occurs. func (_q *RefreshTokenQuery) OnlyX(ctx context.Context) *RefreshToken { node, err := _q.Only(ctx) if err != nil { panic(err) } return node } // OnlyID is like Only, but returns the only RefreshToken ID in the query. // Returns a *NotSingularError when more than one RefreshToken ID is found. // Returns a *NotFoundError when no entities are found. func (_q *RefreshTokenQuery) OnlyID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { return } switch len(ids) { case 1: id = ids[0] case 0: err = &NotFoundError{refreshtoken.Label} default: err = &NotSingularError{refreshtoken.Label} } return } // OnlyIDX is like OnlyID, but panics if an error occurs. func (_q *RefreshTokenQuery) OnlyIDX(ctx context.Context) string { id, err := _q.OnlyID(ctx) if err != nil { panic(err) } return id } // All executes the query and returns a list of RefreshTokens. func (_q *RefreshTokenQuery) All(ctx context.Context) ([]*RefreshToken, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) if err := _q.prepareQuery(ctx); err != nil { return nil, err } qr := querierAll[[]*RefreshToken, *RefreshTokenQuery]() return withInterceptors[[]*RefreshToken](ctx, _q, qr, _q.inters) } // AllX is like All, but panics if an error occurs. func (_q *RefreshTokenQuery) AllX(ctx context.Context) []*RefreshToken { nodes, err := _q.All(ctx) if err != nil { panic(err) } return nodes } // IDs executes the query and returns a list of RefreshToken IDs. func (_q *RefreshTokenQuery) IDs(ctx context.Context) (ids []string, err error) { if _q.ctx.Unique == nil && _q.path != nil { _q.Unique(true) } ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) if err = _q.Select(refreshtoken.FieldID).Scan(ctx, &ids); err != nil { return nil, err } return ids, nil } // IDsX is like IDs, but panics if an error occurs. func (_q *RefreshTokenQuery) IDsX(ctx context.Context) []string { ids, err := _q.IDs(ctx) if err != nil { panic(err) } return ids } // Count returns the count of the given query. func (_q *RefreshTokenQuery) Count(ctx context.Context) (int, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) if err := _q.prepareQuery(ctx); err != nil { return 0, err } return withInterceptors[int](ctx, _q, querierCount[*RefreshTokenQuery](), _q.inters) } // CountX is like Count, but panics if an error occurs. func (_q *RefreshTokenQuery) CountX(ctx context.Context) int { count, err := _q.Count(ctx) if err != nil { panic(err) } return count } // Exist returns true if the query has elements in the graph. func (_q *RefreshTokenQuery) Exist(ctx context.Context) (bool, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) switch _, err := _q.FirstID(ctx); { case IsNotFound(err): return false, nil case err != nil: return false, fmt.Errorf("db: check existence: %w", err) default: return true, nil } } // ExistX is like Exist, but panics if an error occurs. func (_q *RefreshTokenQuery) ExistX(ctx context.Context) bool { exist, err := _q.Exist(ctx) if err != nil { panic(err) } return exist } // Clone returns a duplicate of the RefreshTokenQuery builder, including all associated steps. It can be // used to prepare common query builders and use them differently after the clone is made. func (_q *RefreshTokenQuery) Clone() *RefreshTokenQuery { if _q == nil { return nil } return &RefreshTokenQuery{ config: _q.config, ctx: _q.ctx.Clone(), order: append([]refreshtoken.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.RefreshToken{}, _q.predicates...), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // // Example: // // var v []struct { // ClientID string `json:"client_id,omitempty"` // Count int `json:"count,omitempty"` // } // // client.RefreshToken.Query(). // GroupBy(refreshtoken.FieldClientID). // Aggregate(db.Count()). // Scan(ctx, &v) func (_q *RefreshTokenQuery) GroupBy(field string, fields ...string) *RefreshTokenGroupBy { _q.ctx.Fields = append([]string{field}, fields...) grbuild := &RefreshTokenGroupBy{build: _q} grbuild.flds = &_q.ctx.Fields grbuild.label = refreshtoken.Label grbuild.scan = grbuild.Scan return grbuild } // Select allows the selection one or more fields/columns for the given query, // instead of selecting all fields in the entity. // // Example: // // var v []struct { // ClientID string `json:"client_id,omitempty"` // } // // client.RefreshToken.Query(). // Select(refreshtoken.FieldClientID). // Scan(ctx, &v) func (_q *RefreshTokenQuery) Select(fields ...string) *RefreshTokenSelect { _q.ctx.Fields = append(_q.ctx.Fields, fields...) sbuild := &RefreshTokenSelect{RefreshTokenQuery: _q} sbuild.label = refreshtoken.Label sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan return sbuild } // Aggregate returns a RefreshTokenSelect configured with the given aggregations. func (_q *RefreshTokenQuery) Aggregate(fns ...AggregateFunc) *RefreshTokenSelect { return _q.Select().Aggregate(fns...) } func (_q *RefreshTokenQuery) prepareQuery(ctx context.Context) error { for _, inter := range _q.inters { if inter == nil { return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") } if trv, ok := inter.(Traverser); ok { if err := trv.Traverse(ctx, _q); err != nil { return err } } } for _, f := range _q.ctx.Fields { if !refreshtoken.ValidColumn(f) { return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } } if _q.path != nil { prev, err := _q.path(ctx) if err != nil { return err } _q.sql = prev } return nil } func (_q *RefreshTokenQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*RefreshToken, error) { var ( nodes = []*RefreshToken{} _spec = _q.querySpec() ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*RefreshToken).scanValues(nil, columns) } _spec.Assign = func(columns []string, values []any) error { node := &RefreshToken{config: _q.config} nodes = append(nodes, node) return node.assignValues(columns, values) } for i := range hooks { hooks[i](ctx, _spec) } if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { return nil, err } if len(nodes) == 0 { return nodes, nil } return nodes, nil } func (_q *RefreshTokenQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields if len(_q.ctx.Fields) > 0 { _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique } return sqlgraph.CountNodes(ctx, _q.driver, _spec) } func (_q *RefreshTokenQuery) querySpec() *sqlgraph.QuerySpec { _spec := sqlgraph.NewQuerySpec(refreshtoken.Table, refreshtoken.Columns, sqlgraph.NewFieldSpec(refreshtoken.FieldID, field.TypeString)) _spec.From = _q.sql if unique := _q.ctx.Unique; unique != nil { _spec.Unique = *unique } else if _q.path != nil { _spec.Unique = true } if fields := _q.ctx.Fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, refreshtoken.FieldID) for i := range fields { if fields[i] != refreshtoken.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } } if ps := _q.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if limit := _q.ctx.Limit; limit != nil { _spec.Limit = *limit } if offset := _q.ctx.Offset; offset != nil { _spec.Offset = *offset } if ps := _q.order; len(ps) > 0 { _spec.Order = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } return _spec } func (_q *RefreshTokenQuery) sqlQuery(ctx context.Context) *sql.Selector { builder := sql.Dialect(_q.driver.Dialect()) t1 := builder.Table(refreshtoken.Table) columns := _q.ctx.Fields if len(columns) == 0 { columns = refreshtoken.Columns } selector := builder.Select(t1.Columns(columns...)...).From(t1) if _q.sql != nil { selector = _q.sql selector.Select(selector.Columns(columns...)...) } if _q.ctx.Unique != nil && *_q.ctx.Unique { selector.Distinct() } for _, p := range _q.predicates { p(selector) } for _, p := range _q.order { p(selector) } if offset := _q.ctx.Offset; offset != nil { // limit is mandatory for offset clause. We start // with default value, and override it below if needed. selector.Offset(*offset).Limit(math.MaxInt32) } if limit := _q.ctx.Limit; limit != nil { selector.Limit(*limit) } return selector } // RefreshTokenGroupBy is the group-by builder for RefreshToken entities. type RefreshTokenGroupBy struct { selector build *RefreshTokenQuery } // Aggregate adds the given aggregation functions to the group-by query. func (_g *RefreshTokenGroupBy) Aggregate(fns ...AggregateFunc) *RefreshTokenGroupBy { _g.fns = append(_g.fns, fns...) return _g } // Scan applies the selector query and scans the result into the given value. func (_g *RefreshTokenGroupBy) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) if err := _g.build.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*RefreshTokenQuery, *RefreshTokenGroupBy](ctx, _g.build, _g, _g.build.inters, v) } func (_g *RefreshTokenGroupBy) sqlScan(ctx context.Context, root *RefreshTokenQuery, v any) error { selector := root.sqlQuery(ctx).Select() aggregation := make([]string, 0, len(_g.fns)) for _, fn := range _g.fns { aggregation = append(aggregation, fn(selector)) } if len(selector.SelectedColumns()) == 0 { columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) for _, f := range *_g.flds { columns = append(columns, selector.C(f)) } columns = append(columns, aggregation...) selector.Select(columns...) } selector.GroupBy(selector.Columns(*_g.flds...)...) if err := selector.Err(); err != nil { return err } rows := &sql.Rows{} query, args := selector.Query() if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } // RefreshTokenSelect is the builder for selecting fields of RefreshToken entities. type RefreshTokenSelect struct { *RefreshTokenQuery selector } // Aggregate adds the given aggregation functions to the selector query. func (_s *RefreshTokenSelect) Aggregate(fns ...AggregateFunc) *RefreshTokenSelect { _s.fns = append(_s.fns, fns...) return _s } // Scan applies the selector query and scans the result into the given value. func (_s *RefreshTokenSelect) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) if err := _s.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*RefreshTokenQuery, *RefreshTokenSelect](ctx, _s.RefreshTokenQuery, _s, _s.inters, v) } func (_s *RefreshTokenSelect) sqlScan(ctx context.Context, root *RefreshTokenQuery, v any) error { selector := root.sqlQuery(ctx) aggregation := make([]string, 0, len(_s.fns)) for _, fn := range _s.fns { aggregation = append(aggregation, fn(selector)) } switch n := len(*_s.selector.flds); { case n == 0 && len(aggregation) > 0: selector.Select(aggregation...) case n != 0 && len(aggregation) > 0: selector.AppendSelect(aggregation...) } rows := &sql.Rows{} query, args := selector.Query() if err := _s.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } ================================================ FILE: storage/ent/db/refreshtoken_update.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/predicate" "github.com/dexidp/dex/storage/ent/db/refreshtoken" ) // RefreshTokenUpdate is the builder for updating RefreshToken entities. type RefreshTokenUpdate struct { config hooks []Hook mutation *RefreshTokenMutation } // Where appends a list predicates to the RefreshTokenUpdate builder. func (_u *RefreshTokenUpdate) Where(ps ...predicate.RefreshToken) *RefreshTokenUpdate { _u.mutation.Where(ps...) return _u } // SetClientID sets the "client_id" field. func (_u *RefreshTokenUpdate) SetClientID(v string) *RefreshTokenUpdate { _u.mutation.SetClientID(v) return _u } // SetNillableClientID sets the "client_id" field if the given value is not nil. func (_u *RefreshTokenUpdate) SetNillableClientID(v *string) *RefreshTokenUpdate { if v != nil { _u.SetClientID(*v) } return _u } // SetScopes sets the "scopes" field. func (_u *RefreshTokenUpdate) SetScopes(v []string) *RefreshTokenUpdate { _u.mutation.SetScopes(v) return _u } // AppendScopes appends value to the "scopes" field. func (_u *RefreshTokenUpdate) AppendScopes(v []string) *RefreshTokenUpdate { _u.mutation.AppendScopes(v) return _u } // ClearScopes clears the value of the "scopes" field. func (_u *RefreshTokenUpdate) ClearScopes() *RefreshTokenUpdate { _u.mutation.ClearScopes() return _u } // SetNonce sets the "nonce" field. func (_u *RefreshTokenUpdate) SetNonce(v string) *RefreshTokenUpdate { _u.mutation.SetNonce(v) return _u } // SetNillableNonce sets the "nonce" field if the given value is not nil. func (_u *RefreshTokenUpdate) SetNillableNonce(v *string) *RefreshTokenUpdate { if v != nil { _u.SetNonce(*v) } return _u } // SetClaimsUserID sets the "claims_user_id" field. func (_u *RefreshTokenUpdate) SetClaimsUserID(v string) *RefreshTokenUpdate { _u.mutation.SetClaimsUserID(v) return _u } // SetNillableClaimsUserID sets the "claims_user_id" field if the given value is not nil. func (_u *RefreshTokenUpdate) SetNillableClaimsUserID(v *string) *RefreshTokenUpdate { if v != nil { _u.SetClaimsUserID(*v) } return _u } // SetClaimsUsername sets the "claims_username" field. func (_u *RefreshTokenUpdate) SetClaimsUsername(v string) *RefreshTokenUpdate { _u.mutation.SetClaimsUsername(v) return _u } // SetNillableClaimsUsername sets the "claims_username" field if the given value is not nil. func (_u *RefreshTokenUpdate) SetNillableClaimsUsername(v *string) *RefreshTokenUpdate { if v != nil { _u.SetClaimsUsername(*v) } return _u } // SetClaimsEmail sets the "claims_email" field. func (_u *RefreshTokenUpdate) SetClaimsEmail(v string) *RefreshTokenUpdate { _u.mutation.SetClaimsEmail(v) return _u } // SetNillableClaimsEmail sets the "claims_email" field if the given value is not nil. func (_u *RefreshTokenUpdate) SetNillableClaimsEmail(v *string) *RefreshTokenUpdate { if v != nil { _u.SetClaimsEmail(*v) } return _u } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (_u *RefreshTokenUpdate) SetClaimsEmailVerified(v bool) *RefreshTokenUpdate { _u.mutation.SetClaimsEmailVerified(v) return _u } // SetNillableClaimsEmailVerified sets the "claims_email_verified" field if the given value is not nil. func (_u *RefreshTokenUpdate) SetNillableClaimsEmailVerified(v *bool) *RefreshTokenUpdate { if v != nil { _u.SetClaimsEmailVerified(*v) } return _u } // SetClaimsGroups sets the "claims_groups" field. func (_u *RefreshTokenUpdate) SetClaimsGroups(v []string) *RefreshTokenUpdate { _u.mutation.SetClaimsGroups(v) return _u } // AppendClaimsGroups appends value to the "claims_groups" field. func (_u *RefreshTokenUpdate) AppendClaimsGroups(v []string) *RefreshTokenUpdate { _u.mutation.AppendClaimsGroups(v) return _u } // ClearClaimsGroups clears the value of the "claims_groups" field. func (_u *RefreshTokenUpdate) ClearClaimsGroups() *RefreshTokenUpdate { _u.mutation.ClearClaimsGroups() return _u } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (_u *RefreshTokenUpdate) SetClaimsPreferredUsername(v string) *RefreshTokenUpdate { _u.mutation.SetClaimsPreferredUsername(v) return _u } // SetNillableClaimsPreferredUsername sets the "claims_preferred_username" field if the given value is not nil. func (_u *RefreshTokenUpdate) SetNillableClaimsPreferredUsername(v *string) *RefreshTokenUpdate { if v != nil { _u.SetClaimsPreferredUsername(*v) } return _u } // SetConnectorID sets the "connector_id" field. func (_u *RefreshTokenUpdate) SetConnectorID(v string) *RefreshTokenUpdate { _u.mutation.SetConnectorID(v) return _u } // SetNillableConnectorID sets the "connector_id" field if the given value is not nil. func (_u *RefreshTokenUpdate) SetNillableConnectorID(v *string) *RefreshTokenUpdate { if v != nil { _u.SetConnectorID(*v) } return _u } // SetConnectorData sets the "connector_data" field. func (_u *RefreshTokenUpdate) SetConnectorData(v []byte) *RefreshTokenUpdate { _u.mutation.SetConnectorData(v) return _u } // ClearConnectorData clears the value of the "connector_data" field. func (_u *RefreshTokenUpdate) ClearConnectorData() *RefreshTokenUpdate { _u.mutation.ClearConnectorData() return _u } // SetToken sets the "token" field. func (_u *RefreshTokenUpdate) SetToken(v string) *RefreshTokenUpdate { _u.mutation.SetToken(v) return _u } // SetNillableToken sets the "token" field if the given value is not nil. func (_u *RefreshTokenUpdate) SetNillableToken(v *string) *RefreshTokenUpdate { if v != nil { _u.SetToken(*v) } return _u } // SetObsoleteToken sets the "obsolete_token" field. func (_u *RefreshTokenUpdate) SetObsoleteToken(v string) *RefreshTokenUpdate { _u.mutation.SetObsoleteToken(v) return _u } // SetNillableObsoleteToken sets the "obsolete_token" field if the given value is not nil. func (_u *RefreshTokenUpdate) SetNillableObsoleteToken(v *string) *RefreshTokenUpdate { if v != nil { _u.SetObsoleteToken(*v) } return _u } // SetCreatedAt sets the "created_at" field. func (_u *RefreshTokenUpdate) SetCreatedAt(v time.Time) *RefreshTokenUpdate { _u.mutation.SetCreatedAt(v) return _u } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. func (_u *RefreshTokenUpdate) SetNillableCreatedAt(v *time.Time) *RefreshTokenUpdate { if v != nil { _u.SetCreatedAt(*v) } return _u } // SetLastUsed sets the "last_used" field. func (_u *RefreshTokenUpdate) SetLastUsed(v time.Time) *RefreshTokenUpdate { _u.mutation.SetLastUsed(v) return _u } // SetNillableLastUsed sets the "last_used" field if the given value is not nil. func (_u *RefreshTokenUpdate) SetNillableLastUsed(v *time.Time) *RefreshTokenUpdate { if v != nil { _u.SetLastUsed(*v) } return _u } // Mutation returns the RefreshTokenMutation object of the builder. func (_u *RefreshTokenUpdate) Mutation() *RefreshTokenMutation { return _u.mutation } // Save executes the query and returns the number of nodes affected by the update operation. func (_u *RefreshTokenUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *RefreshTokenUpdate) SaveX(ctx context.Context) int { affected, err := _u.Save(ctx) if err != nil { panic(err) } return affected } // Exec executes the query. func (_u *RefreshTokenUpdate) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *RefreshTokenUpdate) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *RefreshTokenUpdate) check() error { if v, ok := _u.mutation.ClientID(); ok { if err := refreshtoken.ClientIDValidator(v); err != nil { return &ValidationError{Name: "client_id", err: fmt.Errorf(`db: validator failed for field "RefreshToken.client_id": %w`, err)} } } if v, ok := _u.mutation.Nonce(); ok { if err := refreshtoken.NonceValidator(v); err != nil { return &ValidationError{Name: "nonce", err: fmt.Errorf(`db: validator failed for field "RefreshToken.nonce": %w`, err)} } } if v, ok := _u.mutation.ClaimsUserID(); ok { if err := refreshtoken.ClaimsUserIDValidator(v); err != nil { return &ValidationError{Name: "claims_user_id", err: fmt.Errorf(`db: validator failed for field "RefreshToken.claims_user_id": %w`, err)} } } if v, ok := _u.mutation.ClaimsUsername(); ok { if err := refreshtoken.ClaimsUsernameValidator(v); err != nil { return &ValidationError{Name: "claims_username", err: fmt.Errorf(`db: validator failed for field "RefreshToken.claims_username": %w`, err)} } } if v, ok := _u.mutation.ClaimsEmail(); ok { if err := refreshtoken.ClaimsEmailValidator(v); err != nil { return &ValidationError{Name: "claims_email", err: fmt.Errorf(`db: validator failed for field "RefreshToken.claims_email": %w`, err)} } } if v, ok := _u.mutation.ConnectorID(); ok { if err := refreshtoken.ConnectorIDValidator(v); err != nil { return &ValidationError{Name: "connector_id", err: fmt.Errorf(`db: validator failed for field "RefreshToken.connector_id": %w`, err)} } } return nil } func (_u *RefreshTokenUpdate) sqlSave(ctx context.Context) (_node int, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(refreshtoken.Table, refreshtoken.Columns, sqlgraph.NewFieldSpec(refreshtoken.FieldID, field.TypeString)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.ClientID(); ok { _spec.SetField(refreshtoken.FieldClientID, field.TypeString, value) } if value, ok := _u.mutation.Scopes(); ok { _spec.SetField(refreshtoken.FieldScopes, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedScopes(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, refreshtoken.FieldScopes, value) }) } if _u.mutation.ScopesCleared() { _spec.ClearField(refreshtoken.FieldScopes, field.TypeJSON) } if value, ok := _u.mutation.Nonce(); ok { _spec.SetField(refreshtoken.FieldNonce, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUserID(); ok { _spec.SetField(refreshtoken.FieldClaimsUserID, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUsername(); ok { _spec.SetField(refreshtoken.FieldClaimsUsername, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmail(); ok { _spec.SetField(refreshtoken.FieldClaimsEmail, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmailVerified(); ok { _spec.SetField(refreshtoken.FieldClaimsEmailVerified, field.TypeBool, value) } if value, ok := _u.mutation.ClaimsGroups(); ok { _spec.SetField(refreshtoken.FieldClaimsGroups, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedClaimsGroups(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, refreshtoken.FieldClaimsGroups, value) }) } if _u.mutation.ClaimsGroupsCleared() { _spec.ClearField(refreshtoken.FieldClaimsGroups, field.TypeJSON) } if value, ok := _u.mutation.ClaimsPreferredUsername(); ok { _spec.SetField(refreshtoken.FieldClaimsPreferredUsername, field.TypeString, value) } if value, ok := _u.mutation.ConnectorID(); ok { _spec.SetField(refreshtoken.FieldConnectorID, field.TypeString, value) } if value, ok := _u.mutation.ConnectorData(); ok { _spec.SetField(refreshtoken.FieldConnectorData, field.TypeBytes, value) } if _u.mutation.ConnectorDataCleared() { _spec.ClearField(refreshtoken.FieldConnectorData, field.TypeBytes) } if value, ok := _u.mutation.Token(); ok { _spec.SetField(refreshtoken.FieldToken, field.TypeString, value) } if value, ok := _u.mutation.ObsoleteToken(); ok { _spec.SetField(refreshtoken.FieldObsoleteToken, field.TypeString, value) } if value, ok := _u.mutation.CreatedAt(); ok { _spec.SetField(refreshtoken.FieldCreatedAt, field.TypeTime, value) } if value, ok := _u.mutation.LastUsed(); ok { _spec.SetField(refreshtoken.FieldLastUsed, field.TypeTime, value) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{refreshtoken.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return 0, err } _u.mutation.done = true return _node, nil } // RefreshTokenUpdateOne is the builder for updating a single RefreshToken entity. type RefreshTokenUpdateOne struct { config fields []string hooks []Hook mutation *RefreshTokenMutation } // SetClientID sets the "client_id" field. func (_u *RefreshTokenUpdateOne) SetClientID(v string) *RefreshTokenUpdateOne { _u.mutation.SetClientID(v) return _u } // SetNillableClientID sets the "client_id" field if the given value is not nil. func (_u *RefreshTokenUpdateOne) SetNillableClientID(v *string) *RefreshTokenUpdateOne { if v != nil { _u.SetClientID(*v) } return _u } // SetScopes sets the "scopes" field. func (_u *RefreshTokenUpdateOne) SetScopes(v []string) *RefreshTokenUpdateOne { _u.mutation.SetScopes(v) return _u } // AppendScopes appends value to the "scopes" field. func (_u *RefreshTokenUpdateOne) AppendScopes(v []string) *RefreshTokenUpdateOne { _u.mutation.AppendScopes(v) return _u } // ClearScopes clears the value of the "scopes" field. func (_u *RefreshTokenUpdateOne) ClearScopes() *RefreshTokenUpdateOne { _u.mutation.ClearScopes() return _u } // SetNonce sets the "nonce" field. func (_u *RefreshTokenUpdateOne) SetNonce(v string) *RefreshTokenUpdateOne { _u.mutation.SetNonce(v) return _u } // SetNillableNonce sets the "nonce" field if the given value is not nil. func (_u *RefreshTokenUpdateOne) SetNillableNonce(v *string) *RefreshTokenUpdateOne { if v != nil { _u.SetNonce(*v) } return _u } // SetClaimsUserID sets the "claims_user_id" field. func (_u *RefreshTokenUpdateOne) SetClaimsUserID(v string) *RefreshTokenUpdateOne { _u.mutation.SetClaimsUserID(v) return _u } // SetNillableClaimsUserID sets the "claims_user_id" field if the given value is not nil. func (_u *RefreshTokenUpdateOne) SetNillableClaimsUserID(v *string) *RefreshTokenUpdateOne { if v != nil { _u.SetClaimsUserID(*v) } return _u } // SetClaimsUsername sets the "claims_username" field. func (_u *RefreshTokenUpdateOne) SetClaimsUsername(v string) *RefreshTokenUpdateOne { _u.mutation.SetClaimsUsername(v) return _u } // SetNillableClaimsUsername sets the "claims_username" field if the given value is not nil. func (_u *RefreshTokenUpdateOne) SetNillableClaimsUsername(v *string) *RefreshTokenUpdateOne { if v != nil { _u.SetClaimsUsername(*v) } return _u } // SetClaimsEmail sets the "claims_email" field. func (_u *RefreshTokenUpdateOne) SetClaimsEmail(v string) *RefreshTokenUpdateOne { _u.mutation.SetClaimsEmail(v) return _u } // SetNillableClaimsEmail sets the "claims_email" field if the given value is not nil. func (_u *RefreshTokenUpdateOne) SetNillableClaimsEmail(v *string) *RefreshTokenUpdateOne { if v != nil { _u.SetClaimsEmail(*v) } return _u } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (_u *RefreshTokenUpdateOne) SetClaimsEmailVerified(v bool) *RefreshTokenUpdateOne { _u.mutation.SetClaimsEmailVerified(v) return _u } // SetNillableClaimsEmailVerified sets the "claims_email_verified" field if the given value is not nil. func (_u *RefreshTokenUpdateOne) SetNillableClaimsEmailVerified(v *bool) *RefreshTokenUpdateOne { if v != nil { _u.SetClaimsEmailVerified(*v) } return _u } // SetClaimsGroups sets the "claims_groups" field. func (_u *RefreshTokenUpdateOne) SetClaimsGroups(v []string) *RefreshTokenUpdateOne { _u.mutation.SetClaimsGroups(v) return _u } // AppendClaimsGroups appends value to the "claims_groups" field. func (_u *RefreshTokenUpdateOne) AppendClaimsGroups(v []string) *RefreshTokenUpdateOne { _u.mutation.AppendClaimsGroups(v) return _u } // ClearClaimsGroups clears the value of the "claims_groups" field. func (_u *RefreshTokenUpdateOne) ClearClaimsGroups() *RefreshTokenUpdateOne { _u.mutation.ClearClaimsGroups() return _u } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (_u *RefreshTokenUpdateOne) SetClaimsPreferredUsername(v string) *RefreshTokenUpdateOne { _u.mutation.SetClaimsPreferredUsername(v) return _u } // SetNillableClaimsPreferredUsername sets the "claims_preferred_username" field if the given value is not nil. func (_u *RefreshTokenUpdateOne) SetNillableClaimsPreferredUsername(v *string) *RefreshTokenUpdateOne { if v != nil { _u.SetClaimsPreferredUsername(*v) } return _u } // SetConnectorID sets the "connector_id" field. func (_u *RefreshTokenUpdateOne) SetConnectorID(v string) *RefreshTokenUpdateOne { _u.mutation.SetConnectorID(v) return _u } // SetNillableConnectorID sets the "connector_id" field if the given value is not nil. func (_u *RefreshTokenUpdateOne) SetNillableConnectorID(v *string) *RefreshTokenUpdateOne { if v != nil { _u.SetConnectorID(*v) } return _u } // SetConnectorData sets the "connector_data" field. func (_u *RefreshTokenUpdateOne) SetConnectorData(v []byte) *RefreshTokenUpdateOne { _u.mutation.SetConnectorData(v) return _u } // ClearConnectorData clears the value of the "connector_data" field. func (_u *RefreshTokenUpdateOne) ClearConnectorData() *RefreshTokenUpdateOne { _u.mutation.ClearConnectorData() return _u } // SetToken sets the "token" field. func (_u *RefreshTokenUpdateOne) SetToken(v string) *RefreshTokenUpdateOne { _u.mutation.SetToken(v) return _u } // SetNillableToken sets the "token" field if the given value is not nil. func (_u *RefreshTokenUpdateOne) SetNillableToken(v *string) *RefreshTokenUpdateOne { if v != nil { _u.SetToken(*v) } return _u } // SetObsoleteToken sets the "obsolete_token" field. func (_u *RefreshTokenUpdateOne) SetObsoleteToken(v string) *RefreshTokenUpdateOne { _u.mutation.SetObsoleteToken(v) return _u } // SetNillableObsoleteToken sets the "obsolete_token" field if the given value is not nil. func (_u *RefreshTokenUpdateOne) SetNillableObsoleteToken(v *string) *RefreshTokenUpdateOne { if v != nil { _u.SetObsoleteToken(*v) } return _u } // SetCreatedAt sets the "created_at" field. func (_u *RefreshTokenUpdateOne) SetCreatedAt(v time.Time) *RefreshTokenUpdateOne { _u.mutation.SetCreatedAt(v) return _u } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. func (_u *RefreshTokenUpdateOne) SetNillableCreatedAt(v *time.Time) *RefreshTokenUpdateOne { if v != nil { _u.SetCreatedAt(*v) } return _u } // SetLastUsed sets the "last_used" field. func (_u *RefreshTokenUpdateOne) SetLastUsed(v time.Time) *RefreshTokenUpdateOne { _u.mutation.SetLastUsed(v) return _u } // SetNillableLastUsed sets the "last_used" field if the given value is not nil. func (_u *RefreshTokenUpdateOne) SetNillableLastUsed(v *time.Time) *RefreshTokenUpdateOne { if v != nil { _u.SetLastUsed(*v) } return _u } // Mutation returns the RefreshTokenMutation object of the builder. func (_u *RefreshTokenUpdateOne) Mutation() *RefreshTokenMutation { return _u.mutation } // Where appends a list predicates to the RefreshTokenUpdate builder. func (_u *RefreshTokenUpdateOne) Where(ps ...predicate.RefreshToken) *RefreshTokenUpdateOne { _u.mutation.Where(ps...) return _u } // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (_u *RefreshTokenUpdateOne) Select(field string, fields ...string) *RefreshTokenUpdateOne { _u.fields = append([]string{field}, fields...) return _u } // Save executes the query and returns the updated RefreshToken entity. func (_u *RefreshTokenUpdateOne) Save(ctx context.Context) (*RefreshToken, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *RefreshTokenUpdateOne) SaveX(ctx context.Context) *RefreshToken { node, err := _u.Save(ctx) if err != nil { panic(err) } return node } // Exec executes the query on the entity. func (_u *RefreshTokenUpdateOne) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *RefreshTokenUpdateOne) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *RefreshTokenUpdateOne) check() error { if v, ok := _u.mutation.ClientID(); ok { if err := refreshtoken.ClientIDValidator(v); err != nil { return &ValidationError{Name: "client_id", err: fmt.Errorf(`db: validator failed for field "RefreshToken.client_id": %w`, err)} } } if v, ok := _u.mutation.Nonce(); ok { if err := refreshtoken.NonceValidator(v); err != nil { return &ValidationError{Name: "nonce", err: fmt.Errorf(`db: validator failed for field "RefreshToken.nonce": %w`, err)} } } if v, ok := _u.mutation.ClaimsUserID(); ok { if err := refreshtoken.ClaimsUserIDValidator(v); err != nil { return &ValidationError{Name: "claims_user_id", err: fmt.Errorf(`db: validator failed for field "RefreshToken.claims_user_id": %w`, err)} } } if v, ok := _u.mutation.ClaimsUsername(); ok { if err := refreshtoken.ClaimsUsernameValidator(v); err != nil { return &ValidationError{Name: "claims_username", err: fmt.Errorf(`db: validator failed for field "RefreshToken.claims_username": %w`, err)} } } if v, ok := _u.mutation.ClaimsEmail(); ok { if err := refreshtoken.ClaimsEmailValidator(v); err != nil { return &ValidationError{Name: "claims_email", err: fmt.Errorf(`db: validator failed for field "RefreshToken.claims_email": %w`, err)} } } if v, ok := _u.mutation.ConnectorID(); ok { if err := refreshtoken.ConnectorIDValidator(v); err != nil { return &ValidationError{Name: "connector_id", err: fmt.Errorf(`db: validator failed for field "RefreshToken.connector_id": %w`, err)} } } return nil } func (_u *RefreshTokenUpdateOne) sqlSave(ctx context.Context) (_node *RefreshToken, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(refreshtoken.Table, refreshtoken.Columns, sqlgraph.NewFieldSpec(refreshtoken.FieldID, field.TypeString)) id, ok := _u.mutation.ID() if !ok { return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "RefreshToken.id" for update`)} } _spec.Node.ID.Value = id if fields := _u.fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, refreshtoken.FieldID) for _, f := range fields { if !refreshtoken.ValidColumn(f) { return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } if f != refreshtoken.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, f) } } } if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.ClientID(); ok { _spec.SetField(refreshtoken.FieldClientID, field.TypeString, value) } if value, ok := _u.mutation.Scopes(); ok { _spec.SetField(refreshtoken.FieldScopes, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedScopes(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, refreshtoken.FieldScopes, value) }) } if _u.mutation.ScopesCleared() { _spec.ClearField(refreshtoken.FieldScopes, field.TypeJSON) } if value, ok := _u.mutation.Nonce(); ok { _spec.SetField(refreshtoken.FieldNonce, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUserID(); ok { _spec.SetField(refreshtoken.FieldClaimsUserID, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUsername(); ok { _spec.SetField(refreshtoken.FieldClaimsUsername, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmail(); ok { _spec.SetField(refreshtoken.FieldClaimsEmail, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmailVerified(); ok { _spec.SetField(refreshtoken.FieldClaimsEmailVerified, field.TypeBool, value) } if value, ok := _u.mutation.ClaimsGroups(); ok { _spec.SetField(refreshtoken.FieldClaimsGroups, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedClaimsGroups(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, refreshtoken.FieldClaimsGroups, value) }) } if _u.mutation.ClaimsGroupsCleared() { _spec.ClearField(refreshtoken.FieldClaimsGroups, field.TypeJSON) } if value, ok := _u.mutation.ClaimsPreferredUsername(); ok { _spec.SetField(refreshtoken.FieldClaimsPreferredUsername, field.TypeString, value) } if value, ok := _u.mutation.ConnectorID(); ok { _spec.SetField(refreshtoken.FieldConnectorID, field.TypeString, value) } if value, ok := _u.mutation.ConnectorData(); ok { _spec.SetField(refreshtoken.FieldConnectorData, field.TypeBytes, value) } if _u.mutation.ConnectorDataCleared() { _spec.ClearField(refreshtoken.FieldConnectorData, field.TypeBytes) } if value, ok := _u.mutation.Token(); ok { _spec.SetField(refreshtoken.FieldToken, field.TypeString, value) } if value, ok := _u.mutation.ObsoleteToken(); ok { _spec.SetField(refreshtoken.FieldObsoleteToken, field.TypeString, value) } if value, ok := _u.mutation.CreatedAt(); ok { _spec.SetField(refreshtoken.FieldCreatedAt, field.TypeTime, value) } if value, ok := _u.mutation.LastUsed(); ok { _spec.SetField(refreshtoken.FieldLastUsed, field.TypeTime, value) } _node = &RefreshToken{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{refreshtoken.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } _u.mutation.done = true return _node, nil } ================================================ FILE: storage/ent/db/runtime/runtime.go ================================================ // Code generated by ent, DO NOT EDIT. package runtime // The schema-stitching logic is generated in github.com/dexidp/dex/storage/ent/db/runtime.go const ( Version = "v0.14.5" // Version of ent codegen. Sum = "h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=" // Sum of ent codegen. ) ================================================ FILE: storage/ent/db/runtime.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "time" "github.com/dexidp/dex/storage/ent/db/authcode" "github.com/dexidp/dex/storage/ent/db/authrequest" "github.com/dexidp/dex/storage/ent/db/authsession" "github.com/dexidp/dex/storage/ent/db/connector" "github.com/dexidp/dex/storage/ent/db/devicerequest" "github.com/dexidp/dex/storage/ent/db/devicetoken" "github.com/dexidp/dex/storage/ent/db/keys" "github.com/dexidp/dex/storage/ent/db/oauth2client" "github.com/dexidp/dex/storage/ent/db/offlinesession" "github.com/dexidp/dex/storage/ent/db/password" "github.com/dexidp/dex/storage/ent/db/refreshtoken" "github.com/dexidp/dex/storage/ent/db/useridentity" "github.com/dexidp/dex/storage/ent/schema" ) // The init function reads all schema descriptors with runtime code // (default values, validators, hooks and policies) and stitches it // to their package variables. func init() { authcodeFields := schema.AuthCode{}.Fields() _ = authcodeFields // authcodeDescClientID is the schema descriptor for client_id field. authcodeDescClientID := authcodeFields[1].Descriptor() // authcode.ClientIDValidator is a validator for the "client_id" field. It is called by the builders before save. authcode.ClientIDValidator = authcodeDescClientID.Validators[0].(func(string) error) // authcodeDescNonce is the schema descriptor for nonce field. authcodeDescNonce := authcodeFields[3].Descriptor() // authcode.NonceValidator is a validator for the "nonce" field. It is called by the builders before save. authcode.NonceValidator = authcodeDescNonce.Validators[0].(func(string) error) // authcodeDescRedirectURI is the schema descriptor for redirect_uri field. authcodeDescRedirectURI := authcodeFields[4].Descriptor() // authcode.RedirectURIValidator is a validator for the "redirect_uri" field. It is called by the builders before save. authcode.RedirectURIValidator = authcodeDescRedirectURI.Validators[0].(func(string) error) // authcodeDescClaimsUserID is the schema descriptor for claims_user_id field. authcodeDescClaimsUserID := authcodeFields[5].Descriptor() // authcode.ClaimsUserIDValidator is a validator for the "claims_user_id" field. It is called by the builders before save. authcode.ClaimsUserIDValidator = authcodeDescClaimsUserID.Validators[0].(func(string) error) // authcodeDescClaimsUsername is the schema descriptor for claims_username field. authcodeDescClaimsUsername := authcodeFields[6].Descriptor() // authcode.ClaimsUsernameValidator is a validator for the "claims_username" field. It is called by the builders before save. authcode.ClaimsUsernameValidator = authcodeDescClaimsUsername.Validators[0].(func(string) error) // authcodeDescClaimsEmail is the schema descriptor for claims_email field. authcodeDescClaimsEmail := authcodeFields[7].Descriptor() // authcode.ClaimsEmailValidator is a validator for the "claims_email" field. It is called by the builders before save. authcode.ClaimsEmailValidator = authcodeDescClaimsEmail.Validators[0].(func(string) error) // authcodeDescClaimsPreferredUsername is the schema descriptor for claims_preferred_username field. authcodeDescClaimsPreferredUsername := authcodeFields[10].Descriptor() // authcode.DefaultClaimsPreferredUsername holds the default value on creation for the claims_preferred_username field. authcode.DefaultClaimsPreferredUsername = authcodeDescClaimsPreferredUsername.Default.(string) // authcodeDescConnectorID is the schema descriptor for connector_id field. authcodeDescConnectorID := authcodeFields[11].Descriptor() // authcode.ConnectorIDValidator is a validator for the "connector_id" field. It is called by the builders before save. authcode.ConnectorIDValidator = authcodeDescConnectorID.Validators[0].(func(string) error) // authcodeDescCodeChallenge is the schema descriptor for code_challenge field. authcodeDescCodeChallenge := authcodeFields[14].Descriptor() // authcode.DefaultCodeChallenge holds the default value on creation for the code_challenge field. authcode.DefaultCodeChallenge = authcodeDescCodeChallenge.Default.(string) // authcodeDescCodeChallengeMethod is the schema descriptor for code_challenge_method field. authcodeDescCodeChallengeMethod := authcodeFields[15].Descriptor() // authcode.DefaultCodeChallengeMethod holds the default value on creation for the code_challenge_method field. authcode.DefaultCodeChallengeMethod = authcodeDescCodeChallengeMethod.Default.(string) // authcodeDescID is the schema descriptor for id field. authcodeDescID := authcodeFields[0].Descriptor() // authcode.IDValidator is a validator for the "id" field. It is called by the builders before save. authcode.IDValidator = authcodeDescID.Validators[0].(func(string) error) authrequestFields := schema.AuthRequest{}.Fields() _ = authrequestFields // authrequestDescClaimsPreferredUsername is the schema descriptor for claims_preferred_username field. authrequestDescClaimsPreferredUsername := authrequestFields[14].Descriptor() // authrequest.DefaultClaimsPreferredUsername holds the default value on creation for the claims_preferred_username field. authrequest.DefaultClaimsPreferredUsername = authrequestDescClaimsPreferredUsername.Default.(string) // authrequestDescCodeChallenge is the schema descriptor for code_challenge field. authrequestDescCodeChallenge := authrequestFields[18].Descriptor() // authrequest.DefaultCodeChallenge holds the default value on creation for the code_challenge field. authrequest.DefaultCodeChallenge = authrequestDescCodeChallenge.Default.(string) // authrequestDescCodeChallengeMethod is the schema descriptor for code_challenge_method field. authrequestDescCodeChallengeMethod := authrequestFields[19].Descriptor() // authrequest.DefaultCodeChallengeMethod holds the default value on creation for the code_challenge_method field. authrequest.DefaultCodeChallengeMethod = authrequestDescCodeChallengeMethod.Default.(string) // authrequestDescMfaValidated is the schema descriptor for mfa_validated field. authrequestDescMfaValidated := authrequestFields[21].Descriptor() // authrequest.DefaultMfaValidated holds the default value on creation for the mfa_validated field. authrequest.DefaultMfaValidated = authrequestDescMfaValidated.Default.(bool) // authrequestDescPrompt is the schema descriptor for prompt field. authrequestDescPrompt := authrequestFields[22].Descriptor() // authrequest.DefaultPrompt holds the default value on creation for the prompt field. authrequest.DefaultPrompt = authrequestDescPrompt.Default.(string) // authrequestDescMaxAge is the schema descriptor for max_age field. authrequestDescMaxAge := authrequestFields[23].Descriptor() // authrequest.DefaultMaxAge holds the default value on creation for the max_age field. authrequest.DefaultMaxAge = authrequestDescMaxAge.Default.(int) // authrequestDescID is the schema descriptor for id field. authrequestDescID := authrequestFields[0].Descriptor() // authrequest.IDValidator is a validator for the "id" field. It is called by the builders before save. authrequest.IDValidator = authrequestDescID.Validators[0].(func(string) error) authsessionFields := schema.AuthSession{}.Fields() _ = authsessionFields // authsessionDescUserID is the schema descriptor for user_id field. authsessionDescUserID := authsessionFields[1].Descriptor() // authsession.UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. authsession.UserIDValidator = authsessionDescUserID.Validators[0].(func(string) error) // authsessionDescConnectorID is the schema descriptor for connector_id field. authsessionDescConnectorID := authsessionFields[2].Descriptor() // authsession.ConnectorIDValidator is a validator for the "connector_id" field. It is called by the builders before save. authsession.ConnectorIDValidator = authsessionDescConnectorID.Validators[0].(func(string) error) // authsessionDescNonce is the schema descriptor for nonce field. authsessionDescNonce := authsessionFields[3].Descriptor() // authsession.NonceValidator is a validator for the "nonce" field. It is called by the builders before save. authsession.NonceValidator = authsessionDescNonce.Validators[0].(func(string) error) // authsessionDescIPAddress is the schema descriptor for ip_address field. authsessionDescIPAddress := authsessionFields[7].Descriptor() // authsession.DefaultIPAddress holds the default value on creation for the ip_address field. authsession.DefaultIPAddress = authsessionDescIPAddress.Default.(string) // authsessionDescUserAgent is the schema descriptor for user_agent field. authsessionDescUserAgent := authsessionFields[8].Descriptor() // authsession.DefaultUserAgent holds the default value on creation for the user_agent field. authsession.DefaultUserAgent = authsessionDescUserAgent.Default.(string) // authsessionDescID is the schema descriptor for id field. authsessionDescID := authsessionFields[0].Descriptor() // authsession.IDValidator is a validator for the "id" field. It is called by the builders before save. authsession.IDValidator = authsessionDescID.Validators[0].(func(string) error) connectorFields := schema.Connector{}.Fields() _ = connectorFields // connectorDescType is the schema descriptor for type field. connectorDescType := connectorFields[1].Descriptor() // connector.TypeValidator is a validator for the "type" field. It is called by the builders before save. connector.TypeValidator = connectorDescType.Validators[0].(func(string) error) // connectorDescName is the schema descriptor for name field. connectorDescName := connectorFields[2].Descriptor() // connector.NameValidator is a validator for the "name" field. It is called by the builders before save. connector.NameValidator = connectorDescName.Validators[0].(func(string) error) // connectorDescID is the schema descriptor for id field. connectorDescID := connectorFields[0].Descriptor() // connector.IDValidator is a validator for the "id" field. It is called by the builders before save. connector.IDValidator = func() func(string) error { validators := connectorDescID.Validators fns := [...]func(string) error{ validators[0].(func(string) error), validators[1].(func(string) error), } return func(id string) error { for _, fn := range fns { if err := fn(id); err != nil { return err } } return nil } }() devicerequestFields := schema.DeviceRequest{}.Fields() _ = devicerequestFields // devicerequestDescUserCode is the schema descriptor for user_code field. devicerequestDescUserCode := devicerequestFields[0].Descriptor() // devicerequest.UserCodeValidator is a validator for the "user_code" field. It is called by the builders before save. devicerequest.UserCodeValidator = devicerequestDescUserCode.Validators[0].(func(string) error) // devicerequestDescDeviceCode is the schema descriptor for device_code field. devicerequestDescDeviceCode := devicerequestFields[1].Descriptor() // devicerequest.DeviceCodeValidator is a validator for the "device_code" field. It is called by the builders before save. devicerequest.DeviceCodeValidator = devicerequestDescDeviceCode.Validators[0].(func(string) error) // devicerequestDescClientID is the schema descriptor for client_id field. devicerequestDescClientID := devicerequestFields[2].Descriptor() // devicerequest.ClientIDValidator is a validator for the "client_id" field. It is called by the builders before save. devicerequest.ClientIDValidator = devicerequestDescClientID.Validators[0].(func(string) error) // devicerequestDescClientSecret is the schema descriptor for client_secret field. devicerequestDescClientSecret := devicerequestFields[3].Descriptor() // devicerequest.ClientSecretValidator is a validator for the "client_secret" field. It is called by the builders before save. devicerequest.ClientSecretValidator = devicerequestDescClientSecret.Validators[0].(func(string) error) devicetokenFields := schema.DeviceToken{}.Fields() _ = devicetokenFields // devicetokenDescDeviceCode is the schema descriptor for device_code field. devicetokenDescDeviceCode := devicetokenFields[0].Descriptor() // devicetoken.DeviceCodeValidator is a validator for the "device_code" field. It is called by the builders before save. devicetoken.DeviceCodeValidator = devicetokenDescDeviceCode.Validators[0].(func(string) error) // devicetokenDescStatus is the schema descriptor for status field. devicetokenDescStatus := devicetokenFields[1].Descriptor() // devicetoken.StatusValidator is a validator for the "status" field. It is called by the builders before save. devicetoken.StatusValidator = devicetokenDescStatus.Validators[0].(func(string) error) // devicetokenDescCodeChallenge is the schema descriptor for code_challenge field. devicetokenDescCodeChallenge := devicetokenFields[6].Descriptor() // devicetoken.DefaultCodeChallenge holds the default value on creation for the code_challenge field. devicetoken.DefaultCodeChallenge = devicetokenDescCodeChallenge.Default.(string) // devicetokenDescCodeChallengeMethod is the schema descriptor for code_challenge_method field. devicetokenDescCodeChallengeMethod := devicetokenFields[7].Descriptor() // devicetoken.DefaultCodeChallengeMethod holds the default value on creation for the code_challenge_method field. devicetoken.DefaultCodeChallengeMethod = devicetokenDescCodeChallengeMethod.Default.(string) keysFields := schema.Keys{}.Fields() _ = keysFields // keysDescID is the schema descriptor for id field. keysDescID := keysFields[0].Descriptor() // keys.IDValidator is a validator for the "id" field. It is called by the builders before save. keys.IDValidator = keysDescID.Validators[0].(func(string) error) oauth2clientFields := schema.OAuth2Client{}.Fields() _ = oauth2clientFields // oauth2clientDescSecret is the schema descriptor for secret field. oauth2clientDescSecret := oauth2clientFields[1].Descriptor() // oauth2client.SecretValidator is a validator for the "secret" field. It is called by the builders before save. oauth2client.SecretValidator = oauth2clientDescSecret.Validators[0].(func(string) error) // oauth2clientDescName is the schema descriptor for name field. oauth2clientDescName := oauth2clientFields[5].Descriptor() // oauth2client.NameValidator is a validator for the "name" field. It is called by the builders before save. oauth2client.NameValidator = oauth2clientDescName.Validators[0].(func(string) error) // oauth2clientDescLogoURL is the schema descriptor for logo_url field. oauth2clientDescLogoURL := oauth2clientFields[6].Descriptor() // oauth2client.LogoURLValidator is a validator for the "logo_url" field. It is called by the builders before save. oauth2client.LogoURLValidator = oauth2clientDescLogoURL.Validators[0].(func(string) error) // oauth2clientDescID is the schema descriptor for id field. oauth2clientDescID := oauth2clientFields[0].Descriptor() // oauth2client.IDValidator is a validator for the "id" field. It is called by the builders before save. oauth2client.IDValidator = func() func(string) error { validators := oauth2clientDescID.Validators fns := [...]func(string) error{ validators[0].(func(string) error), validators[1].(func(string) error), } return func(id string) error { for _, fn := range fns { if err := fn(id); err != nil { return err } } return nil } }() offlinesessionFields := schema.OfflineSession{}.Fields() _ = offlinesessionFields // offlinesessionDescUserID is the schema descriptor for user_id field. offlinesessionDescUserID := offlinesessionFields[1].Descriptor() // offlinesession.UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. offlinesession.UserIDValidator = offlinesessionDescUserID.Validators[0].(func(string) error) // offlinesessionDescConnID is the schema descriptor for conn_id field. offlinesessionDescConnID := offlinesessionFields[2].Descriptor() // offlinesession.ConnIDValidator is a validator for the "conn_id" field. It is called by the builders before save. offlinesession.ConnIDValidator = offlinesessionDescConnID.Validators[0].(func(string) error) // offlinesessionDescID is the schema descriptor for id field. offlinesessionDescID := offlinesessionFields[0].Descriptor() // offlinesession.IDValidator is a validator for the "id" field. It is called by the builders before save. offlinesession.IDValidator = offlinesessionDescID.Validators[0].(func(string) error) passwordFields := schema.Password{}.Fields() _ = passwordFields // passwordDescEmail is the schema descriptor for email field. passwordDescEmail := passwordFields[0].Descriptor() // password.EmailValidator is a validator for the "email" field. It is called by the builders before save. password.EmailValidator = passwordDescEmail.Validators[0].(func(string) error) // passwordDescUsername is the schema descriptor for username field. passwordDescUsername := passwordFields[2].Descriptor() // password.UsernameValidator is a validator for the "username" field. It is called by the builders before save. password.UsernameValidator = passwordDescUsername.Validators[0].(func(string) error) // passwordDescName is the schema descriptor for name field. passwordDescName := passwordFields[3].Descriptor() // password.DefaultName holds the default value on creation for the name field. password.DefaultName = passwordDescName.Default.(string) // passwordDescPreferredUsername is the schema descriptor for preferred_username field. passwordDescPreferredUsername := passwordFields[4].Descriptor() // password.DefaultPreferredUsername holds the default value on creation for the preferred_username field. password.DefaultPreferredUsername = passwordDescPreferredUsername.Default.(string) // passwordDescUserID is the schema descriptor for user_id field. passwordDescUserID := passwordFields[6].Descriptor() // password.UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. password.UserIDValidator = passwordDescUserID.Validators[0].(func(string) error) refreshtokenFields := schema.RefreshToken{}.Fields() _ = refreshtokenFields // refreshtokenDescClientID is the schema descriptor for client_id field. refreshtokenDescClientID := refreshtokenFields[1].Descriptor() // refreshtoken.ClientIDValidator is a validator for the "client_id" field. It is called by the builders before save. refreshtoken.ClientIDValidator = refreshtokenDescClientID.Validators[0].(func(string) error) // refreshtokenDescNonce is the schema descriptor for nonce field. refreshtokenDescNonce := refreshtokenFields[3].Descriptor() // refreshtoken.NonceValidator is a validator for the "nonce" field. It is called by the builders before save. refreshtoken.NonceValidator = refreshtokenDescNonce.Validators[0].(func(string) error) // refreshtokenDescClaimsUserID is the schema descriptor for claims_user_id field. refreshtokenDescClaimsUserID := refreshtokenFields[4].Descriptor() // refreshtoken.ClaimsUserIDValidator is a validator for the "claims_user_id" field. It is called by the builders before save. refreshtoken.ClaimsUserIDValidator = refreshtokenDescClaimsUserID.Validators[0].(func(string) error) // refreshtokenDescClaimsUsername is the schema descriptor for claims_username field. refreshtokenDescClaimsUsername := refreshtokenFields[5].Descriptor() // refreshtoken.ClaimsUsernameValidator is a validator for the "claims_username" field. It is called by the builders before save. refreshtoken.ClaimsUsernameValidator = refreshtokenDescClaimsUsername.Validators[0].(func(string) error) // refreshtokenDescClaimsEmail is the schema descriptor for claims_email field. refreshtokenDescClaimsEmail := refreshtokenFields[6].Descriptor() // refreshtoken.ClaimsEmailValidator is a validator for the "claims_email" field. It is called by the builders before save. refreshtoken.ClaimsEmailValidator = refreshtokenDescClaimsEmail.Validators[0].(func(string) error) // refreshtokenDescClaimsPreferredUsername is the schema descriptor for claims_preferred_username field. refreshtokenDescClaimsPreferredUsername := refreshtokenFields[9].Descriptor() // refreshtoken.DefaultClaimsPreferredUsername holds the default value on creation for the claims_preferred_username field. refreshtoken.DefaultClaimsPreferredUsername = refreshtokenDescClaimsPreferredUsername.Default.(string) // refreshtokenDescConnectorID is the schema descriptor for connector_id field. refreshtokenDescConnectorID := refreshtokenFields[10].Descriptor() // refreshtoken.ConnectorIDValidator is a validator for the "connector_id" field. It is called by the builders before save. refreshtoken.ConnectorIDValidator = refreshtokenDescConnectorID.Validators[0].(func(string) error) // refreshtokenDescToken is the schema descriptor for token field. refreshtokenDescToken := refreshtokenFields[12].Descriptor() // refreshtoken.DefaultToken holds the default value on creation for the token field. refreshtoken.DefaultToken = refreshtokenDescToken.Default.(string) // refreshtokenDescObsoleteToken is the schema descriptor for obsolete_token field. refreshtokenDescObsoleteToken := refreshtokenFields[13].Descriptor() // refreshtoken.DefaultObsoleteToken holds the default value on creation for the obsolete_token field. refreshtoken.DefaultObsoleteToken = refreshtokenDescObsoleteToken.Default.(string) // refreshtokenDescCreatedAt is the schema descriptor for created_at field. refreshtokenDescCreatedAt := refreshtokenFields[14].Descriptor() // refreshtoken.DefaultCreatedAt holds the default value on creation for the created_at field. refreshtoken.DefaultCreatedAt = refreshtokenDescCreatedAt.Default.(func() time.Time) // refreshtokenDescLastUsed is the schema descriptor for last_used field. refreshtokenDescLastUsed := refreshtokenFields[15].Descriptor() // refreshtoken.DefaultLastUsed holds the default value on creation for the last_used field. refreshtoken.DefaultLastUsed = refreshtokenDescLastUsed.Default.(func() time.Time) // refreshtokenDescID is the schema descriptor for id field. refreshtokenDescID := refreshtokenFields[0].Descriptor() // refreshtoken.IDValidator is a validator for the "id" field. It is called by the builders before save. refreshtoken.IDValidator = refreshtokenDescID.Validators[0].(func(string) error) useridentityFields := schema.UserIdentity{}.Fields() _ = useridentityFields // useridentityDescUserID is the schema descriptor for user_id field. useridentityDescUserID := useridentityFields[1].Descriptor() // useridentity.UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. useridentity.UserIDValidator = useridentityDescUserID.Validators[0].(func(string) error) // useridentityDescConnectorID is the schema descriptor for connector_id field. useridentityDescConnectorID := useridentityFields[2].Descriptor() // useridentity.ConnectorIDValidator is a validator for the "connector_id" field. It is called by the builders before save. useridentity.ConnectorIDValidator = useridentityDescConnectorID.Validators[0].(func(string) error) // useridentityDescClaimsUserID is the schema descriptor for claims_user_id field. useridentityDescClaimsUserID := useridentityFields[3].Descriptor() // useridentity.DefaultClaimsUserID holds the default value on creation for the claims_user_id field. useridentity.DefaultClaimsUserID = useridentityDescClaimsUserID.Default.(string) // useridentityDescClaimsUsername is the schema descriptor for claims_username field. useridentityDescClaimsUsername := useridentityFields[4].Descriptor() // useridentity.DefaultClaimsUsername holds the default value on creation for the claims_username field. useridentity.DefaultClaimsUsername = useridentityDescClaimsUsername.Default.(string) // useridentityDescClaimsPreferredUsername is the schema descriptor for claims_preferred_username field. useridentityDescClaimsPreferredUsername := useridentityFields[5].Descriptor() // useridentity.DefaultClaimsPreferredUsername holds the default value on creation for the claims_preferred_username field. useridentity.DefaultClaimsPreferredUsername = useridentityDescClaimsPreferredUsername.Default.(string) // useridentityDescClaimsEmail is the schema descriptor for claims_email field. useridentityDescClaimsEmail := useridentityFields[6].Descriptor() // useridentity.DefaultClaimsEmail holds the default value on creation for the claims_email field. useridentity.DefaultClaimsEmail = useridentityDescClaimsEmail.Default.(string) // useridentityDescClaimsEmailVerified is the schema descriptor for claims_email_verified field. useridentityDescClaimsEmailVerified := useridentityFields[7].Descriptor() // useridentity.DefaultClaimsEmailVerified holds the default value on creation for the claims_email_verified field. useridentity.DefaultClaimsEmailVerified = useridentityDescClaimsEmailVerified.Default.(bool) // useridentityDescID is the schema descriptor for id field. useridentityDescID := useridentityFields[0].Descriptor() // useridentity.IDValidator is a validator for the "id" field. It is called by the builders before save. useridentity.IDValidator = useridentityDescID.Validators[0].(func(string) error) } ================================================ FILE: storage/ent/db/tx.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "sync" "entgo.io/ent/dialect" ) // Tx is a transactional client that is created by calling Client.Tx(). type Tx struct { config // AuthCode is the client for interacting with the AuthCode builders. AuthCode *AuthCodeClient // AuthRequest is the client for interacting with the AuthRequest builders. AuthRequest *AuthRequestClient // AuthSession is the client for interacting with the AuthSession builders. AuthSession *AuthSessionClient // Connector is the client for interacting with the Connector builders. Connector *ConnectorClient // DeviceRequest is the client for interacting with the DeviceRequest builders. DeviceRequest *DeviceRequestClient // DeviceToken is the client for interacting with the DeviceToken builders. DeviceToken *DeviceTokenClient // Keys is the client for interacting with the Keys builders. Keys *KeysClient // OAuth2Client is the client for interacting with the OAuth2Client builders. OAuth2Client *OAuth2ClientClient // OfflineSession is the client for interacting with the OfflineSession builders. OfflineSession *OfflineSessionClient // Password is the client for interacting with the Password builders. Password *PasswordClient // RefreshToken is the client for interacting with the RefreshToken builders. RefreshToken *RefreshTokenClient // UserIdentity is the client for interacting with the UserIdentity builders. UserIdentity *UserIdentityClient // lazily loaded. client *Client clientOnce sync.Once // ctx lives for the life of the transaction. It is // the same context used by the underlying connection. ctx context.Context } type ( // Committer is the interface that wraps the Commit method. Committer interface { Commit(context.Context, *Tx) error } // The CommitFunc type is an adapter to allow the use of ordinary // function as a Committer. If f is a function with the appropriate // signature, CommitFunc(f) is a Committer that calls f. CommitFunc func(context.Context, *Tx) error // CommitHook defines the "commit middleware". A function that gets a Committer // and returns a Committer. For example: // // hook := func(next ent.Committer) ent.Committer { // return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error { // // Do some stuff before. // if err := next.Commit(ctx, tx); err != nil { // return err // } // // Do some stuff after. // return nil // }) // } // CommitHook func(Committer) Committer ) // Commit calls f(ctx, m). func (f CommitFunc) Commit(ctx context.Context, tx *Tx) error { return f(ctx, tx) } // Commit commits the transaction. func (tx *Tx) Commit() error { txDriver := tx.config.driver.(*txDriver) var fn Committer = CommitFunc(func(context.Context, *Tx) error { return txDriver.tx.Commit() }) txDriver.mu.Lock() hooks := append([]CommitHook(nil), txDriver.onCommit...) txDriver.mu.Unlock() for i := len(hooks) - 1; i >= 0; i-- { fn = hooks[i](fn) } return fn.Commit(tx.ctx, tx) } // OnCommit adds a hook to call on commit. func (tx *Tx) OnCommit(f CommitHook) { txDriver := tx.config.driver.(*txDriver) txDriver.mu.Lock() txDriver.onCommit = append(txDriver.onCommit, f) txDriver.mu.Unlock() } type ( // Rollbacker is the interface that wraps the Rollback method. Rollbacker interface { Rollback(context.Context, *Tx) error } // The RollbackFunc type is an adapter to allow the use of ordinary // function as a Rollbacker. If f is a function with the appropriate // signature, RollbackFunc(f) is a Rollbacker that calls f. RollbackFunc func(context.Context, *Tx) error // RollbackHook defines the "rollback middleware". A function that gets a Rollbacker // and returns a Rollbacker. For example: // // hook := func(next ent.Rollbacker) ent.Rollbacker { // return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error { // // Do some stuff before. // if err := next.Rollback(ctx, tx); err != nil { // return err // } // // Do some stuff after. // return nil // }) // } // RollbackHook func(Rollbacker) Rollbacker ) // Rollback calls f(ctx, m). func (f RollbackFunc) Rollback(ctx context.Context, tx *Tx) error { return f(ctx, tx) } // Rollback rollbacks the transaction. func (tx *Tx) Rollback() error { txDriver := tx.config.driver.(*txDriver) var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error { return txDriver.tx.Rollback() }) txDriver.mu.Lock() hooks := append([]RollbackHook(nil), txDriver.onRollback...) txDriver.mu.Unlock() for i := len(hooks) - 1; i >= 0; i-- { fn = hooks[i](fn) } return fn.Rollback(tx.ctx, tx) } // OnRollback adds a hook to call on rollback. func (tx *Tx) OnRollback(f RollbackHook) { txDriver := tx.config.driver.(*txDriver) txDriver.mu.Lock() txDriver.onRollback = append(txDriver.onRollback, f) txDriver.mu.Unlock() } // Client returns a Client that binds to current transaction. func (tx *Tx) Client() *Client { tx.clientOnce.Do(func() { tx.client = &Client{config: tx.config} tx.client.init() }) return tx.client } func (tx *Tx) init() { tx.AuthCode = NewAuthCodeClient(tx.config) tx.AuthRequest = NewAuthRequestClient(tx.config) tx.AuthSession = NewAuthSessionClient(tx.config) tx.Connector = NewConnectorClient(tx.config) tx.DeviceRequest = NewDeviceRequestClient(tx.config) tx.DeviceToken = NewDeviceTokenClient(tx.config) tx.Keys = NewKeysClient(tx.config) tx.OAuth2Client = NewOAuth2ClientClient(tx.config) tx.OfflineSession = NewOfflineSessionClient(tx.config) tx.Password = NewPasswordClient(tx.config) tx.RefreshToken = NewRefreshTokenClient(tx.config) tx.UserIdentity = NewUserIdentityClient(tx.config) } // txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. // The idea is to support transactions without adding any extra code to the builders. // When a builder calls to driver.Tx(), it gets the same dialect.Tx instance. // Commit and Rollback are nop for the internal builders and the user must call one // of them in order to commit or rollback the transaction. // // If a closed transaction is embedded in one of the generated entities, and the entity // applies a query, for example: AuthCode.QueryXXX(), the query will be executed // through the driver which created this transaction. // // Note that txDriver is not goroutine safe. type txDriver struct { // the driver we started the transaction from. drv dialect.Driver // tx is the underlying transaction. tx dialect.Tx // completion hooks. mu sync.Mutex onCommit []CommitHook onRollback []RollbackHook } // newTx creates a new transactional driver. func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) { tx, err := drv.Tx(ctx) if err != nil { return nil, err } return &txDriver{tx: tx, drv: drv}, nil } // Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls // from the internal builders. Should be called only by the internal builders. func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil } // Dialect returns the dialect of the driver we started the transaction from. func (tx *txDriver) Dialect() string { return tx.drv.Dialect() } // Close is a nop close. func (*txDriver) Close() error { return nil } // Commit is a nop commit for the internal builders. // User must call `Tx.Commit` in order to commit the transaction. func (*txDriver) Commit() error { return nil } // Rollback is a nop rollback for the internal builders. // User must call `Tx.Rollback` in order to rollback the transaction. func (*txDriver) Rollback() error { return nil } // Exec calls tx.Exec. func (tx *txDriver) Exec(ctx context.Context, query string, args, v any) error { return tx.tx.Exec(ctx, query, args, v) } // Query calls tx.Query. func (tx *txDriver) Query(ctx context.Context, query string, args, v any) error { return tx.tx.Query(ctx, query, args, v) } var _ dialect.Driver = (*txDriver)(nil) ================================================ FILE: storage/ent/db/useridentity/useridentity.go ================================================ // Code generated by ent, DO NOT EDIT. package useridentity import ( "entgo.io/ent/dialect/sql" ) const ( // Label holds the string label denoting the useridentity type in the database. Label = "user_identity" // FieldID holds the string denoting the id field in the database. FieldID = "id" // FieldUserID holds the string denoting the user_id field in the database. FieldUserID = "user_id" // FieldConnectorID holds the string denoting the connector_id field in the database. FieldConnectorID = "connector_id" // FieldClaimsUserID holds the string denoting the claims_user_id field in the database. FieldClaimsUserID = "claims_user_id" // FieldClaimsUsername holds the string denoting the claims_username field in the database. FieldClaimsUsername = "claims_username" // FieldClaimsPreferredUsername holds the string denoting the claims_preferred_username field in the database. FieldClaimsPreferredUsername = "claims_preferred_username" // FieldClaimsEmail holds the string denoting the claims_email field in the database. FieldClaimsEmail = "claims_email" // FieldClaimsEmailVerified holds the string denoting the claims_email_verified field in the database. FieldClaimsEmailVerified = "claims_email_verified" // FieldClaimsGroups holds the string denoting the claims_groups field in the database. FieldClaimsGroups = "claims_groups" // FieldConsents holds the string denoting the consents field in the database. FieldConsents = "consents" // FieldMfaSecrets holds the string denoting the mfa_secrets field in the database. FieldMfaSecrets = "mfa_secrets" // FieldCreatedAt holds the string denoting the created_at field in the database. FieldCreatedAt = "created_at" // FieldLastLogin holds the string denoting the last_login field in the database. FieldLastLogin = "last_login" // FieldBlockedUntil holds the string denoting the blocked_until field in the database. FieldBlockedUntil = "blocked_until" // Table holds the table name of the useridentity in the database. Table = "user_identities" ) // Columns holds all SQL columns for useridentity fields. var Columns = []string{ FieldID, FieldUserID, FieldConnectorID, FieldClaimsUserID, FieldClaimsUsername, FieldClaimsPreferredUsername, FieldClaimsEmail, FieldClaimsEmailVerified, FieldClaimsGroups, FieldConsents, FieldMfaSecrets, FieldCreatedAt, FieldLastLogin, FieldBlockedUntil, } // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { if column == Columns[i] { return true } } return false } var ( // UserIDValidator is a validator for the "user_id" field. It is called by the builders before save. UserIDValidator func(string) error // ConnectorIDValidator is a validator for the "connector_id" field. It is called by the builders before save. ConnectorIDValidator func(string) error // DefaultClaimsUserID holds the default value on creation for the "claims_user_id" field. DefaultClaimsUserID string // DefaultClaimsUsername holds the default value on creation for the "claims_username" field. DefaultClaimsUsername string // DefaultClaimsPreferredUsername holds the default value on creation for the "claims_preferred_username" field. DefaultClaimsPreferredUsername string // DefaultClaimsEmail holds the default value on creation for the "claims_email" field. DefaultClaimsEmail string // DefaultClaimsEmailVerified holds the default value on creation for the "claims_email_verified" field. DefaultClaimsEmailVerified bool // IDValidator is a validator for the "id" field. It is called by the builders before save. IDValidator func(string) error ) // OrderOption defines the ordering options for the UserIdentity queries. type OrderOption func(*sql.Selector) // ByID orders the results by the id field. func ByID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldID, opts...).ToFunc() } // ByUserID orders the results by the user_id field. func ByUserID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldUserID, opts...).ToFunc() } // ByConnectorID orders the results by the connector_id field. func ByConnectorID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldConnectorID, opts...).ToFunc() } // ByClaimsUserID orders the results by the claims_user_id field. func ByClaimsUserID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsUserID, opts...).ToFunc() } // ByClaimsUsername orders the results by the claims_username field. func ByClaimsUsername(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsUsername, opts...).ToFunc() } // ByClaimsPreferredUsername orders the results by the claims_preferred_username field. func ByClaimsPreferredUsername(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsPreferredUsername, opts...).ToFunc() } // ByClaimsEmail orders the results by the claims_email field. func ByClaimsEmail(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsEmail, opts...).ToFunc() } // ByClaimsEmailVerified orders the results by the claims_email_verified field. func ByClaimsEmailVerified(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldClaimsEmailVerified, opts...).ToFunc() } // ByCreatedAt orders the results by the created_at field. func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() } // ByLastLogin orders the results by the last_login field. func ByLastLogin(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldLastLogin, opts...).ToFunc() } // ByBlockedUntil orders the results by the blocked_until field. func ByBlockedUntil(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldBlockedUntil, opts...).ToFunc() } ================================================ FILE: storage/ent/db/useridentity/where.go ================================================ // Code generated by ent, DO NOT EDIT. package useridentity import ( "time" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/predicate" ) // ID filters vertices based on their ID field. func ID(id string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldID, id)) } // IDEQ applies the EQ predicate on the ID field. func IDEQ(id string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldID, id)) } // IDNEQ applies the NEQ predicate on the ID field. func IDNEQ(id string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldID, id)) } // IDIn applies the In predicate on the ID field. func IDIn(ids ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIn(FieldID, ids...)) } // IDNotIn applies the NotIn predicate on the ID field. func IDNotIn(ids ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotIn(FieldID, ids...)) } // IDGT applies the GT predicate on the ID field. func IDGT(id string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGT(FieldID, id)) } // IDGTE applies the GTE predicate on the ID field. func IDGTE(id string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGTE(FieldID, id)) } // IDLT applies the LT predicate on the ID field. func IDLT(id string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLT(FieldID, id)) } // IDLTE applies the LTE predicate on the ID field. func IDLTE(id string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLTE(FieldID, id)) } // IDEqualFold applies the EqualFold predicate on the ID field. func IDEqualFold(id string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEqualFold(FieldID, id)) } // IDContainsFold applies the ContainsFold predicate on the ID field. func IDContainsFold(id string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContainsFold(FieldID, id)) } // UserID applies equality check predicate on the "user_id" field. It's identical to UserIDEQ. func UserID(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldUserID, v)) } // ConnectorID applies equality check predicate on the "connector_id" field. It's identical to ConnectorIDEQ. func ConnectorID(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldConnectorID, v)) } // ClaimsUserID applies equality check predicate on the "claims_user_id" field. It's identical to ClaimsUserIDEQ. func ClaimsUserID(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldClaimsUserID, v)) } // ClaimsUsername applies equality check predicate on the "claims_username" field. It's identical to ClaimsUsernameEQ. func ClaimsUsername(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldClaimsUsername, v)) } // ClaimsPreferredUsername applies equality check predicate on the "claims_preferred_username" field. It's identical to ClaimsPreferredUsernameEQ. func ClaimsPreferredUsername(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldClaimsPreferredUsername, v)) } // ClaimsEmail applies equality check predicate on the "claims_email" field. It's identical to ClaimsEmailEQ. func ClaimsEmail(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldClaimsEmail, v)) } // ClaimsEmailVerified applies equality check predicate on the "claims_email_verified" field. It's identical to ClaimsEmailVerifiedEQ. func ClaimsEmailVerified(v bool) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldClaimsEmailVerified, v)) } // Consents applies equality check predicate on the "consents" field. It's identical to ConsentsEQ. func Consents(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldConsents, v)) } // MfaSecrets applies equality check predicate on the "mfa_secrets" field. It's identical to MfaSecretsEQ. func MfaSecrets(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldMfaSecrets, v)) } // CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. func CreatedAt(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldCreatedAt, v)) } // LastLogin applies equality check predicate on the "last_login" field. It's identical to LastLoginEQ. func LastLogin(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldLastLogin, v)) } // BlockedUntil applies equality check predicate on the "blocked_until" field. It's identical to BlockedUntilEQ. func BlockedUntil(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldBlockedUntil, v)) } // UserIDEQ applies the EQ predicate on the "user_id" field. func UserIDEQ(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldUserID, v)) } // UserIDNEQ applies the NEQ predicate on the "user_id" field. func UserIDNEQ(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldUserID, v)) } // UserIDIn applies the In predicate on the "user_id" field. func UserIDIn(vs ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIn(FieldUserID, vs...)) } // UserIDNotIn applies the NotIn predicate on the "user_id" field. func UserIDNotIn(vs ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotIn(FieldUserID, vs...)) } // UserIDGT applies the GT predicate on the "user_id" field. func UserIDGT(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGT(FieldUserID, v)) } // UserIDGTE applies the GTE predicate on the "user_id" field. func UserIDGTE(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGTE(FieldUserID, v)) } // UserIDLT applies the LT predicate on the "user_id" field. func UserIDLT(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLT(FieldUserID, v)) } // UserIDLTE applies the LTE predicate on the "user_id" field. func UserIDLTE(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLTE(FieldUserID, v)) } // UserIDContains applies the Contains predicate on the "user_id" field. func UserIDContains(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContains(FieldUserID, v)) } // UserIDHasPrefix applies the HasPrefix predicate on the "user_id" field. func UserIDHasPrefix(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldHasPrefix(FieldUserID, v)) } // UserIDHasSuffix applies the HasSuffix predicate on the "user_id" field. func UserIDHasSuffix(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldHasSuffix(FieldUserID, v)) } // UserIDEqualFold applies the EqualFold predicate on the "user_id" field. func UserIDEqualFold(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEqualFold(FieldUserID, v)) } // UserIDContainsFold applies the ContainsFold predicate on the "user_id" field. func UserIDContainsFold(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContainsFold(FieldUserID, v)) } // ConnectorIDEQ applies the EQ predicate on the "connector_id" field. func ConnectorIDEQ(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldConnectorID, v)) } // ConnectorIDNEQ applies the NEQ predicate on the "connector_id" field. func ConnectorIDNEQ(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldConnectorID, v)) } // ConnectorIDIn applies the In predicate on the "connector_id" field. func ConnectorIDIn(vs ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIn(FieldConnectorID, vs...)) } // ConnectorIDNotIn applies the NotIn predicate on the "connector_id" field. func ConnectorIDNotIn(vs ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotIn(FieldConnectorID, vs...)) } // ConnectorIDGT applies the GT predicate on the "connector_id" field. func ConnectorIDGT(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGT(FieldConnectorID, v)) } // ConnectorIDGTE applies the GTE predicate on the "connector_id" field. func ConnectorIDGTE(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGTE(FieldConnectorID, v)) } // ConnectorIDLT applies the LT predicate on the "connector_id" field. func ConnectorIDLT(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLT(FieldConnectorID, v)) } // ConnectorIDLTE applies the LTE predicate on the "connector_id" field. func ConnectorIDLTE(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLTE(FieldConnectorID, v)) } // ConnectorIDContains applies the Contains predicate on the "connector_id" field. func ConnectorIDContains(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContains(FieldConnectorID, v)) } // ConnectorIDHasPrefix applies the HasPrefix predicate on the "connector_id" field. func ConnectorIDHasPrefix(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldHasPrefix(FieldConnectorID, v)) } // ConnectorIDHasSuffix applies the HasSuffix predicate on the "connector_id" field. func ConnectorIDHasSuffix(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldHasSuffix(FieldConnectorID, v)) } // ConnectorIDEqualFold applies the EqualFold predicate on the "connector_id" field. func ConnectorIDEqualFold(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEqualFold(FieldConnectorID, v)) } // ConnectorIDContainsFold applies the ContainsFold predicate on the "connector_id" field. func ConnectorIDContainsFold(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContainsFold(FieldConnectorID, v)) } // ClaimsUserIDEQ applies the EQ predicate on the "claims_user_id" field. func ClaimsUserIDEQ(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldClaimsUserID, v)) } // ClaimsUserIDNEQ applies the NEQ predicate on the "claims_user_id" field. func ClaimsUserIDNEQ(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldClaimsUserID, v)) } // ClaimsUserIDIn applies the In predicate on the "claims_user_id" field. func ClaimsUserIDIn(vs ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIn(FieldClaimsUserID, vs...)) } // ClaimsUserIDNotIn applies the NotIn predicate on the "claims_user_id" field. func ClaimsUserIDNotIn(vs ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotIn(FieldClaimsUserID, vs...)) } // ClaimsUserIDGT applies the GT predicate on the "claims_user_id" field. func ClaimsUserIDGT(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGT(FieldClaimsUserID, v)) } // ClaimsUserIDGTE applies the GTE predicate on the "claims_user_id" field. func ClaimsUserIDGTE(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGTE(FieldClaimsUserID, v)) } // ClaimsUserIDLT applies the LT predicate on the "claims_user_id" field. func ClaimsUserIDLT(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLT(FieldClaimsUserID, v)) } // ClaimsUserIDLTE applies the LTE predicate on the "claims_user_id" field. func ClaimsUserIDLTE(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLTE(FieldClaimsUserID, v)) } // ClaimsUserIDContains applies the Contains predicate on the "claims_user_id" field. func ClaimsUserIDContains(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContains(FieldClaimsUserID, v)) } // ClaimsUserIDHasPrefix applies the HasPrefix predicate on the "claims_user_id" field. func ClaimsUserIDHasPrefix(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldHasPrefix(FieldClaimsUserID, v)) } // ClaimsUserIDHasSuffix applies the HasSuffix predicate on the "claims_user_id" field. func ClaimsUserIDHasSuffix(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldHasSuffix(FieldClaimsUserID, v)) } // ClaimsUserIDEqualFold applies the EqualFold predicate on the "claims_user_id" field. func ClaimsUserIDEqualFold(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEqualFold(FieldClaimsUserID, v)) } // ClaimsUserIDContainsFold applies the ContainsFold predicate on the "claims_user_id" field. func ClaimsUserIDContainsFold(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContainsFold(FieldClaimsUserID, v)) } // ClaimsUsernameEQ applies the EQ predicate on the "claims_username" field. func ClaimsUsernameEQ(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldClaimsUsername, v)) } // ClaimsUsernameNEQ applies the NEQ predicate on the "claims_username" field. func ClaimsUsernameNEQ(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldClaimsUsername, v)) } // ClaimsUsernameIn applies the In predicate on the "claims_username" field. func ClaimsUsernameIn(vs ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIn(FieldClaimsUsername, vs...)) } // ClaimsUsernameNotIn applies the NotIn predicate on the "claims_username" field. func ClaimsUsernameNotIn(vs ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotIn(FieldClaimsUsername, vs...)) } // ClaimsUsernameGT applies the GT predicate on the "claims_username" field. func ClaimsUsernameGT(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGT(FieldClaimsUsername, v)) } // ClaimsUsernameGTE applies the GTE predicate on the "claims_username" field. func ClaimsUsernameGTE(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGTE(FieldClaimsUsername, v)) } // ClaimsUsernameLT applies the LT predicate on the "claims_username" field. func ClaimsUsernameLT(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLT(FieldClaimsUsername, v)) } // ClaimsUsernameLTE applies the LTE predicate on the "claims_username" field. func ClaimsUsernameLTE(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLTE(FieldClaimsUsername, v)) } // ClaimsUsernameContains applies the Contains predicate on the "claims_username" field. func ClaimsUsernameContains(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContains(FieldClaimsUsername, v)) } // ClaimsUsernameHasPrefix applies the HasPrefix predicate on the "claims_username" field. func ClaimsUsernameHasPrefix(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldHasPrefix(FieldClaimsUsername, v)) } // ClaimsUsernameHasSuffix applies the HasSuffix predicate on the "claims_username" field. func ClaimsUsernameHasSuffix(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldHasSuffix(FieldClaimsUsername, v)) } // ClaimsUsernameEqualFold applies the EqualFold predicate on the "claims_username" field. func ClaimsUsernameEqualFold(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEqualFold(FieldClaimsUsername, v)) } // ClaimsUsernameContainsFold applies the ContainsFold predicate on the "claims_username" field. func ClaimsUsernameContainsFold(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContainsFold(FieldClaimsUsername, v)) } // ClaimsPreferredUsernameEQ applies the EQ predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameEQ(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameNEQ applies the NEQ predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameNEQ(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameIn applies the In predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameIn(vs ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIn(FieldClaimsPreferredUsername, vs...)) } // ClaimsPreferredUsernameNotIn applies the NotIn predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameNotIn(vs ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotIn(FieldClaimsPreferredUsername, vs...)) } // ClaimsPreferredUsernameGT applies the GT predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameGT(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGT(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameGTE applies the GTE predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameGTE(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGTE(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameLT applies the LT predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameLT(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLT(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameLTE applies the LTE predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameLTE(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLTE(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameContains applies the Contains predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameContains(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContains(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameHasPrefix applies the HasPrefix predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameHasPrefix(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldHasPrefix(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameHasSuffix applies the HasSuffix predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameHasSuffix(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldHasSuffix(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameEqualFold applies the EqualFold predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameEqualFold(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEqualFold(FieldClaimsPreferredUsername, v)) } // ClaimsPreferredUsernameContainsFold applies the ContainsFold predicate on the "claims_preferred_username" field. func ClaimsPreferredUsernameContainsFold(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContainsFold(FieldClaimsPreferredUsername, v)) } // ClaimsEmailEQ applies the EQ predicate on the "claims_email" field. func ClaimsEmailEQ(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldClaimsEmail, v)) } // ClaimsEmailNEQ applies the NEQ predicate on the "claims_email" field. func ClaimsEmailNEQ(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldClaimsEmail, v)) } // ClaimsEmailIn applies the In predicate on the "claims_email" field. func ClaimsEmailIn(vs ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIn(FieldClaimsEmail, vs...)) } // ClaimsEmailNotIn applies the NotIn predicate on the "claims_email" field. func ClaimsEmailNotIn(vs ...string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotIn(FieldClaimsEmail, vs...)) } // ClaimsEmailGT applies the GT predicate on the "claims_email" field. func ClaimsEmailGT(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGT(FieldClaimsEmail, v)) } // ClaimsEmailGTE applies the GTE predicate on the "claims_email" field. func ClaimsEmailGTE(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGTE(FieldClaimsEmail, v)) } // ClaimsEmailLT applies the LT predicate on the "claims_email" field. func ClaimsEmailLT(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLT(FieldClaimsEmail, v)) } // ClaimsEmailLTE applies the LTE predicate on the "claims_email" field. func ClaimsEmailLTE(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLTE(FieldClaimsEmail, v)) } // ClaimsEmailContains applies the Contains predicate on the "claims_email" field. func ClaimsEmailContains(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContains(FieldClaimsEmail, v)) } // ClaimsEmailHasPrefix applies the HasPrefix predicate on the "claims_email" field. func ClaimsEmailHasPrefix(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldHasPrefix(FieldClaimsEmail, v)) } // ClaimsEmailHasSuffix applies the HasSuffix predicate on the "claims_email" field. func ClaimsEmailHasSuffix(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldHasSuffix(FieldClaimsEmail, v)) } // ClaimsEmailEqualFold applies the EqualFold predicate on the "claims_email" field. func ClaimsEmailEqualFold(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEqualFold(FieldClaimsEmail, v)) } // ClaimsEmailContainsFold applies the ContainsFold predicate on the "claims_email" field. func ClaimsEmailContainsFold(v string) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldContainsFold(FieldClaimsEmail, v)) } // ClaimsEmailVerifiedEQ applies the EQ predicate on the "claims_email_verified" field. func ClaimsEmailVerifiedEQ(v bool) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldClaimsEmailVerified, v)) } // ClaimsEmailVerifiedNEQ applies the NEQ predicate on the "claims_email_verified" field. func ClaimsEmailVerifiedNEQ(v bool) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldClaimsEmailVerified, v)) } // ClaimsGroupsIsNil applies the IsNil predicate on the "claims_groups" field. func ClaimsGroupsIsNil() predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIsNull(FieldClaimsGroups)) } // ClaimsGroupsNotNil applies the NotNil predicate on the "claims_groups" field. func ClaimsGroupsNotNil() predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotNull(FieldClaimsGroups)) } // ConsentsEQ applies the EQ predicate on the "consents" field. func ConsentsEQ(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldConsents, v)) } // ConsentsNEQ applies the NEQ predicate on the "consents" field. func ConsentsNEQ(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldConsents, v)) } // ConsentsIn applies the In predicate on the "consents" field. func ConsentsIn(vs ...[]byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIn(FieldConsents, vs...)) } // ConsentsNotIn applies the NotIn predicate on the "consents" field. func ConsentsNotIn(vs ...[]byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotIn(FieldConsents, vs...)) } // ConsentsGT applies the GT predicate on the "consents" field. func ConsentsGT(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGT(FieldConsents, v)) } // ConsentsGTE applies the GTE predicate on the "consents" field. func ConsentsGTE(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGTE(FieldConsents, v)) } // ConsentsLT applies the LT predicate on the "consents" field. func ConsentsLT(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLT(FieldConsents, v)) } // ConsentsLTE applies the LTE predicate on the "consents" field. func ConsentsLTE(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLTE(FieldConsents, v)) } // MfaSecretsEQ applies the EQ predicate on the "mfa_secrets" field. func MfaSecretsEQ(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldMfaSecrets, v)) } // MfaSecretsNEQ applies the NEQ predicate on the "mfa_secrets" field. func MfaSecretsNEQ(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldMfaSecrets, v)) } // MfaSecretsIn applies the In predicate on the "mfa_secrets" field. func MfaSecretsIn(vs ...[]byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIn(FieldMfaSecrets, vs...)) } // MfaSecretsNotIn applies the NotIn predicate on the "mfa_secrets" field. func MfaSecretsNotIn(vs ...[]byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotIn(FieldMfaSecrets, vs...)) } // MfaSecretsGT applies the GT predicate on the "mfa_secrets" field. func MfaSecretsGT(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGT(FieldMfaSecrets, v)) } // MfaSecretsGTE applies the GTE predicate on the "mfa_secrets" field. func MfaSecretsGTE(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGTE(FieldMfaSecrets, v)) } // MfaSecretsLT applies the LT predicate on the "mfa_secrets" field. func MfaSecretsLT(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLT(FieldMfaSecrets, v)) } // MfaSecretsLTE applies the LTE predicate on the "mfa_secrets" field. func MfaSecretsLTE(v []byte) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLTE(FieldMfaSecrets, v)) } // MfaSecretsIsNil applies the IsNil predicate on the "mfa_secrets" field. func MfaSecretsIsNil() predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIsNull(FieldMfaSecrets)) } // MfaSecretsNotNil applies the NotNil predicate on the "mfa_secrets" field. func MfaSecretsNotNil() predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotNull(FieldMfaSecrets)) } // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldCreatedAt, v)) } // CreatedAtNEQ applies the NEQ predicate on the "created_at" field. func CreatedAtNEQ(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldCreatedAt, v)) } // CreatedAtIn applies the In predicate on the "created_at" field. func CreatedAtIn(vs ...time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIn(FieldCreatedAt, vs...)) } // CreatedAtNotIn applies the NotIn predicate on the "created_at" field. func CreatedAtNotIn(vs ...time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotIn(FieldCreatedAt, vs...)) } // CreatedAtGT applies the GT predicate on the "created_at" field. func CreatedAtGT(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGT(FieldCreatedAt, v)) } // CreatedAtGTE applies the GTE predicate on the "created_at" field. func CreatedAtGTE(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGTE(FieldCreatedAt, v)) } // CreatedAtLT applies the LT predicate on the "created_at" field. func CreatedAtLT(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLT(FieldCreatedAt, v)) } // CreatedAtLTE applies the LTE predicate on the "created_at" field. func CreatedAtLTE(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLTE(FieldCreatedAt, v)) } // LastLoginEQ applies the EQ predicate on the "last_login" field. func LastLoginEQ(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldLastLogin, v)) } // LastLoginNEQ applies the NEQ predicate on the "last_login" field. func LastLoginNEQ(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldLastLogin, v)) } // LastLoginIn applies the In predicate on the "last_login" field. func LastLoginIn(vs ...time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIn(FieldLastLogin, vs...)) } // LastLoginNotIn applies the NotIn predicate on the "last_login" field. func LastLoginNotIn(vs ...time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotIn(FieldLastLogin, vs...)) } // LastLoginGT applies the GT predicate on the "last_login" field. func LastLoginGT(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGT(FieldLastLogin, v)) } // LastLoginGTE applies the GTE predicate on the "last_login" field. func LastLoginGTE(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGTE(FieldLastLogin, v)) } // LastLoginLT applies the LT predicate on the "last_login" field. func LastLoginLT(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLT(FieldLastLogin, v)) } // LastLoginLTE applies the LTE predicate on the "last_login" field. func LastLoginLTE(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLTE(FieldLastLogin, v)) } // BlockedUntilEQ applies the EQ predicate on the "blocked_until" field. func BlockedUntilEQ(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldEQ(FieldBlockedUntil, v)) } // BlockedUntilNEQ applies the NEQ predicate on the "blocked_until" field. func BlockedUntilNEQ(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNEQ(FieldBlockedUntil, v)) } // BlockedUntilIn applies the In predicate on the "blocked_until" field. func BlockedUntilIn(vs ...time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldIn(FieldBlockedUntil, vs...)) } // BlockedUntilNotIn applies the NotIn predicate on the "blocked_until" field. func BlockedUntilNotIn(vs ...time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldNotIn(FieldBlockedUntil, vs...)) } // BlockedUntilGT applies the GT predicate on the "blocked_until" field. func BlockedUntilGT(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGT(FieldBlockedUntil, v)) } // BlockedUntilGTE applies the GTE predicate on the "blocked_until" field. func BlockedUntilGTE(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldGTE(FieldBlockedUntil, v)) } // BlockedUntilLT applies the LT predicate on the "blocked_until" field. func BlockedUntilLT(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLT(FieldBlockedUntil, v)) } // BlockedUntilLTE applies the LTE predicate on the "blocked_until" field. func BlockedUntilLTE(v time.Time) predicate.UserIdentity { return predicate.UserIdentity(sql.FieldLTE(FieldBlockedUntil, v)) } // And groups predicates with the AND operator between them. func And(predicates ...predicate.UserIdentity) predicate.UserIdentity { return predicate.UserIdentity(sql.AndPredicates(predicates...)) } // Or groups predicates with the OR operator between them. func Or(predicates ...predicate.UserIdentity) predicate.UserIdentity { return predicate.UserIdentity(sql.OrPredicates(predicates...)) } // Not applies the not operator on the given predicate. func Not(p predicate.UserIdentity) predicate.UserIdentity { return predicate.UserIdentity(sql.NotPredicates(p)) } ================================================ FILE: storage/ent/db/useridentity.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "encoding/json" "fmt" "strings" "time" "entgo.io/ent" "entgo.io/ent/dialect/sql" "github.com/dexidp/dex/storage/ent/db/useridentity" ) // UserIdentity is the model entity for the UserIdentity schema. type UserIdentity struct { config `json:"-"` // ID of the ent. ID string `json:"id,omitempty"` // UserID holds the value of the "user_id" field. UserID string `json:"user_id,omitempty"` // ConnectorID holds the value of the "connector_id" field. ConnectorID string `json:"connector_id,omitempty"` // ClaimsUserID holds the value of the "claims_user_id" field. ClaimsUserID string `json:"claims_user_id,omitempty"` // ClaimsUsername holds the value of the "claims_username" field. ClaimsUsername string `json:"claims_username,omitempty"` // ClaimsPreferredUsername holds the value of the "claims_preferred_username" field. ClaimsPreferredUsername string `json:"claims_preferred_username,omitempty"` // ClaimsEmail holds the value of the "claims_email" field. ClaimsEmail string `json:"claims_email,omitempty"` // ClaimsEmailVerified holds the value of the "claims_email_verified" field. ClaimsEmailVerified bool `json:"claims_email_verified,omitempty"` // ClaimsGroups holds the value of the "claims_groups" field. ClaimsGroups []string `json:"claims_groups,omitempty"` // Consents holds the value of the "consents" field. Consents []byte `json:"consents,omitempty"` // MfaSecrets holds the value of the "mfa_secrets" field. MfaSecrets *[]byte `json:"mfa_secrets,omitempty"` // CreatedAt holds the value of the "created_at" field. CreatedAt time.Time `json:"created_at,omitempty"` // LastLogin holds the value of the "last_login" field. LastLogin time.Time `json:"last_login,omitempty"` // BlockedUntil holds the value of the "blocked_until" field. BlockedUntil time.Time `json:"blocked_until,omitempty"` selectValues sql.SelectValues } // scanValues returns the types for scanning values from sql.Rows. func (*UserIdentity) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { case useridentity.FieldClaimsGroups, useridentity.FieldConsents, useridentity.FieldMfaSecrets: values[i] = new([]byte) case useridentity.FieldClaimsEmailVerified: values[i] = new(sql.NullBool) case useridentity.FieldID, useridentity.FieldUserID, useridentity.FieldConnectorID, useridentity.FieldClaimsUserID, useridentity.FieldClaimsUsername, useridentity.FieldClaimsPreferredUsername, useridentity.FieldClaimsEmail: values[i] = new(sql.NullString) case useridentity.FieldCreatedAt, useridentity.FieldLastLogin, useridentity.FieldBlockedUntil: values[i] = new(sql.NullTime) default: values[i] = new(sql.UnknownType) } } return values, nil } // assignValues assigns the values that were returned from sql.Rows (after scanning) // to the UserIdentity fields. func (_m *UserIdentity) assignValues(columns []string, values []any) error { if m, n := len(values), len(columns); m < n { return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) } for i := range columns { switch columns[i] { case useridentity.FieldID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field id", values[i]) } else if value.Valid { _m.ID = value.String } case useridentity.FieldUserID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field user_id", values[i]) } else if value.Valid { _m.UserID = value.String } case useridentity.FieldConnectorID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field connector_id", values[i]) } else if value.Valid { _m.ConnectorID = value.String } case useridentity.FieldClaimsUserID: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_user_id", values[i]) } else if value.Valid { _m.ClaimsUserID = value.String } case useridentity.FieldClaimsUsername: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_username", values[i]) } else if value.Valid { _m.ClaimsUsername = value.String } case useridentity.FieldClaimsPreferredUsername: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_preferred_username", values[i]) } else if value.Valid { _m.ClaimsPreferredUsername = value.String } case useridentity.FieldClaimsEmail: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field claims_email", values[i]) } else if value.Valid { _m.ClaimsEmail = value.String } case useridentity.FieldClaimsEmailVerified: if value, ok := values[i].(*sql.NullBool); !ok { return fmt.Errorf("unexpected type %T for field claims_email_verified", values[i]) } else if value.Valid { _m.ClaimsEmailVerified = value.Bool } case useridentity.FieldClaimsGroups: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field claims_groups", values[i]) } else if value != nil && len(*value) > 0 { if err := json.Unmarshal(*value, &_m.ClaimsGroups); err != nil { return fmt.Errorf("unmarshal field claims_groups: %w", err) } } case useridentity.FieldConsents: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field consents", values[i]) } else if value != nil { _m.Consents = *value } case useridentity.FieldMfaSecrets: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field mfa_secrets", values[i]) } else if value != nil { _m.MfaSecrets = value } case useridentity.FieldCreatedAt: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field created_at", values[i]) } else if value.Valid { _m.CreatedAt = value.Time } case useridentity.FieldLastLogin: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field last_login", values[i]) } else if value.Valid { _m.LastLogin = value.Time } case useridentity.FieldBlockedUntil: if value, ok := values[i].(*sql.NullTime); !ok { return fmt.Errorf("unexpected type %T for field blocked_until", values[i]) } else if value.Valid { _m.BlockedUntil = value.Time } default: _m.selectValues.Set(columns[i], values[i]) } } return nil } // Value returns the ent.Value that was dynamically selected and assigned to the UserIdentity. // This includes values selected through modifiers, order, etc. func (_m *UserIdentity) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } // Update returns a builder for updating this UserIdentity. // Note that you need to call UserIdentity.Unwrap() before calling this method if this UserIdentity // was returned from a transaction, and the transaction was committed or rolled back. func (_m *UserIdentity) Update() *UserIdentityUpdateOne { return NewUserIdentityClient(_m.config).UpdateOne(_m) } // Unwrap unwraps the UserIdentity entity that was returned from a transaction after it was closed, // so that all future queries will be executed through the driver which created the transaction. func (_m *UserIdentity) Unwrap() *UserIdentity { _tx, ok := _m.config.driver.(*txDriver) if !ok { panic("db: UserIdentity is not a transactional entity") } _m.config.driver = _tx.drv return _m } // String implements the fmt.Stringer. func (_m *UserIdentity) String() string { var builder strings.Builder builder.WriteString("UserIdentity(") builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) builder.WriteString("user_id=") builder.WriteString(_m.UserID) builder.WriteString(", ") builder.WriteString("connector_id=") builder.WriteString(_m.ConnectorID) builder.WriteString(", ") builder.WriteString("claims_user_id=") builder.WriteString(_m.ClaimsUserID) builder.WriteString(", ") builder.WriteString("claims_username=") builder.WriteString(_m.ClaimsUsername) builder.WriteString(", ") builder.WriteString("claims_preferred_username=") builder.WriteString(_m.ClaimsPreferredUsername) builder.WriteString(", ") builder.WriteString("claims_email=") builder.WriteString(_m.ClaimsEmail) builder.WriteString(", ") builder.WriteString("claims_email_verified=") builder.WriteString(fmt.Sprintf("%v", _m.ClaimsEmailVerified)) builder.WriteString(", ") builder.WriteString("claims_groups=") builder.WriteString(fmt.Sprintf("%v", _m.ClaimsGroups)) builder.WriteString(", ") builder.WriteString("consents=") builder.WriteString(fmt.Sprintf("%v", _m.Consents)) builder.WriteString(", ") if v := _m.MfaSecrets; v != nil { builder.WriteString("mfa_secrets=") builder.WriteString(fmt.Sprintf("%v", *v)) } builder.WriteString(", ") builder.WriteString("created_at=") builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("last_login=") builder.WriteString(_m.LastLogin.Format(time.ANSIC)) builder.WriteString(", ") builder.WriteString("blocked_until=") builder.WriteString(_m.BlockedUntil.Format(time.ANSIC)) builder.WriteByte(')') return builder.String() } // UserIdentities is a parsable slice of UserIdentity. type UserIdentities []*UserIdentity ================================================ FILE: storage/ent/db/useridentity_create.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/useridentity" ) // UserIdentityCreate is the builder for creating a UserIdentity entity. type UserIdentityCreate struct { config mutation *UserIdentityMutation hooks []Hook } // SetUserID sets the "user_id" field. func (_c *UserIdentityCreate) SetUserID(v string) *UserIdentityCreate { _c.mutation.SetUserID(v) return _c } // SetConnectorID sets the "connector_id" field. func (_c *UserIdentityCreate) SetConnectorID(v string) *UserIdentityCreate { _c.mutation.SetConnectorID(v) return _c } // SetClaimsUserID sets the "claims_user_id" field. func (_c *UserIdentityCreate) SetClaimsUserID(v string) *UserIdentityCreate { _c.mutation.SetClaimsUserID(v) return _c } // SetNillableClaimsUserID sets the "claims_user_id" field if the given value is not nil. func (_c *UserIdentityCreate) SetNillableClaimsUserID(v *string) *UserIdentityCreate { if v != nil { _c.SetClaimsUserID(*v) } return _c } // SetClaimsUsername sets the "claims_username" field. func (_c *UserIdentityCreate) SetClaimsUsername(v string) *UserIdentityCreate { _c.mutation.SetClaimsUsername(v) return _c } // SetNillableClaimsUsername sets the "claims_username" field if the given value is not nil. func (_c *UserIdentityCreate) SetNillableClaimsUsername(v *string) *UserIdentityCreate { if v != nil { _c.SetClaimsUsername(*v) } return _c } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (_c *UserIdentityCreate) SetClaimsPreferredUsername(v string) *UserIdentityCreate { _c.mutation.SetClaimsPreferredUsername(v) return _c } // SetNillableClaimsPreferredUsername sets the "claims_preferred_username" field if the given value is not nil. func (_c *UserIdentityCreate) SetNillableClaimsPreferredUsername(v *string) *UserIdentityCreate { if v != nil { _c.SetClaimsPreferredUsername(*v) } return _c } // SetClaimsEmail sets the "claims_email" field. func (_c *UserIdentityCreate) SetClaimsEmail(v string) *UserIdentityCreate { _c.mutation.SetClaimsEmail(v) return _c } // SetNillableClaimsEmail sets the "claims_email" field if the given value is not nil. func (_c *UserIdentityCreate) SetNillableClaimsEmail(v *string) *UserIdentityCreate { if v != nil { _c.SetClaimsEmail(*v) } return _c } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (_c *UserIdentityCreate) SetClaimsEmailVerified(v bool) *UserIdentityCreate { _c.mutation.SetClaimsEmailVerified(v) return _c } // SetNillableClaimsEmailVerified sets the "claims_email_verified" field if the given value is not nil. func (_c *UserIdentityCreate) SetNillableClaimsEmailVerified(v *bool) *UserIdentityCreate { if v != nil { _c.SetClaimsEmailVerified(*v) } return _c } // SetClaimsGroups sets the "claims_groups" field. func (_c *UserIdentityCreate) SetClaimsGroups(v []string) *UserIdentityCreate { _c.mutation.SetClaimsGroups(v) return _c } // SetConsents sets the "consents" field. func (_c *UserIdentityCreate) SetConsents(v []byte) *UserIdentityCreate { _c.mutation.SetConsents(v) return _c } // SetMfaSecrets sets the "mfa_secrets" field. func (_c *UserIdentityCreate) SetMfaSecrets(v []byte) *UserIdentityCreate { _c.mutation.SetMfaSecrets(v) return _c } // SetCreatedAt sets the "created_at" field. func (_c *UserIdentityCreate) SetCreatedAt(v time.Time) *UserIdentityCreate { _c.mutation.SetCreatedAt(v) return _c } // SetLastLogin sets the "last_login" field. func (_c *UserIdentityCreate) SetLastLogin(v time.Time) *UserIdentityCreate { _c.mutation.SetLastLogin(v) return _c } // SetBlockedUntil sets the "blocked_until" field. func (_c *UserIdentityCreate) SetBlockedUntil(v time.Time) *UserIdentityCreate { _c.mutation.SetBlockedUntil(v) return _c } // SetID sets the "id" field. func (_c *UserIdentityCreate) SetID(v string) *UserIdentityCreate { _c.mutation.SetID(v) return _c } // Mutation returns the UserIdentityMutation object of the builder. func (_c *UserIdentityCreate) Mutation() *UserIdentityMutation { return _c.mutation } // Save creates the UserIdentity in the database. func (_c *UserIdentityCreate) Save(ctx context.Context) (*UserIdentity, error) { _c.defaults() return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) } // SaveX calls Save and panics if Save returns an error. func (_c *UserIdentityCreate) SaveX(ctx context.Context) *UserIdentity { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *UserIdentityCreate) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *UserIdentityCreate) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } // defaults sets the default values of the builder before save. func (_c *UserIdentityCreate) defaults() { if _, ok := _c.mutation.ClaimsUserID(); !ok { v := useridentity.DefaultClaimsUserID _c.mutation.SetClaimsUserID(v) } if _, ok := _c.mutation.ClaimsUsername(); !ok { v := useridentity.DefaultClaimsUsername _c.mutation.SetClaimsUsername(v) } if _, ok := _c.mutation.ClaimsPreferredUsername(); !ok { v := useridentity.DefaultClaimsPreferredUsername _c.mutation.SetClaimsPreferredUsername(v) } if _, ok := _c.mutation.ClaimsEmail(); !ok { v := useridentity.DefaultClaimsEmail _c.mutation.SetClaimsEmail(v) } if _, ok := _c.mutation.ClaimsEmailVerified(); !ok { v := useridentity.DefaultClaimsEmailVerified _c.mutation.SetClaimsEmailVerified(v) } } // check runs all checks and user-defined validators on the builder. func (_c *UserIdentityCreate) check() error { if _, ok := _c.mutation.UserID(); !ok { return &ValidationError{Name: "user_id", err: errors.New(`db: missing required field "UserIdentity.user_id"`)} } if v, ok := _c.mutation.UserID(); ok { if err := useridentity.UserIDValidator(v); err != nil { return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "UserIdentity.user_id": %w`, err)} } } if _, ok := _c.mutation.ConnectorID(); !ok { return &ValidationError{Name: "connector_id", err: errors.New(`db: missing required field "UserIdentity.connector_id"`)} } if v, ok := _c.mutation.ConnectorID(); ok { if err := useridentity.ConnectorIDValidator(v); err != nil { return &ValidationError{Name: "connector_id", err: fmt.Errorf(`db: validator failed for field "UserIdentity.connector_id": %w`, err)} } } if _, ok := _c.mutation.ClaimsUserID(); !ok { return &ValidationError{Name: "claims_user_id", err: errors.New(`db: missing required field "UserIdentity.claims_user_id"`)} } if _, ok := _c.mutation.ClaimsUsername(); !ok { return &ValidationError{Name: "claims_username", err: errors.New(`db: missing required field "UserIdentity.claims_username"`)} } if _, ok := _c.mutation.ClaimsPreferredUsername(); !ok { return &ValidationError{Name: "claims_preferred_username", err: errors.New(`db: missing required field "UserIdentity.claims_preferred_username"`)} } if _, ok := _c.mutation.ClaimsEmail(); !ok { return &ValidationError{Name: "claims_email", err: errors.New(`db: missing required field "UserIdentity.claims_email"`)} } if _, ok := _c.mutation.ClaimsEmailVerified(); !ok { return &ValidationError{Name: "claims_email_verified", err: errors.New(`db: missing required field "UserIdentity.claims_email_verified"`)} } if _, ok := _c.mutation.Consents(); !ok { return &ValidationError{Name: "consents", err: errors.New(`db: missing required field "UserIdentity.consents"`)} } if _, ok := _c.mutation.CreatedAt(); !ok { return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "UserIdentity.created_at"`)} } if _, ok := _c.mutation.LastLogin(); !ok { return &ValidationError{Name: "last_login", err: errors.New(`db: missing required field "UserIdentity.last_login"`)} } if _, ok := _c.mutation.BlockedUntil(); !ok { return &ValidationError{Name: "blocked_until", err: errors.New(`db: missing required field "UserIdentity.blocked_until"`)} } if v, ok := _c.mutation.ID(); ok { if err := useridentity.IDValidator(v); err != nil { return &ValidationError{Name: "id", err: fmt.Errorf(`db: validator failed for field "UserIdentity.id": %w`, err)} } } return nil } func (_c *UserIdentityCreate) sqlSave(ctx context.Context) (*UserIdentity, error) { if err := _c.check(); err != nil { return nil, err } _node, _spec := _c.createSpec() if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } if _spec.ID.Value != nil { if id, ok := _spec.ID.Value.(string); ok { _node.ID = id } else { return nil, fmt.Errorf("unexpected UserIdentity.ID type: %T", _spec.ID.Value) } } _c.mutation.id = &_node.ID _c.mutation.done = true return _node, nil } func (_c *UserIdentityCreate) createSpec() (*UserIdentity, *sqlgraph.CreateSpec) { var ( _node = &UserIdentity{config: _c.config} _spec = sqlgraph.NewCreateSpec(useridentity.Table, sqlgraph.NewFieldSpec(useridentity.FieldID, field.TypeString)) ) if id, ok := _c.mutation.ID(); ok { _node.ID = id _spec.ID.Value = id } if value, ok := _c.mutation.UserID(); ok { _spec.SetField(useridentity.FieldUserID, field.TypeString, value) _node.UserID = value } if value, ok := _c.mutation.ConnectorID(); ok { _spec.SetField(useridentity.FieldConnectorID, field.TypeString, value) _node.ConnectorID = value } if value, ok := _c.mutation.ClaimsUserID(); ok { _spec.SetField(useridentity.FieldClaimsUserID, field.TypeString, value) _node.ClaimsUserID = value } if value, ok := _c.mutation.ClaimsUsername(); ok { _spec.SetField(useridentity.FieldClaimsUsername, field.TypeString, value) _node.ClaimsUsername = value } if value, ok := _c.mutation.ClaimsPreferredUsername(); ok { _spec.SetField(useridentity.FieldClaimsPreferredUsername, field.TypeString, value) _node.ClaimsPreferredUsername = value } if value, ok := _c.mutation.ClaimsEmail(); ok { _spec.SetField(useridentity.FieldClaimsEmail, field.TypeString, value) _node.ClaimsEmail = value } if value, ok := _c.mutation.ClaimsEmailVerified(); ok { _spec.SetField(useridentity.FieldClaimsEmailVerified, field.TypeBool, value) _node.ClaimsEmailVerified = value } if value, ok := _c.mutation.ClaimsGroups(); ok { _spec.SetField(useridentity.FieldClaimsGroups, field.TypeJSON, value) _node.ClaimsGroups = value } if value, ok := _c.mutation.Consents(); ok { _spec.SetField(useridentity.FieldConsents, field.TypeBytes, value) _node.Consents = value } if value, ok := _c.mutation.MfaSecrets(); ok { _spec.SetField(useridentity.FieldMfaSecrets, field.TypeBytes, value) _node.MfaSecrets = &value } if value, ok := _c.mutation.CreatedAt(); ok { _spec.SetField(useridentity.FieldCreatedAt, field.TypeTime, value) _node.CreatedAt = value } if value, ok := _c.mutation.LastLogin(); ok { _spec.SetField(useridentity.FieldLastLogin, field.TypeTime, value) _node.LastLogin = value } if value, ok := _c.mutation.BlockedUntil(); ok { _spec.SetField(useridentity.FieldBlockedUntil, field.TypeTime, value) _node.BlockedUntil = value } return _node, _spec } // UserIdentityCreateBulk is the builder for creating many UserIdentity entities in bulk. type UserIdentityCreateBulk struct { config err error builders []*UserIdentityCreate } // Save creates the UserIdentity entities in the database. func (_c *UserIdentityCreateBulk) Save(ctx context.Context) ([]*UserIdentity, error) { if _c.err != nil { return nil, _c.err } specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) nodes := make([]*UserIdentity, len(_c.builders)) mutators := make([]Mutator, len(_c.builders)) for i := range _c.builders { func(i int, root context.Context) { builder := _c.builders[i] builder.defaults() var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { mutation, ok := m.(*UserIdentityMutation) if !ok { return nil, fmt.Errorf("unexpected mutation type %T", m) } if err := builder.check(); err != nil { return nil, err } builder.mutation = mutation var err error nodes[i], specs[i] = builder.createSpec() if i < len(mutators)-1 { _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) } else { spec := &sqlgraph.BatchCreateSpec{Nodes: specs} // Invoke the actual operation on the latest mutation in the chain. if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } } } if err != nil { return nil, err } mutation.id = &nodes[i].ID mutation.done = true return nodes[i], nil }) for i := len(builder.hooks) - 1; i >= 0; i-- { mut = builder.hooks[i](mut) } mutators[i] = mut }(i, ctx) } if len(mutators) > 0 { if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { return nil, err } } return nodes, nil } // SaveX is like Save, but panics if an error occurs. func (_c *UserIdentityCreateBulk) SaveX(ctx context.Context) []*UserIdentity { v, err := _c.Save(ctx) if err != nil { panic(err) } return v } // Exec executes the query. func (_c *UserIdentityCreateBulk) Exec(ctx context.Context) error { _, err := _c.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_c *UserIdentityCreateBulk) ExecX(ctx context.Context) { if err := _c.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/useridentity_delete.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/predicate" "github.com/dexidp/dex/storage/ent/db/useridentity" ) // UserIdentityDelete is the builder for deleting a UserIdentity entity. type UserIdentityDelete struct { config hooks []Hook mutation *UserIdentityMutation } // Where appends a list predicates to the UserIdentityDelete builder. func (_d *UserIdentityDelete) Where(ps ...predicate.UserIdentity) *UserIdentityDelete { _d.mutation.Where(ps...) return _d } // Exec executes the deletion query and returns how many vertices were deleted. func (_d *UserIdentityDelete) Exec(ctx context.Context) (int, error) { return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) } // ExecX is like Exec, but panics if an error occurs. func (_d *UserIdentityDelete) ExecX(ctx context.Context) int { n, err := _d.Exec(ctx) if err != nil { panic(err) } return n } func (_d *UserIdentityDelete) sqlExec(ctx context.Context) (int, error) { _spec := sqlgraph.NewDeleteSpec(useridentity.Table, sqlgraph.NewFieldSpec(useridentity.FieldID, field.TypeString)) if ps := _d.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) if err != nil && sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } _d.mutation.done = true return affected, err } // UserIdentityDeleteOne is the builder for deleting a single UserIdentity entity. type UserIdentityDeleteOne struct { _d *UserIdentityDelete } // Where appends a list predicates to the UserIdentityDelete builder. func (_d *UserIdentityDeleteOne) Where(ps ...predicate.UserIdentity) *UserIdentityDeleteOne { _d._d.mutation.Where(ps...) return _d } // Exec executes the deletion query. func (_d *UserIdentityDeleteOne) Exec(ctx context.Context) error { n, err := _d._d.Exec(ctx) switch { case err != nil: return err case n == 0: return &NotFoundError{useridentity.Label} default: return nil } } // ExecX is like Exec, but panics if an error occurs. func (_d *UserIdentityDeleteOne) ExecX(ctx context.Context) { if err := _d.Exec(ctx); err != nil { panic(err) } } ================================================ FILE: storage/ent/db/useridentity_query.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "fmt" "math" "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/predicate" "github.com/dexidp/dex/storage/ent/db/useridentity" ) // UserIdentityQuery is the builder for querying UserIdentity entities. type UserIdentityQuery struct { config ctx *QueryContext order []useridentity.OrderOption inters []Interceptor predicates []predicate.UserIdentity // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) } // Where adds a new predicate for the UserIdentityQuery builder. func (_q *UserIdentityQuery) Where(ps ...predicate.UserIdentity) *UserIdentityQuery { _q.predicates = append(_q.predicates, ps...) return _q } // Limit the number of records to be returned by this query. func (_q *UserIdentityQuery) Limit(limit int) *UserIdentityQuery { _q.ctx.Limit = &limit return _q } // Offset to start from. func (_q *UserIdentityQuery) Offset(offset int) *UserIdentityQuery { _q.ctx.Offset = &offset return _q } // Unique configures the query builder to filter duplicate records on query. // By default, unique is set to true, and can be disabled using this method. func (_q *UserIdentityQuery) Unique(unique bool) *UserIdentityQuery { _q.ctx.Unique = &unique return _q } // Order specifies how the records should be ordered. func (_q *UserIdentityQuery) Order(o ...useridentity.OrderOption) *UserIdentityQuery { _q.order = append(_q.order, o...) return _q } // First returns the first UserIdentity entity from the query. // Returns a *NotFoundError when no UserIdentity was found. func (_q *UserIdentityQuery) First(ctx context.Context) (*UserIdentity, error) { nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) if err != nil { return nil, err } if len(nodes) == 0 { return nil, &NotFoundError{useridentity.Label} } return nodes[0], nil } // FirstX is like First, but panics if an error occurs. func (_q *UserIdentityQuery) FirstX(ctx context.Context) *UserIdentity { node, err := _q.First(ctx) if err != nil && !IsNotFound(err) { panic(err) } return node } // FirstID returns the first UserIdentity ID from the query. // Returns a *NotFoundError when no UserIdentity ID was found. func (_q *UserIdentityQuery) FirstID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { return } if len(ids) == 0 { err = &NotFoundError{useridentity.Label} return } return ids[0], nil } // FirstIDX is like FirstID, but panics if an error occurs. func (_q *UserIdentityQuery) FirstIDX(ctx context.Context) string { id, err := _q.FirstID(ctx) if err != nil && !IsNotFound(err) { panic(err) } return id } // Only returns a single UserIdentity entity found by the query, ensuring it only returns one. // Returns a *NotSingularError when more than one UserIdentity entity is found. // Returns a *NotFoundError when no UserIdentity entities are found. func (_q *UserIdentityQuery) Only(ctx context.Context) (*UserIdentity, error) { nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) if err != nil { return nil, err } switch len(nodes) { case 1: return nodes[0], nil case 0: return nil, &NotFoundError{useridentity.Label} default: return nil, &NotSingularError{useridentity.Label} } } // OnlyX is like Only, but panics if an error occurs. func (_q *UserIdentityQuery) OnlyX(ctx context.Context) *UserIdentity { node, err := _q.Only(ctx) if err != nil { panic(err) } return node } // OnlyID is like Only, but returns the only UserIdentity ID in the query. // Returns a *NotSingularError when more than one UserIdentity ID is found. // Returns a *NotFoundError when no entities are found. func (_q *UserIdentityQuery) OnlyID(ctx context.Context) (id string, err error) { var ids []string if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { return } switch len(ids) { case 1: id = ids[0] case 0: err = &NotFoundError{useridentity.Label} default: err = &NotSingularError{useridentity.Label} } return } // OnlyIDX is like OnlyID, but panics if an error occurs. func (_q *UserIdentityQuery) OnlyIDX(ctx context.Context) string { id, err := _q.OnlyID(ctx) if err != nil { panic(err) } return id } // All executes the query and returns a list of UserIdentities. func (_q *UserIdentityQuery) All(ctx context.Context) ([]*UserIdentity, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) if err := _q.prepareQuery(ctx); err != nil { return nil, err } qr := querierAll[[]*UserIdentity, *UserIdentityQuery]() return withInterceptors[[]*UserIdentity](ctx, _q, qr, _q.inters) } // AllX is like All, but panics if an error occurs. func (_q *UserIdentityQuery) AllX(ctx context.Context) []*UserIdentity { nodes, err := _q.All(ctx) if err != nil { panic(err) } return nodes } // IDs executes the query and returns a list of UserIdentity IDs. func (_q *UserIdentityQuery) IDs(ctx context.Context) (ids []string, err error) { if _q.ctx.Unique == nil && _q.path != nil { _q.Unique(true) } ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) if err = _q.Select(useridentity.FieldID).Scan(ctx, &ids); err != nil { return nil, err } return ids, nil } // IDsX is like IDs, but panics if an error occurs. func (_q *UserIdentityQuery) IDsX(ctx context.Context) []string { ids, err := _q.IDs(ctx) if err != nil { panic(err) } return ids } // Count returns the count of the given query. func (_q *UserIdentityQuery) Count(ctx context.Context) (int, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) if err := _q.prepareQuery(ctx); err != nil { return 0, err } return withInterceptors[int](ctx, _q, querierCount[*UserIdentityQuery](), _q.inters) } // CountX is like Count, but panics if an error occurs. func (_q *UserIdentityQuery) CountX(ctx context.Context) int { count, err := _q.Count(ctx) if err != nil { panic(err) } return count } // Exist returns true if the query has elements in the graph. func (_q *UserIdentityQuery) Exist(ctx context.Context) (bool, error) { ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) switch _, err := _q.FirstID(ctx); { case IsNotFound(err): return false, nil case err != nil: return false, fmt.Errorf("db: check existence: %w", err) default: return true, nil } } // ExistX is like Exist, but panics if an error occurs. func (_q *UserIdentityQuery) ExistX(ctx context.Context) bool { exist, err := _q.Exist(ctx) if err != nil { panic(err) } return exist } // Clone returns a duplicate of the UserIdentityQuery builder, including all associated steps. It can be // used to prepare common query builders and use them differently after the clone is made. func (_q *UserIdentityQuery) Clone() *UserIdentityQuery { if _q == nil { return nil } return &UserIdentityQuery{ config: _q.config, ctx: _q.ctx.Clone(), order: append([]useridentity.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.UserIdentity{}, _q.predicates...), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // // Example: // // var v []struct { // UserID string `json:"user_id,omitempty"` // Count int `json:"count,omitempty"` // } // // client.UserIdentity.Query(). // GroupBy(useridentity.FieldUserID). // Aggregate(db.Count()). // Scan(ctx, &v) func (_q *UserIdentityQuery) GroupBy(field string, fields ...string) *UserIdentityGroupBy { _q.ctx.Fields = append([]string{field}, fields...) grbuild := &UserIdentityGroupBy{build: _q} grbuild.flds = &_q.ctx.Fields grbuild.label = useridentity.Label grbuild.scan = grbuild.Scan return grbuild } // Select allows the selection one or more fields/columns for the given query, // instead of selecting all fields in the entity. // // Example: // // var v []struct { // UserID string `json:"user_id,omitempty"` // } // // client.UserIdentity.Query(). // Select(useridentity.FieldUserID). // Scan(ctx, &v) func (_q *UserIdentityQuery) Select(fields ...string) *UserIdentitySelect { _q.ctx.Fields = append(_q.ctx.Fields, fields...) sbuild := &UserIdentitySelect{UserIdentityQuery: _q} sbuild.label = useridentity.Label sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan return sbuild } // Aggregate returns a UserIdentitySelect configured with the given aggregations. func (_q *UserIdentityQuery) Aggregate(fns ...AggregateFunc) *UserIdentitySelect { return _q.Select().Aggregate(fns...) } func (_q *UserIdentityQuery) prepareQuery(ctx context.Context) error { for _, inter := range _q.inters { if inter == nil { return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") } if trv, ok := inter.(Traverser); ok { if err := trv.Traverse(ctx, _q); err != nil { return err } } } for _, f := range _q.ctx.Fields { if !useridentity.ValidColumn(f) { return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } } if _q.path != nil { prev, err := _q.path(ctx) if err != nil { return err } _q.sql = prev } return nil } func (_q *UserIdentityQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*UserIdentity, error) { var ( nodes = []*UserIdentity{} _spec = _q.querySpec() ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*UserIdentity).scanValues(nil, columns) } _spec.Assign = func(columns []string, values []any) error { node := &UserIdentity{config: _q.config} nodes = append(nodes, node) return node.assignValues(columns, values) } for i := range hooks { hooks[i](ctx, _spec) } if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { return nil, err } if len(nodes) == 0 { return nodes, nil } return nodes, nil } func (_q *UserIdentityQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields if len(_q.ctx.Fields) > 0 { _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique } return sqlgraph.CountNodes(ctx, _q.driver, _spec) } func (_q *UserIdentityQuery) querySpec() *sqlgraph.QuerySpec { _spec := sqlgraph.NewQuerySpec(useridentity.Table, useridentity.Columns, sqlgraph.NewFieldSpec(useridentity.FieldID, field.TypeString)) _spec.From = _q.sql if unique := _q.ctx.Unique; unique != nil { _spec.Unique = *unique } else if _q.path != nil { _spec.Unique = true } if fields := _q.ctx.Fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, useridentity.FieldID) for i := range fields { if fields[i] != useridentity.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) } } } if ps := _q.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if limit := _q.ctx.Limit; limit != nil { _spec.Limit = *limit } if offset := _q.ctx.Offset; offset != nil { _spec.Offset = *offset } if ps := _q.order; len(ps) > 0 { _spec.Order = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } return _spec } func (_q *UserIdentityQuery) sqlQuery(ctx context.Context) *sql.Selector { builder := sql.Dialect(_q.driver.Dialect()) t1 := builder.Table(useridentity.Table) columns := _q.ctx.Fields if len(columns) == 0 { columns = useridentity.Columns } selector := builder.Select(t1.Columns(columns...)...).From(t1) if _q.sql != nil { selector = _q.sql selector.Select(selector.Columns(columns...)...) } if _q.ctx.Unique != nil && *_q.ctx.Unique { selector.Distinct() } for _, p := range _q.predicates { p(selector) } for _, p := range _q.order { p(selector) } if offset := _q.ctx.Offset; offset != nil { // limit is mandatory for offset clause. We start // with default value, and override it below if needed. selector.Offset(*offset).Limit(math.MaxInt32) } if limit := _q.ctx.Limit; limit != nil { selector.Limit(*limit) } return selector } // UserIdentityGroupBy is the group-by builder for UserIdentity entities. type UserIdentityGroupBy struct { selector build *UserIdentityQuery } // Aggregate adds the given aggregation functions to the group-by query. func (_g *UserIdentityGroupBy) Aggregate(fns ...AggregateFunc) *UserIdentityGroupBy { _g.fns = append(_g.fns, fns...) return _g } // Scan applies the selector query and scans the result into the given value. func (_g *UserIdentityGroupBy) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) if err := _g.build.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*UserIdentityQuery, *UserIdentityGroupBy](ctx, _g.build, _g, _g.build.inters, v) } func (_g *UserIdentityGroupBy) sqlScan(ctx context.Context, root *UserIdentityQuery, v any) error { selector := root.sqlQuery(ctx).Select() aggregation := make([]string, 0, len(_g.fns)) for _, fn := range _g.fns { aggregation = append(aggregation, fn(selector)) } if len(selector.SelectedColumns()) == 0 { columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) for _, f := range *_g.flds { columns = append(columns, selector.C(f)) } columns = append(columns, aggregation...) selector.Select(columns...) } selector.GroupBy(selector.Columns(*_g.flds...)...) if err := selector.Err(); err != nil { return err } rows := &sql.Rows{} query, args := selector.Query() if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } // UserIdentitySelect is the builder for selecting fields of UserIdentity entities. type UserIdentitySelect struct { *UserIdentityQuery selector } // Aggregate adds the given aggregation functions to the selector query. func (_s *UserIdentitySelect) Aggregate(fns ...AggregateFunc) *UserIdentitySelect { _s.fns = append(_s.fns, fns...) return _s } // Scan applies the selector query and scans the result into the given value. func (_s *UserIdentitySelect) Scan(ctx context.Context, v any) error { ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) if err := _s.prepareQuery(ctx); err != nil { return err } return scanWithInterceptors[*UserIdentityQuery, *UserIdentitySelect](ctx, _s.UserIdentityQuery, _s, _s.inters, v) } func (_s *UserIdentitySelect) sqlScan(ctx context.Context, root *UserIdentityQuery, v any) error { selector := root.sqlQuery(ctx) aggregation := make([]string, 0, len(_s.fns)) for _, fn := range _s.fns { aggregation = append(aggregation, fn(selector)) } switch n := len(*_s.selector.flds); { case n == 0 && len(aggregation) > 0: selector.Select(aggregation...) case n != 0 && len(aggregation) > 0: selector.AppendSelect(aggregation...) } rows := &sql.Rows{} query, args := selector.Query() if err := _s.driver.Query(ctx, query, args, rows); err != nil { return err } defer rows.Close() return sql.ScanSlice(rows, v) } ================================================ FILE: storage/ent/db/useridentity_update.go ================================================ // Code generated by ent, DO NOT EDIT. package db import ( "context" "errors" "fmt" "time" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" "github.com/dexidp/dex/storage/ent/db/predicate" "github.com/dexidp/dex/storage/ent/db/useridentity" ) // UserIdentityUpdate is the builder for updating UserIdentity entities. type UserIdentityUpdate struct { config hooks []Hook mutation *UserIdentityMutation } // Where appends a list predicates to the UserIdentityUpdate builder. func (_u *UserIdentityUpdate) Where(ps ...predicate.UserIdentity) *UserIdentityUpdate { _u.mutation.Where(ps...) return _u } // SetUserID sets the "user_id" field. func (_u *UserIdentityUpdate) SetUserID(v string) *UserIdentityUpdate { _u.mutation.SetUserID(v) return _u } // SetNillableUserID sets the "user_id" field if the given value is not nil. func (_u *UserIdentityUpdate) SetNillableUserID(v *string) *UserIdentityUpdate { if v != nil { _u.SetUserID(*v) } return _u } // SetConnectorID sets the "connector_id" field. func (_u *UserIdentityUpdate) SetConnectorID(v string) *UserIdentityUpdate { _u.mutation.SetConnectorID(v) return _u } // SetNillableConnectorID sets the "connector_id" field if the given value is not nil. func (_u *UserIdentityUpdate) SetNillableConnectorID(v *string) *UserIdentityUpdate { if v != nil { _u.SetConnectorID(*v) } return _u } // SetClaimsUserID sets the "claims_user_id" field. func (_u *UserIdentityUpdate) SetClaimsUserID(v string) *UserIdentityUpdate { _u.mutation.SetClaimsUserID(v) return _u } // SetNillableClaimsUserID sets the "claims_user_id" field if the given value is not nil. func (_u *UserIdentityUpdate) SetNillableClaimsUserID(v *string) *UserIdentityUpdate { if v != nil { _u.SetClaimsUserID(*v) } return _u } // SetClaimsUsername sets the "claims_username" field. func (_u *UserIdentityUpdate) SetClaimsUsername(v string) *UserIdentityUpdate { _u.mutation.SetClaimsUsername(v) return _u } // SetNillableClaimsUsername sets the "claims_username" field if the given value is not nil. func (_u *UserIdentityUpdate) SetNillableClaimsUsername(v *string) *UserIdentityUpdate { if v != nil { _u.SetClaimsUsername(*v) } return _u } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (_u *UserIdentityUpdate) SetClaimsPreferredUsername(v string) *UserIdentityUpdate { _u.mutation.SetClaimsPreferredUsername(v) return _u } // SetNillableClaimsPreferredUsername sets the "claims_preferred_username" field if the given value is not nil. func (_u *UserIdentityUpdate) SetNillableClaimsPreferredUsername(v *string) *UserIdentityUpdate { if v != nil { _u.SetClaimsPreferredUsername(*v) } return _u } // SetClaimsEmail sets the "claims_email" field. func (_u *UserIdentityUpdate) SetClaimsEmail(v string) *UserIdentityUpdate { _u.mutation.SetClaimsEmail(v) return _u } // SetNillableClaimsEmail sets the "claims_email" field if the given value is not nil. func (_u *UserIdentityUpdate) SetNillableClaimsEmail(v *string) *UserIdentityUpdate { if v != nil { _u.SetClaimsEmail(*v) } return _u } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (_u *UserIdentityUpdate) SetClaimsEmailVerified(v bool) *UserIdentityUpdate { _u.mutation.SetClaimsEmailVerified(v) return _u } // SetNillableClaimsEmailVerified sets the "claims_email_verified" field if the given value is not nil. func (_u *UserIdentityUpdate) SetNillableClaimsEmailVerified(v *bool) *UserIdentityUpdate { if v != nil { _u.SetClaimsEmailVerified(*v) } return _u } // SetClaimsGroups sets the "claims_groups" field. func (_u *UserIdentityUpdate) SetClaimsGroups(v []string) *UserIdentityUpdate { _u.mutation.SetClaimsGroups(v) return _u } // AppendClaimsGroups appends value to the "claims_groups" field. func (_u *UserIdentityUpdate) AppendClaimsGroups(v []string) *UserIdentityUpdate { _u.mutation.AppendClaimsGroups(v) return _u } // ClearClaimsGroups clears the value of the "claims_groups" field. func (_u *UserIdentityUpdate) ClearClaimsGroups() *UserIdentityUpdate { _u.mutation.ClearClaimsGroups() return _u } // SetConsents sets the "consents" field. func (_u *UserIdentityUpdate) SetConsents(v []byte) *UserIdentityUpdate { _u.mutation.SetConsents(v) return _u } // SetMfaSecrets sets the "mfa_secrets" field. func (_u *UserIdentityUpdate) SetMfaSecrets(v []byte) *UserIdentityUpdate { _u.mutation.SetMfaSecrets(v) return _u } // ClearMfaSecrets clears the value of the "mfa_secrets" field. func (_u *UserIdentityUpdate) ClearMfaSecrets() *UserIdentityUpdate { _u.mutation.ClearMfaSecrets() return _u } // SetCreatedAt sets the "created_at" field. func (_u *UserIdentityUpdate) SetCreatedAt(v time.Time) *UserIdentityUpdate { _u.mutation.SetCreatedAt(v) return _u } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. func (_u *UserIdentityUpdate) SetNillableCreatedAt(v *time.Time) *UserIdentityUpdate { if v != nil { _u.SetCreatedAt(*v) } return _u } // SetLastLogin sets the "last_login" field. func (_u *UserIdentityUpdate) SetLastLogin(v time.Time) *UserIdentityUpdate { _u.mutation.SetLastLogin(v) return _u } // SetNillableLastLogin sets the "last_login" field if the given value is not nil. func (_u *UserIdentityUpdate) SetNillableLastLogin(v *time.Time) *UserIdentityUpdate { if v != nil { _u.SetLastLogin(*v) } return _u } // SetBlockedUntil sets the "blocked_until" field. func (_u *UserIdentityUpdate) SetBlockedUntil(v time.Time) *UserIdentityUpdate { _u.mutation.SetBlockedUntil(v) return _u } // SetNillableBlockedUntil sets the "blocked_until" field if the given value is not nil. func (_u *UserIdentityUpdate) SetNillableBlockedUntil(v *time.Time) *UserIdentityUpdate { if v != nil { _u.SetBlockedUntil(*v) } return _u } // Mutation returns the UserIdentityMutation object of the builder. func (_u *UserIdentityUpdate) Mutation() *UserIdentityMutation { return _u.mutation } // Save executes the query and returns the number of nodes affected by the update operation. func (_u *UserIdentityUpdate) Save(ctx context.Context) (int, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *UserIdentityUpdate) SaveX(ctx context.Context) int { affected, err := _u.Save(ctx) if err != nil { panic(err) } return affected } // Exec executes the query. func (_u *UserIdentityUpdate) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *UserIdentityUpdate) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *UserIdentityUpdate) check() error { if v, ok := _u.mutation.UserID(); ok { if err := useridentity.UserIDValidator(v); err != nil { return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "UserIdentity.user_id": %w`, err)} } } if v, ok := _u.mutation.ConnectorID(); ok { if err := useridentity.ConnectorIDValidator(v); err != nil { return &ValidationError{Name: "connector_id", err: fmt.Errorf(`db: validator failed for field "UserIdentity.connector_id": %w`, err)} } } return nil } func (_u *UserIdentityUpdate) sqlSave(ctx context.Context) (_node int, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(useridentity.Table, useridentity.Columns, sqlgraph.NewFieldSpec(useridentity.FieldID, field.TypeString)) if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.UserID(); ok { _spec.SetField(useridentity.FieldUserID, field.TypeString, value) } if value, ok := _u.mutation.ConnectorID(); ok { _spec.SetField(useridentity.FieldConnectorID, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUserID(); ok { _spec.SetField(useridentity.FieldClaimsUserID, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUsername(); ok { _spec.SetField(useridentity.FieldClaimsUsername, field.TypeString, value) } if value, ok := _u.mutation.ClaimsPreferredUsername(); ok { _spec.SetField(useridentity.FieldClaimsPreferredUsername, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmail(); ok { _spec.SetField(useridentity.FieldClaimsEmail, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmailVerified(); ok { _spec.SetField(useridentity.FieldClaimsEmailVerified, field.TypeBool, value) } if value, ok := _u.mutation.ClaimsGroups(); ok { _spec.SetField(useridentity.FieldClaimsGroups, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedClaimsGroups(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, useridentity.FieldClaimsGroups, value) }) } if _u.mutation.ClaimsGroupsCleared() { _spec.ClearField(useridentity.FieldClaimsGroups, field.TypeJSON) } if value, ok := _u.mutation.Consents(); ok { _spec.SetField(useridentity.FieldConsents, field.TypeBytes, value) } if value, ok := _u.mutation.MfaSecrets(); ok { _spec.SetField(useridentity.FieldMfaSecrets, field.TypeBytes, value) } if _u.mutation.MfaSecretsCleared() { _spec.ClearField(useridentity.FieldMfaSecrets, field.TypeBytes) } if value, ok := _u.mutation.CreatedAt(); ok { _spec.SetField(useridentity.FieldCreatedAt, field.TypeTime, value) } if value, ok := _u.mutation.LastLogin(); ok { _spec.SetField(useridentity.FieldLastLogin, field.TypeTime, value) } if value, ok := _u.mutation.BlockedUntil(); ok { _spec.SetField(useridentity.FieldBlockedUntil, field.TypeTime, value) } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{useridentity.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return 0, err } _u.mutation.done = true return _node, nil } // UserIdentityUpdateOne is the builder for updating a single UserIdentity entity. type UserIdentityUpdateOne struct { config fields []string hooks []Hook mutation *UserIdentityMutation } // SetUserID sets the "user_id" field. func (_u *UserIdentityUpdateOne) SetUserID(v string) *UserIdentityUpdateOne { _u.mutation.SetUserID(v) return _u } // SetNillableUserID sets the "user_id" field if the given value is not nil. func (_u *UserIdentityUpdateOne) SetNillableUserID(v *string) *UserIdentityUpdateOne { if v != nil { _u.SetUserID(*v) } return _u } // SetConnectorID sets the "connector_id" field. func (_u *UserIdentityUpdateOne) SetConnectorID(v string) *UserIdentityUpdateOne { _u.mutation.SetConnectorID(v) return _u } // SetNillableConnectorID sets the "connector_id" field if the given value is not nil. func (_u *UserIdentityUpdateOne) SetNillableConnectorID(v *string) *UserIdentityUpdateOne { if v != nil { _u.SetConnectorID(*v) } return _u } // SetClaimsUserID sets the "claims_user_id" field. func (_u *UserIdentityUpdateOne) SetClaimsUserID(v string) *UserIdentityUpdateOne { _u.mutation.SetClaimsUserID(v) return _u } // SetNillableClaimsUserID sets the "claims_user_id" field if the given value is not nil. func (_u *UserIdentityUpdateOne) SetNillableClaimsUserID(v *string) *UserIdentityUpdateOne { if v != nil { _u.SetClaimsUserID(*v) } return _u } // SetClaimsUsername sets the "claims_username" field. func (_u *UserIdentityUpdateOne) SetClaimsUsername(v string) *UserIdentityUpdateOne { _u.mutation.SetClaimsUsername(v) return _u } // SetNillableClaimsUsername sets the "claims_username" field if the given value is not nil. func (_u *UserIdentityUpdateOne) SetNillableClaimsUsername(v *string) *UserIdentityUpdateOne { if v != nil { _u.SetClaimsUsername(*v) } return _u } // SetClaimsPreferredUsername sets the "claims_preferred_username" field. func (_u *UserIdentityUpdateOne) SetClaimsPreferredUsername(v string) *UserIdentityUpdateOne { _u.mutation.SetClaimsPreferredUsername(v) return _u } // SetNillableClaimsPreferredUsername sets the "claims_preferred_username" field if the given value is not nil. func (_u *UserIdentityUpdateOne) SetNillableClaimsPreferredUsername(v *string) *UserIdentityUpdateOne { if v != nil { _u.SetClaimsPreferredUsername(*v) } return _u } // SetClaimsEmail sets the "claims_email" field. func (_u *UserIdentityUpdateOne) SetClaimsEmail(v string) *UserIdentityUpdateOne { _u.mutation.SetClaimsEmail(v) return _u } // SetNillableClaimsEmail sets the "claims_email" field if the given value is not nil. func (_u *UserIdentityUpdateOne) SetNillableClaimsEmail(v *string) *UserIdentityUpdateOne { if v != nil { _u.SetClaimsEmail(*v) } return _u } // SetClaimsEmailVerified sets the "claims_email_verified" field. func (_u *UserIdentityUpdateOne) SetClaimsEmailVerified(v bool) *UserIdentityUpdateOne { _u.mutation.SetClaimsEmailVerified(v) return _u } // SetNillableClaimsEmailVerified sets the "claims_email_verified" field if the given value is not nil. func (_u *UserIdentityUpdateOne) SetNillableClaimsEmailVerified(v *bool) *UserIdentityUpdateOne { if v != nil { _u.SetClaimsEmailVerified(*v) } return _u } // SetClaimsGroups sets the "claims_groups" field. func (_u *UserIdentityUpdateOne) SetClaimsGroups(v []string) *UserIdentityUpdateOne { _u.mutation.SetClaimsGroups(v) return _u } // AppendClaimsGroups appends value to the "claims_groups" field. func (_u *UserIdentityUpdateOne) AppendClaimsGroups(v []string) *UserIdentityUpdateOne { _u.mutation.AppendClaimsGroups(v) return _u } // ClearClaimsGroups clears the value of the "claims_groups" field. func (_u *UserIdentityUpdateOne) ClearClaimsGroups() *UserIdentityUpdateOne { _u.mutation.ClearClaimsGroups() return _u } // SetConsents sets the "consents" field. func (_u *UserIdentityUpdateOne) SetConsents(v []byte) *UserIdentityUpdateOne { _u.mutation.SetConsents(v) return _u } // SetMfaSecrets sets the "mfa_secrets" field. func (_u *UserIdentityUpdateOne) SetMfaSecrets(v []byte) *UserIdentityUpdateOne { _u.mutation.SetMfaSecrets(v) return _u } // ClearMfaSecrets clears the value of the "mfa_secrets" field. func (_u *UserIdentityUpdateOne) ClearMfaSecrets() *UserIdentityUpdateOne { _u.mutation.ClearMfaSecrets() return _u } // SetCreatedAt sets the "created_at" field. func (_u *UserIdentityUpdateOne) SetCreatedAt(v time.Time) *UserIdentityUpdateOne { _u.mutation.SetCreatedAt(v) return _u } // SetNillableCreatedAt sets the "created_at" field if the given value is not nil. func (_u *UserIdentityUpdateOne) SetNillableCreatedAt(v *time.Time) *UserIdentityUpdateOne { if v != nil { _u.SetCreatedAt(*v) } return _u } // SetLastLogin sets the "last_login" field. func (_u *UserIdentityUpdateOne) SetLastLogin(v time.Time) *UserIdentityUpdateOne { _u.mutation.SetLastLogin(v) return _u } // SetNillableLastLogin sets the "last_login" field if the given value is not nil. func (_u *UserIdentityUpdateOne) SetNillableLastLogin(v *time.Time) *UserIdentityUpdateOne { if v != nil { _u.SetLastLogin(*v) } return _u } // SetBlockedUntil sets the "blocked_until" field. func (_u *UserIdentityUpdateOne) SetBlockedUntil(v time.Time) *UserIdentityUpdateOne { _u.mutation.SetBlockedUntil(v) return _u } // SetNillableBlockedUntil sets the "blocked_until" field if the given value is not nil. func (_u *UserIdentityUpdateOne) SetNillableBlockedUntil(v *time.Time) *UserIdentityUpdateOne { if v != nil { _u.SetBlockedUntil(*v) } return _u } // Mutation returns the UserIdentityMutation object of the builder. func (_u *UserIdentityUpdateOne) Mutation() *UserIdentityMutation { return _u.mutation } // Where appends a list predicates to the UserIdentityUpdate builder. func (_u *UserIdentityUpdateOne) Where(ps ...predicate.UserIdentity) *UserIdentityUpdateOne { _u.mutation.Where(ps...) return _u } // Select allows selecting one or more fields (columns) of the returned entity. // The default is selecting all fields defined in the entity schema. func (_u *UserIdentityUpdateOne) Select(field string, fields ...string) *UserIdentityUpdateOne { _u.fields = append([]string{field}, fields...) return _u } // Save executes the query and returns the updated UserIdentity entity. func (_u *UserIdentityUpdateOne) Save(ctx context.Context) (*UserIdentity, error) { return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) } // SaveX is like Save, but panics if an error occurs. func (_u *UserIdentityUpdateOne) SaveX(ctx context.Context) *UserIdentity { node, err := _u.Save(ctx) if err != nil { panic(err) } return node } // Exec executes the query on the entity. func (_u *UserIdentityUpdateOne) Exec(ctx context.Context) error { _, err := _u.Save(ctx) return err } // ExecX is like Exec, but panics if an error occurs. func (_u *UserIdentityUpdateOne) ExecX(ctx context.Context) { if err := _u.Exec(ctx); err != nil { panic(err) } } // check runs all checks and user-defined validators on the builder. func (_u *UserIdentityUpdateOne) check() error { if v, ok := _u.mutation.UserID(); ok { if err := useridentity.UserIDValidator(v); err != nil { return &ValidationError{Name: "user_id", err: fmt.Errorf(`db: validator failed for field "UserIdentity.user_id": %w`, err)} } } if v, ok := _u.mutation.ConnectorID(); ok { if err := useridentity.ConnectorIDValidator(v); err != nil { return &ValidationError{Name: "connector_id", err: fmt.Errorf(`db: validator failed for field "UserIdentity.connector_id": %w`, err)} } } return nil } func (_u *UserIdentityUpdateOne) sqlSave(ctx context.Context) (_node *UserIdentity, err error) { if err := _u.check(); err != nil { return _node, err } _spec := sqlgraph.NewUpdateSpec(useridentity.Table, useridentity.Columns, sqlgraph.NewFieldSpec(useridentity.FieldID, field.TypeString)) id, ok := _u.mutation.ID() if !ok { return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "UserIdentity.id" for update`)} } _spec.Node.ID.Value = id if fields := _u.fields; len(fields) > 0 { _spec.Node.Columns = make([]string, 0, len(fields)) _spec.Node.Columns = append(_spec.Node.Columns, useridentity.FieldID) for _, f := range fields { if !useridentity.ValidColumn(f) { return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} } if f != useridentity.FieldID { _spec.Node.Columns = append(_spec.Node.Columns, f) } } } if ps := _u.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { for i := range ps { ps[i](selector) } } } if value, ok := _u.mutation.UserID(); ok { _spec.SetField(useridentity.FieldUserID, field.TypeString, value) } if value, ok := _u.mutation.ConnectorID(); ok { _spec.SetField(useridentity.FieldConnectorID, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUserID(); ok { _spec.SetField(useridentity.FieldClaimsUserID, field.TypeString, value) } if value, ok := _u.mutation.ClaimsUsername(); ok { _spec.SetField(useridentity.FieldClaimsUsername, field.TypeString, value) } if value, ok := _u.mutation.ClaimsPreferredUsername(); ok { _spec.SetField(useridentity.FieldClaimsPreferredUsername, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmail(); ok { _spec.SetField(useridentity.FieldClaimsEmail, field.TypeString, value) } if value, ok := _u.mutation.ClaimsEmailVerified(); ok { _spec.SetField(useridentity.FieldClaimsEmailVerified, field.TypeBool, value) } if value, ok := _u.mutation.ClaimsGroups(); ok { _spec.SetField(useridentity.FieldClaimsGroups, field.TypeJSON, value) } if value, ok := _u.mutation.AppendedClaimsGroups(); ok { _spec.AddModifier(func(u *sql.UpdateBuilder) { sqljson.Append(u, useridentity.FieldClaimsGroups, value) }) } if _u.mutation.ClaimsGroupsCleared() { _spec.ClearField(useridentity.FieldClaimsGroups, field.TypeJSON) } if value, ok := _u.mutation.Consents(); ok { _spec.SetField(useridentity.FieldConsents, field.TypeBytes, value) } if value, ok := _u.mutation.MfaSecrets(); ok { _spec.SetField(useridentity.FieldMfaSecrets, field.TypeBytes, value) } if _u.mutation.MfaSecretsCleared() { _spec.ClearField(useridentity.FieldMfaSecrets, field.TypeBytes) } if value, ok := _u.mutation.CreatedAt(); ok { _spec.SetField(useridentity.FieldCreatedAt, field.TypeTime, value) } if value, ok := _u.mutation.LastLogin(); ok { _spec.SetField(useridentity.FieldLastLogin, field.TypeTime, value) } if value, ok := _u.mutation.BlockedUntil(); ok { _spec.SetField(useridentity.FieldBlockedUntil, field.TypeTime, value) } _node = &UserIdentity{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{useridentity.Label} } else if sqlgraph.IsConstraintError(err) { err = &ConstraintError{msg: err.Error(), wrap: err} } return nil, err } _u.mutation.done = true return _node, nil } ================================================ FILE: storage/ent/generate.go ================================================ package ent //go:generate go tool entgo.io/ent/cmd/ent generate ./schema --target ./db ================================================ FILE: storage/ent/mysql.go ================================================ package ent import ( "context" "crypto/sha256" "crypto/tls" "crypto/x509" "database/sql" "fmt" "log/slog" "net" "os" "strconv" "time" entSQL "entgo.io/ent/dialect/sql" "github.com/go-sql-driver/mysql" // Register mysql driver. "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/client" "github.com/dexidp/dex/storage/ent/db" ) const ( // MySQL SSL modes mysqlSSLTrue = "true" mysqlSSLFalse = "false" mysqlSSLSkipVerify = "skip-verify" mysqlSSLCustom = "custom" ) // MySQL options for creating an SQL db. type MySQL struct { NetworkDB SSL SSL `json:"ssl"` params map[string]string } // Open always returns a new in sqlite3 storage. func (m *MySQL) Open(logger *slog.Logger) (storage.Storage, error) { logger.Debug("experimental ent-based storage driver is enabled") drv, err := m.driver() if err != nil { return nil, err } databaseClient := client.NewDatabase( client.WithClient(db.NewClient(db.Driver(drv))), client.WithHasher(sha256.New), // Set tx isolation leve for each transaction as dex does for postgres client.WithTxIsolationLevel(sql.LevelSerializable), ) if err := databaseClient.Schema().Create(context.TODO()); err != nil { return nil, err } return databaseClient, nil } func (m *MySQL) driver() (*entSQL.Driver, error) { var tlsConfig string switch { case m.SSL.CAFile != "" || m.SSL.CertFile != "" || m.SSL.KeyFile != "": if err := m.makeTLSConfig(); err != nil { return nil, fmt.Errorf("failed to make TLS config: %v", err) } tlsConfig = mysqlSSLCustom case m.SSL.Mode == "": tlsConfig = mysqlSSLTrue default: tlsConfig = m.SSL.Mode } drv, err := entSQL.Open("mysql", m.dsn(tlsConfig)) if err != nil { return nil, err } if m.MaxIdleConns == 0 { /* Override default behavior to fix https://github.com/dexidp/dex/issues/1608 */ drv.DB().SetMaxIdleConns(0) } else { drv.DB().SetMaxIdleConns(m.MaxIdleConns) } return drv, nil } func (m *MySQL) dsn(tlsConfig string) string { cfg := mysql.Config{ User: m.User, Passwd: m.Password, DBName: m.Database, AllowNativePasswords: true, Timeout: time.Second * time.Duration(m.ConnectionTimeout), TLSConfig: tlsConfig, ParseTime: true, Params: make(map[string]string), } if m.Host != "" { if m.Host[0] != '/' { cfg.Net = "tcp" cfg.Addr = m.Host if m.Port != 0 { cfg.Addr = net.JoinHostPort(m.Host, strconv.Itoa(int(m.Port))) } } else { cfg.Net = "unix" cfg.Addr = m.Host } } for k, v := range m.params { cfg.Params[k] = v } return cfg.FormatDSN() } func (m *MySQL) makeTLSConfig() error { cfg := &tls.Config{} if m.SSL.CAFile != "" { rootCertPool := x509.NewCertPool() pem, err := os.ReadFile(m.SSL.CAFile) if err != nil { return err } if ok := rootCertPool.AppendCertsFromPEM(pem); !ok { return fmt.Errorf("failed to append PEM") } cfg.RootCAs = rootCertPool } if m.SSL.CertFile != "" && m.SSL.KeyFile != "" { clientCert := make([]tls.Certificate, 0, 1) certs, err := tls.LoadX509KeyPair(m.SSL.CertFile, m.SSL.KeyFile) if err != nil { return err } clientCert = append(clientCert, certs) cfg.Certificates = clientCert } mysql.RegisterTLSConfig(mysqlSSLCustom, cfg) return nil } ================================================ FILE: storage/ent/mysql_test.go ================================================ package ent import ( "log/slog" "os" "strconv" "testing" "github.com/stretchr/testify/require" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/conformance" ) const ( MySQLEntHostEnv = "DEX_MYSQL_ENT_HOST" MySQLEntPortEnv = "DEX_MYSQL_ENT_PORT" MySQLEntDatabaseEnv = "DEX_MYSQL_ENT_DATABASE" MySQLEntUserEnv = "DEX_MYSQL_ENT_USER" MySQLEntPasswordEnv = "DEX_MYSQL_ENT_PASSWORD" MySQL8EntHostEnv = "DEX_MYSQL8_ENT_HOST" MySQL8EntPortEnv = "DEX_MYSQL8_ENT_PORT" MySQL8EntDatabaseEnv = "DEX_MYSQL8_ENT_DATABASE" MySQL8EntUserEnv = "DEX_MYSQL8_ENT_USER" MySQL8EntPasswordEnv = "DEX_MYSQL8_ENT_PASSWORD" ) func mysqlTestConfig(host string, port uint64) *MySQL { return &MySQL{ NetworkDB: NetworkDB{ Database: getenv(MySQLEntDatabaseEnv, "mysql"), User: getenv(MySQLEntUserEnv, "mysql"), Password: getenv(MySQLEntPasswordEnv, "mysql"), Host: host, Port: uint16(port), }, SSL: SSL{ // This was originally mysqlSSLSkipVerify. It lead to handshake errors. // See https://github.com/go-sql-driver/mysql/issues/1635 for more details. Mode: mysqlSSLFalse, }, params: map[string]string{ "innodb_lock_wait_timeout": "1", }, } } func mysql8TestConfig(host string, port uint64) *MySQL { return &MySQL{ NetworkDB: NetworkDB{ Database: getenv(MySQL8EntDatabaseEnv, "mysql"), User: getenv(MySQL8EntUserEnv, "mysql"), Password: getenv(MySQL8EntPasswordEnv, "mysql"), Host: host, Port: uint16(port), }, SSL: SSL{ Mode: mysqlSSLFalse, }, params: map[string]string{ "innodb_lock_wait_timeout": "1", }, } } func newMySQL8Storage(t *testing.T, host string, port uint64) storage.Storage { logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug})) cfg := mysql8TestConfig(host, port) s, err := cfg.Open(logger) if err != nil { panic(err) } return s } func newMySQLStorage(t *testing.T, host string, port uint64) storage.Storage { logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug})) cfg := mysqlTestConfig(host, port) s, err := cfg.Open(logger) if err != nil { panic(err) } return s } func TestMySQL(t *testing.T) { host := os.Getenv(MySQLEntHostEnv) if host == "" { t.Skipf("test environment variable %s not set, skipping", MySQLEntHostEnv) } port := uint64(3306) if rawPort := os.Getenv(MySQLEntPortEnv); rawPort != "" { var err error port, err = strconv.ParseUint(rawPort, 10, 32) require.NoError(t, err, "invalid mysql port %q: %s", rawPort, err) } newStorage := func(t *testing.T) storage.Storage { return newMySQLStorage(t, host, port) } conformance.RunTests(t, newStorage) conformance.RunTransactionTests(t, newStorage) // TODO(nabokihms): ent MySQL does not retry on deadlocks (Error 1213, SQLSTATE 40001: // Deadlock found when trying to get lock; try restarting transaction). // Under high contention most updates fail. // conformance.RunConcurrencyTests(t, newStorage) } func TestMySQL8(t *testing.T) { host := os.Getenv(MySQL8EntHostEnv) if host == "" { t.Skipf("test environment variable %s not set, skipping", MySQL8EntHostEnv) } port := uint64(3306) if rawPort := os.Getenv(MySQL8EntPortEnv); rawPort != "" { var err error port, err = strconv.ParseUint(rawPort, 10, 32) require.NoError(t, err, "invalid mysql port %q: %s", rawPort, err) } newStorage := func(t *testing.T) storage.Storage { return newMySQL8Storage(t, host, port) } conformance.RunTests(t, newStorage) conformance.RunTransactionTests(t, newStorage) // TODO(nabokihms): ent MySQL 8 does not retry on deadlocks (Error 1213, SQLSTATE 40001: // Deadlock found when trying to get lock; try restarting transaction). // Under high contention most updates fail. // conformance.RunConcurrencyTests(t, newStorage) } func TestMySQLDSN(t *testing.T) { tests := []struct { name string cfg *MySQL desiredDSN string }{ { name: "Host port", cfg: &MySQL{ NetworkDB: NetworkDB{ Host: "localhost", Port: uint16(3306), }, }, desiredDSN: "tcp(localhost:3306)/?checkConnLiveness=false&parseTime=true&tls=false&maxAllowedPacket=0", }, { name: "Host with port", cfg: &MySQL{ NetworkDB: NetworkDB{ Host: "localhost:3306", }, }, desiredDSN: "tcp(localhost:3306)/?checkConnLiveness=false&parseTime=true&tls=false&maxAllowedPacket=0", }, { name: "Host ipv6 with port", cfg: &MySQL{ NetworkDB: NetworkDB{ Host: "[a:b:c:d]:3306", }, }, desiredDSN: "tcp([a:b:c:d]:3306)/?checkConnLiveness=false&parseTime=true&tls=false&maxAllowedPacket=0", }, { name: "Credentials and timeout", cfg: &MySQL{ NetworkDB: NetworkDB{ Database: "test", User: "test", Password: "test", ConnectionTimeout: 5, }, }, desiredDSN: "test:test@/test?checkConnLiveness=false&parseTime=true&timeout=5s&tls=false&maxAllowedPacket=0", }, { name: "SSL", cfg: &MySQL{ SSL: SSL{ CAFile: "/ca.crt", KeyFile: "/cert.crt", CertFile: "/cert.key", }, }, desiredDSN: "/?checkConnLiveness=false&parseTime=true&tls=false&maxAllowedPacket=0", }, { name: "With Params", cfg: &MySQL{ params: map[string]string{ "innodb_lock_wait_timeout": "1", }, }, desiredDSN: "/?checkConnLiveness=false&parseTime=true&tls=false&maxAllowedPacket=0&innodb_lock_wait_timeout=1", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.desiredDSN, tt.cfg.dsn(mysqlSSLFalse)) }) } } ================================================ FILE: storage/ent/postgres.go ================================================ package ent import ( "context" "crypto/sha256" "database/sql" "fmt" "log/slog" "net" "regexp" "strconv" "strings" "time" entSQL "entgo.io/ent/dialect/sql" _ "github.com/lib/pq" // Register postgres driver. "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/client" "github.com/dexidp/dex/storage/ent/db" ) const ( // postgres SSL modes pgSSLDisable = "disable" pgSSLRequire = "require" pgSSLVerifyCA = "verify-ca" pgSSLVerifyFull = "verify-full" ) // Postgres options for creating an SQL db. type Postgres struct { NetworkDB SSL SSL `json:"ssl"` } // Open always returns a new in sqlite3 storage. func (p *Postgres) Open(logger *slog.Logger) (storage.Storage, error) { logger.Debug("experimental ent-based storage driver is enabled") drv, err := p.driver() if err != nil { return nil, err } databaseClient := client.NewDatabase( client.WithClient(db.NewClient(db.Driver(drv))), client.WithHasher(sha256.New), // The default behavior for Postgres transactions is consistent reads, not consistent writes. // For each transaction opened, ensure it has the correct isolation level. // // See: https://www.postgresql.org/docs/9.3/static/sql-set-transaction.html client.WithTxIsolationLevel(sql.LevelSerializable), ) if err := databaseClient.Schema().Create(context.TODO()); err != nil { return nil, err } return databaseClient, nil } func (p *Postgres) driver() (*entSQL.Driver, error) { drv, err := entSQL.Open("postgres", p.dsn()) if err != nil { return nil, err } // set database/sql tunables if configured if p.ConnMaxLifetime != 0 { drv.DB().SetConnMaxLifetime(time.Duration(p.ConnMaxLifetime) * time.Second) } if p.MaxIdleConns == 0 { drv.DB().SetMaxIdleConns(5) } else { drv.DB().SetMaxIdleConns(p.MaxIdleConns) } if p.MaxOpenConns == 0 { drv.DB().SetMaxOpenConns(5) } else { drv.DB().SetMaxOpenConns(p.MaxOpenConns) } return drv, nil } func (p *Postgres) dsn() string { // detect host:port for backwards-compatibility host, port, err := net.SplitHostPort(p.Host) if err != nil { // not host:port, probably unix socket or bare address host = p.Host if p.Port != 0 { port = strconv.Itoa(int(p.Port)) } } var parameters []string addParam := func(key, val string) { parameters = append(parameters, fmt.Sprintf("%s=%s", key, val)) } addParam("connect_timeout", strconv.Itoa(p.ConnectionTimeout)) if host != "" { addParam("host", dataSourceStr(host)) } if port != "" { addParam("port", port) } if p.User != "" { addParam("user", dataSourceStr(p.User)) } if p.Password != "" { addParam("password", dataSourceStr(p.Password)) } if p.Database != "" { addParam("dbname", dataSourceStr(p.Database)) } if p.SSL.Mode == "" { // Assume the strictest mode if unspecified. addParam("sslmode", dataSourceStr(pgSSLVerifyFull)) } else { addParam("sslmode", dataSourceStr(p.SSL.Mode)) } if p.SSL.CAFile != "" { addParam("sslrootcert", dataSourceStr(p.SSL.CAFile)) } if p.SSL.CertFile != "" { addParam("sslcert", dataSourceStr(p.SSL.CertFile)) } if p.SSL.KeyFile != "" { addParam("sslkey", dataSourceStr(p.SSL.KeyFile)) } return strings.Join(parameters, " ") } var strEsc = regexp.MustCompile(`([\\'])`) func dataSourceStr(str string) string { return "'" + strEsc.ReplaceAllString(str, `\$1`) + "'" } ================================================ FILE: storage/ent/postgres_test.go ================================================ package ent import ( "log/slog" "os" "strconv" "testing" "github.com/stretchr/testify/require" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/conformance" ) const ( PostgresEntHostEnv = "DEX_POSTGRES_ENT_HOST" PostgresEntPortEnv = "DEX_POSTGRES_ENT_PORT" PostgresEntDatabaseEnv = "DEX_POSTGRES_ENT_DATABASE" PostgresEntUserEnv = "DEX_POSTGRES_ENT_USER" PostgresEntPasswordEnv = "DEX_POSTGRES_ENT_PASSWORD" ) func postgresTestConfig(host string, port uint64) *Postgres { return &Postgres{ NetworkDB: NetworkDB{ Database: getenv(PostgresEntDatabaseEnv, "postgres"), User: getenv(PostgresEntUserEnv, "postgres"), Password: getenv(PostgresEntPasswordEnv, "postgres"), Host: host, Port: uint16(port), }, SSL: SSL{ Mode: pgSSLDisable, // Postgres container doesn't support SSL. }, } } func newPostgresStorage(t *testing.T, host string, port uint64) storage.Storage { logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug})) cfg := postgresTestConfig(host, port) s, err := cfg.Open(logger) if err != nil { panic(err) } return s } func TestPostgres(t *testing.T) { host := os.Getenv(PostgresEntHostEnv) if host == "" { t.Skipf("test environment variable %s not set, skipping", PostgresEntHostEnv) } port := uint64(5432) if rawPort := os.Getenv(PostgresEntPortEnv); rawPort != "" { var err error port, err = strconv.ParseUint(rawPort, 10, 32) require.NoError(t, err, "invalid postgres port %q: %s", rawPort, err) } newStorage := func(t *testing.T) storage.Storage { return newPostgresStorage(t, host, port) } conformance.RunTests(t, newStorage) conformance.RunTransactionTests(t, newStorage) // TODO(nabokihms): ent Postgres uses SERIALIZABLE transaction isolation for UpdateRefreshToken, // but does not retry on serialization failures (pq: could not serialize access due to // concurrent update, SQLSTATE 40001). Under high contention most updates fail immediately. // conformance.RunConcurrencyTests(t, newStorage) } func TestPostgresDSN(t *testing.T) { tests := []struct { name string cfg *Postgres desiredDSN string }{ { name: "Host port", cfg: &Postgres{ NetworkDB: NetworkDB{ Host: "localhost", Port: uint16(5432), }, }, desiredDSN: "connect_timeout=0 host='localhost' port=5432 sslmode='verify-full'", }, { name: "Host with port", cfg: &Postgres{ NetworkDB: NetworkDB{ Host: "localhost:5432", }, }, desiredDSN: "connect_timeout=0 host='localhost' port=5432 sslmode='verify-full'", }, { name: "Host ipv6 with port", cfg: &Postgres{ NetworkDB: NetworkDB{ Host: "[a:b:c:d]:5432", }, }, desiredDSN: "connect_timeout=0 host='a:b:c:d' port=5432 sslmode='verify-full'", }, { name: "Credentials and timeout", cfg: &Postgres{ NetworkDB: NetworkDB{ Database: "test", User: "test", Password: "test", ConnectionTimeout: 5, }, }, desiredDSN: "connect_timeout=5 user='test' password='test' dbname='test' sslmode='verify-full'", }, { name: "SSL", cfg: &Postgres{ SSL: SSL{ Mode: pgSSLRequire, CAFile: "/ca.crt", KeyFile: "/cert.crt", CertFile: "/cert.key", }, }, desiredDSN: "connect_timeout=0 sslmode='require' sslrootcert='/ca.crt' sslcert='/cert.key' sslkey='/cert.crt'", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.desiredDSN, tt.cfg.dsn()) }) } } func TestPostgresDriver(t *testing.T) { host := os.Getenv(PostgresEntHostEnv) if host == "" { t.Skipf("test environment variable %s not set, skipping", PostgresEntHostEnv) } port := uint64(5432) if rawPort := os.Getenv(PostgresEntPortEnv); rawPort != "" { var err error port, err = strconv.ParseUint(rawPort, 10, 32) require.NoError(t, err, "invalid postgres port %q: %s", rawPort, err) } tests := []struct { name string cfg func() *Postgres desiredConns int }{ { name: "Defaults", cfg: func() *Postgres { return postgresTestConfig(host, port) }, desiredConns: 5, }, { name: "Tune", cfg: func() *Postgres { cfg := postgresTestConfig(host, port) cfg.MaxOpenConns = 101 return cfg }, desiredConns: 101, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { drv, err := tt.cfg().driver() require.NoError(t, err) require.Equal(t, tt.desiredConns, drv.DB().Stats().MaxOpenConnections) }) } } ================================================ FILE: storage/ent/schema/authcode.go ================================================ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" ) /* Original SQL table: create table auth_code ( id text not null primary key, client_id text not null, scopes blob not null, nonce text not null, redirect_uri text not null, claims_user_id text not null, claims_username text not null, claims_email text not null, claims_email_verified integer not null, claims_groups blob not null, connector_id text not null, connector_data blob, expiry timestamp not null, claims_preferred_username text default '' not null, code_challenge text default '' not null, code_challenge_method text default '' not null ); */ // AuthCode holds the schema definition for the AuthCode entity. type AuthCode struct { ent.Schema } // Fields of the AuthCode. func (AuthCode) Fields() []ent.Field { return []ent.Field{ field.Text("id"). SchemaType(textSchema). NotEmpty(). Unique(), field.Text("client_id"). SchemaType(textSchema). NotEmpty(), field.JSON("scopes", []string{}). Optional(), field.Text("nonce"). SchemaType(textSchema). NotEmpty(), field.Text("redirect_uri"). SchemaType(textSchema). NotEmpty(), field.Text("claims_user_id"). SchemaType(textSchema). NotEmpty(), field.Text("claims_username"). SchemaType(textSchema). NotEmpty(), field.Text("claims_email"). SchemaType(textSchema). NotEmpty(), field.Bool("claims_email_verified"), field.JSON("claims_groups", []string{}). Optional(), field.Text("claims_preferred_username"). SchemaType(textSchema). Default(""), field.Text("connector_id"). SchemaType(textSchema). NotEmpty(), field.Bytes("connector_data"). Nillable(). Optional(), field.Time("expiry"). SchemaType(timeSchema), field.Text("code_challenge"). SchemaType(textSchema). Default(""), field.Text("code_challenge_method"). SchemaType(textSchema). Default(""), field.Time("auth_time"). SchemaType(timeSchema). Optional(), } } // Edges of the AuthCode. func (AuthCode) Edges() []ent.Edge { return []ent.Edge{} } ================================================ FILE: storage/ent/schema/authrequest.go ================================================ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" ) /* Original SQL table: create table auth_request ( id text not null primary key, client_id text not null, response_types blob not null, scopes blob not null, redirect_uri text not null, nonce text not null, state text not null, force_approval_prompt integer not null, logged_in integer not null, claims_user_id text not null, claims_username text not null, claims_email text not null, claims_email_verified integer not null, claims_groups blob not null, connector_id text not null, connector_data blob, expiry timestamp not null, claims_preferred_username text default '' not null, code_challenge text default '' not null, code_challenge_method text default '' not null, hmac_key blob ); */ // AuthRequest holds the schema definition for the AuthRequest entity. type AuthRequest struct { ent.Schema } // Fields of the AuthRequest. func (AuthRequest) Fields() []ent.Field { return []ent.Field{ field.Text("id"). SchemaType(textSchema). NotEmpty(). Unique(), field.Text("client_id"). SchemaType(textSchema), field.JSON("scopes", []string{}). Optional(), field.JSON("response_types", []string{}). Optional(), field.Text("redirect_uri"). SchemaType(textSchema), field.Text("nonce"). SchemaType(textSchema), field.Text("state"). SchemaType(textSchema), field.Bool("force_approval_prompt"), field.Bool("logged_in"), field.Text("claims_user_id"). SchemaType(textSchema), field.Text("claims_username"). SchemaType(textSchema), field.Text("claims_email"). SchemaType(textSchema), field.Bool("claims_email_verified"), field.JSON("claims_groups", []string{}). Optional(), field.Text("claims_preferred_username"). SchemaType(textSchema). Default(""), field.Text("connector_id"). SchemaType(textSchema), field.Bytes("connector_data"). Nillable(). Optional(), field.Time("expiry"). SchemaType(timeSchema), field.Text("code_challenge"). SchemaType(textSchema). Default(""), field.Text("code_challenge_method"). SchemaType(textSchema). Default(""), field.Bytes("hmac_key"), field.Bool("mfa_validated"). Default(false), field.Text("prompt").SchemaType(textSchema).Default(""), field.Int("max_age").Default(-1), field.Time("auth_time").SchemaType(timeSchema).Optional(), } } // Edges of the AuthRequest. func (AuthRequest) Edges() []ent.Edge { return []ent.Edge{} } ================================================ FILE: storage/ent/schema/authsession.go ================================================ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" ) // AuthSession holds the schema definition for the AuthSession entity. type AuthSession struct { ent.Schema } // Fields of the AuthSession. func (AuthSession) Fields() []ent.Field { return []ent.Field{ field.Text("id"). SchemaType(textSchema). NotEmpty(). Unique(), field.Text("user_id"). SchemaType(textSchema). NotEmpty(), field.Text("connector_id"). SchemaType(textSchema). NotEmpty(), field.Text("nonce"). SchemaType(textSchema). NotEmpty(), field.Bytes("client_states"), field.Time("created_at"). SchemaType(timeSchema), field.Time("last_activity"). SchemaType(timeSchema), field.Text("ip_address"). SchemaType(textSchema). Default(""), field.Text("user_agent"). SchemaType(textSchema). Default(""), field.Time("absolute_expiry"). SchemaType(timeSchema), field.Time("idle_expiry"). SchemaType(timeSchema), } } // Edges of the AuthSession. func (AuthSession) Edges() []ent.Edge { return []ent.Edge{} } ================================================ FILE: storage/ent/schema/client.go ================================================ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" ) /* Original SQL table: create table client ( id text not null primary key, secret text not null, redirect_uris blob not null, trusted_peers blob not null, public integer not null, name text not null, logo_url text not null ); */ // OAuth2Client holds the schema definition for the Client entity. type OAuth2Client struct { ent.Schema } // Fields of the OAuth2Client. func (OAuth2Client) Fields() []ent.Field { return []ent.Field{ field.Text("id"). SchemaType(textSchema). MaxLen(100). NotEmpty(). Unique(), field.Text("secret"). SchemaType(textSchema). NotEmpty(), field.JSON("redirect_uris", []string{}). Optional(), field.JSON("trusted_peers", []string{}). Optional(), field.Bool("public"), field.Text("name"). SchemaType(textSchema). NotEmpty(), field.Text("logo_url"). SchemaType(textSchema). NotEmpty(), field.JSON("allowed_connectors", []string{}). Optional(), field.JSON("mfa_chain", []string{}). Optional(), } } // Edges of the OAuth2Client. func (OAuth2Client) Edges() []ent.Edge { return []ent.Edge{} } ================================================ FILE: storage/ent/schema/connector.go ================================================ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" ) /* Original SQL table: create table connector ( id text not null primary key, type text not null, name text not null, resource_version text not null, config blob ); */ // Connector holds the schema definition for the Client entity. type Connector struct { ent.Schema } // Fields of the Connector. func (Connector) Fields() []ent.Field { return []ent.Field{ field.Text("id"). SchemaType(textSchema). MaxLen(100). NotEmpty(). Unique(), field.Text("type"). SchemaType(textSchema). NotEmpty(), field.Text("name"). SchemaType(textSchema). NotEmpty(), field.Text("resource_version"). SchemaType(textSchema), field.Bytes("config"), field.JSON("grant_types", []string{}). Optional(), } } // Edges of the Connector. func (Connector) Edges() []ent.Edge { return []ent.Edge{} } ================================================ FILE: storage/ent/schema/devicerequest.go ================================================ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" ) /* Original SQL table: create table device_request ( user_code text not null primary key, device_code text not null, client_id text not null, client_secret text, scopes blob not null, expiry timestamp not null ); */ // DeviceRequest holds the schema definition for the DeviceRequest entity. type DeviceRequest struct { ent.Schema } // Fields of the DeviceRequest. func (DeviceRequest) Fields() []ent.Field { return []ent.Field{ field.Text("user_code"). SchemaType(textSchema). NotEmpty(). Unique(), field.Text("device_code"). SchemaType(textSchema). NotEmpty(), field.Text("client_id"). SchemaType(textSchema). NotEmpty(), field.Text("client_secret"). SchemaType(textSchema). NotEmpty(), field.JSON("scopes", []string{}). Optional(), field.Time("expiry"). SchemaType(timeSchema), } } // Edges of the DeviceRequest. func (DeviceRequest) Edges() []ent.Edge { return []ent.Edge{} } ================================================ FILE: storage/ent/schema/devicetoken.go ================================================ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" ) /* Original SQL table: create table device_token ( device_code text not null primary key, status text not null, token blob, expiry timestamp not null, last_request timestamp not null, poll_interval integer not null, code_challenge text default '' not null, code_challenge_method text default '' not null ); */ // DeviceToken holds the schema definition for the DeviceToken entity. type DeviceToken struct { ent.Schema } // Fields of the DeviceToken. func (DeviceToken) Fields() []ent.Field { return []ent.Field{ field.Text("device_code"). SchemaType(textSchema). NotEmpty(). Unique(), field.Text("status"). SchemaType(textSchema). NotEmpty(), field.Bytes("token").Nillable().Optional(), field.Time("expiry"). SchemaType(timeSchema), field.Time("last_request"). SchemaType(timeSchema), field.Int("poll_interval"), field.Text("code_challenge"). SchemaType(textSchema). Default(""), field.Text("code_challenge_method"). SchemaType(textSchema). Default(""), } } // Edges of the DeviceToken. func (DeviceToken) Edges() []ent.Edge { return []ent.Edge{} } ================================================ FILE: storage/ent/schema/dialects.go ================================================ package schema import ( "entgo.io/ent/dialect" ) var textSchema = map[string]string{ dialect.Postgres: "text", dialect.SQLite: "text", // MySQL doesn't support indices on text fields w/o // specifying key length. Use varchar instead (767 byte // is the max key length for InnoDB with 4k pages). // For compound indexes (with two keys) even less. dialect.MySQL: "varchar(384)", } var timeSchema = map[string]string{ dialect.Postgres: "timestamptz", dialect.SQLite: "timestamp", dialect.MySQL: "datetime(3)", } ================================================ FILE: storage/ent/schema/keys.go ================================================ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" "github.com/go-jose/go-jose/v4" "github.com/dexidp/dex/storage" ) /* Original SQL table: create table keys ( id text not null primary key, verification_keys blob not null, signing_key blob not null, signing_key_pub blob not null, next_rotation timestamp not null ); */ // Keys holds the schema definition for the Keys entity. type Keys struct { ent.Schema } // Fields of the Keys. func (Keys) Fields() []ent.Field { return []ent.Field{ field.Text("id"). SchemaType(textSchema). NotEmpty(). Unique(), field.JSON("verification_keys", []storage.VerificationKey{}), field.JSON("signing_key", jose.JSONWebKey{}), field.JSON("signing_key_pub", jose.JSONWebKey{}), field.Time("next_rotation"). SchemaType(timeSchema), } } // Edges of the Keys. func (Keys) Edges() []ent.Edge { return []ent.Edge{} } ================================================ FILE: storage/ent/schema/offlinesession.go ================================================ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" ) /* Original SQL table: create table offline_session ( user_id text not null, conn_id text not null, refresh blob not null, connector_data blob, primary key (user_id, conn_id) ); */ // OfflineSession holds the schema definition for the OfflineSession entity. type OfflineSession struct { ent.Schema } // Fields of the OfflineSession. func (OfflineSession) Fields() []ent.Field { return []ent.Field{ // Using id field here because it's impossible to create multi-key primary yet field.Text("id"). SchemaType(textSchema). NotEmpty(). Unique(), field.Text("user_id"). SchemaType(textSchema). NotEmpty(), field.Text("conn_id"). SchemaType(textSchema). NotEmpty(), field.Bytes("refresh"), field.Bytes("connector_data").Nillable().Optional(), } } // Edges of the OfflineSession. func (OfflineSession) Edges() []ent.Edge { return []ent.Edge{} } ================================================ FILE: storage/ent/schema/password.go ================================================ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" ) /* Original SQL table: create table password ( email text not null primary key, hash blob not null, username text not null, user_id text not null ); */ // Password holds the schema definition for the Password entity. type Password struct { ent.Schema } // Fields of the Password. func (Password) Fields() []ent.Field { return []ent.Field{ field.Text("email"). SchemaType(textSchema). StorageKey("email"). // use email as ID field to make querying easier NotEmpty(). Unique(), field.Bytes("hash"), field.Text("username"). SchemaType(textSchema). NotEmpty(), field.Text("name"). SchemaType(textSchema). Default(""), field.Text("preferred_username"). SchemaType(textSchema). Default(""), field.Bool("email_verified"). Optional(). Nillable(), field.Text("user_id"). SchemaType(textSchema). NotEmpty(), field.JSON("groups", []string{}). Optional(), } } // Edges of the Password. func (Password) Edges() []ent.Edge { return []ent.Edge{} } ================================================ FILE: storage/ent/schema/refreshtoken.go ================================================ package schema import ( "time" "entgo.io/ent" "entgo.io/ent/schema/field" ) /* Original SQL table: create table refresh_token ( id text not null primary key, client_id text not null, scopes blob not null, nonce text not null, claims_user_id text not null, claims_username text not null, claims_email text not null, claims_email_verified integer not null, claims_groups blob not null, connector_id text not null, connector_data blob, token text default '' not null, created_at timestamp default '0001-01-01 00:00:00 UTC' not null, last_used timestamp default '0001-01-01 00:00:00 UTC' not null, claims_preferred_username text default '' not null, obsolete_token text default '' ); */ // RefreshToken holds the schema definition for the RefreshToken entity. type RefreshToken struct { ent.Schema } // Fields of the RefreshToken. func (RefreshToken) Fields() []ent.Field { return []ent.Field{ field.Text("id"). SchemaType(textSchema). NotEmpty(). Unique(), field.Text("client_id"). SchemaType(textSchema). NotEmpty(), field.JSON("scopes", []string{}). Optional(), field.Text("nonce"). SchemaType(textSchema). NotEmpty(), field.Text("claims_user_id"). SchemaType(textSchema). NotEmpty(), field.Text("claims_username"). SchemaType(textSchema). NotEmpty(), field.Text("claims_email"). SchemaType(textSchema). NotEmpty(), field.Bool("claims_email_verified"), field.JSON("claims_groups", []string{}). Optional(), field.Text("claims_preferred_username"). SchemaType(textSchema). Default(""), field.Text("connector_id"). SchemaType(textSchema). NotEmpty(), field.Bytes("connector_data"). Nillable(). Optional(), field.Text("token"). SchemaType(textSchema). Default(""), field.Text("obsolete_token"). SchemaType(textSchema). Default(""), field.Time("created_at"). SchemaType(timeSchema). Default(time.Now), field.Time("last_used"). SchemaType(timeSchema). Default(time.Now), } } // Edges of the RefreshToken. func (RefreshToken) Edges() []ent.Edge { return []ent.Edge{} } ================================================ FILE: storage/ent/schema/useridentity.go ================================================ package schema import ( "entgo.io/ent" "entgo.io/ent/schema/field" ) // UserIdentity holds the schema definition for the UserIdentity entity. type UserIdentity struct { ent.Schema } // Fields of the UserIdentity. func (UserIdentity) Fields() []ent.Field { return []ent.Field{ // Using id field here because it's impossible to create multi-key primary yet field.Text("id"). SchemaType(textSchema). NotEmpty(). Unique(), field.Text("user_id"). SchemaType(textSchema). NotEmpty(), field.Text("connector_id"). SchemaType(textSchema). NotEmpty(), field.Text("claims_user_id"). SchemaType(textSchema). Default(""), field.Text("claims_username"). SchemaType(textSchema). Default(""), field.Text("claims_preferred_username"). SchemaType(textSchema). Default(""), field.Text("claims_email"). SchemaType(textSchema). Default(""), field.Bool("claims_email_verified"). Default(false), field.JSON("claims_groups", []string{}). Optional(), field.Bytes("consents"), field.Bytes("mfa_secrets"). Nillable(). Optional(), field.Time("created_at"). SchemaType(timeSchema), field.Time("last_login"). SchemaType(timeSchema), field.Time("blocked_until"). SchemaType(timeSchema), } } // Edges of the UserIdentity. func (UserIdentity) Edges() []ent.Edge { return []ent.Edge{} } ================================================ FILE: storage/ent/sqlite.go ================================================ package ent import ( "context" "crypto/sha256" "log/slog" "strings" "entgo.io/ent/dialect/sql" _ "github.com/mattn/go-sqlite3" // Register sqlite driver. "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/client" "github.com/dexidp/dex/storage/ent/db" ) // SQLite3 options for creating an SQL db. type SQLite3 struct { File string `json:"file"` } // Open always returns a new in sqlite3 storage. func (s *SQLite3) Open(logger *slog.Logger) (storage.Storage, error) { logger.Debug("experimental ent-based storage driver is enabled") // Implicitly set foreign_keys pragma to "on" because it is required by ent s.File = addFK(s.File) drv, err := sql.Open("sqlite3", s.File) if err != nil { return nil, err } // always allow only one connection to sqlite3, any other thread/go-routine // attempting concurrent access will have to wait pool := drv.DB() pool.SetMaxOpenConns(1) databaseClient := client.NewDatabase( client.WithClient(db.NewClient(db.Driver(drv))), client.WithHasher(sha256.New), ) if err := databaseClient.Schema().Create(context.TODO()); err != nil { return nil, err } return databaseClient, nil } func addFK(dsn string) string { if strings.Contains(dsn, "_fk") { return dsn } delim := "?" if strings.Contains(dsn, "?") { delim = "&" } return dsn + delim + "_fk=1" } ================================================ FILE: storage/ent/sqlite_test.go ================================================ package ent import ( "log/slog" "testing" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/conformance" ) func newSQLiteStorage(t *testing.T) storage.Storage { logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug})) cfg := SQLite3{File: ":memory:"} s, err := cfg.Open(logger) if err != nil { panic(err) } return s } func TestSQLite3(t *testing.T) { conformance.RunTests(t, newSQLiteStorage) conformance.RunConcurrencyTests(t, newSQLiteStorage) } ================================================ FILE: storage/ent/types.go ================================================ package ent // NetworkDB contains options common to SQL databases accessed over network. type NetworkDB struct { Database string User string Password string Host string Port uint16 ConnectionTimeout int // Seconds MaxOpenConns int // default: 5 MaxIdleConns int // default: 5 ConnMaxLifetime int // Seconds, default: not set } // SSL represents SSL options for network databases. type SSL struct { Mode string CAFile string // Files for client auth. KeyFile string CertFile string } ================================================ FILE: storage/ent/utils.go ================================================ package ent import "os" func getenv(key, defaultVal string) string { if val := os.Getenv(key); val != "" { return val } return defaultVal } ================================================ FILE: storage/etcd/config.go ================================================ package etcd import ( "log/slog" "time" "go.etcd.io/etcd/client/pkg/v3/transport" clientv3 "go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/client/v3/namespace" "github.com/dexidp/dex/storage" ) var defaultDialTimeout = 2 * time.Second // SSL represents SSL options for etcd databases. type SSL struct { ServerName string `json:"serverName"` CAFile string `json:"caFile"` KeyFile string `json:"keyFile"` CertFile string `json:"certFile"` } // Etcd options for connecting to etcd databases. // If you are using a shared etcd cluster for storage, it might be useful to // configure an etcd namespace either via Namespace field or using `etcd grpc-proxy // --namespace=` type Etcd struct { Endpoints []string `json:"endpoints"` Namespace string `json:"namespace"` Username string `json:"username"` Password string `json:"password"` SSL SSL `json:"ssl"` } // Open creates a new storage implementation backed by Etcd func (p *Etcd) Open(logger *slog.Logger) (storage.Storage, error) { return p.open(logger) } func (p *Etcd) open(logger *slog.Logger) (*conn, error) { cfg := clientv3.Config{ Endpoints: p.Endpoints, DialTimeout: defaultDialTimeout, Username: p.Username, Password: p.Password, } var cfgtls *transport.TLSInfo tlsinfo := transport.TLSInfo{} if p.SSL.CertFile != "" { tlsinfo.CertFile = p.SSL.CertFile cfgtls = &tlsinfo } if p.SSL.KeyFile != "" { tlsinfo.KeyFile = p.SSL.KeyFile cfgtls = &tlsinfo } if p.SSL.CAFile != "" { tlsinfo.TrustedCAFile = p.SSL.CAFile cfgtls = &tlsinfo } if p.SSL.ServerName != "" { tlsinfo.ServerName = p.SSL.ServerName cfgtls = &tlsinfo } if cfgtls != nil { clientTLS, err := cfgtls.ClientConfig() if err != nil { return nil, err } cfg.TLS = clientTLS } db, err := clientv3.New(cfg) if err != nil { return nil, err } if len(p.Namespace) > 0 { db.KV = namespace.NewKV(db.KV, p.Namespace) } c := &conn{ db: db, logger: logger, } return c, nil } ================================================ FILE: storage/etcd/etcd.go ================================================ package etcd import ( "context" "encoding/json" "fmt" "log/slog" "strings" "time" clientv3 "go.etcd.io/etcd/client/v3" "github.com/dexidp/dex/storage" ) const ( clientPrefix = "client/" authCodePrefix = "auth_code/" refreshTokenPrefix = "refresh_token/" authRequestPrefix = "auth_req/" passwordPrefix = "password/" offlineSessionPrefix = "offline_session/" connectorPrefix = "connector/" keysName = "openid-connect-keys" deviceRequestPrefix = "device_req/" deviceTokenPrefix = "device_token/" userIdentityPrefix = "user_identity/" authSessionPrefix = "auth_session/" // defaultStorageTimeout will be applied to all storage's operations. defaultStorageTimeout = 5 * time.Second ) var _ storage.Storage = (*conn)(nil) type conn struct { db *clientv3.Client logger *slog.Logger } func (c *conn) Close() error { return c.db.Close() } func (c *conn) GarbageCollect(ctx context.Context, now time.Time) (result storage.GCResult, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() authRequests, err := c.listAuthRequests(ctx) if err != nil { return result, err } var delErr error for _, authRequest := range authRequests { if now.After(authRequest.Expiry) { if err := c.deleteKey(ctx, keyID(authRequestPrefix, authRequest.ID)); err != nil { c.logger.Error("failed to delete auth request", "err", err) delErr = fmt.Errorf("failed to delete auth request: %v", err) } result.AuthRequests++ } } if delErr != nil { return result, delErr } authCodes, err := c.listAuthCodes(ctx) if err != nil { return result, err } for _, authCode := range authCodes { if now.After(authCode.Expiry) { if err := c.deleteKey(ctx, keyID(authCodePrefix, authCode.ID)); err != nil { c.logger.Error("failed to delete auth code", "err", err) delErr = fmt.Errorf("failed to delete auth code: %v", err) } result.AuthCodes++ } } deviceRequests, err := c.listDeviceRequests(ctx) if err != nil { return result, err } for _, deviceRequest := range deviceRequests { if now.After(deviceRequest.Expiry) { if err := c.deleteKey(ctx, keyID(deviceRequestPrefix, deviceRequest.UserCode)); err != nil { c.logger.Error("failed to delete device request", "err", err) delErr = fmt.Errorf("failed to delete device request: %v", err) } result.DeviceRequests++ } } deviceTokens, err := c.listDeviceTokens(ctx) if err != nil { return result, err } for _, deviceToken := range deviceTokens { if now.After(deviceToken.Expiry) { if err := c.deleteKey(ctx, keyID(deviceTokenPrefix, deviceToken.DeviceCode)); err != nil { c.logger.Error("failed to delete device token", "err", err) delErr = fmt.Errorf("failed to delete device token: %v", err) } result.DeviceTokens++ } } authSessions, err := c.listAuthSessionsInternal(ctx) if err != nil { return result, err } for _, authSession := range authSessions { if now.After(authSession.AbsoluteExpiry) || now.After(authSession.IdleExpiry) { if err := c.deleteKey(ctx, keyAuthSession(authSession.UserID, authSession.ConnectorID)); err != nil { c.logger.Error("failed to delete auth session", "err", err) delErr = fmt.Errorf("failed to delete auth session: %v", err) } else { result.AuthSessions++ } } } return result, delErr } func (c *conn) CreateAuthRequest(ctx context.Context, a storage.AuthRequest) error { return c.txnCreate(ctx, keyID(authRequestPrefix, a.ID), fromStorageAuthRequest(a)) } func (c *conn) GetAuthRequest(ctx context.Context, id string) (a storage.AuthRequest, err error) { // TODO: Add this to other funcs?? ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() var req AuthRequest if err = c.getKey(ctx, keyID(authRequestPrefix, id), &req); err != nil { return } return toStorageAuthRequest(req), nil } func (c *conn) UpdateAuthRequest(ctx context.Context, id string, updater func(a storage.AuthRequest) (storage.AuthRequest, error)) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.txnUpdate(ctx, keyID(authRequestPrefix, id), func(currentValue []byte) ([]byte, error) { var current AuthRequest if len(currentValue) > 0 { if err := json.Unmarshal(currentValue, ¤t); err != nil { return nil, err } } updated, err := updater(toStorageAuthRequest(current)) if err != nil { return nil, err } return json.Marshal(fromStorageAuthRequest(updated)) }) } func (c *conn) DeleteAuthRequest(ctx context.Context, id string) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.deleteKey(ctx, keyID(authRequestPrefix, id)) } func (c *conn) CreateAuthCode(ctx context.Context, a storage.AuthCode) error { return c.txnCreate(ctx, keyID(authCodePrefix, a.ID), fromStorageAuthCode(a)) } func (c *conn) GetAuthCode(ctx context.Context, id string) (a storage.AuthCode, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() var ac AuthCode err = c.getKey(ctx, keyID(authCodePrefix, id), &ac) if err == nil { a = toStorageAuthCode(ac) } return a, err } func (c *conn) DeleteAuthCode(ctx context.Context, id string) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.deleteKey(ctx, keyID(authCodePrefix, id)) } func (c *conn) CreateRefresh(ctx context.Context, r storage.RefreshToken) error { return c.txnCreate(ctx, keyID(refreshTokenPrefix, r.ID), fromStorageRefreshToken(r)) } func (c *conn) GetRefresh(ctx context.Context, id string) (r storage.RefreshToken, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() var token RefreshToken if err = c.getKey(ctx, keyID(refreshTokenPrefix, id), &token); err != nil { return } return toStorageRefreshToken(token), nil } func (c *conn) UpdateRefreshToken(ctx context.Context, id string, updater func(old storage.RefreshToken) (storage.RefreshToken, error)) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.txnUpdate(ctx, keyID(refreshTokenPrefix, id), func(currentValue []byte) ([]byte, error) { var current RefreshToken if len(currentValue) > 0 { if err := json.Unmarshal(currentValue, ¤t); err != nil { return nil, err } } updated, err := updater(toStorageRefreshToken(current)) if err != nil { return nil, err } return json.Marshal(fromStorageRefreshToken(updated)) }) } func (c *conn) DeleteRefresh(ctx context.Context, id string) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.deleteKey(ctx, keyID(refreshTokenPrefix, id)) } func (c *conn) ListRefreshTokens(ctx context.Context) (tokens []storage.RefreshToken, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() res, err := c.db.Get(ctx, refreshTokenPrefix, clientv3.WithPrefix()) if err != nil { return tokens, err } for _, v := range res.Kvs { var token RefreshToken if err = json.Unmarshal(v.Value, &token); err != nil { return tokens, err } tokens = append(tokens, toStorageRefreshToken(token)) } return tokens, nil } func (c *conn) CreateClient(ctx context.Context, cli storage.Client) error { return c.txnCreate(ctx, keyID(clientPrefix, cli.ID), cli) } func (c *conn) GetClient(ctx context.Context, id string) (cli storage.Client, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() err = c.getKey(ctx, keyID(clientPrefix, id), &cli) return cli, err } func (c *conn) UpdateClient(ctx context.Context, id string, updater func(old storage.Client) (storage.Client, error)) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.txnUpdate(ctx, keyID(clientPrefix, id), func(currentValue []byte) ([]byte, error) { var current storage.Client if len(currentValue) > 0 { if err := json.Unmarshal(currentValue, ¤t); err != nil { return nil, err } } updated, err := updater(current) if err != nil { return nil, err } return json.Marshal(updated) }) } func (c *conn) DeleteClient(ctx context.Context, id string) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.deleteKey(ctx, keyID(clientPrefix, id)) } func (c *conn) ListClients(ctx context.Context) (clients []storage.Client, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() res, err := c.db.Get(ctx, clientPrefix, clientv3.WithPrefix()) if err != nil { return clients, err } for _, v := range res.Kvs { var cli storage.Client if err = json.Unmarshal(v.Value, &cli); err != nil { return clients, err } clients = append(clients, cli) } return clients, nil } func (c *conn) CreatePassword(ctx context.Context, p storage.Password) error { return c.txnCreate(ctx, passwordPrefix+strings.ToLower(p.Email), p) } func (c *conn) GetPassword(ctx context.Context, email string) (p storage.Password, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() err = c.getKey(ctx, keyEmail(passwordPrefix, email), &p) return p, err } func (c *conn) UpdatePassword(ctx context.Context, email string, updater func(p storage.Password) (storage.Password, error)) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.txnUpdate(ctx, keyEmail(passwordPrefix, email), func(currentValue []byte) ([]byte, error) { var current storage.Password if len(currentValue) > 0 { if err := json.Unmarshal(currentValue, ¤t); err != nil { return nil, err } } updated, err := updater(current) if err != nil { return nil, err } return json.Marshal(updated) }) } func (c *conn) DeletePassword(ctx context.Context, email string) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.deleteKey(ctx, keyEmail(passwordPrefix, email)) } func (c *conn) ListPasswords(ctx context.Context) (passwords []storage.Password, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() res, err := c.db.Get(ctx, passwordPrefix, clientv3.WithPrefix()) if err != nil { return passwords, err } for _, v := range res.Kvs { var p storage.Password if err = json.Unmarshal(v.Value, &p); err != nil { return passwords, err } passwords = append(passwords, p) } return passwords, nil } func (c *conn) CreateOfflineSessions(ctx context.Context, s storage.OfflineSessions) error { return c.txnCreate(ctx, keySession(s.UserID, s.ConnID), fromStorageOfflineSessions(s)) } func (c *conn) UpdateOfflineSessions(ctx context.Context, userID string, connID string, updater func(s storage.OfflineSessions) (storage.OfflineSessions, error)) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.txnUpdate(ctx, keySession(userID, connID), func(currentValue []byte) ([]byte, error) { var current OfflineSessions if len(currentValue) > 0 { if err := json.Unmarshal(currentValue, ¤t); err != nil { return nil, err } } updated, err := updater(toStorageOfflineSessions(current)) if err != nil { return nil, err } return json.Marshal(fromStorageOfflineSessions(updated)) }) } func (c *conn) GetOfflineSessions(ctx context.Context, userID string, connID string) (s storage.OfflineSessions, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() var os OfflineSessions if err = c.getKey(ctx, keySession(userID, connID), &os); err != nil { return } return toStorageOfflineSessions(os), nil } func (c *conn) DeleteOfflineSessions(ctx context.Context, userID string, connID string) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.deleteKey(ctx, keySession(userID, connID)) } func (c *conn) CreateUserIdentity(ctx context.Context, u storage.UserIdentity) error { return c.txnCreate(ctx, keyUserIdentity(u.UserID, u.ConnectorID), fromStorageUserIdentity(u)) } func (c *conn) GetUserIdentity(ctx context.Context, userID, connectorID string) (u storage.UserIdentity, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() var ui UserIdentity if err = c.getKey(ctx, keyUserIdentity(userID, connectorID), &ui); err != nil { return } return toStorageUserIdentity(ui), nil } func (c *conn) UpdateUserIdentity(ctx context.Context, userID, connectorID string, updater func(u storage.UserIdentity) (storage.UserIdentity, error)) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.txnUpdate(ctx, keyUserIdentity(userID, connectorID), func(currentValue []byte) ([]byte, error) { var current UserIdentity if len(currentValue) > 0 { if err := json.Unmarshal(currentValue, ¤t); err != nil { return nil, err } } updated, err := updater(toStorageUserIdentity(current)) if err != nil { return nil, err } return json.Marshal(fromStorageUserIdentity(updated)) }) } func (c *conn) DeleteUserIdentity(ctx context.Context, userID, connectorID string) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.deleteKey(ctx, keyUserIdentity(userID, connectorID)) } func (c *conn) ListUserIdentities(ctx context.Context) (identities []storage.UserIdentity, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() res, err := c.db.Get(ctx, userIdentityPrefix, clientv3.WithPrefix()) if err != nil { return identities, err } for _, v := range res.Kvs { var ui UserIdentity if err = json.Unmarshal(v.Value, &ui); err != nil { return identities, err } identities = append(identities, toStorageUserIdentity(ui)) } return identities, nil } func (c *conn) CreateAuthSession(ctx context.Context, s storage.AuthSession) error { return c.txnCreate(ctx, keyAuthSession(s.UserID, s.ConnectorID), fromStorageAuthSession(s)) } func (c *conn) GetAuthSession(ctx context.Context, userID, connectorID string) (storage.AuthSession, error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() var s AuthSession if err := c.getKey(ctx, keyAuthSession(userID, connectorID), &s); err != nil { return storage.AuthSession{}, err } return toStorageAuthSession(s), nil } func (c *conn) UpdateAuthSession(ctx context.Context, userID, connectorID string, updater func(s storage.AuthSession) (storage.AuthSession, error)) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.txnUpdate(ctx, keyAuthSession(userID, connectorID), func(currentValue []byte) ([]byte, error) { var current AuthSession if len(currentValue) > 0 { if err := json.Unmarshal(currentValue, ¤t); err != nil { return nil, err } } updated, err := updater(toStorageAuthSession(current)) if err != nil { return nil, err } return json.Marshal(fromStorageAuthSession(updated)) }) } func (c *conn) ListAuthSessions(ctx context.Context) (sessions []storage.AuthSession, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() res, err := c.listAuthSessionsInternal(ctx) if err != nil { return sessions, err } for _, v := range res { sessions = append(sessions, toStorageAuthSession(v)) } return sessions, nil } func (c *conn) DeleteAuthSession(ctx context.Context, userID, connectorID string) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.deleteKey(ctx, keyAuthSession(userID, connectorID)) } func (c *conn) CreateConnector(ctx context.Context, connector storage.Connector) error { return c.txnCreate(ctx, keyID(connectorPrefix, connector.ID), connector) } func (c *conn) GetConnector(ctx context.Context, id string) (conn storage.Connector, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() err = c.getKey(ctx, keyID(connectorPrefix, id), &conn) return conn, err } func (c *conn) UpdateConnector(ctx context.Context, id string, updater func(s storage.Connector) (storage.Connector, error)) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.txnUpdate(ctx, keyID(connectorPrefix, id), func(currentValue []byte) ([]byte, error) { var current storage.Connector if len(currentValue) > 0 { if err := json.Unmarshal(currentValue, ¤t); err != nil { return nil, err } } updated, err := updater(current) if err != nil { return nil, err } return json.Marshal(updated) }) } func (c *conn) DeleteConnector(ctx context.Context, id string) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.deleteKey(ctx, keyID(connectorPrefix, id)) } func (c *conn) ListConnectors(ctx context.Context) (connectors []storage.Connector, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() res, err := c.db.Get(ctx, connectorPrefix, clientv3.WithPrefix()) if err != nil { return nil, err } for _, v := range res.Kvs { var c storage.Connector if err = json.Unmarshal(v.Value, &c); err != nil { return nil, err } connectors = append(connectors, c) } return connectors, nil } func (c *conn) GetKeys(ctx context.Context) (keys storage.Keys, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() res, err := c.db.Get(ctx, keysName) if err != nil { return keys, err } if res.Count > 0 && len(res.Kvs) > 0 { err = json.Unmarshal(res.Kvs[0].Value, &keys) } return keys, err } func (c *conn) UpdateKeys(ctx context.Context, updater func(old storage.Keys) (storage.Keys, error)) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.txnUpdate(ctx, keysName, func(currentValue []byte) ([]byte, error) { var current storage.Keys if len(currentValue) > 0 { if err := json.Unmarshal(currentValue, ¤t); err != nil { return nil, err } } updated, err := updater(current) if err != nil { return nil, err } return json.Marshal(updated) }) } func (c *conn) deleteKey(ctx context.Context, key string) error { res, err := c.db.Delete(ctx, key) if err != nil { return err } if res.Deleted == 0 { return storage.ErrNotFound } return nil } func (c *conn) getKey(ctx context.Context, key string, value interface{}) error { r, err := c.db.Get(ctx, key) if err != nil { return err } if r.Count == 0 { return storage.ErrNotFound } return json.Unmarshal(r.Kvs[0].Value, value) } func (c *conn) listAuthRequests(ctx context.Context) (reqs []AuthRequest, err error) { res, err := c.db.Get(ctx, authRequestPrefix, clientv3.WithPrefix()) if err != nil { return reqs, err } for _, v := range res.Kvs { var r AuthRequest if err = json.Unmarshal(v.Value, &r); err != nil { return reqs, err } reqs = append(reqs, r) } return reqs, nil } func (c *conn) listAuthCodes(ctx context.Context) (codes []AuthCode, err error) { res, err := c.db.Get(ctx, authCodePrefix, clientv3.WithPrefix()) if err != nil { return codes, err } for _, v := range res.Kvs { var c AuthCode if err = json.Unmarshal(v.Value, &c); err != nil { return codes, err } codes = append(codes, c) } return codes, nil } func (c *conn) listAuthSessionsInternal(ctx context.Context) (sessions []AuthSession, err error) { res, err := c.db.Get(ctx, authSessionPrefix, clientv3.WithPrefix()) if err != nil { return sessions, err } for _, v := range res.Kvs { var s AuthSession if err = json.Unmarshal(v.Value, &s); err != nil { return sessions, err } sessions = append(sessions, s) } return sessions, nil } func (c *conn) txnCreate(ctx context.Context, key string, value interface{}) error { b, err := json.Marshal(value) if err != nil { return err } txn := c.db.Txn(ctx) res, err := txn. If(clientv3.Compare(clientv3.CreateRevision(key), "=", 0)). Then(clientv3.OpPut(key, string(b))). Commit() if err != nil { return err } if !res.Succeeded { return storage.ErrAlreadyExists } return nil } func (c *conn) txnUpdate(ctx context.Context, key string, update func(current []byte) ([]byte, error)) error { getResp, err := c.db.Get(ctx, key) if err != nil { return err } var currentValue []byte var modRev int64 if len(getResp.Kvs) > 0 { currentValue = getResp.Kvs[0].Value modRev = getResp.Kvs[0].ModRevision } updatedValue, err := update(currentValue) if err != nil { return err } txn := c.db.Txn(ctx) updateResp, err := txn. If(clientv3.Compare(clientv3.ModRevision(key), "=", modRev)). Then(clientv3.OpPut(key, string(updatedValue))). Commit() if err != nil { return err } if !updateResp.Succeeded { return fmt.Errorf("failed to update key=%q: concurrent conflicting update happened", key) } return nil } func keyID(prefix, id string) string { return prefix + id } func keyEmail(prefix, email string) string { return prefix + strings.ToLower(email) } func keySession(userID, connID string) string { return offlineSessionPrefix + strings.ToLower(userID+"|"+connID) } func keyUserIdentity(userID, connectorID string) string { return userIdentityPrefix + strings.ToLower(userID+"|"+connectorID) } func keyAuthSession(userID, connectorID string) string { return authSessionPrefix + strings.ToLower(userID+"|"+connectorID) } func (c *conn) CreateDeviceRequest(ctx context.Context, d storage.DeviceRequest) error { return c.txnCreate(ctx, keyID(deviceRequestPrefix, d.UserCode), fromStorageDeviceRequest(d)) } func (c *conn) GetDeviceRequest(ctx context.Context, userCode string) (r storage.DeviceRequest, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() var dr DeviceRequest if err = c.getKey(ctx, keyID(deviceRequestPrefix, userCode), &dr); err == nil { r = toStorageDeviceRequest(dr) } return } func (c *conn) listDeviceRequests(ctx context.Context) (requests []DeviceRequest, err error) { res, err := c.db.Get(ctx, deviceRequestPrefix, clientv3.WithPrefix()) if err != nil { return requests, err } for _, v := range res.Kvs { var r DeviceRequest if err = json.Unmarshal(v.Value, &r); err != nil { return requests, err } requests = append(requests, r) } return requests, nil } func (c *conn) CreateDeviceToken(ctx context.Context, t storage.DeviceToken) error { return c.txnCreate(ctx, keyID(deviceTokenPrefix, t.DeviceCode), fromStorageDeviceToken(t)) } func (c *conn) GetDeviceToken(ctx context.Context, deviceCode string) (t storage.DeviceToken, err error) { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() var dt DeviceToken if err = c.getKey(ctx, keyID(deviceTokenPrefix, deviceCode), &dt); err == nil { t = toStorageDeviceToken(dt) } return } func (c *conn) listDeviceTokens(ctx context.Context) (deviceTokens []DeviceToken, err error) { res, err := c.db.Get(ctx, deviceTokenPrefix, clientv3.WithPrefix()) if err != nil { return deviceTokens, err } for _, v := range res.Kvs { var dt DeviceToken if err = json.Unmarshal(v.Value, &dt); err != nil { return deviceTokens, err } deviceTokens = append(deviceTokens, dt) } return deviceTokens, nil } func (c *conn) UpdateDeviceToken(ctx context.Context, deviceCode string, updater func(old storage.DeviceToken) (storage.DeviceToken, error)) error { ctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout) defer cancel() return c.txnUpdate(ctx, keyID(deviceTokenPrefix, deviceCode), func(currentValue []byte) ([]byte, error) { var current DeviceToken if len(currentValue) > 0 { if err := json.Unmarshal(currentValue, ¤t); err != nil { return nil, err } } updated, err := updater(toStorageDeviceToken(current)) if err != nil { return nil, err } return json.Marshal(fromStorageDeviceToken(updated)) }) } ================================================ FILE: storage/etcd/etcd_test.go ================================================ package etcd import ( "context" "fmt" "log/slog" "os" "runtime" "strings" "testing" "time" clientv3 "go.etcd.io/etcd/client/v3" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/conformance" ) func withTimeout(t time.Duration, f func()) { c := make(chan struct{}) defer close(c) go func() { select { case <-c: case <-time.After(t): // Dump a stack trace of the program. Useful for debugging deadlocks. buf := make([]byte, 2<<20) fmt.Fprintf(os.Stderr, "%s\n", buf[:runtime.Stack(buf, true)]) panic("test took too long") } }() f() } func cleanDB(c *conn) error { ctx := context.TODO() for _, prefix := range []string{ clientPrefix, authCodePrefix, refreshTokenPrefix, authRequestPrefix, passwordPrefix, offlineSessionPrefix, connectorPrefix, deviceRequestPrefix, deviceTokenPrefix, } { _, err := c.db.Delete(ctx, prefix, clientv3.WithPrefix()) if err != nil { return err } } return nil } func TestEtcd(t *testing.T) { testEtcdEnv := "DEX_ETCD_ENDPOINTS" endpointsStr := os.Getenv(testEtcdEnv) if endpointsStr == "" { t.Skipf("test environment variable %q not set, skipping", testEtcdEnv) return } endpoints := strings.Split(endpointsStr, ",") newStorage := func(t *testing.T) storage.Storage { s := &Etcd{ Endpoints: endpoints, } logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug})) conn, err := s.open(logger) if err != nil { fmt.Fprintln(os.Stdout, err) t.Fatal(err) } if err := cleanDB(conn); err != nil { fmt.Fprintln(os.Stdout, err) t.Fatal(err) } return conn } withTimeout(time.Second*10, func() { conformance.RunTests(t, newStorage) }) withTimeout(time.Minute*1, func() { conformance.RunTransactionTests(t, newStorage) }) // TODO(nabokihms): etcd uses compare-and-swap (txnUpdate) for UpdateRefreshToken, // but does not retry on CAS conflicts ("concurrent conflicting update happened"). // Under high contention virtually all updates fail — only the first writer succeeds. // withTimeout(time.Minute*1, func() { // conformance.RunConcurrencyTests(t, newStorage) // }) } ================================================ FILE: storage/etcd/types.go ================================================ package etcd import ( "time" "github.com/go-jose/go-jose/v4" "github.com/dexidp/dex/storage" ) // AuthCode is a mirrored struct from storage with JSON struct tags type AuthCode struct { ID string `json:"ID"` ClientID string `json:"clientID"` RedirectURI string `json:"redirectURI"` Nonce string `json:"nonce,omitempty"` Scopes []string `json:"scopes,omitempty"` ConnectorID string `json:"connectorID,omitempty"` ConnectorData []byte `json:"connectorData,omitempty"` Claims Claims `json:"claims,omitempty"` Expiry time.Time `json:"expiry"` CodeChallenge string `json:"code_challenge,omitempty"` CodeChallengeMethod string `json:"code_challenge_method,omitempty"` AuthTime time.Time `json:"auth_time"` } func toStorageAuthCode(a AuthCode) storage.AuthCode { return storage.AuthCode{ ID: a.ID, ClientID: a.ClientID, RedirectURI: a.RedirectURI, ConnectorID: a.ConnectorID, ConnectorData: a.ConnectorData, Nonce: a.Nonce, Scopes: a.Scopes, Claims: toStorageClaims(a.Claims), Expiry: a.Expiry, PKCE: storage.PKCE{ CodeChallenge: a.CodeChallenge, CodeChallengeMethod: a.CodeChallengeMethod, }, AuthTime: a.AuthTime, } } func fromStorageAuthCode(a storage.AuthCode) AuthCode { return AuthCode{ ID: a.ID, ClientID: a.ClientID, RedirectURI: a.RedirectURI, ConnectorID: a.ConnectorID, ConnectorData: a.ConnectorData, Nonce: a.Nonce, Scopes: a.Scopes, Claims: fromStorageClaims(a.Claims), Expiry: a.Expiry, CodeChallenge: a.PKCE.CodeChallenge, CodeChallengeMethod: a.PKCE.CodeChallengeMethod, AuthTime: a.AuthTime, } } // AuthRequest is a mirrored struct from storage with JSON struct tags type AuthRequest struct { ID string `json:"id"` ClientID string `json:"client_id"` ResponseTypes []string `json:"response_types"` Scopes []string `json:"scopes"` RedirectURI string `json:"redirect_uri"` Nonce string `json:"nonce"` State string `json:"state"` ForceApprovalPrompt bool `json:"force_approval_prompt"` Expiry time.Time `json:"expiry"` LoggedIn bool `json:"logged_in"` Claims Claims `json:"claims"` ConnectorID string `json:"connector_id"` ConnectorData []byte `json:"connector_data"` CodeChallenge string `json:"code_challenge,omitempty"` CodeChallengeMethod string `json:"code_challenge_method,omitempty"` HMACKey []byte `json:"hmac_key"` MFAValidated bool `json:"mfa_validated"` Prompt string `json:"prompt,omitempty"` MaxAge int `json:"max_age"` AuthTime time.Time `json:"auth_time"` } func fromStorageAuthRequest(a storage.AuthRequest) AuthRequest { return AuthRequest{ ID: a.ID, ClientID: a.ClientID, ResponseTypes: a.ResponseTypes, Scopes: a.Scopes, RedirectURI: a.RedirectURI, Nonce: a.Nonce, State: a.State, ForceApprovalPrompt: a.ForceApprovalPrompt, Expiry: a.Expiry, LoggedIn: a.LoggedIn, Claims: fromStorageClaims(a.Claims), ConnectorID: a.ConnectorID, ConnectorData: a.ConnectorData, CodeChallenge: a.PKCE.CodeChallenge, CodeChallengeMethod: a.PKCE.CodeChallengeMethod, HMACKey: a.HMACKey, MFAValidated: a.MFAValidated, Prompt: a.Prompt, MaxAge: a.MaxAge, AuthTime: a.AuthTime, } } func toStorageAuthRequest(a AuthRequest) storage.AuthRequest { return storage.AuthRequest{ ID: a.ID, ClientID: a.ClientID, ResponseTypes: a.ResponseTypes, Scopes: a.Scopes, RedirectURI: a.RedirectURI, Nonce: a.Nonce, State: a.State, ForceApprovalPrompt: a.ForceApprovalPrompt, LoggedIn: a.LoggedIn, ConnectorID: a.ConnectorID, ConnectorData: a.ConnectorData, Expiry: a.Expiry, Claims: toStorageClaims(a.Claims), PKCE: storage.PKCE{ CodeChallenge: a.CodeChallenge, CodeChallengeMethod: a.CodeChallengeMethod, }, HMACKey: a.HMACKey, MFAValidated: a.MFAValidated, Prompt: a.Prompt, MaxAge: a.MaxAge, AuthTime: a.AuthTime, } } // RefreshToken is a mirrored struct from storage with JSON struct tags type RefreshToken struct { ID string `json:"id"` Token string `json:"token"` ObsoleteToken string `json:"obsolete_token"` CreatedAt time.Time `json:"created_at"` LastUsed time.Time `json:"last_used"` ClientID string `json:"client_id"` ConnectorID string `json:"connector_id"` ConnectorData []byte `json:"connector_data"` Claims Claims `json:"claims"` Scopes []string `json:"scopes"` Nonce string `json:"nonce"` } func toStorageRefreshToken(r RefreshToken) storage.RefreshToken { return storage.RefreshToken{ ID: r.ID, Token: r.Token, ObsoleteToken: r.ObsoleteToken, CreatedAt: r.CreatedAt, LastUsed: r.LastUsed, ClientID: r.ClientID, ConnectorID: r.ConnectorID, ConnectorData: r.ConnectorData, Scopes: r.Scopes, Nonce: r.Nonce, Claims: toStorageClaims(r.Claims), } } func fromStorageRefreshToken(r storage.RefreshToken) RefreshToken { return RefreshToken{ ID: r.ID, Token: r.Token, ObsoleteToken: r.ObsoleteToken, CreatedAt: r.CreatedAt, LastUsed: r.LastUsed, ClientID: r.ClientID, ConnectorID: r.ConnectorID, ConnectorData: r.ConnectorData, Scopes: r.Scopes, Nonce: r.Nonce, Claims: fromStorageClaims(r.Claims), } } // Claims is a mirrored struct from storage with JSON struct tags. type Claims struct { UserID string `json:"userID"` Username string `json:"username"` PreferredUsername string `json:"preferredUsername"` Email string `json:"email"` EmailVerified bool `json:"emailVerified"` Groups []string `json:"groups,omitempty"` } func fromStorageClaims(i storage.Claims) Claims { return Claims{ UserID: i.UserID, Username: i.Username, PreferredUsername: i.PreferredUsername, Email: i.Email, EmailVerified: i.EmailVerified, Groups: i.Groups, } } func toStorageClaims(i Claims) storage.Claims { return storage.Claims{ UserID: i.UserID, Username: i.Username, PreferredUsername: i.PreferredUsername, Email: i.Email, EmailVerified: i.EmailVerified, Groups: i.Groups, } } // Keys is a mirrored struct from storage with JSON struct tags type Keys struct { SigningKey *jose.JSONWebKey `json:"signing_key,omitempty"` SigningKeyPub *jose.JSONWebKey `json:"signing_key_pub,omitempty"` VerificationKeys []storage.VerificationKey `json:"verification_keys"` NextRotation time.Time `json:"next_rotation"` } // OfflineSessions is a mirrored struct from storage with JSON struct tags type OfflineSessions struct { UserID string `json:"user_id,omitempty"` ConnID string `json:"conn_id,omitempty"` Refresh map[string]*storage.RefreshTokenRef `json:"refresh,omitempty"` ConnectorData []byte `json:"connectorData,omitempty"` } func fromStorageOfflineSessions(o storage.OfflineSessions) OfflineSessions { return OfflineSessions{ UserID: o.UserID, ConnID: o.ConnID, Refresh: o.Refresh, ConnectorData: o.ConnectorData, } } func toStorageOfflineSessions(o OfflineSessions) storage.OfflineSessions { s := storage.OfflineSessions{ UserID: o.UserID, ConnID: o.ConnID, Refresh: o.Refresh, ConnectorData: o.ConnectorData, } if s.Refresh == nil { // Server code assumes this will be non-nil. s.Refresh = make(map[string]*storage.RefreshTokenRef) } return s } // UserIdentity is a mirrored struct from storage with JSON struct tags type UserIdentity struct { UserID string `json:"user_id,omitempty"` ConnectorID string `json:"connector_id,omitempty"` Claims Claims `json:"claims,omitempty"` Consents map[string][]string `json:"consents,omitempty"` MFASecrets map[string]*storage.MFASecret `json:"mfa_secrets,omitempty"` CreatedAt time.Time `json:"created_at"` LastLogin time.Time `json:"last_login"` BlockedUntil time.Time `json:"blocked_until"` } func fromStorageUserIdentity(u storage.UserIdentity) UserIdentity { return UserIdentity{ UserID: u.UserID, ConnectorID: u.ConnectorID, Claims: fromStorageClaims(u.Claims), Consents: u.Consents, MFASecrets: u.MFASecrets, CreatedAt: u.CreatedAt, LastLogin: u.LastLogin, BlockedUntil: u.BlockedUntil, } } func toStorageUserIdentity(u UserIdentity) storage.UserIdentity { s := storage.UserIdentity{ UserID: u.UserID, ConnectorID: u.ConnectorID, Claims: toStorageClaims(u.Claims), Consents: u.Consents, MFASecrets: u.MFASecrets, CreatedAt: u.CreatedAt, LastLogin: u.LastLogin, BlockedUntil: u.BlockedUntil, } if s.Consents == nil { // Server code assumes this will be non-nil. s.Consents = make(map[string][]string) } return s } // AuthSession is a mirrored struct from storage with JSON struct tags. type AuthSession struct { UserID string `json:"user_id,omitempty"` ConnectorID string `json:"connector_id,omitempty"` Nonce string `json:"nonce,omitempty"` ClientStates map[string]*storage.ClientAuthState `json:"client_states,omitempty"` CreatedAt time.Time `json:"created_at"` LastActivity time.Time `json:"last_activity"` IPAddress string `json:"ip_address,omitempty"` UserAgent string `json:"user_agent,omitempty"` AbsoluteExpiry time.Time `json:"absolute_expiry"` IdleExpiry time.Time `json:"idle_expiry"` } func fromStorageAuthSession(s storage.AuthSession) AuthSession { return AuthSession{ UserID: s.UserID, ConnectorID: s.ConnectorID, Nonce: s.Nonce, ClientStates: s.ClientStates, CreatedAt: s.CreatedAt, LastActivity: s.LastActivity, IPAddress: s.IPAddress, UserAgent: s.UserAgent, AbsoluteExpiry: s.AbsoluteExpiry, IdleExpiry: s.IdleExpiry, } } func toStorageAuthSession(s AuthSession) storage.AuthSession { result := storage.AuthSession{ UserID: s.UserID, ConnectorID: s.ConnectorID, Nonce: s.Nonce, ClientStates: s.ClientStates, CreatedAt: s.CreatedAt, LastActivity: s.LastActivity, IPAddress: s.IPAddress, UserAgent: s.UserAgent, AbsoluteExpiry: s.AbsoluteExpiry, IdleExpiry: s.IdleExpiry, } if result.ClientStates == nil { result.ClientStates = make(map[string]*storage.ClientAuthState) } return result } // DeviceRequest is a mirrored struct from storage with JSON struct tags type DeviceRequest struct { UserCode string `json:"user_code"` DeviceCode string `json:"device_code"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` Scopes []string `json:"scopes"` Expiry time.Time `json:"expiry"` } func fromStorageDeviceRequest(d storage.DeviceRequest) DeviceRequest { return DeviceRequest{ UserCode: d.UserCode, DeviceCode: d.DeviceCode, ClientID: d.ClientID, ClientSecret: d.ClientSecret, Scopes: d.Scopes, Expiry: d.Expiry, } } func toStorageDeviceRequest(d DeviceRequest) storage.DeviceRequest { return storage.DeviceRequest{ UserCode: d.UserCode, DeviceCode: d.DeviceCode, ClientID: d.ClientID, ClientSecret: d.ClientSecret, Scopes: d.Scopes, Expiry: d.Expiry, } } // DeviceToken is a mirrored struct from storage with JSON struct tags type DeviceToken struct { DeviceCode string `json:"device_code"` Status string `json:"status"` Token string `json:"token"` Expiry time.Time `json:"expiry"` LastRequestTime time.Time `json:"last_request"` PollIntervalSeconds int `json:"poll_interval"` CodeChallenge string `json:"code_challenge,omitempty"` CodeChallengeMethod string `json:"code_challenge_method,omitempty"` } func fromStorageDeviceToken(t storage.DeviceToken) DeviceToken { return DeviceToken{ DeviceCode: t.DeviceCode, Status: t.Status, Token: t.Token, Expiry: t.Expiry, LastRequestTime: t.LastRequestTime, PollIntervalSeconds: t.PollIntervalSeconds, CodeChallenge: t.PKCE.CodeChallenge, CodeChallengeMethod: t.PKCE.CodeChallengeMethod, } } func toStorageDeviceToken(t DeviceToken) storage.DeviceToken { return storage.DeviceToken{ DeviceCode: t.DeviceCode, Status: t.Status, Token: t.Token, Expiry: t.Expiry, LastRequestTime: t.LastRequestTime, PollIntervalSeconds: t.PollIntervalSeconds, PKCE: storage.PKCE{ CodeChallenge: t.CodeChallenge, CodeChallengeMethod: t.CodeChallengeMethod, }, } } ================================================ FILE: storage/health.go ================================================ package storage import ( "context" "crypto" "fmt" "time" ) // NewCustomHealthCheckFunc returns a new health check function. func NewCustomHealthCheckFunc(s Storage, now func() time.Time) func(context.Context) (details interface{}, err error) { return func(ctx context.Context) (details interface{}, err error) { a := AuthRequest{ ID: NewID(), ClientID: NewID(), // Set a short expiry so if the delete fails this will be cleaned up quickly by garbage collection. Expiry: now().Add(time.Minute), HMACKey: NewHMACKey(crypto.SHA256), } if err := s.CreateAuthRequest(ctx, a); err != nil { return nil, fmt.Errorf("create auth request: %v", err) } if err := s.DeleteAuthRequest(ctx, a.ID); err != nil { return nil, fmt.Errorf("delete auth request: %v", err) } return nil, nil } } ================================================ FILE: storage/kubernetes/client.go ================================================ package kubernetes import ( "bytes" "context" "crypto/tls" "crypto/x509" "encoding/base32" "encoding/base64" "encoding/json" "errors" "fmt" "hash" "hash/fnv" "io" "log/slog" "net" "net/http" "net/url" "os" "path" "regexp" "strconv" "strings" "time" "github.com/Masterminds/semver" "github.com/ghodss/yaml" "golang.org/x/net/http2" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/kubernetes/k8sapi" ) const ( serviceAccountPath = "/var/run/secrets/kubernetes.io/serviceaccount/" serviceAccountTokenPath = serviceAccountPath + "token" serviceAccountCAPath = serviceAccountPath + "ca.crt" serviceAccountNamespacePath = serviceAccountPath + "namespace" kubernetesServiceHostENV = "KUBERNETES_SERVICE_HOST" kubernetesServicePortENV = "KUBERNETES_SERVICE_PORT" kubernetesPodNamespaceENV = "KUBERNETES_POD_NAMESPACE" ) type client struct { client *http.Client baseURL string namespace string logger *slog.Logger // Hash function to map IDs (which could span a large range) to Kubernetes names. // While this is not currently upgradable, it could be in the future. // // The default hash is a non-cryptographic hash, because cryptographic hashes // always produce sums too long to fit into a Kubernetes name. Because of this, // gets, updates, and deletes are _always_ checked for collisions. hash func() hash.Hash // API version of the oidc resources. For example "oidc.coreos.com". This is // currently not configurable, but could be in the future. apiVersion string // API version of the custom resource definitions. // Different Kubernetes version requires to create CRD in certain API. It will be discovered automatically on // storage opening. crdAPIVersion string // CRD handling behavior controls how missing Custom Resource Definitions are handled: // - "ensure": Attempt to create all missing CRDs. Fails if any CRD creation fails. (default) // - "check": Fail if any CRDs are missing, with error "storage is not initialized, CRDs are not created" crdHandling string // This is called once the client's Close method is called to signal goroutines, // such as the one creating third party resources, to stop. cancel context.CancelFunc } // idToName maps an arbitrary ID, such as an email or client ID to a Kubernetes object name. func (cli *client) idToName(s string) string { return idToName(s, cli.hash) } // offlineTokenName maps two arbitrary IDs, to a single Kubernetes object name. // This is used when more than one field is used to uniquely identify the object. func (cli *client) offlineTokenName(userID string, connID string) string { return offlineTokenName(userID, connID, cli.hash) } // Kubernetes names must match the regexp '[a-z0-9]([-a-z0-9]*[a-z0-9])?'. var encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567") func idToName(s string, h func() hash.Hash) string { return strings.TrimRight(encoding.EncodeToString(h().Sum([]byte(s))), "=") } func offlineTokenName(userID string, connID string, h func() hash.Hash) string { hash := h() hash.Write([]byte(userID)) hash.Write([]byte(connID)) return strings.TrimRight(encoding.EncodeToString(hash.Sum(nil)), "=") } // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names const kubeResourceMaxLen = 253 var kubeResourceNameRegex = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) func (cli *client) urlForWithParams( apiVersion, namespace, resource, name string, params url.Values, ) (string, error) { basePath := "apis/" if apiVersion == "v1" { basePath = "api/" } if name != "" && (len(name) > kubeResourceMaxLen || !kubeResourceNameRegex.MatchString(name)) { // The actual name can be found in auth request or auth code objects and equals to the state value return "", fmt.Errorf( "invalid kubernetes resource name: must match the pattern %s and be no longer than %d characters", kubeResourceNameRegex.String(), kubeResourceMaxLen) } var p string if namespace != "" { p = path.Join(basePath, apiVersion, "namespaces", namespace, resource, name) } else { p = path.Join(basePath, apiVersion, resource, name) } encodedParams := params.Encode() paramsSuffix := "" if len(encodedParams) > 0 { paramsSuffix = "?" + encodedParams } if strings.HasSuffix(cli.baseURL, "/") { return cli.baseURL + p + paramsSuffix, nil } return cli.baseURL + "/" + p + paramsSuffix, nil } func (cli *client) urlFor(apiVersion, namespace, resource, name string) (string, error) { return cli.urlForWithParams(apiVersion, namespace, resource, name, url.Values{}) } // Define an error interface so we can get at the underlying status code if it's // absolutely necessary. For instance when we need to see if an error indicates // a resource already exists. type httpError interface { StatusCode() int } var _ httpError = (*httpErr)(nil) type httpErr struct { method string url string status int body []byte } func (e *httpErr) StatusCode() int { return e.status } func (e *httpErr) Error() string { return fmt.Sprintf("%s %s %s: response from server \"%s\"", e.method, e.url, http.StatusText(e.status), bytes.TrimSpace(e.body)) } func checkHTTPErr(r *http.Response, validStatusCodes ...int) error { for _, status := range validStatusCodes { if r.StatusCode == status { return nil } } body, err := io.ReadAll(io.LimitReader(r.Body, 2<<15)) // 64 KiB if err != nil { return fmt.Errorf("read response body: %v", err) } // Check this case after we read the body so the connection can be reused. if r.StatusCode == http.StatusNotFound { return storage.ErrNotFound } if r.Request.Method == http.MethodPost && r.StatusCode == http.StatusConflict { return storage.ErrAlreadyExists } var url, method string if r.Request != nil { method = r.Request.Method url = r.Request.URL.String() } return &httpErr{method, url, r.StatusCode, body} } // Close the response body. The initial request is drained so the connection can // be reused. func closeResp(r *http.Response) { io.Copy(io.Discard, r.Body) r.Body.Close() } func (cli *client) get(resource, name string, v interface{}) error { return cli.getResource(cli.apiVersion, cli.namespace, resource, name, v) } func (cli *client) getURL(url string, v interface{}) error { resp, err := cli.client.Get(url) if err != nil { return err } defer closeResp(resp) if err := checkHTTPErr(resp, http.StatusOK); err != nil { return err } return json.NewDecoder(resp.Body).Decode(v) } func (cli *client) getResource(apiVersion, namespace, resource, name string, v interface{}) error { u, err := cli.urlFor(apiVersion, namespace, resource, name) if err != nil { return err } return cli.getURL(u, v) } func (cli *client) listN(resource string, v interface{}, n int) error { //nolint:unparam // In practice, n is the gcResultLimit constant. params := url.Values{} params.Add("limit", fmt.Sprintf("%d", n)) u, err := cli.urlForWithParams(cli.apiVersion, cli.namespace, resource, "", params) if err != nil { return err } return cli.getURL(u, v) } func (cli *client) list(resource string, v interface{}) error { return cli.get(resource, "", v) } func (cli *client) post(resource string, v interface{}) error { return cli.postResource(cli.apiVersion, cli.namespace, resource, v) } func (cli *client) postResource(apiVersion, namespace, resource string, v interface{}) error { body, err := json.Marshal(v) if err != nil { return fmt.Errorf("marshal object: %v", err) } url, err := cli.urlFor(apiVersion, namespace, resource, "") if err != nil { return err } resp, err := cli.client.Post(url, "application/json", bytes.NewReader(body)) if err != nil { return err } defer closeResp(resp) return checkHTTPErr(resp, http.StatusCreated) } func (cli *client) detectKubernetesVersion() error { var version struct{ GitVersion string } url := cli.baseURL + "/version" resp, err := cli.client.Get(url) if err != nil { return err } defer closeResp(resp) if err := checkHTTPErr(resp, http.StatusOK); err != nil { return err } if err := json.NewDecoder(resp.Body).Decode(&version); err != nil { return err } clusterVersion, err := semver.NewVersion(version.GitVersion) if err != nil { cli.logger.Warn("cannot detect Kubernetes version", "version", clusterVersion, "err", err) return nil } if clusterVersion.LessThan(semver.MustParse("v1.16.0")) { cli.crdAPIVersion = legacyCRDAPIVersion } return nil } func (cli *client) delete(resource, name string) error { url, err := cli.urlFor(cli.apiVersion, cli.namespace, resource, name) if err != nil { return err } req, err := http.NewRequest("DELETE", url, nil) if err != nil { return fmt.Errorf("create delete request: %v", err) } resp, err := cli.client.Do(req) if err != nil { return fmt.Errorf("delete request: %v", err) } defer closeResp(resp) return checkHTTPErr(resp, http.StatusOK) } func (cli *client) deleteAll(resource string) error { var list struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ListMeta `json:"metadata,omitempty"` Items []struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` } `json:"items"` } if err := cli.list(resource, &list); err != nil { return err } for _, item := range list.Items { if err := cli.delete(resource, item.Name); err != nil { return err } } return nil } func (cli *client) put(resource, name string, v interface{}) error { body, err := json.Marshal(v) if err != nil { return fmt.Errorf("marshal object: %v", err) } url, err := cli.urlFor(cli.apiVersion, cli.namespace, resource, name) if err != nil { return err } req, err := http.NewRequest("PUT", url, bytes.NewReader(body)) if err != nil { return fmt.Errorf("create patch request: %v", err) } req.Header.Set("Content-Length", strconv.Itoa(len(body))) resp, err := cli.client.Do(req) if err != nil { return fmt.Errorf("patch request: %v", err) } defer closeResp(resp) return checkHTTPErr(resp, http.StatusOK) } // Copied from https://github.com/gtank/cryptopasta func defaultTLSConfig() *tls.Config { return &tls.Config{ // Avoids most of the memorably-named TLS attacks MinVersion: tls.VersionTLS12, // Causes servers to use Go's default ciphersuite preferences, // which are tuned to avoid attacks. Does nothing on clients. PreferServerCipherSuites: true, // Only use curves which have constant-time implementations CurvePreferences: []tls.CurveID{ tls.CurveP256, }, } } func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, logger *slog.Logger, inCluster bool, crdHandling string) (*client, error) { tlsConfig := defaultTLSConfig() data := func(b string, file string) ([]byte, error) { if b != "" { return base64.StdEncoding.DecodeString(b) } if file == "" { return nil, nil } return os.ReadFile(file) } if caData, err := data(cluster.CertificateAuthorityData, cluster.CertificateAuthority); err != nil { return nil, err } else if caData != nil { tlsConfig.RootCAs = x509.NewCertPool() if !tlsConfig.RootCAs.AppendCertsFromPEM(caData) { return nil, fmt.Errorf("no certificate data found: %v", err) } } clientCert, err := data(user.ClientCertificateData, user.ClientCertificate) if err != nil { return nil, err } clientKey, err := data(user.ClientKeyData, user.ClientKey) if err != nil { return nil, err } if clientCert != nil && clientKey != nil { cert, err := tls.X509KeyPair(clientCert, clientKey) if err != nil { return nil, fmt.Errorf("failed to load client cert: %v", err) } tlsConfig.Certificates = []tls.Certificate{cert} } var t http.RoundTripper httpTransport := &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, TLSClientConfig: tlsConfig, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, } // Since we set a custom TLS client config we have to explicitly // enable HTTP/2. // // https://github.com/golang/go/blob/go1.7.4/src/net/http/transport.go#L200-L206 if err := http2.ConfigureTransport(httpTransport); err != nil { return nil, err } t = wrapRoundTripper(httpTransport, user, inCluster) apiVersion := "dex.coreos.com/v1" logger.Info("kubernetes client", "api_version", apiVersion) return &client{ client: &http.Client{ Transport: t, Timeout: 15 * time.Second, }, baseURL: cluster.Server, hash: func() hash.Hash { return fnv.New64() }, namespace: namespace, apiVersion: apiVersion, crdAPIVersion: crdAPIVersion, crdHandling: crdHandling, logger: logger, }, nil } func loadKubeConfig(kubeConfigPath string) (cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, err error) { data, err := os.ReadFile(kubeConfigPath) if err != nil { err = fmt.Errorf("read %s: %v", kubeConfigPath, err) return } var c k8sapi.Config if err = yaml.Unmarshal(data, &c); err != nil { err = fmt.Errorf("unmarshal %s: %v", kubeConfigPath, err) return } cluster, user, namespace, err = currentContext(&c) if namespace == "" { namespace = "default" } return } func namespaceFromServiceAccountJWT(s string) (string, error) { // The service account token is just a JWT. Parse it as such. parts := strings.Split(s, ".") if len(parts) < 2 { // It's extremely important we don't log the actual service account token. return "", fmt.Errorf("malformed service account token: expected 3 parts got %d", len(parts)) } payload, err := base64.RawURLEncoding.DecodeString(parts[1]) if err != nil { return "", fmt.Errorf("malformed service account token: %v", err) } var data struct { // The claim Kubernetes uses to identify which namespace a service account belongs to. // // See: https://github.com/kubernetes/kubernetes/blob/v1.4.3/pkg/serviceaccount/jwt.go#L42 Namespace string `json:"kubernetes.io/serviceaccount/namespace"` } if err := json.Unmarshal(payload, &data); err != nil { return "", fmt.Errorf("malformed service account token: %v", err) } if data.Namespace == "" { return "", errors.New(`jwt claim "kubernetes.io/serviceaccount/namespace" not found`) } return data.Namespace, nil } func namespaceFromFile(path string) (string, error) { data, err := os.ReadFile(path) if err != nil { return "", err } return string(data), nil } func getInClusterConfigNamespace(token, namespaceENV, namespacePath string) (string, error) { namespace := os.Getenv(namespaceENV) if namespace != "" { return namespace, nil } namespace, err := namespaceFromServiceAccountJWT(token) if err == nil { return namespace, nil } err = fmt.Errorf("inspect service account token: %v", err) namespace, fileErr := namespaceFromFile(namespacePath) if fileErr == nil { return namespace, nil } return "", fmt.Errorf("%v: trying to get namespace from file: %v", err, fileErr) } func getInClusterConnectOptions(host, port string) (k8sapi.Cluster, error) { if len(host) == 0 || len(port) == 0 { return k8sapi.Cluster{}, fmt.Errorf( "unable to load in-cluster configuration, %s and %s must be defined", kubernetesServiceHostENV, kubernetesServicePortENV, ) } cluster := k8sapi.Cluster{ Server: "https://" + net.JoinHostPort(host, port), CertificateAuthority: serviceAccountCAPath, } return cluster, nil } func inClusterConfig() (k8sapi.Cluster, k8sapi.AuthInfo, string, error) { cluster, err := getInClusterConnectOptions(os.Getenv(kubernetesServiceHostENV), os.Getenv(kubernetesServicePortENV)) if err != nil { return cluster, k8sapi.AuthInfo{}, "", err } token, err := os.ReadFile(serviceAccountTokenPath) if err != nil { return cluster, k8sapi.AuthInfo{}, "", err } user := k8sapi.AuthInfo{Token: string(token)} namespace, err := getInClusterConfigNamespace(user.Token, kubernetesPodNamespaceENV, serviceAccountNamespacePath) if err != nil { return cluster, user, "", err } return cluster, user, namespace, nil } func currentContext(config *k8sapi.Config) (cluster k8sapi.Cluster, user k8sapi.AuthInfo, ns string, err error) { if config.CurrentContext == "" { if len(config.Contexts) == 1 { config.CurrentContext = config.Contexts[0].Name } else { return cluster, user, "", errors.New("kubeconfig has no current context") } } k8sContext, ok := func() (k8sapi.Context, bool) { for _, namedContext := range config.Contexts { if namedContext.Name == config.CurrentContext { return namedContext.Context, true } } return k8sapi.Context{}, false }() if !ok { return cluster, user, "", fmt.Errorf("no context named %q found", config.CurrentContext) } cluster, ok = func() (k8sapi.Cluster, bool) { for _, namedCluster := range config.Clusters { if namedCluster.Name == k8sContext.Cluster { return namedCluster.Cluster, true } } return k8sapi.Cluster{}, false }() if !ok { return cluster, user, "", fmt.Errorf("no cluster named %q found", k8sContext.Cluster) } user, ok = func() (k8sapi.AuthInfo, bool) { for _, namedAuthInfo := range config.AuthInfos { if namedAuthInfo.Name == k8sContext.AuthInfo { return namedAuthInfo.AuthInfo, true } } return k8sapi.AuthInfo{}, false }() if !ok { return cluster, user, "", fmt.Errorf("no user named %q found", k8sContext.AuthInfo) } return cluster, user, k8sContext.Namespace, nil } ================================================ FILE: storage/kubernetes/client_test.go ================================================ package kubernetes import ( "hash" "hash/fnv" "log/slog" "net/http" "os" "path/filepath" "sync" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/dexidp/dex/storage/kubernetes/k8sapi" ) // This test does not have an explicit error condition but is used // with the race detector to detect the safety of idToName. func TestIDToName(t *testing.T) { n := 100 var wg sync.WaitGroup wg.Add(n) c := make(chan struct{}) h := func() hash.Hash { return fnv.New64() } for i := 0; i < n; i++ { go func() { <-c name := idToName("foo", h) _ = name wg.Done() }() } close(c) wg.Wait() } func TestOfflineTokenName(t *testing.T) { h := func() hash.Hash { return fnv.New64() } userID1 := "john" userID2 := "jane" id1 := offlineTokenName(userID1, "local", h) id2 := offlineTokenName(userID2, "local", h) if id1 == id2 { t.Errorf("expected offlineTokenName to produce different hashes") } } func TestInClusterTransport(t *testing.T) { logger := slog.New(slog.DiscardHandler) user := k8sapi.AuthInfo{Token: "abc"} cli, err := newClient( k8sapi.Cluster{}, user, "test", logger, true, "", ) require.NoError(t, err) fpath := filepath.Join(os.TempDir(), "test.in_cluster") defer os.RemoveAll(fpath) err = os.WriteFile(fpath, []byte("def"), 0o644) require.NoError(t, err) tests := []struct { name string time func() time.Time expected string }{ { name: "Stale token", time: func() time.Time { return time.Now().Add(-24 * time.Hour) }, expected: "def", }, { name: "Normal token", time: func() time.Time { return time.Time{} }, expected: "abc", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { helper := newInClusterTransportHelper(user) helper.now = tc.time helper.tokenLocation = fpath cli.client.Transport = transport{ updateReq: func(r *http.Request) { helper.UpdateToken() r.Header.Set("Authorization", "Bearer "+helper.GetToken()) }, base: cli.client.Transport, } _ = cli.isCRDReady("test") require.Equal(t, tc.expected, helper.info.Token) }) } } func TestNamespaceFromServiceAccountJWT(t *testing.T) { namespace, err := namespaceFromServiceAccountJWT(serviceAccountToken) if err != nil { t.Fatal(err) } wantNamespace := "dex-test-namespace" if namespace != wantNamespace { t.Errorf("expected namespace %q got %q", wantNamespace, namespace) } } func TestGetClusterConfigNamespace(t *testing.T) { const namespaceENVVariableName = "TEST_GET_CLUSTER_CONFIG_NAMESPACE" { os.Setenv(namespaceENVVariableName, "namespace-from-env") defer os.Unsetenv(namespaceENVVariableName) } var namespaceFile string { tmpfile, err := os.CreateTemp(os.TempDir(), "test-get-cluster-config-namespace") require.NoError(t, err) _, err = tmpfile.Write([]byte("namespace-from-file")) require.NoError(t, err) namespaceFile = tmpfile.Name() defer os.Remove(namespaceFile) } tests := []struct { name string token string fileName string envVariable string expectedError bool expectedNamespace string }{ { name: "With env variable", envVariable: "TEST_GET_CLUSTER_CONFIG_NAMESPACE", expectedNamespace: "namespace-from-env", }, { name: "With token", token: serviceAccountToken, expectedNamespace: "dex-test-namespace", }, { name: "With namespace file", fileName: namespaceFile, expectedNamespace: "namespace-from-file", }, { name: "With file and token", fileName: namespaceFile, token: serviceAccountToken, expectedNamespace: "dex-test-namespace", }, { name: "With file and env", fileName: namespaceFile, envVariable: "TEST_GET_CLUSTER_CONFIG_NAMESPACE", expectedNamespace: "namespace-from-env", }, { name: "With token and env", envVariable: "TEST_GET_CLUSTER_CONFIG_NAMESPACE", token: serviceAccountToken, expectedNamespace: "namespace-from-env", }, { name: "With file, token and env", fileName: namespaceFile, token: serviceAccountToken, envVariable: "TEST_GET_CLUSTER_CONFIG_NAMESPACE", expectedNamespace: "namespace-from-env", }, { name: "Without anything", expectedError: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { namespace, err := getInClusterConfigNamespace(tc.token, tc.envVariable, tc.fileName) if tc.expectedError { require.Error(t, err) } else { require.NoError(t, err) } require.Equal(t, namespace, tc.expectedNamespace) }) } } func TestGetInClusterConnectOptions(t *testing.T) { type testCase struct { name string host string port string expectedURL string expectError bool } testCases := []testCase{ { name: "valid IPv4", host: "10.1.1.1", port: "443", expectedURL: "https://10.1.1.1:443", }, { name: "valid IPv6", host: "fd00::1", port: "8443", expectedURL: "https://[fd00::1]:8443", }, { name: "valid DNS name", host: "kubernetes.default.svc", port: "443", expectedURL: "https://kubernetes.default.svc:443", }, { name: "empty host", host: "", port: "443", expectError: true, }, { name: "empty port", host: "127.0.0.1", port: "", expectError: true, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cluster, err := getInClusterConnectOptions(tc.host, tc.port) if tc.expectError { assert.Error(t, err) return } require.NoError(t, err) assert.Equal(t, tc.expectedURL, cluster.Server) assert.Equal(t, "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt", cluster.CertificateAuthority) }) } } const serviceAccountToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZXgtdGVzdC1uYW1lc3BhY2UiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoiZG90aGVyb2JvdC1zZWNyZXQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZG90aGVyb2JvdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjQyYjJhOTRmLTk4MjAtMTFlNi1iZDc0LTJlZmQzOGYxMjYxYyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZXgtdGVzdC1uYW1lc3BhY2U6ZG90aGVyb2JvdCJ9.KViBpPwCiBwxDvAjYUUXoVvLVwqV011aLlYQpNtX12Bh8M-QAFch-3RWlo_SR00bcdFg_nZo9JKACYlF_jHMEsf__PaYms9r7vEaSg0jPfkqnL2WXZktzQRyLBr0n-bxeUrbwIWsKOAC0DfFB5nM8XoXljRmq8yAx8BAdmQp7MIFb4EOV9nYthhua6pjzYyaFSiDiYTjw7HtXOvoL8oepodJ3-37pUKS8vdBvnvUoqC4M1YAhkO5L36JF6KV_RfmG8GPEdNQfXotHcsR-3jKi1n8S5l7Xd-rhrGOhSGQizH3dORzo9GvBAhYeqbq1O-NLzm2EQUiMQayIUx7o4g3Kw" // The following program was used to generate the example token. Since we don't want to // import Kubernetes, just leave it as a comment. /* package main import ( "crypto/rand" "crypto/rsa" "fmt" "log" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/serviceaccount" "k8s.io/kubernetes/pkg/util/uuid" ) func main() { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { log.Fatal(err) } sa := api.ServiceAccount{ ObjectMeta: api.ObjectMeta{ Namespace: "dex-test-namespace", Name: "dotherobot", UID: uuid.NewUUID(), }, } secret := api.Secret{ ObjectMeta: api.ObjectMeta{ Namespace: "dex-test-namespace", Name: "dotherobot-secret", UID: uuid.NewUUID(), }, } token, err := serviceaccount.JWTTokenGenerator(key).GenerateToken(sa, secret) if err != nil { log.Fatal(err) } fmt.Println(token) } */ ================================================ FILE: storage/kubernetes/doc.go ================================================ // Package kubernetes provides a storage implementation using Kubernetes third party APIs. package kubernetes ================================================ FILE: storage/kubernetes/k8sapi/client.go ================================================ /* Copyright 2014 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package k8sapi // Where possible, json tags match the cli argument names. // Top level config objects and all values required for proper functioning are not "omitempty". Any truly optional piece of config is allowed to be omitted. // Config holds the information needed to build connect to remote kubernetes clusters as a given user. type Config struct { // Legacy field from pkg/api/types.go TypeMeta. // TODO(jlowdermilk): remove this after eliminating downstream dependencies. Kind string `json:"kind,omitempty"` // Deprecated: APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc). // Because a cluster can run multiple API groups and potentially multiple versions of each, it no longer makes sense to specify // a single value for the cluster version. // This field isn't really needed anyway, so we are deprecating it without replacement. // It will be ignored if it is present. APIVersion string `json:"apiVersion,omitempty"` // Preferences holds general information to be use for cli interactions Preferences Preferences `json:"preferences"` // Clusters is a map of referenceable names to cluster configs Clusters []NamedCluster `json:"clusters"` // AuthInfos is a map of referenceable names to user configs AuthInfos []NamedAuthInfo `json:"users"` // Contexts is a map of referenceable names to context configs Contexts []NamedContext `json:"contexts"` // CurrentContext is the name of the context that you would like to use by default CurrentContext string `json:"current-context"` // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields Extensions []NamedExtension `json:"extensions,omitempty"` } // Preferences contains information about the users command line experience preferences. type Preferences struct { Colors bool `json:"colors,omitempty"` // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields Extensions []NamedExtension `json:"extensions,omitempty"` } // Cluster contains information about how to communicate with a kubernetes cluster. type Cluster struct { // Server is the address of the kubernetes cluster (https://hostname:port). Server string `json:"server"` // APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc). APIVersion string `json:"api-version,omitempty"` // InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure. InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"` // CertificateAuthority is the path to a cert file for the certificate authority. CertificateAuthority string `json:"certificate-authority,omitempty"` // CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority // // NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string. CertificateAuthorityData string `json:"certificate-authority-data,omitempty"` // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields Extensions []NamedExtension `json:"extensions,omitempty"` } // AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are. type AuthInfo struct { // ClientCertificate is the path to a client cert file for TLS. ClientCertificate string `json:"client-certificate,omitempty"` // ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate // // NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string. ClientCertificateData string `json:"client-certificate-data,omitempty"` // ClientKey is the path to a client key file for TLS. ClientKey string `json:"client-key,omitempty"` // ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey // // NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string. ClientKeyData string `json:"client-key-data,omitempty"` // Token is the bearer token for authentication to the kubernetes cluster. Token string `json:"token,omitempty"` // Impersonate is the username to impersonate. The name matches the flag. Impersonate string `json:"as,omitempty"` // Username is the username for basic authentication to the kubernetes cluster. Username string `json:"username,omitempty"` // Password is the password for basic authentication to the kubernetes cluster. Password string `json:"password,omitempty"` // AuthProvider specifies a custom authentication plugin for the kubernetes cluster. AuthProvider *AuthProviderConfig `json:"auth-provider,omitempty"` // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields Extensions []NamedExtension `json:"extensions,omitempty"` } // Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), // a user (how do I identify myself), and a namespace (what subset of resources do I want to work with). type Context struct { // Cluster is the name of the cluster for this context Cluster string `json:"cluster"` // AuthInfo is the name of the authInfo for this context AuthInfo string `json:"user"` // Namespace is the default namespace to use on unspecified requests Namespace string `json:"namespace,omitempty"` // Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields Extensions []NamedExtension `json:"extensions,omitempty"` } // NamedCluster relates nicknames to cluster information type NamedCluster struct { // Name is the nickname for this Cluster Name string `json:"name"` // Cluster holds the cluster information Cluster Cluster `json:"cluster"` } // NamedContext relates nicknames to context information type NamedContext struct { // Name is the nickname for this Context Name string `json:"name"` // Context holds the context information Context Context `json:"context"` } // NamedAuthInfo relates nicknames to auth information type NamedAuthInfo struct { // Name is the nickname for this AuthInfo Name string `json:"name"` // AuthInfo holds the auth information AuthInfo AuthInfo `json:"user"` } // NamedExtension relates nicknames to extension information type NamedExtension struct { // Name is the nickname for this Extension Name string `json:"name"` } // AuthProviderConfig holds the configuration for a specified auth provider. type AuthProviderConfig struct { Name string `json:"name"` Config map[string]string `json:"config"` } ================================================ FILE: storage/kubernetes/k8sapi/crd_extensions.go ================================================ /* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package k8sapi // CustomResourceDefinitionSpec describes how a user wants their resource to appear type CustomResourceDefinitionSpec struct { // Group is the group this resource belongs in Group string `json:"group" protobuf:"bytes,1,opt,name=group"` // Version is the version this resource belongs in Version string `json:"version" protobuf:"bytes,2,opt,name=version"` // Names are the names used to describe this custom resource Names CustomResourceDefinitionNames `json:"names" protobuf:"bytes,3,opt,name=names"` // Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced Scope ResourceScope `json:"scope" protobuf:"bytes,4,opt,name=scope,casttype=ResourceScope"` // versions is the list of all API versions of the defined custom resource. // Version names are used to compute the order in which served versions are listed in API discovery. // If the version string is "kube-like", it will sort above non "kube-like" version strings, which are ordered // lexicographically. "Kube-like" versions start with a "v", then are followed by a number (the major version), // then optionally the string "alpha" or "beta" and another number (the minor version). These are sorted first // by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing // major version, then minor version. An example sorted list of versions: // v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10. Versions []CustomResourceDefinitionVersion `json:"versions" protobuf:"bytes,7,rep,name=versions"` } // CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition type CustomResourceDefinitionNames struct { // Plural is the plural name of the resource to serve. It must match the name of the CustomResourceDefinition-registration // too: plural.group and it must be all lowercase. Plural string `json:"plural" protobuf:"bytes,1,opt,name=plural"` // Singular is the singular name of the resource. It must be all lowercase Defaults to lowercased Singular string `json:"singular,omitempty" protobuf:"bytes,2,opt,name=singular"` // ShortNames are short names for the resource. It must be all lowercase. ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,3,opt,name=shortNames"` // Kind is the serialized kind of the resource. It is normally CamelCase and singular. Kind string `json:"kind" protobuf:"bytes,4,opt,name=kind"` // ListKind is the serialized kind of the list for this resource. Defaults to List. ListKind string `json:"listKind,omitempty" protobuf:"bytes,5,opt,name=listKind"` } // ResourceScope is an enum defining the different scopes available to a custom resource type ResourceScope string const ( // ClusterScoped is the `cluster` scope for a custom resource. ClusterScoped ResourceScope = "Cluster" // NamespaceScoped is the `namespaced` scope for a custom resource. NamespaceScoped ResourceScope = "Namespaced" ) // ConditionStatus reflects if a resource type ConditionStatus string // These are valid condition statuses. "ConditionTrue" means a resource is in the condition. // "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes // can't decide if a resource is in the condition or not. In the future, we could add other // intermediate conditions, e.g. ConditionDegraded. const ( ConditionTrue ConditionStatus = "True" ConditionFalse ConditionStatus = "False" ConditionUnknown ConditionStatus = "Unknown" ) // CustomResourceDefinitionConditionType is a valid value for CustomResourceDefinitionCondition.Type type CustomResourceDefinitionConditionType string const ( // Established means that the resource has become active. A resource is established when all names are // accepted without a conflict for the first time. A resource stays established until deleted, even during // a later NamesAccepted due to changed names. Note that not all names can be changed. Established CustomResourceDefinitionConditionType = "Established" // NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in // the group and are therefore accepted. NamesAccepted CustomResourceDefinitionConditionType = "NamesAccepted" // Terminating means that the CustomResourceDefinition has been deleted and is cleaning up. Terminating CustomResourceDefinitionConditionType = "Terminating" ) // CustomResourceDefinitionCondition contains details for the current condition of this pod. type CustomResourceDefinitionCondition struct { // Type is the type of the condition. Type CustomResourceDefinitionConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=CustomResourceDefinitionConditionType"` // Status is the status of the condition. // Can be True, False, Unknown. Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=ConditionStatus"` // Last time the condition transitioned from one status to another. // +optional LastTransitionTime Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"` // Unique, one-word, CamelCase reason for the condition's last transition. // +optional Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` // Human-readable message indicating details about last transition. // +optional Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` } // CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition type CustomResourceDefinitionStatus struct { // Conditions indicate state for particular aspects of a CustomResourceDefinition Conditions []CustomResourceDefinitionCondition `json:"conditions" protobuf:"bytes,1,opt,name=conditions"` // AcceptedNames are the names that are actually being used to serve discovery // They may be different than the names in spec. AcceptedNames CustomResourceDefinitionNames `json:"acceptedNames" protobuf:"bytes,2,opt,name=acceptedNames"` } // CustomResourceCleanupFinalizer is the name of the finalizer which will delete instances of // a CustomResourceDefinition const CustomResourceCleanupFinalizer = "customresourcecleanup.apiextensions.k8s.io" // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format // <.spec.name>.<.spec.group>. type CustomResourceDefinition struct { TypeMeta `json:",inline"` ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Spec describes how the user wants the resources to appear Spec CustomResourceDefinitionSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` // Status indicates the actual state of the CustomResourceDefinition Status CustomResourceDefinitionStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // CustomResourceDefinitionList is a list of CustomResourceDefinition objects. type CustomResourceDefinitionList struct { TypeMeta `json:",inline"` ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` // Items individual CustomResourceDefinitions Items []CustomResourceDefinition `json:"items" protobuf:"bytes,2,rep,name=items"` } type CustomResourceDefinitionVersion struct { // name is the version name, e.g. “v1”, “v2beta1”, etc. // The custom resources are served under this version at `/apis///...` if `served` is true. Name string `json:"name" protobuf:"bytes,1,opt,name=name"` // served is a flag enabling/disabling this version from being served via REST APIs Served bool `json:"served" protobuf:"varint,2,opt,name=served"` // storage indicates this version should be used when persisting custom resources to storage. // There must be exactly one version with storage=true. Storage bool `json:"storage" protobuf:"varint,3,opt,name=storage"` // schema describes the schema used for validation, pruning, and defaulting of this version of the custom resource. // +optional Schema *CustomResourceValidation `json:"schema,omitempty" protobuf:"bytes,4,opt,name=schema"` } // CustomResourceValidation is a list of validation methods for CustomResources. type CustomResourceValidation struct { // OpenAPIV3Schema is the OpenAPI v3 schema to be validated against. OpenAPIV3Schema *JSONSchemaProps `json:"openAPIV3Schema,omitempty" protobuf:"bytes,1,opt,name=openAPIV3Schema"` } // JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/). type JSONSchemaProps struct { Type string `json:"type,omitempty" protobuf:"bytes,5,opt,name=type"` XPreserveUnknownFields *bool `json:"x-kubernetes-preserve-unknown-fields,omitempty" protobuf:"bytes,38,opt,name=xKubernetesPreserveUnknownFields"` } ================================================ FILE: storage/kubernetes/k8sapi/doc.go ================================================ // Package k8sapi holds vendored Kubernetes types. package k8sapi ================================================ FILE: storage/kubernetes/k8sapi/extensions.go ================================================ /* Copyright 2015 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package k8sapi // An APIVersion represents a single concrete version of an object model. type APIVersion struct { // Name of this version (e.g. 'v1'). Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` } ================================================ FILE: storage/kubernetes/k8sapi/time.go ================================================ /* Copyright 2014 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package k8sapi import ( "encoding/json" "time" ) // Time is a wrapper around time.Time which supports correct // marshaling to YAML and JSON. Wrappers are provided for many // of the factory methods that the time package offers. // // +protobuf.options.marshal=false // +protobuf.as=Timestamp type Time struct { time.Time `protobuf:"-"` } // NewTime returns a wrapped instance of the provided time func NewTime(time time.Time) Time { return Time{time} } // Date returns the Time corresponding to the supplied parameters // by wrapping time.Date. func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time { return Time{time.Date(year, month, day, hour, min, sec, nsec, loc)} } // Now returns the current local time. func Now() Time { return Time{time.Now()} } // IsZero returns true if the value is nil or time is zero. func (t *Time) IsZero() bool { if t == nil { return true } return t.Time.IsZero() } // Before reports whether the time instant t is before u. func (t Time) Before(u Time) bool { return t.Time.Before(u.Time) } // Equal reports whether the time instant t is equal to u. func (t Time) Equal(u Time) bool { return t.Time.Equal(u.Time) } // Unix returns the local time corresponding to the given Unix time // by wrapping time.Unix. func Unix(sec int64, nsec int64) Time { return Time{time.Unix(sec, nsec)} } // Rfc3339Copy returns a copy of the Time at second-level precision. func (t Time) Rfc3339Copy() Time { copied, _ := time.Parse(time.RFC3339, t.Format(time.RFC3339)) return Time{copied} } // UnmarshalJSON implements the json.Unmarshaller interface. func (t *Time) UnmarshalJSON(b []byte) error { if len(b) == 4 && string(b) == "null" { t.Time = time.Time{} return nil } var str string json.Unmarshal(b, &str) pt, err := time.Parse(time.RFC3339, str) if err != nil { return err } t.Time = pt.Local() return nil } // UnmarshalQueryParameter converts from a URL query parameter value to an object func (t *Time) UnmarshalQueryParameter(str string) error { if len(str) == 0 { t.Time = time.Time{} return nil } // Tolerate requests from older clients that used JSON serialization to build query params if len(str) == 4 && str == "null" { t.Time = time.Time{} return nil } pt, err := time.Parse(time.RFC3339, str) if err != nil { return err } t.Time = pt.Local() return nil } // MarshalJSON implements the json.Marshaler interface. func (t Time) MarshalJSON() ([]byte, error) { if t.IsZero() { // Encode unset/nil objects as JSON's "null". return []byte("null"), nil } return json.Marshal(t.UTC().Format(time.RFC3339)) } // MarshalQueryParameter converts to a URL query parameter value func (t Time) MarshalQueryParameter() (string, error) { if t.IsZero() { // Encode unset/nil objects as an empty string return "", nil } return t.UTC().Format(time.RFC3339), nil } ================================================ FILE: storage/kubernetes/k8sapi/unversioned.go ================================================ /* Copyright 2015 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package k8sapi // TypeMeta describes an individual object in an API response or request // with strings representing the type of the object and its API schema version. // Structures that are versioned or persisted should inline TypeMeta. type TypeMeta struct { // Kind is a string value representing the REST resource this object represents. // Servers may infer this from the endpoint the client submits requests to. // Cannot be updated. // In CamelCase. // More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#types-kinds Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"` // APIVersion defines the versioned schema of this representation of an object. // Servers should convert recognized schemas to the latest internal value, and // may reject unrecognized values. // More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#resources APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"` } // ListMeta describes metadata that synthetic resources must have, including lists and // various status objects. A resource may have only one of {ObjectMeta, ListMeta}. type ListMeta struct { // SelfLink is a URL representing this object. // Populated by the system. // Read-only. SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,1,opt,name=selfLink"` // String that identifies the server's internal version of this object that // can be used by clients to determine when objects have changed. // Value must be treated as opaque by clients and passed unmodified back to the server. // Populated by the system. // Read-only. // More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#concurrency-control-and-consistency ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,2,opt,name=resourceVersion"` } ================================================ FILE: storage/kubernetes/k8sapi/v1.go ================================================ /* Copyright 2015 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package k8sapi // ObjectMeta is metadata that all persisted resources must have, which includes all objects // users must create. type ObjectMeta struct { // Name must be unique within a namespace. Is required when creating resources, although // some resources may allow a client to request the generation of an appropriate name // automatically. Name is primarily intended for creation idempotence and configuration // definition. // Cannot be updated. // More info: http://releases.k8s.io/release-1.3/docs/user-guide/identifiers.md#names Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` // GenerateName is an optional prefix, used by the server, to generate a unique // name ONLY IF the Name field has not been provided. // If this field is used, the name returned to the client will be different // than the name passed. This value will also be combined with a unique suffix. // The provided value has the same validation rules as the Name field, // and may be truncated by the length of the suffix required to make the value // unique on the server. // // If this field is specified and the generated name exists, the server will // NOT return a 409 - instead, it will either return 201 Created or 500 with Reason // ServerTimeout indicating a unique name could not be found in the time allotted, and the client // should retry (optionally after the time indicated in the Retry-After header). // // Applied only if Name is not specified. // More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#idempotency GenerateName string `json:"generateName,omitempty" protobuf:"bytes,2,opt,name=generateName"` // Namespace defines the space within each name must be unique. An empty namespace is // equivalent to the "default" namespace, but "default" is the canonical representation. // Not all objects are required to be scoped to a namespace - the value of this field for // those objects will be empty. // // Must be a DNS_LABEL. // Cannot be updated. // More info: http://releases.k8s.io/release-1.3/docs/user-guide/namespaces.md Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"` // SelfLink is a URL representing this object. // Populated by the system. // Read-only. SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,4,opt,name=selfLink"` // UID is the unique in time and space value for this object. It is typically generated by // the server on successful creation of a resource and is not allowed to change on PUT // operations. // // Populated by the system. // Read-only. // More info: http://releases.k8s.io/release-1.3/docs/user-guide/identifiers.md#uids UID string `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"` // An opaque value that represents the internal version of this object that can // be used by clients to determine when objects have changed. May be used for optimistic // concurrency, change detection, and the watch operation on a resource or set of resources. // Clients must treat these values as opaque and passed unmodified back to the server. // They may only be valid for a particular resource or set of resources. // // Populated by the system. // Read-only. // Value must be treated as opaque by clients and . // More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#concurrency-control-and-consistency ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"` // A sequence number representing a specific generation of the desired state. // Populated by the system. Read-only. Generation int64 `json:"generation,omitempty" protobuf:"varint,7,opt,name=generation"` // CreationTimestamp is a timestamp representing the server time when this object was // created. It is not guaranteed to be set in happens-before order across separate operations. // Clients may not set this value. It is represented in RFC3339 form and is in UTC. // // Populated by the system. // Read-only. // Null for lists. // More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#metadata CreationTimestamp Time `json:"creationTimestamp,omitempty" protobuf:"bytes,8,opt,name=creationTimestamp"` // DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This // field is set by the server when a graceful deletion is requested by the user, and is not // directly settable by a client. The resource will be deleted (no longer visible from // resource lists, and not reachable by name) after the time in this field. Once set, this // value may not be unset or be set further into the future, although it may be shortened // or the resource may be deleted prior to this time. For example, a user may request that // a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination // signal to the containers in the pod. Once the resource is deleted in the API, the Kubelet // will send a hard termination signal to the container. // If not set, graceful deletion of the object has not been requested. // // Populated by the system when a graceful deletion is requested. // Read-only. // More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#metadata DeletionTimestamp *Time `json:"deletionTimestamp,omitempty" protobuf:"bytes,9,opt,name=deletionTimestamp"` // Number of seconds allowed for this object to gracefully terminate before // it will be removed from the system. Only set when deletionTimestamp is also set. // May only be shortened. // Read-only. DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty" protobuf:"varint,10,opt,name=deletionGracePeriodSeconds"` // Map of string keys and values that can be used to organize and categorize // (scope and select) objects. May match selectors of replication controllers // and services. // More info: http://releases.k8s.io/release-1.3/docs/user-guide/labels.md // TODO: replace map[string]string with labels.LabelSet type Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"` // Annotations is an unstructured key value map stored with a resource that may be // set by external tools to store and retrieve arbitrary metadata. They are not // queryable and should be preserved when modifying objects. // More info: http://releases.k8s.io/release-1.3/docs/user-guide/annotations.md Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"` // List of objects depended by this object. If ALL objects in the list have // been deleted, this object will be garbage collected. If this object is managed by a controller, // then an entry in this list will point to this controller, with the controller field set to true. // There cannot be more than one managing controller. OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid" protobuf:"bytes,13,rep,name=ownerReferences"` // Must be empty before the object is deleted from the registry. Each entry // is an identifier for the responsible component that will remove the entry // from the list. If the deletionTimestamp of the object is non-nil, entries // in this list can only be removed. Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"` } // OwnerReference contains enough information to let you identify an owning // object. Currently, an owning object must be in the same namespace, so there // is no namespace field. type OwnerReference struct { // API version of the referent. APIVersion string `json:"apiVersion" protobuf:"bytes,5,opt,name=apiVersion"` // Kind of the referent. // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"` // Name of the referent. // More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#names Name string `json:"name" protobuf:"bytes,3,opt,name=name"` // UID of the referent. // More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#uids UID string `json:"uid" protobuf:"bytes,4,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"` // If true, this reference points to the managing controller. Controller *bool `json:"controller,omitempty" protobuf:"varint,6,opt,name=controller"` } ================================================ FILE: storage/kubernetes/lock.go ================================================ package kubernetes import ( "fmt" "time" ) const ( lockAnnotation = "dexidp.com/resource-lock" lockTimeFormat = time.RFC3339 ) var ( lockTimeout = 10 * time.Second lockCheckPeriod = 100 * time.Millisecond ) // refreshTokenLock is an implementation of annotation-based optimistic locking. // // Refresh token contains data to refresh identity in external authentication system. // There is a requirement that refresh should be called only once because of several reasons: // - Some of OIDC providers could use the refresh token rotation feature which requires calling refresh only once. // - Providers can limit the rate of requests to the token endpoint, which will lead to the error // in case of many concurrent requests. // // The lock uses a Kubernetes annotation on the refresh token resource as a mutex. // Only one goroutine can hold the lock at a time; others poll until the annotation // is removed (unlocked) or expires (broken). The Kubernetes resourceVersion on put // acts as compare-and-swap: if two goroutines race to set the annotation, only one // succeeds and the other gets a 409 Conflict. type refreshTokenLock struct { cli *client // waitingState tracks whether this lock instance has lost a compare-and-swap race // and is now polling for the lock to be released. Used by Unlock to skip the // annotation removal — only the goroutine that successfully wrote the annotation // should remove it. waitingState bool } func newRefreshTokenLock(cli *client) *refreshTokenLock { return &refreshTokenLock{cli: cli} } // Lock polls until the lock annotation can be set on the refresh token resource. // Returns nil when the lock is acquired, or an error on timeout (200 attempts × 100ms). func (l *refreshTokenLock) Lock(id string) error { for i := 0; i <= 200; i++ { ok, err := l.setLockAnnotation(id) if err != nil { return err } if !ok { return nil } time.Sleep(lockCheckPeriod) } return fmt.Errorf("timeout waiting for refresh token %s lock", id) } // Unlock removes the lock annotation from the refresh token resource. // Only the holder of the lock (waitingState == false) performs the removal. func (l *refreshTokenLock) Unlock(id string) { if l.waitingState { // This goroutine never successfully wrote the annotation, so there's // nothing to remove. Another goroutine holds (or held) the lock. return } r, err := l.cli.getRefreshToken(id) if err != nil { l.cli.logger.Debug("failed to get resource to release lock for refresh token", "token_id", id, "err", err) return } r.Annotations = nil err = l.cli.put(resourceRefreshToken, r.ObjectMeta.Name, r) if err != nil { l.cli.logger.Debug("failed to release lock for refresh token", "token_id", id, "err", err) } } // setLockAnnotation attempts to acquire the lock by writing an annotation with // an expiration timestamp. Returns (true, nil) when the caller should keep waiting, // (false, nil) when the lock is acquired, or (false, err) on a non-retriable error. // // The locking protocol relies on Kubernetes optimistic concurrency: every put // includes the resource's current resourceVersion, so concurrent writes to the // same object result in a 409 Conflict for all but one writer. func (l *refreshTokenLock) setLockAnnotation(id string) (bool, error) { r, err := l.cli.getRefreshToken(id) if err != nil { return false, err } currentTime := time.Now() lockData := map[string]string{ lockAnnotation: currentTime.Add(lockTimeout).Format(lockTimeFormat), } val, ok := r.Annotations[lockAnnotation] if !ok { // No annotation means the lock is free. Every goroutine — whether it's // a first-time caller or was previously waiting — must compete by writing // the annotation. The put uses the current resourceVersion, so only one // writer succeeds; the rest get a 409 Conflict and go back to polling. r.Annotations = lockData err := l.cli.put(resourceRefreshToken, r.ObjectMeta.Name, r) if err == nil { l.waitingState = false return false, nil } if isKubernetesAPIConflictError(err) { l.waitingState = true return true, nil } return false, err } until, err := time.Parse(lockTimeFormat, val) if err != nil { return false, fmt.Errorf("lock annotation value is malformed: %v", err) } if !currentTime.After(until) { // Lock is held by another goroutine and has not expired yet — keep polling. l.waitingState = true return true, nil } // Lock has expired (holder crashed or is too slow). Attempt to break it by // overwriting the annotation with a new expiration. Again, only one writer // can win the compare-and-swap race. r.Annotations = lockData err = l.cli.put(resourceRefreshToken, r.ObjectMeta.Name, r) if err == nil { return false, nil } l.cli.logger.Debug("break lock annotation", "error", err) if isKubernetesAPIConflictError(err) { l.waitingState = true return true, nil } return false, err } ================================================ FILE: storage/kubernetes/storage.go ================================================ package kubernetes import ( "context" "errors" "fmt" "log/slog" "math/rand" "net/http" "strings" "time" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/kubernetes/k8sapi" ) const ( kindAuthCode = "AuthCode" kindAuthRequest = "AuthRequest" kindClient = "OAuth2Client" kindRefreshToken = "RefreshToken" kindKeys = "SigningKey" kindPassword = "Password" kindOfflineSessions = "OfflineSessions" kindConnector = "Connector" kindDeviceRequest = "DeviceRequest" kindDeviceToken = "DeviceToken" kindUserIdentity = "UserIdentity" kindAuthSession = "AuthSession" ) const ( resourceAuthCode = "authcodes" resourceAuthRequest = "authrequests" resourceClient = "oauth2clients" resourceRefreshToken = "refreshtokens" resourceKeys = "signingkeies" // Kubernetes attempts to pluralize. resourcePassword = "passwords" resourceOfflineSessions = "offlinesessionses" // Again attempts to pluralize. resourceConnector = "connectors" resourceDeviceRequest = "devicerequests" resourceDeviceToken = "devicetokens" resourceUserIdentity = "useridentities" resourceAuthSession = "authsessions" ) const ( crdHandlingEnsure = "ensure" crdHandlingCheck = "check" ) var _ storage.Storage = (*client)(nil) const ( gcResultLimit = 500 ) // Config values for the Kubernetes storage type. type Config struct { InCluster bool `json:"inCluster"` KubeConfigFile string `json:"kubeConfigFile"` // CRDHandling controls how the storage handles Custom Resource Definitions (CRDs). // Supported values: // - "ensure": Attempt to create all missing CRDs. If any CRD creation fails, initialization fails. (default) // - "check": Fail immediately if any CRDs are missing with message "storage is not initialized, CRDs are not created" CRDHandling string `json:"crdHandling"` } // Open returns a storage using Kubernetes third party resource. func (c *Config) Open(logger *slog.Logger) (storage.Storage, error) { return c.open(logger, false) } // open returns a kubernetes client, initializing the third party resources used // by dex. // // waitForResources controls if errors creating the resources cause this method to return // immediately (used during testing), or if the client will asynchronously retry. func (c *Config) open(logger *slog.Logger, waitForResources bool) (*client, error) { if c.CRDHandling == "" { c.CRDHandling = crdHandlingEnsure } if c.InCluster && (c.KubeConfigFile != "") { return nil, errors.New("cannot specify both 'inCluster' and 'kubeConfigFile'") } if !c.InCluster && (c.KubeConfigFile == "") { return nil, errors.New("must specify either 'inCluster' or 'kubeConfigFile'") } var ( cluster k8sapi.Cluster user k8sapi.AuthInfo namespace string err error ) if c.InCluster { cluster, user, namespace, err = inClusterConfig() } else { cluster, user, namespace, err = loadKubeConfig(c.KubeConfigFile) } if err != nil { return nil, err } cli, err := newClient(cluster, user, namespace, logger, c.InCluster, c.CRDHandling) if err != nil { return nil, fmt.Errorf("create client: %v", err) } if err = cli.detectKubernetesVersion(); err != nil { return nil, fmt.Errorf("cannot get kubernetes version: %v", err) } ctx, cancel := context.WithCancel(context.Background()) logger.Info("creating custom Kubernetes resources") if !cli.registerCustomResources() { if waitForResources { cancel() return nil, fmt.Errorf("failed creating custom resources") } // Try to synchronously create the custom resources once. This doesn't mean // they'll immediately be available, but ensures that the client will actually try // once. go func() { for { if cli.registerCustomResources() { return } select { case <-ctx.Done(): return case <-time.After(30 * time.Second): } } }() } if waitForResources { if err := cli.waitForCRDs(ctx); err != nil { cancel() return nil, err } } // If the client is closed, stop trying to create resources. cli.cancel = cancel return cli, nil } // registerCustomResources attempts to create the custom resources dex // requires or identifies that they're already enabled. This function creates // custom resource definitions(CRDs) // It logs all errors, returning true if the resources were created successfully. // // Creating a custom resource does not mean that they'll be immediately available. func (cli *client) registerCustomResources() bool { definitions := customResourceDefinitions(cli.crdAPIVersion) // First pass: collect all CRDs that don't exist var missingCRDs []k8sapi.CustomResourceDefinition for _, r := range definitions { var i interface{} cli.logger.Info("checking if custom resource has already been created...", "object", r.ObjectMeta.Name) if err := cli.listN(r.Spec.Names.Plural, &i, 1); err == nil { cli.logger.Info("the custom resource already available, skipping create", "object", r.ObjectMeta.Name) } else { cli.logger.Info("custom resource not found", "object", r.ObjectMeta.Name, "err", err) missingCRDs = append(missingCRDs, r) } } // Second pass: handle missing CRDs based on crdHandling option if len(missingCRDs) > 0 { cli.logger.Info("found missing CRDs", "count", len(missingCRDs)) switch cli.crdHandling { case crdHandlingCheck: // For "check" mode, fail and report that CRDs are not initialized cli.logger.Error("storage is not initialized, CRDs are not created", "crdHandling", cli.crdHandling, "missing_count", len(missingCRDs)) return false case crdHandlingEnsure: cli.logger.Info("crdHandling is 'ensure', attempting to create missing CRDs") for _, r := range missingCRDs { resourceName := r.ObjectMeta.Name err := cli.postResource(cli.crdAPIVersion, "", "customresourcedefinitions", r) if err != nil { if !errors.Is(err, storage.ErrAlreadyExists) { cli.logger.Error("failed to create custom resource", "object", resourceName, "err", err) return false } cli.logger.Info("custom resource already created", "object", resourceName) } else { cli.logger.Info("successfully created custom resource", "object", resourceName) } } return true default: cli.logger.Error("invalid crdHandling value", "value", cli.crdHandling) return false } } // All CRDs exist return true } // waitForCRDs waits for all CRDs to be in a ready state, and is used // by the tests to synchronize before running conformance. func (cli *client) waitForCRDs(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, time.Second*30) defer cancel() for _, crd := range customResourceDefinitions(cli.crdAPIVersion) { for { err := cli.isCRDReady(crd.Name) if err == nil { break } cli.logger.ErrorContext(ctx, "checking CRD", "err", err) select { case <-ctx.Done(): return errors.New("timed out waiting for CRDs to be available") case <-time.After(time.Millisecond * 100): } } } return nil } // isCRDReady determines if a CRD is ready by inspecting its conditions. func (cli *client) isCRDReady(name string) error { var r k8sapi.CustomResourceDefinition err := cli.getResource(cli.crdAPIVersion, "", "customresourcedefinitions", name, &r) if err != nil { return fmt.Errorf("get crd %s: %v", name, err) } conds := make(map[string]string) // For debugging, keep the conditions around. for _, c := range r.Status.Conditions { if c.Type == k8sapi.Established && c.Status == k8sapi.ConditionTrue { return nil } conds[string(c.Type)] = string(c.Status) } return fmt.Errorf("crd %s not ready %#v", name, conds) } func (cli *client) Close() error { if cli.cancel != nil { cli.cancel() } return nil } func (cli *client) CreateAuthRequest(ctx context.Context, a storage.AuthRequest) error { return cli.post(resourceAuthRequest, cli.fromStorageAuthRequest(a)) } func (cli *client) CreateClient(ctx context.Context, c storage.Client) error { return cli.post(resourceClient, cli.fromStorageClient(c)) } func (cli *client) CreateAuthCode(ctx context.Context, c storage.AuthCode) error { return cli.post(resourceAuthCode, cli.fromStorageAuthCode(c)) } func (cli *client) CreatePassword(ctx context.Context, p storage.Password) error { return cli.post(resourcePassword, cli.fromStoragePassword(p)) } func (cli *client) CreateRefresh(ctx context.Context, r storage.RefreshToken) error { return cli.post(resourceRefreshToken, cli.fromStorageRefreshToken(r)) } func (cli *client) CreateOfflineSessions(ctx context.Context, o storage.OfflineSessions) error { return cli.post(resourceOfflineSessions, cli.fromStorageOfflineSessions(o)) } func (cli *client) CreateConnector(ctx context.Context, c storage.Connector) error { return cli.post(resourceConnector, cli.fromStorageConnector(c)) } func (cli *client) GetAuthRequest(ctx context.Context, id string) (storage.AuthRequest, error) { var req AuthRequest if err := cli.get(resourceAuthRequest, id, &req); err != nil { return storage.AuthRequest{}, err } return toStorageAuthRequest(req), nil } func (cli *client) GetAuthCode(ctx context.Context, id string) (storage.AuthCode, error) { var code AuthCode if err := cli.get(resourceAuthCode, id, &code); err != nil { return storage.AuthCode{}, err } return toStorageAuthCode(code), nil } func (cli *client) GetClient(ctx context.Context, id string) (storage.Client, error) { c, err := cli.getClient(id) if err != nil { return storage.Client{}, err } return toStorageClient(c), nil } func (cli *client) getClient(id string) (Client, error) { var c Client name := cli.idToName(id) if err := cli.get(resourceClient, name, &c); err != nil { return Client{}, err } if c.ID != id { return Client{}, fmt.Errorf("get client: ID %q mapped to client with ID %q", id, c.ID) } return c, nil } func (cli *client) GetPassword(ctx context.Context, email string) (storage.Password, error) { p, err := cli.getPassword(email) if err != nil { return storage.Password{}, err } return toStoragePassword(p), nil } func (cli *client) getPassword(email string) (Password, error) { // TODO(ericchiang): Figure out whose job it is to lowercase emails. email = strings.ToLower(email) var p Password name := cli.idToName(email) if err := cli.get(resourcePassword, name, &p); err != nil { return Password{}, err } if email != p.Email { return Password{}, fmt.Errorf("get email: email %q mapped to password with email %q", email, p.Email) } return p, nil } func (cli *client) GetKeys(ctx context.Context) (storage.Keys, error) { var keys Keys if err := cli.get(resourceKeys, keysName, &keys); err != nil { return storage.Keys{}, err } return toStorageKeys(keys), nil } func (cli *client) GetRefresh(ctx context.Context, id string) (storage.RefreshToken, error) { r, err := cli.getRefreshToken(id) if err != nil { return storage.RefreshToken{}, err } return toStorageRefreshToken(r), nil } func (cli *client) getRefreshToken(id string) (r RefreshToken, err error) { err = cli.get(resourceRefreshToken, id, &r) return } func (cli *client) GetOfflineSessions(ctx context.Context, userID string, connID string) (storage.OfflineSessions, error) { o, err := cli.getOfflineSessions(userID, connID) if err != nil { return storage.OfflineSessions{}, err } return toStorageOfflineSessions(o), nil } func (cli *client) getOfflineSessions(userID string, connID string) (o OfflineSessions, err error) { name := cli.offlineTokenName(userID, connID) if err = cli.get(resourceOfflineSessions, name, &o); err != nil { return OfflineSessions{}, err } if userID != o.UserID || connID != o.ConnID { return OfflineSessions{}, fmt.Errorf("get offline session: wrong session retrieved") } return o, nil } func (cli *client) GetConnector(ctx context.Context, id string) (storage.Connector, error) { var c Connector if err := cli.get(resourceConnector, id, &c); err != nil { return storage.Connector{}, err } return toStorageConnector(c), nil } func (cli *client) ListClients(ctx context.Context) ([]storage.Client, error) { return nil, errors.New("not implemented") } func (cli *client) ListRefreshTokens(ctx context.Context) ([]storage.RefreshToken, error) { return nil, errors.New("not implemented") } func (cli *client) ListPasswords(ctx context.Context) (passwords []storage.Password, err error) { var passwordList PasswordList if err = cli.list(resourcePassword, &passwordList); err != nil { return passwords, fmt.Errorf("failed to list passwords: %v", err) } for _, password := range passwordList.Passwords { passwords = append(passwords, toStoragePassword(password)) } return } func (cli *client) ListConnectors(ctx context.Context) (connectors []storage.Connector, err error) { var connectorList ConnectorList if err = cli.list(resourceConnector, &connectorList); err != nil { return connectors, fmt.Errorf("failed to list connectors: %v", err) } connectors = make([]storage.Connector, len(connectorList.Connectors)) for i, connector := range connectorList.Connectors { connectors[i] = toStorageConnector(connector) } return } func (cli *client) DeleteAuthRequest(ctx context.Context, id string) error { return cli.delete(resourceAuthRequest, id) } func (cli *client) DeleteAuthCode(ctx context.Context, code string) error { return cli.delete(resourceAuthCode, code) } func (cli *client) DeleteClient(ctx context.Context, id string) error { // Check for hash collision. c, err := cli.getClient(id) if err != nil { return err } return cli.delete(resourceClient, c.ObjectMeta.Name) } func (cli *client) DeleteRefresh(ctx context.Context, id string) error { return cli.delete(resourceRefreshToken, id) } func (cli *client) DeletePassword(ctx context.Context, email string) error { // Check for hash collision. p, err := cli.getPassword(email) if err != nil { return err } return cli.delete(resourcePassword, p.ObjectMeta.Name) } func (cli *client) DeleteOfflineSessions(ctx context.Context, userID string, connID string) error { // Check for hash collision. o, err := cli.getOfflineSessions(userID, connID) if err != nil { return err } return cli.delete(resourceOfflineSessions, o.ObjectMeta.Name) } func (cli *client) DeleteConnector(ctx context.Context, id string) error { return cli.delete(resourceConnector, id) } func (cli *client) UpdateRefreshToken(ctx context.Context, id string, updater func(old storage.RefreshToken) (storage.RefreshToken, error)) error { lock := newRefreshTokenLock(cli) if err := lock.Lock(id); err != nil { return err } defer lock.Unlock(id) return retryOnConflict(ctx, func() error { r, err := cli.getRefreshToken(id) if err != nil { return err } updated, err := updater(toStorageRefreshToken(r)) if err != nil { return err } updated.ID = id newToken := cli.fromStorageRefreshToken(updated) newToken.ObjectMeta = r.ObjectMeta return cli.put(resourceRefreshToken, r.ObjectMeta.Name, newToken) }) } func (cli *client) UpdateClient(ctx context.Context, id string, updater func(old storage.Client) (storage.Client, error)) error { c, err := cli.getClient(id) if err != nil { return err } updated, err := updater(toStorageClient(c)) if err != nil { return err } updated.ID = c.ID newClient := cli.fromStorageClient(updated) newClient.ObjectMeta = c.ObjectMeta return cli.put(resourceClient, c.ObjectMeta.Name, newClient) } func (cli *client) UpdatePassword(ctx context.Context, email string, updater func(old storage.Password) (storage.Password, error)) error { p, err := cli.getPassword(email) if err != nil { return err } updated, err := updater(toStoragePassword(p)) if err != nil { return err } updated.Email = p.Email newPassword := cli.fromStoragePassword(updated) newPassword.ObjectMeta = p.ObjectMeta return cli.put(resourcePassword, p.ObjectMeta.Name, newPassword) } func (cli *client) UpdateOfflineSessions(ctx context.Context, userID string, connID string, updater func(old storage.OfflineSessions) (storage.OfflineSessions, error)) error { return retryOnConflict(ctx, func() error { o, err := cli.getOfflineSessions(userID, connID) if err != nil { return err } updated, err := updater(toStorageOfflineSessions(o)) if err != nil { return err } newOfflineSessions := cli.fromStorageOfflineSessions(updated) newOfflineSessions.ObjectMeta = o.ObjectMeta return cli.put(resourceOfflineSessions, o.ObjectMeta.Name, newOfflineSessions) }) } func (cli *client) UpdateKeys(ctx context.Context, updater func(old storage.Keys) (storage.Keys, error)) error { firstUpdate := false var keys Keys if err := cli.get(resourceKeys, keysName, &keys); err != nil { if err != storage.ErrNotFound { return err } firstUpdate = true } var oldKeys storage.Keys if !firstUpdate { oldKeys = toStorageKeys(keys) } updated, err := updater(oldKeys) if err != nil { return err } newKeys := cli.fromStorageKeys(updated) if firstUpdate { err = cli.post(resourceKeys, newKeys) if err != nil && errors.Is(err, storage.ErrAlreadyExists) { // We need to tolerate conflicts here in case of HA mode. cli.logger.Debug("Keys creation failed. It is possible that keys have already been created by another dex instance.", "err", err) return errors.New("keys already created by another server instance") } return err } newKeys.ObjectMeta = keys.ObjectMeta err = cli.put(resourceKeys, keysName, newKeys) if isKubernetesAPIConflictError(err) { // We need to tolerate conflicts here in case of HA mode. // Dex instances run keys rotation at the same time because they use SigningKey.nextRotation CR field as a trigger. cli.logger.Debug("Keys rotation failed. It is possible that keys have already been rotated by another dex instance.", "err", err) return errors.New("keys already rotated by another server instance") } return err } func (cli *client) UpdateAuthRequest(ctx context.Context, id string, updater func(a storage.AuthRequest) (storage.AuthRequest, error)) error { var req AuthRequest err := cli.get(resourceAuthRequest, id, &req) if err != nil { return err } updated, err := updater(toStorageAuthRequest(req)) if err != nil { return err } newReq := cli.fromStorageAuthRequest(updated) newReq.ObjectMeta = req.ObjectMeta return cli.put(resourceAuthRequest, id, newReq) } func (cli *client) UpdateConnector(ctx context.Context, id string, updater func(a storage.Connector) (storage.Connector, error)) error { return retryOnConflict(ctx, func() error { var c Connector err := cli.get(resourceConnector, id, &c) if err != nil { return err } updated, err := updater(toStorageConnector(c)) if err != nil { return err } newConn := cli.fromStorageConnector(updated) newConn.ObjectMeta = c.ObjectMeta return cli.put(resourceConnector, id, newConn) }) } func (cli *client) GarbageCollect(ctx context.Context, now time.Time) (result storage.GCResult, err error) { var authRequests AuthRequestList if err := cli.listN(resourceAuthRequest, &authRequests, gcResultLimit); err != nil { return result, fmt.Errorf("failed to list auth requests: %v", err) } var delErr error for _, authRequest := range authRequests.AuthRequests { if now.After(authRequest.Expiry) { if err := cli.delete(resourceAuthRequest, authRequest.ObjectMeta.Name); err != nil { cli.logger.Error("failed to delete auth request", "err", err) delErr = fmt.Errorf("failed to delete auth request: %v", err) } result.AuthRequests++ } } if delErr != nil { return result, delErr } var authCodes AuthCodeList if err := cli.listN(resourceAuthCode, &authCodes, gcResultLimit); err != nil { return result, fmt.Errorf("failed to list auth codes: %v", err) } for _, authCode := range authCodes.AuthCodes { if now.After(authCode.Expiry) { if err := cli.delete(resourceAuthCode, authCode.ObjectMeta.Name); err != nil { cli.logger.Error("failed to delete auth code", "err", err) delErr = fmt.Errorf("failed to delete auth code: %v", err) } result.AuthCodes++ } } var deviceRequests DeviceRequestList if err := cli.listN(resourceDeviceRequest, &deviceRequests, gcResultLimit); err != nil { return result, fmt.Errorf("failed to list device requests: %v", err) } for _, deviceRequest := range deviceRequests.DeviceRequests { if now.After(deviceRequest.Expiry) { if err := cli.delete(resourceDeviceRequest, deviceRequest.ObjectMeta.Name); err != nil { cli.logger.Error("failed to delete device request", "err", err) delErr = fmt.Errorf("failed to delete device request: %v", err) } result.DeviceRequests++ } } var deviceTokens DeviceTokenList if err := cli.listN(resourceDeviceToken, &deviceTokens, gcResultLimit); err != nil { return result, fmt.Errorf("failed to list device tokens: %v", err) } for _, deviceToken := range deviceTokens.DeviceTokens { if now.After(deviceToken.Expiry) { if err := cli.delete(resourceDeviceToken, deviceToken.ObjectMeta.Name); err != nil { cli.logger.Error("failed to delete device token", "err", err) delErr = fmt.Errorf("failed to delete device token: %v", err) } result.DeviceTokens++ } } var authSessions AuthSessionList if err := cli.listN(resourceAuthSession, &authSessions, gcResultLimit); err != nil { return result, fmt.Errorf("failed to list auth sessions: %v", err) } for _, authSession := range authSessions.AuthSessions { if now.After(authSession.AbsoluteExpiry) || now.After(authSession.IdleExpiry) { if err := cli.delete(resourceAuthSession, authSession.ObjectMeta.Name); err != nil { cli.logger.Error("failed to delete auth session", "err", err) delErr = fmt.Errorf("failed to delete auth session: %v", err) } else { result.AuthSessions++ } } } if delErr != nil { return result, delErr } return result, delErr } func (cli *client) CreateDeviceRequest(ctx context.Context, d storage.DeviceRequest) error { return cli.post(resourceDeviceRequest, cli.fromStorageDeviceRequest(d)) } func (cli *client) GetDeviceRequest(ctx context.Context, userCode string) (storage.DeviceRequest, error) { var req DeviceRequest if err := cli.get(resourceDeviceRequest, strings.ToLower(userCode), &req); err != nil { return storage.DeviceRequest{}, err } return toStorageDeviceRequest(req), nil } func (cli *client) CreateDeviceToken(ctx context.Context, t storage.DeviceToken) error { return cli.post(resourceDeviceToken, cli.fromStorageDeviceToken(t)) } func (cli *client) GetDeviceToken(ctx context.Context, deviceCode string) (storage.DeviceToken, error) { var token DeviceToken if err := cli.get(resourceDeviceToken, deviceCode, &token); err != nil { return storage.DeviceToken{}, err } return toStorageDeviceToken(token), nil } func (cli *client) getDeviceToken(deviceCode string) (t DeviceToken, err error) { err = cli.get(resourceDeviceToken, deviceCode, &t) return } func (cli *client) UpdateDeviceToken(ctx context.Context, deviceCode string, updater func(old storage.DeviceToken) (storage.DeviceToken, error)) error { return retryOnConflict(ctx, func() error { r, err := cli.getDeviceToken(deviceCode) if err != nil { return err } updated, err := updater(toStorageDeviceToken(r)) if err != nil { return err } updated.DeviceCode = deviceCode newToken := cli.fromStorageDeviceToken(updated) newToken.ObjectMeta = r.ObjectMeta return cli.put(resourceDeviceToken, r.ObjectMeta.Name, newToken) }) } func (cli *client) CreateUserIdentity(ctx context.Context, u storage.UserIdentity) error { return cli.post(resourceUserIdentity, cli.fromStorageUserIdentity(u)) } func (cli *client) GetUserIdentity(ctx context.Context, userID, connectorID string) (storage.UserIdentity, error) { u, err := cli.getUserIdentity(userID, connectorID) if err != nil { return storage.UserIdentity{}, err } return toStorageUserIdentity(u), nil } func (cli *client) getUserIdentity(userID, connectorID string) (u UserIdentity, err error) { name := cli.offlineTokenName(userID, connectorID) if err = cli.get(resourceUserIdentity, name, &u); err != nil { return UserIdentity{}, err } if userID != u.UserID || connectorID != u.ConnectorID { return UserIdentity{}, fmt.Errorf("get user identity: wrong identity retrieved") } return u, nil } func (cli *client) UpdateUserIdentity(ctx context.Context, userID, connectorID string, updater func(old storage.UserIdentity) (storage.UserIdentity, error)) error { return retryOnConflict(ctx, func() error { u, err := cli.getUserIdentity(userID, connectorID) if err != nil { return err } updated, err := updater(toStorageUserIdentity(u)) if err != nil { return err } newUserIdentity := cli.fromStorageUserIdentity(updated) newUserIdentity.ObjectMeta = u.ObjectMeta return cli.put(resourceUserIdentity, u.ObjectMeta.Name, newUserIdentity) }) } func (cli *client) DeleteUserIdentity(ctx context.Context, userID, connectorID string) error { // Check for hash collision. u, err := cli.getUserIdentity(userID, connectorID) if err != nil { return err } return cli.delete(resourceUserIdentity, u.ObjectMeta.Name) } func (cli *client) ListUserIdentities(ctx context.Context) ([]storage.UserIdentity, error) { var userIdentityList UserIdentityList if err := cli.list(resourceUserIdentity, &userIdentityList); err != nil { return nil, fmt.Errorf("failed to list user identities: %v", err) } userIdentities := make([]storage.UserIdentity, len(userIdentityList.UserIdentities)) for i, u := range userIdentityList.UserIdentities { userIdentities[i] = toStorageUserIdentity(u) } return userIdentities, nil } func (cli *client) CreateAuthSession(ctx context.Context, s storage.AuthSession) error { return cli.post(resourceAuthSession, cli.fromStorageAuthSession(s)) } func (cli *client) getAuthSession(userID, connectorID string) (AuthSession, error) { var s AuthSession name := offlineTokenName(userID, connectorID, cli.hash) if err := cli.get(resourceAuthSession, name, &s); err != nil { return AuthSession{}, err } return s, nil } func (cli *client) GetAuthSession(ctx context.Context, userID, connectorID string) (storage.AuthSession, error) { s, err := cli.getAuthSession(userID, connectorID) if err != nil { return storage.AuthSession{}, err } return toStorageAuthSession(s), nil } func (cli *client) UpdateAuthSession(ctx context.Context, userID, connectorID string, updater func(old storage.AuthSession) (storage.AuthSession, error)) error { return retryOnConflict(ctx, func() error { s, err := cli.getAuthSession(userID, connectorID) if err != nil { return err } updated, err := updater(toStorageAuthSession(s)) if err != nil { return err } newSession := cli.fromStorageAuthSession(updated) newSession.ObjectMeta = s.ObjectMeta return cli.put(resourceAuthSession, s.ObjectMeta.Name, newSession) }) } func (cli *client) ListAuthSessions(ctx context.Context) ([]storage.AuthSession, error) { var authSessionList AuthSessionList if err := cli.list(resourceAuthSession, &authSessionList); err != nil { return nil, fmt.Errorf("failed to list auth sessions: %v", err) } sessions := make([]storage.AuthSession, len(authSessionList.AuthSessions)) for i, s := range authSessionList.AuthSessions { sessions[i] = toStorageAuthSession(s) } return sessions, nil } func (cli *client) DeleteAuthSession(ctx context.Context, userID, connectorID string) error { s, err := cli.getAuthSession(userID, connectorID) if err != nil { return err } return cli.delete(resourceAuthSession, s.ObjectMeta.Name) } func isKubernetesAPIConflictError(err error) bool { if httpErr, ok := err.(httpError); ok { if httpErr.StatusCode() == http.StatusConflict { return true } } return false } func retryOnConflict(ctx context.Context, action func() error) error { policy := []int{10, 20, 100, 300, 600} attempts := 0 getNextStep := func() time.Duration { step := policy[attempts] return time.Duration(step*5+rand.Intn(step)) * time.Microsecond } if err := action(); err == nil || !isKubernetesAPIConflictError(err) { return err } for { select { case <-time.After(getNextStep()): err := action() if err == nil || !isKubernetesAPIConflictError(err) { return err } attempts++ if attempts >= 4 { return fmt.Errorf("maximum timeout reached while retrying a conflicted request: %w", err) } case <-ctx.Done(): return errors.New("canceled") } } } ================================================ FILE: storage/kubernetes/storage_test.go ================================================ package kubernetes import ( "context" "crypto/tls" "errors" "fmt" "log/slog" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/conformance" ) const kubeconfigPathVariableName = "DEX_KUBERNETES_CONFIG_PATH" func TestStorage(t *testing.T) { if os.Getenv(kubeconfigPathVariableName) == "" { t.Skipf("variable %q not set, skipping kubernetes storage tests\n", kubeconfigPathVariableName) } suite.Run(t, new(StorageTestSuite)) } type StorageTestSuite struct { suite.Suite client *client } func expandDir(dir string) (string, error) { dir = strings.Trim(dir, `"`) if strings.HasPrefix(dir, "~/") { homedir, err := os.UserHomeDir() if err != nil { return "", err } dir = filepath.Join(homedir, strings.TrimPrefix(dir, "~/")) } return dir, nil } func (s *StorageTestSuite) SetupTest() { kubeconfigPath, err := expandDir(os.Getenv(kubeconfigPathVariableName)) s.Require().NoError(err) config := Config{ KubeConfigFile: kubeconfigPath, } logger := slog.New(slog.NewTextHandler(s.T().Output(), &slog.HandlerOptions{Level: slog.LevelDebug})) kubeClient, err := config.open(logger, true) s.Require().NoError(err) s.client = kubeClient } func (s *StorageTestSuite) TestStorage() { newStorage := func(t *testing.T) storage.Storage { for _, resource := range []string{ resourceAuthCode, resourceAuthRequest, resourceDeviceRequest, resourceDeviceToken, resourceClient, resourceRefreshToken, resourceKeys, resourcePassword, } { if err := s.client.deleteAll(resource); err != nil { s.T().Fatalf("delete all %q failed: %v", resource, err) } } return s.client } conformance.RunTests(s.T(), newStorage) conformance.RunConcurrencyTests(s.T(), newStorage) conformance.RunTransactionTests(s.T(), newStorage) } func TestURLFor(t *testing.T) { tests := []struct { apiVersion, namespace, resource, name string baseURL string want string }{ { "v1", "default", "pods", "a", "https://k8s.example.com", "https://k8s.example.com/api/v1/namespaces/default/pods/a", }, { "foo/v1", "default", "bar", "a", "https://k8s.example.com", "https://k8s.example.com/apis/foo/v1/namespaces/default/bar/a", }, { "foo/v1", "default", "bar", "a", "https://k8s.example.com/", "https://k8s.example.com/apis/foo/v1/namespaces/default/bar/a", }, { "foo/v1", "default", "bar", "a", "https://k8s.example.com/", "https://k8s.example.com/apis/foo/v1/namespaces/default/bar/a", }, { // no namespace "foo/v1", "", "bar", "a", "https://k8s.example.com", "https://k8s.example.com/apis/foo/v1/bar/a", }, } for _, test := range tests { c := &client{baseURL: test.baseURL} got, err := c.urlFor(test.apiVersion, test.namespace, test.resource, test.name) if err != nil { t.Errorf("got error: %v", err) } if got != test.want { t.Errorf("(&client{baseURL:%q}).urlFor(%q, %q, %q, %q): expected %q got %q", test.baseURL, test.apiVersion, test.namespace, test.resource, test.name, test.want, got, ) } } } func TestUpdateKeys(t *testing.T) { fakeUpdater := func(old storage.Keys) (storage.Keys, error) { return storage.Keys{}, nil } tests := []struct { name string updater func(old storage.Keys) (storage.Keys, error) getResponseCode int actionResponseCode int wantErr bool exactErr error }{ { "Create OK test", fakeUpdater, 404, 201, false, nil, }, { "Update should be OK", fakeUpdater, 200, 200, false, nil, }, { "Create conflict should be OK", fakeUpdater, 404, 409, true, errors.New("keys already created by another server instance"), }, { "Update conflict should be OK", fakeUpdater, 200, 409, true, errors.New("keys already rotated by another server instance"), }, { "Client error is error", fakeUpdater, 404, 500, true, nil, }, { "Client error during update is error", fakeUpdater, 200, 500, true, nil, }, { "Get error is error", fakeUpdater, 500, 200, true, nil, }, { "Updater error is error", func(old storage.Keys) (storage.Keys, error) { return storage.Keys{}, fmt.Errorf("test") }, 200, 201, true, nil, }, } for _, test := range tests { client := newStatusCodesResponseTestClient(test.getResponseCode, test.actionResponseCode) err := client.UpdateKeys(context.TODO(), test.updater) if err != nil { if !test.wantErr { t.Fatalf("Test %q: %v", test.name, err) } if test.exactErr != nil && test.exactErr.Error() != err.Error() { t.Fatalf("Test %q: %v, wanted: %v", test.name, err, test.exactErr) } } } } func newStatusCodesResponseTestClient(getResponseCode, actionResponseCode int) *client { s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { w.WriteHeader(getResponseCode) } else { w.WriteHeader(actionResponseCode) } w.Write([]byte(`{}`)) // Empty json is enough, we will test only response codes here })) tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } return &client{ client: &http.Client{Transport: tr}, baseURL: s.URL, logger: slog.New(slog.DiscardHandler), } } func TestRetryOnConflict(t *testing.T) { tests := []struct { name string action func() error exactErr string }{ { "Timeout reached", func() error { err := httpErr{status: 409}; return error(&err) }, "maximum timeout reached while retrying a conflicted request: Conflict: response from server \"\"", }, { "HTTP Error", func() error { err := httpErr{status: 500}; return error(&err) }, " Internal Server Error: response from server \"\"", }, { "Error", func() error { return errors.New("test") }, "test", }, { "OK", func() error { return nil }, "", }, } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { err := retryOnConflict(context.TODO(), testCase.action) if testCase.exactErr != "" { require.EqualError(t, err, testCase.exactErr) } else { require.NoError(t, err) } }) } } func TestRefreshTokenLock(t *testing.T) { ctx := context.Background() if os.Getenv(kubeconfigPathVariableName) == "" { t.Skipf("variable %q not set, skipping kubernetes storage tests\n", kubeconfigPathVariableName) } kubeconfigPath, err := expandDir(os.Getenv(kubeconfigPathVariableName)) require.NoError(t, err) config := Config{ KubeConfigFile: kubeconfigPath, } logger := slog.New(slog.DiscardHandler) kubeClient, err := config.open(logger, true) require.NoError(t, err) lockCheckPeriod = time.Nanosecond // Creating a storage with an existing refresh token and offline session for the user. id := storage.NewID() r := storage.RefreshToken{ ID: id, Token: "bar", Nonce: "foo", ClientID: "client_id", ConnectorID: "client_secret", Scopes: []string{"openid", "email", "profile"}, CreatedAt: time.Now().UTC().Round(time.Millisecond), LastUsed: time.Now().UTC().Round(time.Millisecond), Claims: storage.Claims{ UserID: "1", Username: "jane", Email: "jane.doe@example.com", EmailVerified: true, Groups: []string{"a", "b"}, }, ConnectorData: []byte(`{"some":"data"}`), } err = kubeClient.CreateRefresh(ctx, r) require.NoError(t, err) t.Run("Timeout lock error", func(t *testing.T) { err = kubeClient.UpdateRefreshToken(ctx, r.ID, func(r storage.RefreshToken) (storage.RefreshToken, error) { r.Token = "update-result-1" err := kubeClient.UpdateRefreshToken(ctx, r.ID, func(r storage.RefreshToken) (storage.RefreshToken, error) { r.Token = "timeout-err" return r, nil }) require.Equal(t, fmt.Errorf("timeout waiting for refresh token %s lock", r.ID), err) return r, nil }) require.NoError(t, err) token, err := kubeClient.GetRefresh(context.TODO(), r.ID) require.NoError(t, err) require.Equal(t, "update-result-1", token.Token) }) t.Run("Break the lock", func(t *testing.T) { var lockBroken bool lockTimeout = -time.Hour err = kubeClient.UpdateRefreshToken(ctx, r.ID, func(r storage.RefreshToken) (storage.RefreshToken, error) { r.Token = "update-result-2" if lockBroken { return r, nil } err := kubeClient.UpdateRefreshToken(ctx, r.ID, func(r storage.RefreshToken) (storage.RefreshToken, error) { r.Token = "should-break-the-lock-and-finish-updating" return r, nil }) require.NoError(t, err) lockBroken = true return r, nil }) require.NoError(t, err) token, err := kubeClient.GetRefresh(context.TODO(), r.ID) require.NoError(t, err) // Because concurrent update breaks the lock, the final result will be the value of the first update require.Equal(t, "update-result-2", token.Token) }) } ================================================ FILE: storage/kubernetes/transport.go ================================================ package kubernetes import ( "net/http" "os" "sync" "time" "github.com/dexidp/dex/storage/kubernetes/k8sapi" ) // transport is a simple http.Transport wrapper type transport struct { updateReq func(r *http.Request) base http.RoundTripper } func (t transport) RoundTrip(r *http.Request) (*http.Response, error) { // shallow copy of the struct r2 := new(http.Request) *r2 = *r // deep copy of the Header r2.Header = make(http.Header, len(r.Header)) for k, s := range r.Header { r2.Header[k] = append([]string(nil), s...) } t.updateReq(r2) return t.base.RoundTrip(r2) } func wrapRoundTripper(base http.RoundTripper, user k8sapi.AuthInfo, inCluster bool) http.RoundTripper { if inCluster { inClusterTransportHelper := newInClusterTransportHelper(user) return transport{ updateReq: func(r *http.Request) { inClusterTransportHelper.UpdateToken() r.Header.Set("Authorization", "Bearer "+inClusterTransportHelper.GetToken()) }, base: base, } } if user.Token != "" { return transport{ updateReq: func(r *http.Request) { r.Header.Set("Authorization", "Bearer "+user.Token) }, base: base, } } if user.Username != "" && user.Password != "" { return transport{ updateReq: func(r *http.Request) { r.SetBasicAuth(user.Username, user.Password) }, base: base, } } return base } // renewTokenPeriod is the interval after which dex will read the token from a well-known file. // // By Kubernetes documentation, this interval should be at least one minute long. // Kubernetes client-go v0.15+ uses 10 seconds long interval. // Dex uses the reasonable value between these two. const renewTokenPeriod = 30 * time.Second // inClusterTransportHelper is capable of safely updating the user token. // // BoundServiceAccountTokenVolume feature is enabled in Kubernetes >=1.21 by default. // With this feature, the service account token in the pod becomes periodically updated. // Therefore, Dex needs to re-read the token from the disk after some time to be sure that it uses the valid token. type inClusterTransportHelper struct { mu sync.RWMutex info k8sapi.AuthInfo expiry time.Time now func() time.Time tokenLocation string } func newInClusterTransportHelper(info k8sapi.AuthInfo) *inClusterTransportHelper { user := &inClusterTransportHelper{ info: info, now: time.Now, tokenLocation: "/var/run/secrets/kubernetes.io/serviceaccount/token", } user.UpdateToken() return user } func (c *inClusterTransportHelper) UpdateToken() { c.mu.RLock() exp := c.expiry c.mu.RUnlock() if !c.now().After(exp) { // Do not need to update token yet return } token, err := os.ReadFile(c.tokenLocation) if err != nil { return } c.mu.Lock() defer c.mu.Unlock() c.info.Token = string(token) c.expiry = c.now().Add(renewTokenPeriod) } func (c *inClusterTransportHelper) GetToken() string { c.mu.RLock() defer c.mu.RUnlock() return c.info.Token } ================================================ FILE: storage/kubernetes/types.go ================================================ package kubernetes import ( "strings" "time" "github.com/go-jose/go-jose/v4" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/kubernetes/k8sapi" ) const ( apiGroup = "dex.coreos.com" legacyCRDAPIVersion = "apiextensions.k8s.io/v1beta1" crdAPIVersion = "apiextensions.k8s.io/v1" ) // The set of custom resource definitions required by the storage. These are managed by // the storage so it can migrate itself by creating new resources. func customResourceDefinitions(apiVersion string) []k8sapi.CustomResourceDefinition { crdMeta := k8sapi.TypeMeta{ APIVersion: apiVersion, Kind: "CustomResourceDefinition", } var version string var scope k8sapi.ResourceScope var versions []k8sapi.CustomResourceDefinitionVersion switch apiVersion { case crdAPIVersion: preserveUnknownFields := true versions = []k8sapi.CustomResourceDefinitionVersion{ { Name: "v1", Served: true, Storage: true, Schema: &k8sapi.CustomResourceValidation{ OpenAPIV3Schema: &k8sapi.JSONSchemaProps{ Type: "object", XPreserveUnknownFields: &preserveUnknownFields, }, }, }, } scope = k8sapi.NamespaceScoped case legacyCRDAPIVersion: version = "v1" default: panic("unknown apiVersion " + apiVersion) } return []k8sapi.CustomResourceDefinition{ { ObjectMeta: k8sapi.ObjectMeta{ Name: "authcodes.dex.coreos.com", }, TypeMeta: crdMeta, Spec: k8sapi.CustomResourceDefinitionSpec{ Group: apiGroup, Version: version, Versions: versions, Scope: scope, Names: k8sapi.CustomResourceDefinitionNames{ Plural: "authcodes", Singular: "authcode", Kind: "AuthCode", }, }, }, { ObjectMeta: k8sapi.ObjectMeta{ Name: "authrequests.dex.coreos.com", }, TypeMeta: crdMeta, Spec: k8sapi.CustomResourceDefinitionSpec{ Group: apiGroup, Version: version, Versions: versions, Scope: scope, Names: k8sapi.CustomResourceDefinitionNames{ Plural: "authrequests", Singular: "authrequest", Kind: "AuthRequest", }, }, }, { ObjectMeta: k8sapi.ObjectMeta{ Name: "oauth2clients.dex.coreos.com", }, TypeMeta: crdMeta, Spec: k8sapi.CustomResourceDefinitionSpec{ Group: apiGroup, Version: version, Versions: versions, Scope: scope, Names: k8sapi.CustomResourceDefinitionNames{ Plural: "oauth2clients", Singular: "oauth2client", Kind: "OAuth2Client", }, }, }, { ObjectMeta: k8sapi.ObjectMeta{ Name: "signingkeies.dex.coreos.com", }, TypeMeta: crdMeta, Spec: k8sapi.CustomResourceDefinitionSpec{ Group: apiGroup, Version: version, Versions: versions, Scope: scope, Names: k8sapi.CustomResourceDefinitionNames{ // `signingkeies` is an artifact from the old TPR pluralization. // Users don't directly interact with this value, hence leaving it // as is. Plural: "signingkeies", Singular: "signingkey", Kind: "SigningKey", }, }, }, { ObjectMeta: k8sapi.ObjectMeta{ Name: "refreshtokens.dex.coreos.com", }, TypeMeta: crdMeta, Spec: k8sapi.CustomResourceDefinitionSpec{ Group: apiGroup, Version: version, Versions: versions, Scope: scope, Names: k8sapi.CustomResourceDefinitionNames{ Plural: "refreshtokens", Singular: "refreshtoken", Kind: "RefreshToken", }, }, }, { ObjectMeta: k8sapi.ObjectMeta{ Name: "passwords.dex.coreos.com", }, TypeMeta: crdMeta, Spec: k8sapi.CustomResourceDefinitionSpec{ Group: apiGroup, Version: version, Versions: versions, Scope: scope, Names: k8sapi.CustomResourceDefinitionNames{ Plural: "passwords", Singular: "password", Kind: "Password", }, }, }, { ObjectMeta: k8sapi.ObjectMeta{ Name: "offlinesessionses.dex.coreos.com", }, TypeMeta: crdMeta, Spec: k8sapi.CustomResourceDefinitionSpec{ Group: apiGroup, Version: version, Versions: versions, Scope: scope, Names: k8sapi.CustomResourceDefinitionNames{ Plural: "offlinesessionses", Singular: "offlinesessions", Kind: "OfflineSessions", }, }, }, { ObjectMeta: k8sapi.ObjectMeta{ Name: "connectors.dex.coreos.com", }, TypeMeta: crdMeta, Spec: k8sapi.CustomResourceDefinitionSpec{ Group: apiGroup, Version: version, Versions: versions, Scope: scope, Names: k8sapi.CustomResourceDefinitionNames{ Plural: "connectors", Singular: "connector", Kind: "Connector", }, }, }, { ObjectMeta: k8sapi.ObjectMeta{ Name: "devicerequests.dex.coreos.com", }, TypeMeta: crdMeta, Spec: k8sapi.CustomResourceDefinitionSpec{ Group: apiGroup, Version: version, Versions: versions, Scope: scope, Names: k8sapi.CustomResourceDefinitionNames{ Plural: "devicerequests", Singular: "devicerequest", Kind: "DeviceRequest", }, }, }, { ObjectMeta: k8sapi.ObjectMeta{ Name: "devicetokens.dex.coreos.com", }, TypeMeta: crdMeta, Spec: k8sapi.CustomResourceDefinitionSpec{ Group: apiGroup, Version: version, Versions: versions, Scope: scope, Names: k8sapi.CustomResourceDefinitionNames{ Plural: "devicetokens", Singular: "devicetoken", Kind: "DeviceToken", }, }, }, { ObjectMeta: k8sapi.ObjectMeta{ Name: "useridentities.dex.coreos.com", }, TypeMeta: crdMeta, Spec: k8sapi.CustomResourceDefinitionSpec{ Group: apiGroup, Version: version, Versions: versions, Scope: scope, Names: k8sapi.CustomResourceDefinitionNames{ Plural: "useridentities", Singular: "useridentity", Kind: "UserIdentity", }, }, }, { ObjectMeta: k8sapi.ObjectMeta{ Name: "authsessions.dex.coreos.com", }, TypeMeta: crdMeta, Spec: k8sapi.CustomResourceDefinitionSpec{ Group: apiGroup, Version: version, Versions: versions, Scope: scope, Names: k8sapi.CustomResourceDefinitionNames{ Plural: "authsessions", Singular: "authsession", Kind: "AuthSession", }, }, }, } } // There will only ever be a single keys resource. Maintain this by setting a // common name. const keysName = "openid-connect-keys" // Client is a mirrored struct from storage with JSON struct tags and // Kubernetes type metadata. type Client struct { // Name is a hash of the ID. k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` // ID is immutable, since it's a primary key and should not be changed. ID string `json:"id,omitempty"` Secret string `json:"secret,omitempty"` RedirectURIs []string `json:"redirectURIs,omitempty"` TrustedPeers []string `json:"trustedPeers,omitempty"` Public bool `json:"public"` Name string `json:"name,omitempty"` LogoURL string `json:"logoURL,omitempty"` AllowedConnectors []string `json:"allowedConnectors,omitempty"` MFAChain []string `json:"mfaChain,omitempty"` } // ClientList is a list of Clients. type ClientList struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ListMeta `json:"metadata,omitempty"` Clients []Client `json:"items"` } func (cli *client) fromStorageClient(c storage.Client) Client { return Client{ TypeMeta: k8sapi.TypeMeta{ Kind: kindClient, APIVersion: cli.apiVersion, }, ObjectMeta: k8sapi.ObjectMeta{ Name: cli.idToName(c.ID), Namespace: cli.namespace, }, ID: c.ID, Secret: c.Secret, RedirectURIs: c.RedirectURIs, TrustedPeers: c.TrustedPeers, Public: c.Public, Name: c.Name, LogoURL: c.LogoURL, AllowedConnectors: c.AllowedConnectors, MFAChain: c.MFAChain, } } func toStorageClient(c Client) storage.Client { return storage.Client{ ID: c.ID, Secret: c.Secret, RedirectURIs: c.RedirectURIs, TrustedPeers: c.TrustedPeers, Public: c.Public, Name: c.Name, LogoURL: c.LogoURL, AllowedConnectors: c.AllowedConnectors, MFAChain: c.MFAChain, } } // Claims is a mirrored struct from storage with JSON struct tags. type Claims struct { UserID string `json:"userID"` Username string `json:"username"` PreferredUsername string `json:"preferredUsername"` Email string `json:"email"` EmailVerified bool `json:"emailVerified"` Groups []string `json:"groups,omitempty"` } func fromStorageClaims(i storage.Claims) Claims { return Claims{ UserID: i.UserID, Username: i.Username, PreferredUsername: i.PreferredUsername, Email: i.Email, EmailVerified: i.EmailVerified, Groups: i.Groups, } } func toStorageClaims(i Claims) storage.Claims { return storage.Claims{ UserID: i.UserID, Username: i.Username, PreferredUsername: i.PreferredUsername, Email: i.Email, EmailVerified: i.EmailVerified, Groups: i.Groups, } } // AuthRequest is a mirrored struct from storage with JSON struct tags and // Kubernetes type metadata. type AuthRequest struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` ClientID string `json:"clientID"` ResponseTypes []string `json:"responseTypes,omitempty"` Scopes []string `json:"scopes,omitempty"` RedirectURI string `json:"redirectURI"` Nonce string `json:"nonce,omitempty"` State string `json:"state,omitempty"` // The client has indicated that the end user must be shown an approval prompt // on all requests. The server cannot cache their initial action for subsequent // attempts. ForceApprovalPrompt bool `json:"forceApprovalPrompt,omitempty"` LoggedIn bool `json:"loggedIn"` // The identity of the end user. Generally nil until the user authenticates // with a backend. Claims Claims `json:"claims,omitempty"` // The connector used to login the user. Set when the user authenticates. ConnectorID string `json:"connectorID,omitempty"` ConnectorData []byte `json:"connectorData,omitempty"` Expiry time.Time `json:"expiry"` CodeChallenge string `json:"code_challenge,omitempty"` CodeChallengeMethod string `json:"code_challenge_method,omitempty"` HMACKey []byte `json:"hmac_key"` MFAValidated bool `json:"mfa_validated"` Prompt string `json:"prompt,omitempty"` MaxAge int `json:"maxAge"` AuthTime time.Time `json:"authTime,omitempty"` } // AuthRequestList is a list of AuthRequests. type AuthRequestList struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ListMeta `json:"metadata,omitempty"` AuthRequests []AuthRequest `json:"items"` } func toStorageAuthRequest(req AuthRequest) storage.AuthRequest { a := storage.AuthRequest{ ID: req.ObjectMeta.Name, ClientID: req.ClientID, ResponseTypes: req.ResponseTypes, Scopes: req.Scopes, RedirectURI: req.RedirectURI, Nonce: req.Nonce, State: req.State, ForceApprovalPrompt: req.ForceApprovalPrompt, LoggedIn: req.LoggedIn, ConnectorID: req.ConnectorID, ConnectorData: req.ConnectorData, Expiry: req.Expiry, Claims: toStorageClaims(req.Claims), PKCE: storage.PKCE{ CodeChallenge: req.CodeChallenge, CodeChallengeMethod: req.CodeChallengeMethod, }, HMACKey: req.HMACKey, MFAValidated: req.MFAValidated, Prompt: req.Prompt, MaxAge: req.MaxAge, AuthTime: req.AuthTime, } return a } func (cli *client) fromStorageAuthRequest(a storage.AuthRequest) AuthRequest { req := AuthRequest{ TypeMeta: k8sapi.TypeMeta{ Kind: kindAuthRequest, APIVersion: cli.apiVersion, }, ObjectMeta: k8sapi.ObjectMeta{ Name: a.ID, Namespace: cli.namespace, }, ClientID: a.ClientID, ResponseTypes: a.ResponseTypes, Scopes: a.Scopes, RedirectURI: a.RedirectURI, Nonce: a.Nonce, State: a.State, LoggedIn: a.LoggedIn, ForceApprovalPrompt: a.ForceApprovalPrompt, ConnectorID: a.ConnectorID, ConnectorData: a.ConnectorData, Expiry: a.Expiry, Claims: fromStorageClaims(a.Claims), CodeChallenge: a.PKCE.CodeChallenge, CodeChallengeMethod: a.PKCE.CodeChallengeMethod, HMACKey: a.HMACKey, MFAValidated: a.MFAValidated, Prompt: a.Prompt, MaxAge: a.MaxAge, AuthTime: a.AuthTime, } return req } // Password is a mirrored struct from the storage with JSON struct tags and // Kubernetes type metadata. type Password struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` // The Kubernetes name is actually an encoded version of this value. // // This field is IMMUTABLE. Do not change. Email string `json:"email,omitempty"` Hash []byte `json:"hash,omitempty"` Username string `json:"username,omitempty"` Name string `json:"name,omitempty"` PreferredUsername string `json:"preferredUsername,omitempty"` EmailVerified *bool `json:"emailVerified,omitempty"` UserID string `json:"userID,omitempty"` Groups []string `json:"groups,omitempty"` } // PasswordList is a list of Passwords. type PasswordList struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ListMeta `json:"metadata,omitempty"` Passwords []Password `json:"items"` } func (cli *client) fromStoragePassword(p storage.Password) Password { email := strings.ToLower(p.Email) return Password{ TypeMeta: k8sapi.TypeMeta{ Kind: kindPassword, APIVersion: cli.apiVersion, }, ObjectMeta: k8sapi.ObjectMeta{ Name: cli.idToName(email), Namespace: cli.namespace, }, Email: email, Hash: p.Hash, Username: p.Username, Name: p.Name, PreferredUsername: p.PreferredUsername, EmailVerified: p.EmailVerified, UserID: p.UserID, Groups: p.Groups, } } func toStoragePassword(p Password) storage.Password { return storage.Password{ Email: p.Email, Hash: p.Hash, Username: p.Username, Name: p.Name, PreferredUsername: p.PreferredUsername, EmailVerified: p.EmailVerified, UserID: p.UserID, Groups: p.Groups, } } // AuthCode is a mirrored struct from storage with JSON struct tags and // Kubernetes type metadata. type AuthCode struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` ClientID string `json:"clientID"` Scopes []string `json:"scopes,omitempty"` RedirectURI string `json:"redirectURI"` Nonce string `json:"nonce,omitempty"` State string `json:"state,omitempty"` Claims Claims `json:"claims,omitempty"` ConnectorID string `json:"connectorID,omitempty"` ConnectorData []byte `json:"connectorData,omitempty"` Expiry time.Time `json:"expiry"` CodeChallenge string `json:"code_challenge,omitempty"` CodeChallengeMethod string `json:"code_challenge_method,omitempty"` AuthTime time.Time `json:"authTime,omitempty"` } // AuthCodeList is a list of AuthCodes. type AuthCodeList struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ListMeta `json:"metadata,omitempty"` AuthCodes []AuthCode `json:"items"` } func (cli *client) fromStorageAuthCode(a storage.AuthCode) AuthCode { return AuthCode{ TypeMeta: k8sapi.TypeMeta{ Kind: kindAuthCode, APIVersion: cli.apiVersion, }, ObjectMeta: k8sapi.ObjectMeta{ Name: a.ID, Namespace: cli.namespace, }, ClientID: a.ClientID, RedirectURI: a.RedirectURI, ConnectorID: a.ConnectorID, ConnectorData: a.ConnectorData, Nonce: a.Nonce, Scopes: a.Scopes, Claims: fromStorageClaims(a.Claims), Expiry: a.Expiry, CodeChallenge: a.PKCE.CodeChallenge, CodeChallengeMethod: a.PKCE.CodeChallengeMethod, AuthTime: a.AuthTime, } } func toStorageAuthCode(a AuthCode) storage.AuthCode { return storage.AuthCode{ ID: a.ObjectMeta.Name, ClientID: a.ClientID, RedirectURI: a.RedirectURI, ConnectorID: a.ConnectorID, ConnectorData: a.ConnectorData, Nonce: a.Nonce, Scopes: a.Scopes, Claims: toStorageClaims(a.Claims), Expiry: a.Expiry, PKCE: storage.PKCE{ CodeChallenge: a.CodeChallenge, CodeChallengeMethod: a.CodeChallengeMethod, }, AuthTime: a.AuthTime, } } // RefreshToken is a mirrored struct from storage with JSON struct tags and // Kubernetes type metadata. type RefreshToken struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` CreatedAt time.Time LastUsed time.Time ClientID string `json:"clientID"` Scopes []string `json:"scopes,omitempty"` Token string `json:"token,omitempty"` ObsoleteToken string `json:"obsoleteToken,omitempty"` Nonce string `json:"nonce,omitempty"` Claims Claims `json:"claims,omitempty"` ConnectorID string `json:"connectorID,omitempty"` ConnectorData []byte `json:"connectorData,omitempty"` } // RefreshList is a list of refresh tokens. type RefreshList struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ListMeta `json:"metadata,omitempty"` RefreshTokens []RefreshToken `json:"items"` } func toStorageRefreshToken(r RefreshToken) storage.RefreshToken { return storage.RefreshToken{ ID: r.ObjectMeta.Name, Token: r.Token, ObsoleteToken: r.ObsoleteToken, CreatedAt: r.CreatedAt, LastUsed: r.LastUsed, ClientID: r.ClientID, ConnectorID: r.ConnectorID, ConnectorData: r.ConnectorData, Scopes: r.Scopes, Nonce: r.Nonce, Claims: toStorageClaims(r.Claims), } } func (cli *client) fromStorageRefreshToken(r storage.RefreshToken) RefreshToken { return RefreshToken{ TypeMeta: k8sapi.TypeMeta{ Kind: kindRefreshToken, APIVersion: cli.apiVersion, }, ObjectMeta: k8sapi.ObjectMeta{ Name: r.ID, Namespace: cli.namespace, }, Token: r.Token, ObsoleteToken: r.ObsoleteToken, CreatedAt: r.CreatedAt, LastUsed: r.LastUsed, ClientID: r.ClientID, ConnectorID: r.ConnectorID, ConnectorData: r.ConnectorData, Scopes: r.Scopes, Nonce: r.Nonce, Claims: fromStorageClaims(r.Claims), } } // Keys is a mirrored struct from storage with JSON struct tags and Kubernetes // type metadata. type Keys struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` // Key for creating and verifying signatures. These may be nil. SigningKey *jose.JSONWebKey `json:"signingKey,omitempty"` SigningKeyPub *jose.JSONWebKey `json:"signingKeyPub,omitempty"` // Old signing keys which have been rotated but can still be used to validate // existing signatures. VerificationKeys []storage.VerificationKey `json:"verificationKeys,omitempty"` // The next time the signing key will rotate. // // For caching purposes, implementations MUST NOT update keys before this time. NextRotation time.Time `json:"nextRotation"` } func (cli *client) fromStorageKeys(keys storage.Keys) Keys { return Keys{ TypeMeta: k8sapi.TypeMeta{ Kind: kindKeys, APIVersion: cli.apiVersion, }, ObjectMeta: k8sapi.ObjectMeta{ Name: keysName, Namespace: cli.namespace, }, SigningKey: keys.SigningKey, SigningKeyPub: keys.SigningKeyPub, VerificationKeys: keys.VerificationKeys, NextRotation: keys.NextRotation, } } func toStorageKeys(keys Keys) storage.Keys { return storage.Keys{ SigningKey: keys.SigningKey, SigningKeyPub: keys.SigningKeyPub, VerificationKeys: keys.VerificationKeys, NextRotation: keys.NextRotation, } } // OfflineSessions is a mirrored struct from storage with JSON struct tags and Kubernetes // type metadata. type OfflineSessions struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` UserID string `json:"userID,omitempty"` ConnID string `json:"connID,omitempty"` Refresh map[string]*storage.RefreshTokenRef `json:"refresh,omitempty"` ConnectorData []byte `json:"connectorData,omitempty"` } func (cli *client) fromStorageOfflineSessions(o storage.OfflineSessions) OfflineSessions { return OfflineSessions{ TypeMeta: k8sapi.TypeMeta{ Kind: kindOfflineSessions, APIVersion: cli.apiVersion, }, ObjectMeta: k8sapi.ObjectMeta{ Name: cli.offlineTokenName(o.UserID, o.ConnID), Namespace: cli.namespace, }, UserID: o.UserID, ConnID: o.ConnID, Refresh: o.Refresh, ConnectorData: o.ConnectorData, } } func toStorageOfflineSessions(o OfflineSessions) storage.OfflineSessions { s := storage.OfflineSessions{ UserID: o.UserID, ConnID: o.ConnID, Refresh: o.Refresh, ConnectorData: o.ConnectorData, } if s.Refresh == nil { // Server code assumes this will be non-nil. s.Refresh = make(map[string]*storage.RefreshTokenRef) } return s } // Connector is a mirrored struct from storage with JSON struct tags and Kubernetes // type metadata. type Connector struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` ID string `json:"id,omitempty"` Type string `json:"type,omitempty"` Name string `json:"name,omitempty"` // Config holds connector specific configuration information Config []byte `json:"config,omitempty"` // GrantTypes is a list of grant types that this connector is allowed to be used with. GrantTypes []string `json:"grantTypes,omitempty"` } func (cli *client) fromStorageConnector(c storage.Connector) Connector { return Connector{ TypeMeta: k8sapi.TypeMeta{ Kind: kindConnector, APIVersion: cli.apiVersion, }, ObjectMeta: k8sapi.ObjectMeta{ Name: c.ID, Namespace: cli.namespace, }, ID: c.ID, Type: c.Type, Name: c.Name, Config: c.Config, GrantTypes: c.GrantTypes, } } func toStorageConnector(c Connector) storage.Connector { return storage.Connector{ ID: c.ID, Type: c.Type, Name: c.Name, ResourceVersion: c.ObjectMeta.ResourceVersion, Config: c.Config, GrantTypes: c.GrantTypes, } } // ConnectorList is a list of Connectors. type ConnectorList struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ListMeta `json:"metadata,omitempty"` Connectors []Connector `json:"items"` } // DeviceRequest is a mirrored struct from storage with JSON struct tags and // Kubernetes type metadata. type DeviceRequest struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` DeviceCode string `json:"device_code,omitempty"` ClientID string `json:"client_id,omitempty"` ClientSecret string `json:"client_secret,omitempty"` Scopes []string `json:"scopes,omitempty"` Expiry time.Time `json:"expiry"` } // DeviceRequestList is a list of DeviceRequests. type DeviceRequestList struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ListMeta `json:"metadata,omitempty"` DeviceRequests []DeviceRequest `json:"items"` } func (cli *client) fromStorageDeviceRequest(a storage.DeviceRequest) DeviceRequest { req := DeviceRequest{ TypeMeta: k8sapi.TypeMeta{ Kind: kindDeviceRequest, APIVersion: cli.apiVersion, }, ObjectMeta: k8sapi.ObjectMeta{ Name: strings.ToLower(a.UserCode), Namespace: cli.namespace, }, DeviceCode: a.DeviceCode, ClientID: a.ClientID, ClientSecret: a.ClientSecret, Scopes: a.Scopes, Expiry: a.Expiry, } return req } func toStorageDeviceRequest(req DeviceRequest) storage.DeviceRequest { return storage.DeviceRequest{ UserCode: strings.ToUpper(req.ObjectMeta.Name), DeviceCode: req.DeviceCode, ClientID: req.ClientID, ClientSecret: req.ClientSecret, Scopes: req.Scopes, Expiry: req.Expiry, } } // DeviceToken is a mirrored struct from storage with JSON struct tags and // Kubernetes type metadata. type DeviceToken struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` Status string `json:"status,omitempty"` Token string `json:"token,omitempty"` Expiry time.Time `json:"expiry"` LastRequestTime time.Time `json:"last_request"` PollIntervalSeconds int `json:"poll_interval"` CodeChallenge string `json:"code_challenge,omitempty"` CodeChallengeMethod string `json:"code_challenge_method,omitempty"` } // DeviceTokenList is a list of DeviceTokens. type DeviceTokenList struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ListMeta `json:"metadata,omitempty"` DeviceTokens []DeviceToken `json:"items"` } func (cli *client) fromStorageDeviceToken(t storage.DeviceToken) DeviceToken { req := DeviceToken{ TypeMeta: k8sapi.TypeMeta{ Kind: kindDeviceToken, APIVersion: cli.apiVersion, }, ObjectMeta: k8sapi.ObjectMeta{ Name: t.DeviceCode, Namespace: cli.namespace, }, Status: t.Status, Token: t.Token, Expiry: t.Expiry, LastRequestTime: t.LastRequestTime, PollIntervalSeconds: t.PollIntervalSeconds, CodeChallenge: t.PKCE.CodeChallenge, CodeChallengeMethod: t.PKCE.CodeChallengeMethod, } return req } func toStorageDeviceToken(t DeviceToken) storage.DeviceToken { return storage.DeviceToken{ DeviceCode: t.ObjectMeta.Name, Status: t.Status, Token: t.Token, Expiry: t.Expiry, LastRequestTime: t.LastRequestTime, PollIntervalSeconds: t.PollIntervalSeconds, PKCE: storage.PKCE{ CodeChallenge: t.CodeChallenge, CodeChallengeMethod: t.CodeChallengeMethod, }, } } // UserIdentity is a mirrored struct from storage with JSON struct tags and Kubernetes // type metadata. type UserIdentity struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` UserID string `json:"userID,omitempty"` ConnectorID string `json:"connectorID,omitempty"` Claims Claims `json:"claims,omitempty"` Consents map[string][]string `json:"consents,omitempty"` MFASecrets map[string]*storage.MFASecret `json:"mfaSecrets,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"` LastLogin time.Time `json:"lastLogin,omitempty"` BlockedUntil time.Time `json:"blockedUntil,omitempty"` } // UserIdentityList is a list of UserIdentities. type UserIdentityList struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ListMeta `json:"metadata,omitempty"` UserIdentities []UserIdentity `json:"items"` } func (cli *client) fromStorageUserIdentity(u storage.UserIdentity) UserIdentity { return UserIdentity{ TypeMeta: k8sapi.TypeMeta{ Kind: kindUserIdentity, APIVersion: cli.apiVersion, }, ObjectMeta: k8sapi.ObjectMeta{ Name: cli.offlineTokenName(u.UserID, u.ConnectorID), Namespace: cli.namespace, }, UserID: u.UserID, ConnectorID: u.ConnectorID, Claims: fromStorageClaims(u.Claims), Consents: u.Consents, MFASecrets: u.MFASecrets, CreatedAt: u.CreatedAt, LastLogin: u.LastLogin, BlockedUntil: u.BlockedUntil, } } func toStorageUserIdentity(u UserIdentity) storage.UserIdentity { s := storage.UserIdentity{ UserID: u.UserID, ConnectorID: u.ConnectorID, Claims: toStorageClaims(u.Claims), Consents: u.Consents, MFASecrets: u.MFASecrets, CreatedAt: u.CreatedAt, LastLogin: u.LastLogin, BlockedUntil: u.BlockedUntil, } if s.Consents == nil { // Server code assumes this will be non-nil. s.Consents = make(map[string][]string) } return s } // AuthSession is a Kubernetes representation of a storage AuthSession. type AuthSession struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ObjectMeta `json:"metadata,omitempty"` UserID string `json:"userID,omitempty"` ConnectorID string `json:"connectorID,omitempty"` Nonce string `json:"nonce,omitempty"` ClientStates map[string]*storage.ClientAuthState `json:"clientStates,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"` LastActivity time.Time `json:"lastActivity,omitempty"` IPAddress string `json:"ipAddress,omitempty"` UserAgent string `json:"userAgent,omitempty"` AbsoluteExpiry time.Time `json:"absoluteExpiry,omitempty"` IdleExpiry time.Time `json:"idleExpiry,omitempty"` } // AuthSessionList is a list of AuthSessions. type AuthSessionList struct { k8sapi.TypeMeta `json:",inline"` k8sapi.ListMeta `json:"metadata,omitempty"` AuthSessions []AuthSession `json:"items"` } func (cli *client) fromStorageAuthSession(s storage.AuthSession) AuthSession { return AuthSession{ TypeMeta: k8sapi.TypeMeta{ Kind: kindAuthSession, APIVersion: cli.apiVersion, }, ObjectMeta: k8sapi.ObjectMeta{ Name: offlineTokenName(s.UserID, s.ConnectorID, cli.hash), Namespace: cli.namespace, }, UserID: s.UserID, ConnectorID: s.ConnectorID, Nonce: s.Nonce, ClientStates: s.ClientStates, CreatedAt: s.CreatedAt, LastActivity: s.LastActivity, IPAddress: s.IPAddress, UserAgent: s.UserAgent, AbsoluteExpiry: s.AbsoluteExpiry, IdleExpiry: s.IdleExpiry, } } func toStorageAuthSession(s AuthSession) storage.AuthSession { result := storage.AuthSession{ UserID: s.UserID, ConnectorID: s.ConnectorID, Nonce: s.Nonce, ClientStates: s.ClientStates, CreatedAt: s.CreatedAt, LastActivity: s.LastActivity, IPAddress: s.IPAddress, UserAgent: s.UserAgent, AbsoluteExpiry: s.AbsoluteExpiry, IdleExpiry: s.IdleExpiry, } if result.ClientStates == nil { result.ClientStates = make(map[string]*storage.ClientAuthState) } return result } ================================================ FILE: storage/memory/memory.go ================================================ // Package memory provides an in memory implementation of the storage interface. package memory import ( "context" "log/slog" "strings" "sync" "time" "github.com/dexidp/dex/storage" ) var _ storage.Storage = (*memStorage)(nil) // New returns an in memory storage. func New(logger *slog.Logger) storage.Storage { return &memStorage{ clients: make(map[string]storage.Client), authCodes: make(map[string]storage.AuthCode), refreshTokens: make(map[string]storage.RefreshToken), authReqs: make(map[string]storage.AuthRequest), passwords: make(map[string]storage.Password), offlineSessions: make(map[compositeKeyID]storage.OfflineSessions), userIdentities: make(map[compositeKeyID]storage.UserIdentity), authSessions: make(map[compositeKeyID]storage.AuthSession), connectors: make(map[string]storage.Connector), deviceRequests: make(map[string]storage.DeviceRequest), deviceTokens: make(map[string]storage.DeviceToken), logger: logger, } } // Config is an implementation of a storage configuration. // // TODO(ericchiang): Actually define a storage config interface and have registration. type Config struct { // The in memory implementation has no config. } // Open always returns a new in memory storage. func (c *Config) Open(logger *slog.Logger) (storage.Storage, error) { return New(logger), nil } type memStorage struct { mu sync.Mutex clients map[string]storage.Client authCodes map[string]storage.AuthCode refreshTokens map[string]storage.RefreshToken authReqs map[string]storage.AuthRequest passwords map[string]storage.Password offlineSessions map[compositeKeyID]storage.OfflineSessions userIdentities map[compositeKeyID]storage.UserIdentity authSessions map[compositeKeyID]storage.AuthSession connectors map[string]storage.Connector deviceRequests map[string]storage.DeviceRequest deviceTokens map[string]storage.DeviceToken keys storage.Keys logger *slog.Logger } type compositeKeyID struct { userID string connID string } func (s *memStorage) tx(f func()) { s.mu.Lock() defer s.mu.Unlock() f() } func (s *memStorage) Close() error { return nil } func (s *memStorage) GarbageCollect(ctx context.Context, now time.Time) (result storage.GCResult, err error) { s.tx(func() { for id, a := range s.authCodes { if now.After(a.Expiry) { delete(s.authCodes, id) result.AuthCodes++ } } for id, a := range s.authReqs { if now.After(a.Expiry) { delete(s.authReqs, id) result.AuthRequests++ } } for id, a := range s.deviceRequests { if now.After(a.Expiry) { delete(s.deviceRequests, id) result.DeviceRequests++ } } for id, a := range s.deviceTokens { if now.After(a.Expiry) { delete(s.deviceTokens, id) result.DeviceTokens++ } } for id, a := range s.authSessions { if now.After(a.AbsoluteExpiry) || now.After(a.IdleExpiry) { delete(s.authSessions, id) result.AuthSessions++ } } }) return result, nil } func (s *memStorage) CreateClient(ctx context.Context, c storage.Client) (err error) { s.tx(func() { if _, ok := s.clients[c.ID]; ok { err = storage.ErrAlreadyExists } else { s.clients[c.ID] = c } }) return } func (s *memStorage) CreateAuthCode(ctx context.Context, c storage.AuthCode) (err error) { s.tx(func() { if _, ok := s.authCodes[c.ID]; ok { err = storage.ErrAlreadyExists } else { s.authCodes[c.ID] = c } }) return } func (s *memStorage) CreateRefresh(ctx context.Context, r storage.RefreshToken) (err error) { s.tx(func() { if _, ok := s.refreshTokens[r.ID]; ok { err = storage.ErrAlreadyExists } else { s.refreshTokens[r.ID] = r } }) return } func (s *memStorage) CreateAuthRequest(ctx context.Context, a storage.AuthRequest) (err error) { s.tx(func() { if _, ok := s.authReqs[a.ID]; ok { err = storage.ErrAlreadyExists } else { s.authReqs[a.ID] = a } }) return } func (s *memStorage) CreatePassword(ctx context.Context, p storage.Password) (err error) { lowerEmail := strings.ToLower(p.Email) s.tx(func() { if _, ok := s.passwords[lowerEmail]; ok { err = storage.ErrAlreadyExists } else { s.passwords[lowerEmail] = p } }) return } func (s *memStorage) CreateOfflineSessions(ctx context.Context, o storage.OfflineSessions) (err error) { id := compositeKeyID{ userID: o.UserID, connID: o.ConnID, } s.tx(func() { if _, ok := s.offlineSessions[id]; ok { err = storage.ErrAlreadyExists } else { s.offlineSessions[id] = o } }) return } func (s *memStorage) CreateUserIdentity(ctx context.Context, u storage.UserIdentity) (err error) { id := compositeKeyID{ userID: u.UserID, connID: u.ConnectorID, } s.tx(func() { if _, ok := s.userIdentities[id]; ok { err = storage.ErrAlreadyExists } else { s.userIdentities[id] = u } }) return } func (s *memStorage) GetUserIdentity(ctx context.Context, userID, connectorID string) (u storage.UserIdentity, err error) { id := compositeKeyID{ userID: userID, connID: connectorID, } s.tx(func() { var ok bool if u, ok = s.userIdentities[id]; !ok { err = storage.ErrNotFound return } }) return } func (s *memStorage) UpdateUserIdentity(ctx context.Context, userID, connectorID string, updater func(u storage.UserIdentity) (storage.UserIdentity, error)) (err error) { id := compositeKeyID{ userID: userID, connID: connectorID, } s.tx(func() { r, ok := s.userIdentities[id] if !ok { err = storage.ErrNotFound return } if r, err = updater(r); err == nil { s.userIdentities[id] = r } }) return } func (s *memStorage) DeleteUserIdentity(ctx context.Context, userID, connectorID string) (err error) { id := compositeKeyID{ userID: userID, connID: connectorID, } s.tx(func() { if _, ok := s.userIdentities[id]; !ok { err = storage.ErrNotFound return } delete(s.userIdentities, id) }) return } func (s *memStorage) ListUserIdentities(ctx context.Context) (identities []storage.UserIdentity, err error) { s.tx(func() { for _, u := range s.userIdentities { identities = append(identities, u) } }) return } func (s *memStorage) ListAuthSessions(ctx context.Context) (sessions []storage.AuthSession, err error) { s.tx(func() { for _, session := range s.authSessions { sessions = append(sessions, session) } }) return } func (s *memStorage) CreateAuthSession(ctx context.Context, session storage.AuthSession) (err error) { id := compositeKeyID{userID: session.UserID, connID: session.ConnectorID} s.tx(func() { if _, ok := s.authSessions[id]; ok { err = storage.ErrAlreadyExists } else { s.authSessions[id] = session } }) return } func (s *memStorage) GetAuthSession(ctx context.Context, userID, connectorID string) (session storage.AuthSession, err error) { id := compositeKeyID{userID: userID, connID: connectorID} s.tx(func() { var ok bool if session, ok = s.authSessions[id]; !ok { err = storage.ErrNotFound } }) return } func (s *memStorage) UpdateAuthSession(ctx context.Context, userID, connectorID string, updater func(s storage.AuthSession) (storage.AuthSession, error)) (err error) { id := compositeKeyID{userID: userID, connID: connectorID} s.tx(func() { r, ok := s.authSessions[id] if !ok { err = storage.ErrNotFound return } if r, err = updater(r); err == nil { s.authSessions[id] = r } }) return } func (s *memStorage) DeleteAuthSession(ctx context.Context, userID, connectorID string) (err error) { id := compositeKeyID{userID: userID, connID: connectorID} s.tx(func() { if _, ok := s.authSessions[id]; !ok { err = storage.ErrNotFound return } delete(s.authSessions, id) }) return } func (s *memStorage) CreateConnector(ctx context.Context, connector storage.Connector) (err error) { s.tx(func() { if _, ok := s.connectors[connector.ID]; ok { err = storage.ErrAlreadyExists } else { s.connectors[connector.ID] = connector } }) return } func (s *memStorage) GetAuthCode(ctx context.Context, id string) (c storage.AuthCode, err error) { s.tx(func() { var ok bool if c, ok = s.authCodes[id]; !ok { err = storage.ErrNotFound return } }) return } func (s *memStorage) GetPassword(ctx context.Context, email string) (p storage.Password, err error) { email = strings.ToLower(email) s.tx(func() { var ok bool if p, ok = s.passwords[email]; !ok { err = storage.ErrNotFound } }) return } func (s *memStorage) GetClient(ctx context.Context, id string) (client storage.Client, err error) { s.tx(func() { var ok bool if client, ok = s.clients[id]; !ok { err = storage.ErrNotFound } }) return } func (s *memStorage) GetKeys(ctx context.Context) (keys storage.Keys, err error) { s.tx(func() { keys = s.keys }) return } func (s *memStorage) GetRefresh(ctx context.Context, id string) (tok storage.RefreshToken, err error) { s.tx(func() { var ok bool if tok, ok = s.refreshTokens[id]; !ok { err = storage.ErrNotFound return } }) return } func (s *memStorage) GetAuthRequest(ctx context.Context, id string) (req storage.AuthRequest, err error) { s.tx(func() { var ok bool if req, ok = s.authReqs[id]; !ok { err = storage.ErrNotFound return } }) return } func (s *memStorage) GetOfflineSessions(ctx context.Context, userID string, connID string) (o storage.OfflineSessions, err error) { id := compositeKeyID{ userID: userID, connID: connID, } s.tx(func() { var ok bool if o, ok = s.offlineSessions[id]; !ok { err = storage.ErrNotFound return } }) return } func (s *memStorage) GetConnector(ctx context.Context, id string) (connector storage.Connector, err error) { s.tx(func() { var ok bool if connector, ok = s.connectors[id]; !ok { err = storage.ErrNotFound } }) return } func (s *memStorage) ListClients(ctx context.Context) (clients []storage.Client, err error) { s.tx(func() { for _, client := range s.clients { clients = append(clients, client) } }) return } func (s *memStorage) ListRefreshTokens(ctx context.Context) (tokens []storage.RefreshToken, err error) { s.tx(func() { for _, refresh := range s.refreshTokens { tokens = append(tokens, refresh) } }) return } func (s *memStorage) ListPasswords(ctx context.Context) (passwords []storage.Password, err error) { s.tx(func() { for _, password := range s.passwords { passwords = append(passwords, password) } }) return } func (s *memStorage) ListConnectors(ctx context.Context) (conns []storage.Connector, err error) { s.tx(func() { for _, c := range s.connectors { conns = append(conns, c) } }) return } func (s *memStorage) DeletePassword(ctx context.Context, email string) (err error) { email = strings.ToLower(email) s.tx(func() { if _, ok := s.passwords[email]; !ok { err = storage.ErrNotFound return } delete(s.passwords, email) }) return } func (s *memStorage) DeleteClient(ctx context.Context, id string) (err error) { s.tx(func() { if _, ok := s.clients[id]; !ok { err = storage.ErrNotFound return } delete(s.clients, id) }) return } func (s *memStorage) DeleteRefresh(ctx context.Context, id string) (err error) { s.tx(func() { if _, ok := s.refreshTokens[id]; !ok { err = storage.ErrNotFound return } delete(s.refreshTokens, id) }) return } func (s *memStorage) DeleteAuthCode(ctx context.Context, id string) (err error) { s.tx(func() { if _, ok := s.authCodes[id]; !ok { err = storage.ErrNotFound return } delete(s.authCodes, id) }) return } func (s *memStorage) DeleteAuthRequest(ctx context.Context, id string) (err error) { s.tx(func() { if _, ok := s.authReqs[id]; !ok { err = storage.ErrNotFound return } delete(s.authReqs, id) }) return } func (s *memStorage) DeleteOfflineSessions(ctx context.Context, userID string, connID string) (err error) { id := compositeKeyID{ userID: userID, connID: connID, } s.tx(func() { if _, ok := s.offlineSessions[id]; !ok { err = storage.ErrNotFound return } delete(s.offlineSessions, id) }) return } func (s *memStorage) DeleteConnector(ctx context.Context, id string) (err error) { s.tx(func() { if _, ok := s.connectors[id]; !ok { err = storage.ErrNotFound return } delete(s.connectors, id) }) return } func (s *memStorage) UpdateClient(ctx context.Context, id string, updater func(old storage.Client) (storage.Client, error)) (err error) { s.tx(func() { client, ok := s.clients[id] if !ok { err = storage.ErrNotFound return } if client, err = updater(client); err == nil { s.clients[id] = client } }) return } func (s *memStorage) UpdateKeys(ctx context.Context, updater func(old storage.Keys) (storage.Keys, error)) (err error) { s.tx(func() { var keys storage.Keys if keys, err = updater(s.keys); err == nil { s.keys = keys } }) return } func (s *memStorage) UpdateAuthRequest(ctx context.Context, id string, updater func(old storage.AuthRequest) (storage.AuthRequest, error)) (err error) { s.tx(func() { req, ok := s.authReqs[id] if !ok { err = storage.ErrNotFound return } if req, err = updater(req); err == nil { s.authReqs[id] = req } }) return } func (s *memStorage) UpdatePassword(ctx context.Context, email string, updater func(p storage.Password) (storage.Password, error)) (err error) { email = strings.ToLower(email) s.tx(func() { req, ok := s.passwords[email] if !ok { err = storage.ErrNotFound return } if req, err = updater(req); err == nil { s.passwords[email] = req } }) return } func (s *memStorage) UpdateRefreshToken(ctx context.Context, id string, updater func(p storage.RefreshToken) (storage.RefreshToken, error)) (err error) { s.tx(func() { r, ok := s.refreshTokens[id] if !ok { err = storage.ErrNotFound return } if r, err = updater(r); err == nil { s.refreshTokens[id] = r } }) return } func (s *memStorage) UpdateOfflineSessions(ctx context.Context, userID string, connID string, updater func(o storage.OfflineSessions) (storage.OfflineSessions, error)) (err error) { id := compositeKeyID{ userID: userID, connID: connID, } s.tx(func() { r, ok := s.offlineSessions[id] if !ok { err = storage.ErrNotFound return } if r, err = updater(r); err == nil { s.offlineSessions[id] = r } }) return } func (s *memStorage) UpdateConnector(ctx context.Context, id string, updater func(c storage.Connector) (storage.Connector, error)) (err error) { s.tx(func() { r, ok := s.connectors[id] if !ok { err = storage.ErrNotFound return } if r, err = updater(r); err == nil { s.connectors[id] = r } }) return } func (s *memStorage) CreateDeviceRequest(ctx context.Context, d storage.DeviceRequest) (err error) { s.tx(func() { if _, ok := s.deviceRequests[d.UserCode]; ok { err = storage.ErrAlreadyExists } else { s.deviceRequests[d.UserCode] = d } }) return } func (s *memStorage) GetDeviceRequest(ctx context.Context, userCode string) (req storage.DeviceRequest, err error) { s.tx(func() { var ok bool if req, ok = s.deviceRequests[userCode]; !ok { err = storage.ErrNotFound return } }) return } func (s *memStorage) CreateDeviceToken(ctx context.Context, t storage.DeviceToken) (err error) { s.tx(func() { if _, ok := s.deviceTokens[t.DeviceCode]; ok { err = storage.ErrAlreadyExists } else { s.deviceTokens[t.DeviceCode] = t } }) return } func (s *memStorage) GetDeviceToken(ctx context.Context, deviceCode string) (t storage.DeviceToken, err error) { s.tx(func() { var ok bool if t, ok = s.deviceTokens[deviceCode]; !ok { err = storage.ErrNotFound return } }) return } func (s *memStorage) UpdateDeviceToken(ctx context.Context, deviceCode string, updater func(p storage.DeviceToken) (storage.DeviceToken, error)) (err error) { s.tx(func() { r, ok := s.deviceTokens[deviceCode] if !ok { err = storage.ErrNotFound return } if r, err = updater(r); err == nil { s.deviceTokens[deviceCode] = r } }) return } ================================================ FILE: storage/memory/memory_test.go ================================================ package memory import ( "log/slog" "testing" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/conformance" ) func TestStorage(t *testing.T) { newStorage := func(t *testing.T) storage.Storage { logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug})) return New(logger) } conformance.RunTests(t, newStorage) conformance.RunConcurrencyTests(t, newStorage) } ================================================ FILE: storage/memory/static_test.go ================================================ package memory import ( "context" "fmt" "log/slog" "strings" "testing" "github.com/dexidp/dex/storage" ) func TestStaticClients(t *testing.T) { ctx := context.Background() logger := slog.New(slog.DiscardHandler) backing := New(logger) c1 := storage.Client{ID: "foo", Secret: "foo_secret"} c2 := storage.Client{ID: "bar", Secret: "bar_secret"} c3 := storage.Client{ID: "spam", Secret: "spam_secret"} backing.CreateClient(ctx, c1) s := storage.WithStaticClients(backing, []storage.Client{c2}) tests := []struct { name string action func() error wantErr bool }{ { name: "get client from static storage", action: func() error { _, err := s.GetClient(ctx, c2.ID) return err }, }, { name: "get client from backing storage", action: func() error { _, err := s.GetClient(ctx, c1.ID) return err }, }, { name: "update static client", action: func() error { updater := func(c storage.Client) (storage.Client, error) { c.Secret = "new_" + c.Secret return c, nil } return s.UpdateClient(ctx, c2.ID, updater) }, wantErr: true, }, { name: "update non-static client", action: func() error { updater := func(c storage.Client) (storage.Client, error) { c.Secret = "new_" + c.Secret return c, nil } return s.UpdateClient(ctx, c1.ID, updater) }, }, { name: "list clients", action: func() error { clients, err := s.ListClients(ctx) if err != nil { return err } if n := len(clients); n != 2 { return fmt.Errorf("expected 2 clients got %d", n) } return nil }, }, { name: "create client", action: func() error { return s.CreateClient(ctx, c3) }, }, } for _, tc := range tests { err := tc.action() if err != nil && !tc.wantErr { t.Errorf("%s: %v", tc.name, err) } if err == nil && tc.wantErr { t.Errorf("%s: expected error, didn't get one", tc.name) } } } func TestStaticPasswords(t *testing.T) { ctx := context.Background() logger := slog.New(slog.DiscardHandler) backing := New(logger) p1 := storage.Password{Email: "foo@example.com", Username: "foo_secret"} p2 := storage.Password{Email: "bar@example.com", Username: "bar_secret"} p3 := storage.Password{Email: "spam@example.com", Username: "spam_secret"} p4 := storage.Password{Email: "Spam@example.com", Username: "Spam_secret"} backing.CreatePassword(ctx, p1) s := storage.WithStaticPasswords(backing, []storage.Password{p2}, logger) tests := []struct { name string action func() error wantErr bool }{ { name: "get password from static storage", action: func() error { _, err := s.GetPassword(ctx, p2.Email) return err }, }, { name: "get password from backing storage", action: func() error { _, err := s.GetPassword(ctx, p1.Email) return err }, }, { name: "get password from static storage with casing", action: func() error { _, err := s.GetPassword(ctx, strings.ToUpper(p2.Email)) return err }, }, { name: "update static password", action: func() error { updater := func(p storage.Password) (storage.Password, error) { p.Username = "new_" + p.Username return p, nil } return s.UpdatePassword(ctx, p2.Email, updater) }, wantErr: true, }, { name: "update non-static password", action: func() error { updater := func(p storage.Password) (storage.Password, error) { p.Username = "new_" + p.Username return p, nil } return s.UpdatePassword(ctx, p1.Email, updater) }, }, { name: "create passwords", action: func() error { if err := s.CreatePassword(ctx, p4); err != nil { return err } return s.CreatePassword(ctx, p3) }, wantErr: true, }, { name: "get password", action: func() error { p, err := s.GetPassword(ctx, p4.Email) if err != nil { return err } if strings.Compare(p.Email, p4.Email) != 0 { return fmt.Errorf("expected %s passwords got %s", p4.Email, p.Email) } return nil }, }, { name: "list passwords", action: func() error { passwords, err := s.ListPasswords(ctx) if err != nil { return err } if n := len(passwords); n != 3 { return fmt.Errorf("expected 3 passwords got %d", n) } return nil }, }, } for _, tc := range tests { err := tc.action() if err != nil && !tc.wantErr { t.Errorf("%s: %v", tc.name, err) } if err == nil && tc.wantErr { t.Errorf("%s: expected error, didn't get one", tc.name) } } } func TestStaticConnectors(t *testing.T) { ctx := context.Background() logger := slog.New(slog.DiscardHandler) backing := New(logger) config1 := []byte(`{"issuer": "https://accounts.google.com"}`) config2 := []byte(`{"host": "ldap.example.com:636"}`) config3 := []byte(`{"issuer": "https://example.com"}`) c1 := storage.Connector{ID: storage.NewID(), Type: "oidc", Name: "oidc", ResourceVersion: "1", Config: config1} c2 := storage.Connector{ID: storage.NewID(), Type: "ldap", Name: "ldap", ResourceVersion: "1", Config: config2} c3 := storage.Connector{ID: storage.NewID(), Type: "saml", Name: "saml", ResourceVersion: "1", Config: config3} backing.CreateConnector(ctx, c1) s := storage.WithStaticConnectors(backing, []storage.Connector{c2}) tests := []struct { name string action func() error wantErr bool }{ { name: "get connector from static storage", action: func() error { _, err := s.GetConnector(ctx, c2.ID) return err }, }, { name: "get connector from backing storage", action: func() error { _, err := s.GetConnector(ctx, c1.ID) return err }, }, { name: "update static connector", action: func() error { updater := func(c storage.Connector) (storage.Connector, error) { c.Name = "New" return c, nil } return s.UpdateConnector(ctx, c2.ID, updater) }, wantErr: true, }, { name: "update non-static connector", action: func() error { updater := func(c storage.Connector) (storage.Connector, error) { c.Name = "New" return c, nil } return s.UpdateConnector(ctx, c1.ID, updater) }, }, { name: "list connectors", action: func() error { connectors, err := s.ListConnectors(ctx) if err != nil { return err } if n := len(connectors); n != 2 { return fmt.Errorf("expected 2 connectors got %d", n) } return nil }, }, { name: "create connector", action: func() error { return s.CreateConnector(ctx, c3) }, }, } for _, tc := range tests { err := tc.action() if err != nil && !tc.wantErr { t.Errorf("%s: %v", tc.name, err) } if err == nil && tc.wantErr { t.Errorf("%s: expected error, didn't get one", tc.name) } } } ================================================ FILE: storage/sql/config.go ================================================ package sql import ( "crypto/tls" "crypto/x509" "database/sql" "fmt" "log/slog" "net" "os" "regexp" "strconv" "strings" "time" "github.com/go-sql-driver/mysql" "github.com/lib/pq" "github.com/dexidp/dex/storage" ) const ( // postgres error codes pgErrUniqueViolation = "23505" // unique_violation ) const ( // MySQL error codes mysqlErrDupEntry = 1062 mysqlErrDupEntryWithKeyName = 1586 mysqlErrUnknownSysVar = 1193 ) const ( // postgres SSL modes pgSSLDisable = "disable" pgSSLRequire = "require" pgSSLVerifyCA = "verify-ca" pgSSLVerifyFull = "verify-full" ) const ( // MySQL SSL modes mysqlSSLTrue = "true" mysqlSSLFalse = "false" mysqlSSLSkipVerify = "skip-verify" mysqlSSLCustom = "custom" ) // NetworkDB contains options common to SQL databases accessed over network. type NetworkDB struct { Database string User string Password string Host string Port uint16 ConnectionTimeout int // Seconds // database/sql tunables, see // https://golang.org/pkg/database/sql/#DB.SetConnMaxLifetime and below // Note: defaults will be set if these are 0 MaxOpenConns int // default: 5 MaxIdleConns int // default: 5 ConnMaxLifetime int // Seconds, default: not set } // SSL represents SSL options for network databases. type SSL struct { Mode string CAFile string // Files for client auth. KeyFile string CertFile string } // Postgres options for creating an SQL db. type Postgres struct { NetworkDB SSL SSL `json:"ssl"` } // Open creates a new storage implementation backed by Postgres. func (p *Postgres) Open(logger *slog.Logger) (storage.Storage, error) { conn, err := p.open(logger) if err != nil { return nil, err } return conn, nil } var strEsc = regexp.MustCompile(`([\\'])`) func dataSourceStr(str string) string { return "'" + strEsc.ReplaceAllString(str, `\$1`) + "'" } // createDataSourceName takes the configuration provided via the Postgres // struct to create a data-source name that Go's database/sql package can // make use of. func (p *Postgres) createDataSourceName() string { parameters := []string{} addParam := func(key, val string) { parameters = append(parameters, fmt.Sprintf("%s=%s", key, val)) } addParam("connect_timeout", strconv.Itoa(p.ConnectionTimeout)) // detect host:port for backwards-compatibility host, port, err := net.SplitHostPort(p.Host) if err != nil { // not host:port, probably unix socket or bare address host = p.Host if p.Port != 0 { port = strconv.Itoa(int(p.Port)) } } if host != "" { addParam("host", dataSourceStr(host)) } if port != "" { addParam("port", port) } if p.User != "" { addParam("user", dataSourceStr(p.User)) } if p.Password != "" { addParam("password", dataSourceStr(p.Password)) } if p.Database != "" { addParam("dbname", dataSourceStr(p.Database)) } if p.SSL.Mode == "" { // Assume the strictest mode if unspecified. addParam("sslmode", dataSourceStr(pgSSLVerifyFull)) } else { addParam("sslmode", dataSourceStr(p.SSL.Mode)) } if p.SSL.CAFile != "" { addParam("sslrootcert", dataSourceStr(p.SSL.CAFile)) } if p.SSL.CertFile != "" { addParam("sslcert", dataSourceStr(p.SSL.CertFile)) } if p.SSL.KeyFile != "" { addParam("sslkey", dataSourceStr(p.SSL.KeyFile)) } return strings.Join(parameters, " ") } func (p *Postgres) open(logger *slog.Logger) (*conn, error) { dataSourceName := p.createDataSourceName() db, err := sql.Open("postgres", dataSourceName) if err != nil { return nil, err } // set database/sql tunables if configured if p.ConnMaxLifetime != 0 { db.SetConnMaxLifetime(time.Duration(p.ConnMaxLifetime) * time.Second) } if p.MaxIdleConns == 0 { db.SetMaxIdleConns(5) } else { db.SetMaxIdleConns(p.MaxIdleConns) } if p.MaxOpenConns == 0 { db.SetMaxOpenConns(5) } else { db.SetMaxOpenConns(p.MaxOpenConns) } errCheck := func(err error) bool { sqlErr, ok := err.(*pq.Error) if !ok { return false } return sqlErr.Code == pgErrUniqueViolation } c := &conn{db, &flavorPostgres, logger, errCheck} if _, err := c.migrate(); err != nil { return nil, fmt.Errorf("failed to perform migrations: %v", err) } return c, nil } // MySQL options for creating a MySQL db. type MySQL struct { NetworkDB SSL SSL `json:"ssl"` // TODO(pborzenkov): used by tests to reduce lock wait timeout. Should // we make it exported and allow users to provide arbitrary params? params map[string]string } // Open creates a new storage implementation backed by MySQL. func (s *MySQL) Open(logger *slog.Logger) (storage.Storage, error) { conn, err := s.open(logger) if err != nil { return nil, err } return conn, nil } func (s *MySQL) open(logger *slog.Logger) (*conn, error) { cfg := mysql.Config{ User: s.User, Passwd: s.Password, DBName: s.Database, AllowNativePasswords: true, Timeout: time.Second * time.Duration(s.ConnectionTimeout), ParseTime: true, Params: map[string]string{ "transaction_isolation": "'SERIALIZABLE'", }, } if s.Host != "" { if s.Host[0] != '/' { cfg.Net = "tcp" cfg.Addr = s.Host if s.Port != 0 { cfg.Addr = net.JoinHostPort(s.Host, strconv.Itoa(int(s.Port))) } } else { cfg.Net = "unix" cfg.Addr = s.Host } } switch { case s.SSL.CAFile != "" || s.SSL.CertFile != "" || s.SSL.KeyFile != "": if err := s.makeTLSConfig(); err != nil { return nil, fmt.Errorf("failed to make TLS config: %v", err) } cfg.TLSConfig = mysqlSSLCustom case s.SSL.Mode == "": cfg.TLSConfig = mysqlSSLTrue default: cfg.TLSConfig = s.SSL.Mode } for k, v := range s.params { cfg.Params[k] = v } db, err := sql.Open("mysql", cfg.FormatDSN()) if err != nil { return nil, err } if s.MaxIdleConns == 0 { /*Override default behavior to fix https://github.com/dexidp/dex/issues/1608*/ db.SetMaxIdleConns(0) } else { db.SetMaxIdleConns(s.MaxIdleConns) } err = db.Ping() if err != nil { if mysqlErr, ok := err.(*mysql.MySQLError); ok && mysqlErr.Number == mysqlErrUnknownSysVar { logger.Info("reconnecting with MySQL pre-5.7.20 compatibility mode") // MySQL 5.7.20 introduced transaction_isolation and deprecated tx_isolation. // MySQL 8.0 doesn't have tx_isolation at all. // https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_transaction_isolation delete(cfg.Params, "transaction_isolation") cfg.Params["tx_isolation"] = "'SERIALIZABLE'" db, err = sql.Open("mysql", cfg.FormatDSN()) if err != nil { return nil, err } } else { return nil, err } } errCheck := func(err error) bool { sqlErr, ok := err.(*mysql.MySQLError) if !ok { return false } return sqlErr.Number == mysqlErrDupEntry || sqlErr.Number == mysqlErrDupEntryWithKeyName } c := &conn{db, &flavorMySQL, logger, errCheck} if _, err := c.migrate(); err != nil { return nil, fmt.Errorf("failed to perform migrations: %v", err) } return c, nil } func (s *MySQL) makeTLSConfig() error { cfg := &tls.Config{} if s.SSL.CAFile != "" { rootCertPool := x509.NewCertPool() pem, err := os.ReadFile(s.SSL.CAFile) if err != nil { return err } if ok := rootCertPool.AppendCertsFromPEM(pem); !ok { return fmt.Errorf("failed to append PEM") } cfg.RootCAs = rootCertPool } if s.SSL.CertFile != "" && s.SSL.KeyFile != "" { clientCert := make([]tls.Certificate, 0, 1) certs, err := tls.LoadX509KeyPair(s.SSL.CertFile, s.SSL.KeyFile) if err != nil { return err } clientCert = append(clientCert, certs) cfg.Certificates = clientCert } mysql.RegisterTLSConfig(mysqlSSLCustom, cfg) return nil } ================================================ FILE: storage/sql/config_test.go ================================================ package sql import ( "fmt" "log/slog" "os" "runtime" "strconv" "testing" "time" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/conformance" ) func withTimeout(t time.Duration, f func()) { c := make(chan struct{}) defer close(c) go func() { select { case <-c: case <-time.After(t): // Dump a stack trace of the program. Useful for debugging deadlocks. buf := make([]byte, 2<<20) fmt.Fprintf(os.Stderr, "%s\n", buf[:runtime.Stack(buf, true)]) panic("test took too long") } }() f() } func cleanDB(c *conn) error { tables := []string{ "client", "auth_request", "auth_code", "refresh_token", "keys", "password", } for _, tbl := range tables { _, err := c.Exec("delete from " + tbl) if err != nil { return err } } return nil } type opener interface { open(logger *slog.Logger) (*conn, error) } func testDB(t *testing.T, o opener, withTransactions, withConcurrentTests bool) { // t.Fatal has a bad habit of not actually printing the error fatal := func(i any) { fmt.Fprintln(os.Stdout, i) t.Fatal(i) } newStorage := func(t *testing.T) storage.Storage { logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug})) conn, err := o.open(logger) if err != nil { fatal(err) } if err := cleanDB(conn); err != nil { fatal(err) } return conn } withTimeout(time.Minute*1, func() { conformance.RunTests(t, newStorage) }) if withTransactions { withTimeout(time.Minute*1, func() { conformance.RunTransactionTests(t, newStorage) }) } if withConcurrentTests { withTimeout(time.Minute*1, func() { conformance.RunConcurrencyTests(t, newStorage) }) } } func getenv(key, defaultVal string) string { if val := os.Getenv(key); val != "" { return val } return defaultVal } const testPostgresEnv = "DEX_POSTGRES_HOST" func TestCreateDataSourceName(t *testing.T) { testCases := []struct { description string input *Postgres expected string }{ { description: "with no configuration", input: &Postgres{}, expected: "connect_timeout=0 sslmode='verify-full'", }, { description: "with typical configuration", input: &Postgres{ NetworkDB: NetworkDB{ Host: "1.2.3.4", Port: 6543, User: "some-user", Password: "some-password", Database: "some-db", }, }, expected: "connect_timeout=0 host='1.2.3.4' port=6543 user='some-user' password='some-password' dbname='some-db' sslmode='verify-full'", }, { description: "with unix socket host", input: &Postgres{ NetworkDB: NetworkDB{ Host: "/var/run/postgres", }, SSL: SSL{ Mode: "disable", }, }, expected: "connect_timeout=0 host='/var/run/postgres' sslmode='disable'", }, { description: "with tcp host", input: &Postgres{ NetworkDB: NetworkDB{ Host: "coreos.com", }, SSL: SSL{ Mode: "disable", }, }, expected: "connect_timeout=0 host='coreos.com' sslmode='disable'", }, { description: "with tcp host:port", input: &Postgres{ NetworkDB: NetworkDB{ Host: "coreos.com:6543", }, }, expected: "connect_timeout=0 host='coreos.com' port=6543 sslmode='verify-full'", }, { description: "with tcp host and port", input: &Postgres{ NetworkDB: NetworkDB{ Host: "coreos.com", Port: 6543, }, }, expected: "connect_timeout=0 host='coreos.com' port=6543 sslmode='verify-full'", }, { description: "with ssl ca cert", input: &Postgres{ NetworkDB: NetworkDB{ Host: "coreos.com", }, SSL: SSL{ Mode: "verify-ca", CAFile: "/some/file/path", }, }, expected: "connect_timeout=0 host='coreos.com' sslmode='verify-ca' sslrootcert='/some/file/path'", }, { description: "with ssl client cert", input: &Postgres{ NetworkDB: NetworkDB{ Host: "coreos.com", }, SSL: SSL{ Mode: "verify-ca", CAFile: "/some/ca/path", CertFile: "/some/cert/path", KeyFile: "/some/key/path", }, }, expected: "connect_timeout=0 host='coreos.com' sslmode='verify-ca' sslrootcert='/some/ca/path' sslcert='/some/cert/path' sslkey='/some/key/path'", }, { description: "with funny characters in credentials", input: &Postgres{ NetworkDB: NetworkDB{ Host: "coreos.com", User: `some'user\slashed`, Password: "some'password!", }, }, expected: `connect_timeout=0 host='coreos.com' user='some\'user\\slashed' password='some\'password!' sslmode='verify-full'`, }, } var actual string for _, testCase := range testCases { t.Run(testCase.description, func(t *testing.T) { actual = testCase.input.createDataSourceName() if actual != testCase.expected { t.Fatalf("%s != %s", actual, testCase.expected) } }) } } func TestPostgres(t *testing.T) { host := os.Getenv(testPostgresEnv) if host == "" { t.Skipf("test environment variable %q not set, skipping", testPostgresEnv) } port := uint64(5432) if rawPort := os.Getenv("DEX_POSTGRES_PORT"); rawPort != "" { var err error port, err = strconv.ParseUint(rawPort, 10, 32) if err != nil { t.Fatalf("invalid postgres port %q: %s", rawPort, err) } } p := &Postgres{ NetworkDB: NetworkDB{ Database: getenv("DEX_POSTGRES_DATABASE", "postgres"), User: getenv("DEX_POSTGRES_USER", "postgres"), Password: getenv("DEX_POSTGRES_PASSWORD", "postgres"), Host: host, Port: uint16(port), ConnectionTimeout: 5, }, SSL: SSL{ Mode: pgSSLDisable, // Postgres container doesn't support SSL. }, } testDB(t, p, true, false) } const testMySQLEnv = "DEX_MYSQL_HOST" func TestMySQL(t *testing.T) { host := os.Getenv(testMySQLEnv) if host == "" { t.Skipf("test environment variable %q not set, skipping", testMySQLEnv) } port := uint64(3306) if rawPort := os.Getenv("DEX_MYSQL_PORT"); rawPort != "" { var err error port, err = strconv.ParseUint(rawPort, 10, 32) if err != nil { t.Fatalf("invalid mysql port %q: %s", rawPort, err) } } s := &MySQL{ NetworkDB: NetworkDB{ Database: getenv("DEX_MYSQL_DATABASE", "mysql"), User: getenv("DEX_MYSQL_USER", "mysql"), Password: getenv("DEX_MYSQL_PASSWORD", "mysql"), Host: host, Port: uint16(port), ConnectionTimeout: 5, }, SSL: SSL{ Mode: mysqlSSLFalse, }, params: map[string]string{ "innodb_lock_wait_timeout": "3", }, } testDB(t, s, true, false) } const testMySQL8Env = "DEX_MYSQL8_HOST" func TestMySQL8(t *testing.T) { host := os.Getenv(testMySQL8Env) if host == "" { t.Skipf("test environment variable %q not set, skipping", testMySQL8Env) } port := uint64(3306) if rawPort := os.Getenv("DEX_MYSQL8_PORT"); rawPort != "" { var err error port, err = strconv.ParseUint(rawPort, 10, 32) if err != nil { t.Fatalf("invalid mysql port %q: %s", rawPort, err) } } s := &MySQL{ NetworkDB: NetworkDB{ Database: getenv("DEX_MYSQL8_DATABASE", "mysql"), User: getenv("DEX_MYSQL8_USER", "mysql"), Password: getenv("DEX_MYSQL8_PASSWORD", "mysql"), Host: host, Port: uint16(port), ConnectionTimeout: 5, }, SSL: SSL{ Mode: mysqlSSLFalse, }, params: map[string]string{ "innodb_lock_wait_timeout": "3", }, } testDB(t, s, true, false) } ================================================ FILE: storage/sql/crud.go ================================================ package sql import ( "context" "database/sql" "database/sql/driver" "encoding/json" "errors" "fmt" "strings" "time" "github.com/dexidp/dex/storage" ) // TODO(ericchiang): The update, insert, and select methods queries are all // very repetitive. Consider creating them programmatically. // keysRowID is the ID of the only row we expect to populate the "keys" table. const keysRowID = "keys" // encoder wraps the underlying value in a JSON marshaler which is automatically // called by the database/sql package. // // s := []string{"planes", "bears"} // err := db.Exec(`insert into t1 (id, things) values (1, $1)`, encoder(s)) // if err != nil { // // handle error // } // // var r []byte // err = db.QueryRow(`select things from t1 where id = 1;`).Scan(&r) // if err != nil { // // handle error // } // fmt.Printf("%s\n", r) // ["planes","bears"] func encoder(i interface{}) driver.Valuer { return jsonEncoder{i} } // decoder wraps the underlying value in a JSON unmarshaler which can then be passed // to a database Scan() method. func decoder(i interface{}) sql.Scanner { return jsonDecoder{i} } type jsonEncoder struct { i interface{} } func (j jsonEncoder) Value() (driver.Value, error) { b, err := json.Marshal(j.i) if err != nil { return nil, fmt.Errorf("marshal: %v", err) } return b, nil } type jsonDecoder struct { i interface{} } func (j jsonDecoder) Scan(dest interface{}) error { if dest == nil { return errors.New("nil value") } b, ok := dest.([]byte) if !ok { return fmt.Errorf("expected []byte got %T", dest) } if err := json.Unmarshal(b, &j.i); err != nil { return fmt.Errorf("unmarshal: %v", err) } return nil } // Abstract conn vs trans. type querier interface { QueryRow(query string, args ...interface{}) *sql.Row } // Abstract row vs rows. type scanner interface { Scan(dest ...interface{}) error } var _ storage.Storage = (*conn)(nil) func (c *conn) GarbageCollect(ctc context.Context, now time.Time) (storage.GCResult, error) { result := storage.GCResult{} r, err := c.Exec(`delete from auth_request where expiry < $1`, now) if err != nil { return result, fmt.Errorf("gc auth_request: %v", err) } if n, err := r.RowsAffected(); err == nil { result.AuthRequests = n } r, err = c.Exec(`delete from auth_code where expiry < $1`, now) if err != nil { return result, fmt.Errorf("gc auth_code: %v", err) } if n, err := r.RowsAffected(); err == nil { result.AuthCodes = n } r, err = c.Exec(`delete from device_request where expiry < $1`, now) if err != nil { return result, fmt.Errorf("gc device_request: %v", err) } if n, err := r.RowsAffected(); err == nil { result.DeviceRequests = n } r, err = c.Exec(`delete from device_token where expiry < $1`, now) if err != nil { return result, fmt.Errorf("gc device_token: %v", err) } if n, err := r.RowsAffected(); err == nil { result.DeviceTokens = n } r, err = c.Exec(`delete from auth_session where absolute_expiry < $1 OR idle_expiry < $2`, now, now) if err != nil { return result, fmt.Errorf("gc auth_session: %v", err) } if n, err := r.RowsAffected(); err == nil { result.AuthSessions = n } return result, nil } func (c *conn) CreateAuthRequest(ctx context.Context, a storage.AuthRequest) error { _, err := c.Exec(` insert into auth_request ( id, client_id, response_types, scopes, redirect_uri, nonce, state, force_approval_prompt, logged_in, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, connector_id, connector_data, expiry, code_challenge, code_challenge_method, hmac_key, mfa_validated, prompt, max_age, auth_time ) values ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25 ); `, a.ID, a.ClientID, encoder(a.ResponseTypes), encoder(a.Scopes), a.RedirectURI, a.Nonce, a.State, a.ForceApprovalPrompt, a.LoggedIn, a.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername, a.Claims.Email, a.Claims.EmailVerified, encoder(a.Claims.Groups), a.ConnectorID, a.ConnectorData, a.Expiry, a.PKCE.CodeChallenge, a.PKCE.CodeChallengeMethod, a.HMACKey, a.MFAValidated, a.Prompt, a.MaxAge, a.AuthTime, ) if err != nil { if c.alreadyExistsCheck(err) { return storage.ErrAlreadyExists } return fmt.Errorf("insert auth request: %v", err) } return nil } func (c *conn) UpdateAuthRequest(ctx context.Context, id string, updater func(a storage.AuthRequest) (storage.AuthRequest, error)) error { return c.ExecTx(func(tx *trans) error { r, err := getAuthRequest(ctx, tx, id) if err != nil { return err } a, err := updater(r) if err != nil { return err } _, err = tx.Exec(` update auth_request set client_id = $1, response_types = $2, scopes = $3, redirect_uri = $4, nonce = $5, state = $6, force_approval_prompt = $7, logged_in = $8, claims_user_id = $9, claims_username = $10, claims_preferred_username = $11, claims_email = $12, claims_email_verified = $13, claims_groups = $14, connector_id = $15, connector_data = $16, expiry = $17, code_challenge = $18, code_challenge_method = $19, hmac_key = $20, mfa_validated = $21, prompt = $22, max_age = $23, auth_time = $24 where id = $25; `, a.ClientID, encoder(a.ResponseTypes), encoder(a.Scopes), a.RedirectURI, a.Nonce, a.State, a.ForceApprovalPrompt, a.LoggedIn, a.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername, a.Claims.Email, a.Claims.EmailVerified, encoder(a.Claims.Groups), a.ConnectorID, a.ConnectorData, a.Expiry, a.PKCE.CodeChallenge, a.PKCE.CodeChallengeMethod, a.HMACKey, a.MFAValidated, a.Prompt, a.MaxAge, a.AuthTime, r.ID, ) if err != nil { return fmt.Errorf("update auth request: %v", err) } return nil }) } func (c *conn) GetAuthRequest(ctx context.Context, id string) (storage.AuthRequest, error) { return getAuthRequest(ctx, c, id) } func getAuthRequest(ctx context.Context, q querier, id string) (a storage.AuthRequest, err error) { err = q.QueryRow(` select id, client_id, response_types, scopes, redirect_uri, nonce, state, force_approval_prompt, logged_in, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, connector_id, connector_data, expiry, code_challenge, code_challenge_method, hmac_key, mfa_validated, prompt, max_age, auth_time from auth_request where id = $1; `, id).Scan( &a.ID, &a.ClientID, decoder(&a.ResponseTypes), decoder(&a.Scopes), &a.RedirectURI, &a.Nonce, &a.State, &a.ForceApprovalPrompt, &a.LoggedIn, &a.Claims.UserID, &a.Claims.Username, &a.Claims.PreferredUsername, &a.Claims.Email, &a.Claims.EmailVerified, decoder(&a.Claims.Groups), &a.ConnectorID, &a.ConnectorData, &a.Expiry, &a.PKCE.CodeChallenge, &a.PKCE.CodeChallengeMethod, &a.HMACKey, &a.MFAValidated, &a.Prompt, &a.MaxAge, &a.AuthTime, ) if err != nil { if err == sql.ErrNoRows { return a, storage.ErrNotFound } return a, fmt.Errorf("select auth request: %v", err) } return a, nil } func (c *conn) CreateAuthCode(ctx context.Context, a storage.AuthCode) error { _, err := c.Exec(` insert into auth_code ( id, client_id, scopes, nonce, redirect_uri, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, connector_id, connector_data, expiry, code_challenge, code_challenge_method, auth_time ) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17); `, a.ID, a.ClientID, encoder(a.Scopes), a.Nonce, a.RedirectURI, a.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername, a.Claims.Email, a.Claims.EmailVerified, encoder(a.Claims.Groups), a.ConnectorID, a.ConnectorData, a.Expiry, a.PKCE.CodeChallenge, a.PKCE.CodeChallengeMethod, a.AuthTime, ) if err != nil { if c.alreadyExistsCheck(err) { return storage.ErrAlreadyExists } return fmt.Errorf("insert auth code: %v", err) } return nil } func (c *conn) GetAuthCode(ctx context.Context, id string) (a storage.AuthCode, err error) { err = c.QueryRow(` select id, client_id, scopes, nonce, redirect_uri, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, connector_id, connector_data, expiry, code_challenge, code_challenge_method, auth_time from auth_code where id = $1; `, id).Scan( &a.ID, &a.ClientID, decoder(&a.Scopes), &a.Nonce, &a.RedirectURI, &a.Claims.UserID, &a.Claims.Username, &a.Claims.PreferredUsername, &a.Claims.Email, &a.Claims.EmailVerified, decoder(&a.Claims.Groups), &a.ConnectorID, &a.ConnectorData, &a.Expiry, &a.PKCE.CodeChallenge, &a.PKCE.CodeChallengeMethod, &a.AuthTime, ) if err != nil { if err == sql.ErrNoRows { return a, storage.ErrNotFound } return a, fmt.Errorf("select auth code: %v", err) } return a, nil } func (c *conn) CreateRefresh(ctx context.Context, r storage.RefreshToken) error { _, err := c.Exec(` insert into refresh_token ( id, client_id, scopes, nonce, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, connector_id, connector_data, token, obsolete_token, created_at, last_used ) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16); `, r.ID, r.ClientID, encoder(r.Scopes), r.Nonce, r.Claims.UserID, r.Claims.Username, r.Claims.PreferredUsername, r.Claims.Email, r.Claims.EmailVerified, encoder(r.Claims.Groups), r.ConnectorID, r.ConnectorData, r.Token, r.ObsoleteToken, r.CreatedAt, r.LastUsed, ) if err != nil { if c.alreadyExistsCheck(err) { return storage.ErrAlreadyExists } return fmt.Errorf("insert refresh_token: %v", err) } return nil } func (c *conn) UpdateRefreshToken(ctx context.Context, id string, updater func(old storage.RefreshToken) (storage.RefreshToken, error)) error { return c.ExecTx(func(tx *trans) error { r, err := getRefresh(ctx, tx, id) if err != nil { return err } if r, err = updater(r); err != nil { return err } _, err = tx.Exec(` update refresh_token set client_id = $1, scopes = $2, nonce = $3, claims_user_id = $4, claims_username = $5, claims_preferred_username = $6, claims_email = $7, claims_email_verified = $8, claims_groups = $9, connector_id = $10, connector_data = $11, token = $12, obsolete_token = $13, created_at = $14, last_used = $15 where id = $16 `, r.ClientID, encoder(r.Scopes), r.Nonce, r.Claims.UserID, r.Claims.Username, r.Claims.PreferredUsername, r.Claims.Email, r.Claims.EmailVerified, encoder(r.Claims.Groups), r.ConnectorID, r.ConnectorData, r.Token, r.ObsoleteToken, r.CreatedAt, r.LastUsed, id, ) if err != nil { return fmt.Errorf("update refresh token: %v", err) } return nil }) } func (c *conn) GetRefresh(ctx context.Context, id string) (storage.RefreshToken, error) { return getRefresh(ctx, c, id) } func getRefresh(ctx context.Context, q querier, id string) (storage.RefreshToken, error) { return scanRefresh(q.QueryRow(` select id, client_id, scopes, nonce, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, connector_id, connector_data, token, obsolete_token, created_at, last_used from refresh_token where id = $1; `, id)) } func (c *conn) ListRefreshTokens(ctx context.Context) ([]storage.RefreshToken, error) { rows, err := c.Query(` select id, client_id, scopes, nonce, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, connector_id, connector_data, token, obsolete_token, created_at, last_used from refresh_token; `) if err != nil { return nil, fmt.Errorf("query: %v", err) } defer rows.Close() var tokens []storage.RefreshToken for rows.Next() { r, err := scanRefresh(rows) if err != nil { return nil, err } tokens = append(tokens, r) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("scan: %v", err) } return tokens, nil } func scanRefresh(s scanner) (r storage.RefreshToken, err error) { err = s.Scan( &r.ID, &r.ClientID, decoder(&r.Scopes), &r.Nonce, &r.Claims.UserID, &r.Claims.Username, &r.Claims.PreferredUsername, &r.Claims.Email, &r.Claims.EmailVerified, decoder(&r.Claims.Groups), &r.ConnectorID, &r.ConnectorData, &r.Token, &r.ObsoleteToken, &r.CreatedAt, &r.LastUsed, ) if err != nil { if err == sql.ErrNoRows { return r, storage.ErrNotFound } return r, fmt.Errorf("scan refresh_token: %v", err) } return r, nil } func (c *conn) UpdateKeys(ctx context.Context, updater func(old storage.Keys) (storage.Keys, error)) error { return c.ExecTx(func(tx *trans) error { firstUpdate := false // TODO(ericchiang): errors may cause a transaction be rolled back by the SQL // server. Test this, and consider adding a COUNT() command beforehand. old, err := getKeys(ctx, tx) if err != nil { if err != storage.ErrNotFound { return fmt.Errorf("get keys: %v", err) } firstUpdate = true old = storage.Keys{} } nk, err := updater(old) if err != nil { return err } if firstUpdate { _, err = tx.Exec(` insert into keys ( id, verification_keys, signing_key, signing_key_pub, next_rotation ) values ($1, $2, $3, $4, $5); `, keysRowID, encoder(nk.VerificationKeys), encoder(nk.SigningKey), encoder(nk.SigningKeyPub), nk.NextRotation, ) if err != nil { return fmt.Errorf("insert: %v", err) } } else { _, err = tx.Exec(` update keys set verification_keys = $1, signing_key = $2, signing_key_pub = $3, next_rotation = $4 where id = $5; `, encoder(nk.VerificationKeys), encoder(nk.SigningKey), encoder(nk.SigningKeyPub), nk.NextRotation, keysRowID, ) if err != nil { return fmt.Errorf("update: %v", err) } } return nil }) } func (c *conn) GetKeys(ctx context.Context) (keys storage.Keys, err error) { return getKeys(ctx, c) } func getKeys(ctx context.Context, q querier) (keys storage.Keys, err error) { err = q.QueryRow(` select verification_keys, signing_key, signing_key_pub, next_rotation from keys where id=$1 `, keysRowID).Scan( decoder(&keys.VerificationKeys), decoder(&keys.SigningKey), decoder(&keys.SigningKeyPub), &keys.NextRotation, ) if err != nil { if err == sql.ErrNoRows { return keys, storage.ErrNotFound } return keys, fmt.Errorf("query keys: %v", err) } return keys, nil } func (c *conn) UpdateClient(ctx context.Context, id string, updater func(old storage.Client) (storage.Client, error)) error { return c.ExecTx(func(tx *trans) error { cli, err := getClient(ctx, tx, id) if err != nil { return err } nc, err := updater(cli) if err != nil { return err } _, err = tx.Exec(` update client set secret = $1, redirect_uris = $2, trusted_peers = $3, public = $4, name = $5, logo_url = $6, allowed_connectors = $7, mfa_chain = $8 where id = $9; `, nc.Secret, encoder(nc.RedirectURIs), encoder(nc.TrustedPeers), nc.Public, nc.Name, nc.LogoURL, encoder(nc.AllowedConnectors), encoder(nc.MFAChain), id, ) if err != nil { return fmt.Errorf("update client: %v", err) } return nil }) } func (c *conn) CreateClient(ctx context.Context, cli storage.Client) error { _, err := c.Exec(` insert into client ( id, secret, redirect_uris, trusted_peers, public, name, logo_url, allowed_connectors, mfa_chain ) values ($1, $2, $3, $4, $5, $6, $7, $8, $9); `, cli.ID, cli.Secret, encoder(cli.RedirectURIs), encoder(cli.TrustedPeers), cli.Public, cli.Name, cli.LogoURL, encoder(cli.AllowedConnectors), encoder(cli.MFAChain), ) if err != nil { if c.alreadyExistsCheck(err) { return storage.ErrAlreadyExists } return fmt.Errorf("insert client: %v", err) } return nil } func getClient(ctx context.Context, q querier, id string) (storage.Client, error) { return scanClient(q.QueryRow(` select id, secret, redirect_uris, trusted_peers, public, name, logo_url, allowed_connectors, mfa_chain from client where id = $1; `, id)) } func (c *conn) GetClient(ctx context.Context, id string) (storage.Client, error) { return getClient(ctx, c, id) } func (c *conn) ListClients(ctx context.Context) ([]storage.Client, error) { rows, err := c.Query(` select id, secret, redirect_uris, trusted_peers, public, name, logo_url, allowed_connectors, mfa_chain from client; `) if err != nil { return nil, err } defer rows.Close() var clients []storage.Client for rows.Next() { cli, err := scanClient(rows) if err != nil { return nil, err } clients = append(clients, cli) } if err := rows.Err(); err != nil { return nil, err } return clients, nil } func scanClient(s scanner) (cli storage.Client, err error) { var allowedConnectors []byte var mfaChain []byte err = s.Scan( &cli.ID, &cli.Secret, decoder(&cli.RedirectURIs), decoder(&cli.TrustedPeers), &cli.Public, &cli.Name, &cli.LogoURL, &allowedConnectors, &mfaChain, ) if err != nil { if err == sql.ErrNoRows { return cli, storage.ErrNotFound } return cli, fmt.Errorf("get client: %v", err) } if len(allowedConnectors) > 0 { if err := json.Unmarshal(allowedConnectors, &cli.AllowedConnectors); err != nil { return cli, fmt.Errorf("unmarshal client allowed connectors: %v", err) } } if len(mfaChain) > 0 { if err := json.Unmarshal(mfaChain, &cli.MFAChain); err != nil { return cli, fmt.Errorf("unmarshal client mfa chain: %v", err) } } return cli, nil } func (c *conn) CreatePassword(ctx context.Context, p storage.Password) error { p.Email = strings.ToLower(p.Email) _, err := c.Exec(` insert into password ( email, hash, username, preferred_username, user_id, groups, name, email_verified ) values ( $1, $2, $3, $4, $5, $6, $7, $8 ); `, p.Email, p.Hash, p.Username, p.PreferredUsername, p.UserID, encoder(p.Groups), p.Name, p.EmailVerified, ) if err != nil { if c.alreadyExistsCheck(err) { return storage.ErrAlreadyExists } return fmt.Errorf("insert password: %v", err) } return nil } func (c *conn) UpdatePassword(ctx context.Context, email string, updater func(p storage.Password) (storage.Password, error)) error { return c.ExecTx(func(tx *trans) error { p, err := getPassword(ctx, tx, email) if err != nil { return err } np, err := updater(p) if err != nil { return err } _, err = tx.Exec(` update password set hash = $1, username = $2, preferred_username = $3, user_id = $4, groups = $5, name = $6, email_verified = $7 where email = $8; `, np.Hash, np.Username, np.PreferredUsername, np.UserID, encoder(np.Groups), np.Name, np.EmailVerified, p.Email, ) if err != nil { return fmt.Errorf("update password: %v", err) } return nil }) } func (c *conn) GetPassword(ctx context.Context, email string) (storage.Password, error) { return getPassword(ctx, c, email) } func getPassword(ctx context.Context, q querier, email string) (p storage.Password, err error) { return scanPassword(q.QueryRow(` select email, hash, username, preferred_username, user_id, groups, name, email_verified from password where email = $1; `, strings.ToLower(email))) } func (c *conn) ListPasswords(ctx context.Context) ([]storage.Password, error) { rows, err := c.Query(` select email, hash, username, preferred_username, user_id, groups, name, email_verified from password; `) if err != nil { return nil, err } defer rows.Close() var passwords []storage.Password for rows.Next() { p, err := scanPassword(rows) if err != nil { return nil, err } passwords = append(passwords, p) } if err := rows.Err(); err != nil { return nil, err } return passwords, nil } func scanPassword(s scanner) (p storage.Password, err error) { var emailVerified sql.NullBool err = s.Scan( &p.Email, &p.Hash, &p.Username, &p.PreferredUsername, &p.UserID, decoder(&p.Groups), &p.Name, &emailVerified, ) if err != nil { if err == sql.ErrNoRows { return p, storage.ErrNotFound } return p, fmt.Errorf("select password: %v", err) } if emailVerified.Valid { p.EmailVerified = &emailVerified.Bool } return p, nil } func (c *conn) CreateOfflineSessions(ctx context.Context, s storage.OfflineSessions) error { _, err := c.Exec(` insert into offline_session ( user_id, conn_id, refresh, connector_data ) values ( $1, $2, $3, $4 ); `, s.UserID, s.ConnID, encoder(s.Refresh), s.ConnectorData, ) if err != nil { if c.alreadyExistsCheck(err) { return storage.ErrAlreadyExists } return fmt.Errorf("insert offline session: %v", err) } return nil } func (c *conn) UpdateOfflineSessions(ctx context.Context, userID string, connID string, updater func(s storage.OfflineSessions) (storage.OfflineSessions, error)) error { return c.ExecTx(func(tx *trans) error { s, err := getOfflineSessions(ctx, tx, userID, connID) if err != nil { return err } newSession, err := updater(s) if err != nil { return err } _, err = tx.Exec(` update offline_session set refresh = $1, connector_data = $2 where user_id = $3 AND conn_id = $4; `, encoder(newSession.Refresh), newSession.ConnectorData, s.UserID, s.ConnID, ) if err != nil { return fmt.Errorf("update offline session: %v", err) } return nil }) } func (c *conn) GetOfflineSessions(ctx context.Context, userID string, connID string) (storage.OfflineSessions, error) { return getOfflineSessions(ctx, c, userID, connID) } func getOfflineSessions(ctx context.Context, q querier, userID string, connID string) (storage.OfflineSessions, error) { return scanOfflineSessions(q.QueryRow(` select user_id, conn_id, refresh, connector_data from offline_session where user_id = $1 AND conn_id = $2; `, userID, connID)) } func scanOfflineSessions(s scanner) (o storage.OfflineSessions, err error) { err = s.Scan( &o.UserID, &o.ConnID, decoder(&o.Refresh), &o.ConnectorData, ) if err != nil { if err == sql.ErrNoRows { return o, storage.ErrNotFound } return o, fmt.Errorf("select offline session: %v", err) } return o, nil } func (c *conn) CreateUserIdentity(ctx context.Context, u storage.UserIdentity) error { _, err := c.Exec(` insert into user_identity ( user_id, connector_id, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, consents, mfa_secrets, created_at, last_login, blocked_until ) values ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13 ); `, u.UserID, u.ConnectorID, u.Claims.UserID, u.Claims.Username, u.Claims.PreferredUsername, u.Claims.Email, u.Claims.EmailVerified, encoder(u.Claims.Groups), encoder(u.Consents), encoder(u.MFASecrets), u.CreatedAt, u.LastLogin, u.BlockedUntil, ) if err != nil { if c.alreadyExistsCheck(err) { return storage.ErrAlreadyExists } return fmt.Errorf("insert user identity: %v", err) } return nil } func (c *conn) UpdateUserIdentity(ctx context.Context, userID, connectorID string, updater func(u storage.UserIdentity) (storage.UserIdentity, error)) error { return c.ExecTx(func(tx *trans) error { u, err := getUserIdentity(ctx, tx, userID, connectorID) if err != nil { return err } newIdentity, err := updater(u) if err != nil { return err } _, err = tx.Exec(` update user_identity set claims_user_id = $1, claims_username = $2, claims_preferred_username = $3, claims_email = $4, claims_email_verified = $5, claims_groups = $6, consents = $7, mfa_secrets = $8, created_at = $9, last_login = $10, blocked_until = $11 where user_id = $12 AND connector_id = $13; `, newIdentity.Claims.UserID, newIdentity.Claims.Username, newIdentity.Claims.PreferredUsername, newIdentity.Claims.Email, newIdentity.Claims.EmailVerified, encoder(newIdentity.Claims.Groups), encoder(newIdentity.Consents), encoder(newIdentity.MFASecrets), newIdentity.CreatedAt, newIdentity.LastLogin, newIdentity.BlockedUntil, u.UserID, u.ConnectorID, ) if err != nil { return fmt.Errorf("update user identity: %v", err) } return nil }) } func (c *conn) GetUserIdentity(ctx context.Context, userID, connectorID string) (storage.UserIdentity, error) { return getUserIdentity(ctx, c, userID, connectorID) } func getUserIdentity(ctx context.Context, q querier, userID, connectorID string) (storage.UserIdentity, error) { return scanUserIdentity(q.QueryRow(` select user_id, connector_id, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, consents, mfa_secrets, created_at, last_login, blocked_until from user_identity where user_id = $1 AND connector_id = $2; `, userID, connectorID)) } func (c *conn) ListUserIdentities(ctx context.Context) ([]storage.UserIdentity, error) { rows, err := c.Query(` select user_id, connector_id, claims_user_id, claims_username, claims_preferred_username, claims_email, claims_email_verified, claims_groups, consents, mfa_secrets, created_at, last_login, blocked_until from user_identity; `) if err != nil { return nil, fmt.Errorf("query: %v", err) } defer rows.Close() var identities []storage.UserIdentity for rows.Next() { u, err := scanUserIdentity(rows) if err != nil { return nil, err } identities = append(identities, u) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("scan: %v", err) } return identities, nil } func scanUserIdentity(s scanner) (u storage.UserIdentity, err error) { var mfaSecrets []byte err = s.Scan( &u.UserID, &u.ConnectorID, &u.Claims.UserID, &u.Claims.Username, &u.Claims.PreferredUsername, &u.Claims.Email, &u.Claims.EmailVerified, decoder(&u.Claims.Groups), decoder(&u.Consents), &mfaSecrets, &u.CreatedAt, &u.LastLogin, &u.BlockedUntil, ) if err != nil { if err == sql.ErrNoRows { return u, storage.ErrNotFound } return u, fmt.Errorf("select user identity: %v", err) } if u.Consents == nil { u.Consents = make(map[string][]string) } if len(mfaSecrets) > 0 { if err := json.Unmarshal(mfaSecrets, &u.MFASecrets); err != nil { return u, fmt.Errorf("unmarshal user identity mfa secrets: %v", err) } } return u, nil } func (c *conn) DeleteUserIdentity(ctx context.Context, userID, connectorID string) error { result, err := c.Exec(`delete from user_identity where user_id = $1 AND connector_id = $2`, userID, connectorID) if err != nil { return fmt.Errorf("delete user_identity: user_id = %s, connector_id = %s: %w", userID, connectorID, err) } // For now mandate that the driver implements RowsAffected. If we ever need to support // a driver that doesn't implement this, we can run this in a transaction with a get beforehand. n, err := result.RowsAffected() if err != nil { return fmt.Errorf("rows affected: %v", err) } if n < 1 { return storage.ErrNotFound } return nil } func (c *conn) CreateAuthSession(ctx context.Context, s storage.AuthSession) error { _, err := c.Exec(` insert into auth_session ( user_id, connector_id, nonce, client_states, created_at, last_activity, ip_address, user_agent, absolute_expiry, idle_expiry ) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10); `, s.UserID, s.ConnectorID, s.Nonce, encoder(s.ClientStates), s.CreatedAt, s.LastActivity, s.IPAddress, s.UserAgent, s.AbsoluteExpiry, s.IdleExpiry, ) if err != nil { if c.alreadyExistsCheck(err) { return storage.ErrAlreadyExists } return fmt.Errorf("insert auth session: %v", err) } return nil } func (c *conn) UpdateAuthSession(ctx context.Context, userID, connectorID string, updater func(s storage.AuthSession) (storage.AuthSession, error)) error { return c.ExecTx(func(tx *trans) error { s, err := getAuthSession(ctx, tx, userID, connectorID) if err != nil { return err } newSession, err := updater(s) if err != nil { return err } _, err = tx.Exec(` update auth_session set client_states = $1, last_activity = $2, ip_address = $3, user_agent = $4 where user_id = $5 AND connector_id = $6; `, encoder(newSession.ClientStates), newSession.LastActivity, newSession.IPAddress, newSession.UserAgent, userID, connectorID, ) if err != nil { return fmt.Errorf("update auth session: %v", err) } return nil }) } func (c *conn) GetAuthSession(ctx context.Context, userID, connectorID string) (storage.AuthSession, error) { return getAuthSession(ctx, c, userID, connectorID) } func getAuthSession(ctx context.Context, q querier, userID, connectorID string) (storage.AuthSession, error) { return scanAuthSession(q.QueryRow(` select user_id, connector_id, nonce, client_states, created_at, last_activity, ip_address, user_agent, absolute_expiry, idle_expiry from auth_session where user_id = $1 AND connector_id = $2; `, userID, connectorID)) } func scanAuthSession(s scanner) (session storage.AuthSession, err error) { err = s.Scan( &session.UserID, &session.ConnectorID, &session.Nonce, decoder(&session.ClientStates), &session.CreatedAt, &session.LastActivity, &session.IPAddress, &session.UserAgent, &session.AbsoluteExpiry, &session.IdleExpiry, ) if err != nil { if err == sql.ErrNoRows { return session, storage.ErrNotFound } return session, fmt.Errorf("select auth session: %v", err) } if session.ClientStates == nil { session.ClientStates = make(map[string]*storage.ClientAuthState) } return session, nil } func (c *conn) ListAuthSessions(ctx context.Context) ([]storage.AuthSession, error) { rows, err := c.Query(` select user_id, connector_id, nonce, client_states, created_at, last_activity, ip_address, user_agent, absolute_expiry, idle_expiry from auth_session; `) if err != nil { return nil, fmt.Errorf("query: %v", err) } defer rows.Close() var sessions []storage.AuthSession for rows.Next() { s, err := scanAuthSession(rows) if err != nil { return nil, err } sessions = append(sessions, s) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("scan: %v", err) } return sessions, nil } func (c *conn) DeleteAuthSession(ctx context.Context, userID, connectorID string) error { result, err := c.Exec(`delete from auth_session where user_id = $1 AND connector_id = $2`, userID, connectorID) if err != nil { return fmt.Errorf("delete auth_session: user_id = %s, connector_id = %s: %w", userID, connectorID, err) } n, err := result.RowsAffected() if err != nil { return fmt.Errorf("rows affected: %v", err) } if n < 1 { return storage.ErrNotFound } return nil } func (c *conn) CreateConnector(ctx context.Context, connector storage.Connector) error { grantTypes, err := json.Marshal(connector.GrantTypes) if err != nil { return fmt.Errorf("marshal connector grant types: %v", err) } _, err = c.Exec(` insert into connector ( id, type, name, resource_version, config, grant_types ) values ( $1, $2, $3, $4, $5, $6 ); `, connector.ID, connector.Type, connector.Name, connector.ResourceVersion, connector.Config, grantTypes, ) if err != nil { if c.alreadyExistsCheck(err) { return storage.ErrAlreadyExists } return fmt.Errorf("insert connector: %v", err) } return nil } func (c *conn) UpdateConnector(ctx context.Context, id string, updater func(s storage.Connector) (storage.Connector, error)) error { return c.ExecTx(func(tx *trans) error { connector, err := getConnector(ctx, tx, id) if err != nil { return err } newConn, err := updater(connector) if err != nil { return err } grantTypes, err := json.Marshal(newConn.GrantTypes) if err != nil { return fmt.Errorf("marshal connector grant types: %v", err) } _, err = tx.Exec(` update connector set type = $1, name = $2, resource_version = $3, config = $4, grant_types = $5 where id = $6; `, newConn.Type, newConn.Name, newConn.ResourceVersion, newConn.Config, grantTypes, connector.ID, ) if err != nil { return fmt.Errorf("update connector: %v", err) } return nil }) } func (c *conn) GetConnector(ctx context.Context, id string) (storage.Connector, error) { return getConnector(ctx, c, id) } func getConnector(ctx context.Context, q querier, id string) (storage.Connector, error) { return scanConnector(q.QueryRow(` select id, type, name, resource_version, config, grant_types from connector where id = $1; `, id)) } func scanConnector(s scanner) (c storage.Connector, err error) { var grantTypes []byte err = s.Scan( &c.ID, &c.Type, &c.Name, &c.ResourceVersion, &c.Config, &grantTypes, ) if err != nil { if err == sql.ErrNoRows { return c, storage.ErrNotFound } return c, fmt.Errorf("select connector: %v", err) } if len(grantTypes) > 0 { if err := json.Unmarshal(grantTypes, &c.GrantTypes); err != nil { return c, fmt.Errorf("unmarshal connector grant types: %v", err) } } return c, nil } func (c *conn) ListConnectors(ctx context.Context) ([]storage.Connector, error) { rows, err := c.Query(` select id, type, name, resource_version, config, grant_types from connector; `) if err != nil { return nil, err } defer rows.Close() var connectors []storage.Connector for rows.Next() { conn, err := scanConnector(rows) if err != nil { return nil, err } connectors = append(connectors, conn) } if err := rows.Err(); err != nil { return nil, err } return connectors, nil } func (c *conn) DeleteAuthRequest(ctx context.Context, id string) error { return c.delete("auth_request", "id", id) } func (c *conn) DeleteAuthCode(ctx context.Context, id string) error { return c.delete("auth_code", "id", id) } func (c *conn) DeleteClient(ctx context.Context, id string) error { return c.delete("client", "id", id) } func (c *conn) DeleteRefresh(ctx context.Context, id string) error { return c.delete("refresh_token", "id", id) } func (c *conn) DeletePassword(ctx context.Context, email string) error { return c.delete("password", "email", strings.ToLower(email)) } func (c *conn) DeleteConnector(ctx context.Context, id string) error { return c.delete("connector", "id", id) } func (c *conn) DeleteOfflineSessions(ctx context.Context, userID string, connID string) error { result, err := c.Exec(`delete from offline_session where user_id = $1 AND conn_id = $2`, userID, connID) if err != nil { return fmt.Errorf("delete offline_session: user_id = %s, conn_id = %s", userID, connID) } // For now mandate that the driver implements RowsAffected. If we ever need to support // a driver that doesn't implement this, we can run this in a transaction with a get beforehand. n, err := result.RowsAffected() if err != nil { return fmt.Errorf("rows affected: %v", err) } if n < 1 { return storage.ErrNotFound } return nil } // Do NOT call directly. Does not escape table. func (c *conn) delete(table, field, id string) error { result, err := c.Exec(`delete from `+table+` where `+field+` = $1`, id) if err != nil { return fmt.Errorf("delete %s: %v", table, id) } // For now mandate that the driver implements RowsAffected. If we ever need to support // a driver that doesn't implement this, we can run this in a transaction with a get beforehand. n, err := result.RowsAffected() if err != nil { return fmt.Errorf("rows affected: %v", err) } if n < 1 { return storage.ErrNotFound } return nil } func (c *conn) CreateDeviceRequest(ctx context.Context, d storage.DeviceRequest) error { _, err := c.Exec(` insert into device_request ( user_code, device_code, client_id, client_secret, scopes, expiry ) values ( $1, $2, $3, $4, $5, $6 );`, d.UserCode, d.DeviceCode, d.ClientID, d.ClientSecret, encoder(d.Scopes), d.Expiry, ) if err != nil { if c.alreadyExistsCheck(err) { return storage.ErrAlreadyExists } return fmt.Errorf("insert device request: %v", err) } return nil } func (c *conn) CreateDeviceToken(ctx context.Context, t storage.DeviceToken) error { _, err := c.Exec(` insert into device_token ( device_code, status, token, expiry, last_request, poll_interval, code_challenge, code_challenge_method ) values ( $1, $2, $3, $4, $5, $6, $7, $8 );`, t.DeviceCode, t.Status, t.Token, t.Expiry, t.LastRequestTime, t.PollIntervalSeconds, t.PKCE.CodeChallenge, t.PKCE.CodeChallengeMethod, ) if err != nil { if c.alreadyExistsCheck(err) { return storage.ErrAlreadyExists } return fmt.Errorf("insert device token: %v", err) } return nil } func (c *conn) GetDeviceRequest(ctx context.Context, userCode string) (storage.DeviceRequest, error) { return getDeviceRequest(ctx, c, userCode) } func getDeviceRequest(ctx context.Context, q querier, userCode string) (d storage.DeviceRequest, err error) { err = q.QueryRow(` select device_code, client_id, client_secret, scopes, expiry from device_request where user_code = $1; `, userCode).Scan( &d.DeviceCode, &d.ClientID, &d.ClientSecret, decoder(&d.Scopes), &d.Expiry, ) if err != nil { if err == sql.ErrNoRows { return d, storage.ErrNotFound } return d, fmt.Errorf("select device token: %v", err) } d.UserCode = userCode return d, nil } func (c *conn) GetDeviceToken(ctx context.Context, deviceCode string) (storage.DeviceToken, error) { return getDeviceToken(ctx, c, deviceCode) } func getDeviceToken(ctx context.Context, q querier, deviceCode string) (a storage.DeviceToken, err error) { err = q.QueryRow(` select status, token, expiry, last_request, poll_interval, code_challenge, code_challenge_method from device_token where device_code = $1; `, deviceCode).Scan( &a.Status, &a.Token, &a.Expiry, &a.LastRequestTime, &a.PollIntervalSeconds, &a.PKCE.CodeChallenge, &a.PKCE.CodeChallengeMethod, ) if err != nil { if err == sql.ErrNoRows { return a, storage.ErrNotFound } return a, fmt.Errorf("select device token: %v", err) } a.DeviceCode = deviceCode return a, nil } func (c *conn) UpdateDeviceToken(ctx context.Context, deviceCode string, updater func(old storage.DeviceToken) (storage.DeviceToken, error)) error { return c.ExecTx(func(tx *trans) error { r, err := getDeviceToken(ctx, tx, deviceCode) if err != nil { return err } if r, err = updater(r); err != nil { return err } _, err = tx.Exec(` update device_token set status = $1, token = $2, last_request = $3, poll_interval = $4, code_challenge = $5, code_challenge_method = $6 where device_code = $7 `, r.Status, r.Token, r.LastRequestTime, r.PollIntervalSeconds, r.PKCE.CodeChallenge, r.PKCE.CodeChallengeMethod, r.DeviceCode, ) if err != nil { return fmt.Errorf("update device token: %v", err) } return nil }) } ================================================ FILE: storage/sql/crud_test.go ================================================ //go:build cgo // +build cgo package sql import ( "database/sql" "reflect" "testing" ) func TestDecoder(t *testing.T) { db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatal(err) } defer db.Close() if _, err := db.Exec(`create table foo ( id integer primary key, bar blob );`); err != nil { t.Fatal(err) } if _, err := db.Exec(`insert into foo ( id, bar ) values (1, ?);`, []byte(`["a", "b"]`)); err != nil { t.Fatal(err) } var got []string if err := db.QueryRow(`select bar from foo where id = 1;`).Scan(decoder(&got)); err != nil { t.Fatal(err) } want := []string{"a", "b"} if !reflect.DeepEqual(got, want) { t.Errorf("wanted %q got %q", want, got) } } func TestEncoder(t *testing.T) { db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatal(err) } defer db.Close() if _, err := db.Exec(`create table foo ( id integer primary key, bar blob );`); err != nil { t.Fatal(err) } put := []string{"a", "b"} if _, err := db.Exec(`insert into foo ( id, bar ) values (1, ?)`, encoder(put)); err != nil { t.Fatal(err) } var got []byte if err := db.QueryRow(`select bar from foo where id = 1;`).Scan(&got); err != nil { t.Fatal(err) } want := []byte(`["a","b"]`) if !reflect.DeepEqual(got, want) { t.Errorf("wanted %q got %q", want, got) } } ================================================ FILE: storage/sql/migrate.go ================================================ package sql import ( "database/sql" "fmt" ) func (c *conn) migrate() (int, error) { _, err := c.Exec(` create table if not exists migrations ( num integer not null, at timestamptz not null ); `) if err != nil { return 0, fmt.Errorf("creating migration table: %v", err) } i := 0 done := false var flavorMigrations []migration for _, m := range migrations { if m.flavor == nil || m.flavor == c.flavor { flavorMigrations = append(flavorMigrations, m) } } for { err := c.ExecTx(func(tx *trans) error { // Within a transaction, perform a single migration. var ( num sql.NullInt64 n int ) if err := tx.QueryRow(`select max(num) from migrations;`).Scan(&num); err != nil { return fmt.Errorf("select max migration: %v", err) } if num.Valid { n = int(num.Int64) } if n >= len(flavorMigrations) { done = true return nil } migrationNum := n + 1 m := flavorMigrations[n] for i := range m.stmts { if _, err := tx.Exec(m.stmts[i]); err != nil { return fmt.Errorf("migration %d statement %d failed: %v", migrationNum, i+1, err) } } q := `insert into migrations (num, at) values ($1, now());` if _, err := tx.Exec(q, migrationNum); err != nil { return fmt.Errorf("update migration table: %v", err) } return nil }) if err != nil { return i, err } if done { break } i++ } return i, nil } type migration struct { stmts []string // If flavor is nil the migration will take place for all database backend flavors. // If specified, only for that corresponding flavor, in that case stmts can be written // in the specific SQL dialect. flavor *flavor } // All SQL flavors share migration strategies. var migrations = []migration{ { stmts: []string{ ` create table client ( id text not null primary key, secret text not null, redirect_uris bytea not null, -- JSON array of strings trusted_peers bytea not null, -- JSON array of strings public boolean not null, name text not null, logo_url text not null );`, ` create table auth_request ( id text not null primary key, client_id text not null, response_types bytea not null, -- JSON array of strings scopes bytea not null, -- JSON array of strings redirect_uri text not null, nonce text not null, state text not null, force_approval_prompt boolean not null, logged_in boolean not null, claims_user_id text not null, claims_username text not null, claims_email text not null, claims_email_verified boolean not null, claims_groups bytea not null, -- JSON array of strings connector_id text not null, connector_data bytea, expiry timestamptz not null );`, ` create table auth_code ( id text not null primary key, client_id text not null, scopes bytea not null, -- JSON array of strings nonce text not null, redirect_uri text not null, claims_user_id text not null, claims_username text not null, claims_email text not null, claims_email_verified boolean not null, claims_groups bytea not null, -- JSON array of strings connector_id text not null, connector_data bytea, expiry timestamptz not null );`, ` create table refresh_token ( id text not null primary key, client_id text not null, scopes bytea not null, -- JSON array of strings nonce text not null, claims_user_id text not null, claims_username text not null, claims_email text not null, claims_email_verified boolean not null, claims_groups bytea not null, -- JSON array of strings connector_id text not null, connector_data bytea );`, ` create table password ( email text not null primary key, hash bytea not null, username text not null, user_id text not null );`, ` -- keys is a weird table because we only ever expect there to be a single row create table keys ( id text not null primary key, verification_keys bytea not null, -- JSON array signing_key bytea not null, -- JSON object signing_key_pub bytea not null, -- JSON object next_rotation timestamptz not null );`, }, }, { stmts: []string{ ` alter table refresh_token add column token text not null default '';`, ` alter table refresh_token add column created_at timestamptz not null default '0001-01-01 00:00:00 UTC';`, ` alter table refresh_token add column last_used timestamptz not null default '0001-01-01 00:00:00 UTC';`, }, }, { stmts: []string{ ` create table offline_session ( user_id text not null, conn_id text not null, refresh bytea not null, PRIMARY KEY (user_id, conn_id) );`, }, }, { stmts: []string{ ` create table connector ( id text not null primary key, type text not null, name text not null, resource_version text not null, config bytea );`, }, }, { stmts: []string{ ` alter table auth_code add column claims_preferred_username text not null default '';`, ` alter table auth_request add column claims_preferred_username text not null default '';`, ` alter table refresh_token add column claims_preferred_username text not null default '';`, }, }, { stmts: []string{ ` alter table offline_session add column connector_data bytea; `, }, }, { stmts: []string{ ` alter table auth_request modify column state varchar(4096); `, }, flavor: &flavorMySQL, }, { stmts: []string{ ` create table device_request ( user_code text not null primary key, device_code text not null, client_id text not null, client_secret text , scopes bytea not null, -- JSON array of strings expiry timestamptz not null );`, ` create table device_token ( device_code text not null primary key, status text not null, token bytea, expiry timestamptz not null, last_request timestamptz not null, poll_interval integer not null );`, }, }, { stmts: []string{ ` alter table auth_request add column code_challenge text not null default '';`, ` alter table auth_request add column code_challenge_method text not null default '';`, ` alter table auth_code add column code_challenge text not null default '';`, ` alter table auth_code add column code_challenge_method text not null default '';`, }, }, { stmts: []string{ ` alter table refresh_token add column obsolete_token text default '';`, }, }, { stmts: []string{ ` alter table device_token add column code_challenge text not null default '';`, ` alter table device_token add column code_challenge_method text not null default '';`, }, }, { stmts: []string{ ` alter table auth_request add column hmac_key bytea;`, }, }, { stmts: []string{ ` alter table password add column preferred_username text not null default '';`, ` alter table password add column groups bytea not null default convert_to('[]', 'UTF8');`, }, flavor: &flavorPostgres, }, { stmts: []string{ ` alter table password add column preferred_username text not null default '';`, ` alter table password add column groups bytea not null default '[]';`, }, flavor: &flavorSQLite3, }, { stmts: []string{ ` alter table password add column preferred_username text not null default '';`, ` alter table password add column groups bytea;`, ` update password set groups = '[]' where groups is null;`, ` alter table password modify column groups bytea not null;`, }, flavor: &flavorMySQL, }, // Migration for adding name and email_verified to password table (Postgres) { stmts: []string{ ` alter table password add column name text not null default '';`, ` alter table password add column email_verified boolean;`, }, flavor: &flavorPostgres, }, // Migration for adding name and email_verified to password table (SQLite3) { stmts: []string{ ` alter table password add column name text not null default '';`, ` alter table password add column email_verified boolean;`, }, flavor: &flavorSQLite3, }, // Migration for adding name and email_verified to password table (MySQL) { stmts: []string{ ` alter table password add column name text not null default '';`, ` alter table password add column email_verified boolean;`, }, flavor: &flavorMySQL, }, { stmts: []string{ ` alter table connector add column grant_types bytea;`, }, }, // Migration for adding allowed_connectors to client table { stmts: []string{ ` alter table client add column allowed_connectors bytea;`, }, }, { stmts: []string{ ` create table user_identity ( user_id text not null, connector_id text not null, claims_user_id text not null, claims_username text not null, claims_preferred_username text not null default '', claims_email text not null, claims_email_verified boolean not null, claims_groups bytea not null, consents bytea not null, created_at timestamptz not null, last_login timestamptz not null, blocked_until timestamptz not null, PRIMARY KEY (user_id, connector_id) );`, }, }, { stmts: []string{ ` create table auth_session ( user_id text not null, connector_id text not null, nonce text not null default '', client_states bytea not null, created_at timestamptz not null, last_activity timestamptz not null, ip_address text not null default '', user_agent text not null default '', PRIMARY KEY (user_id, connector_id) );`, }, }, { stmts: []string{ ` alter table auth_request add column mfa_validated boolean not null default false;`, ` alter table user_identity add column mfa_secrets bytea;`, ` alter table client add column mfa_chain bytea;`, `alter table auth_request add column prompt text not null default '';`, `alter table auth_request add column max_age integer not null default -1;`, `alter table auth_request add column auth_time timestamptz not null default '1970-01-01 00:00:00';`, `alter table auth_code add column auth_time timestamptz not null default '1970-01-01 00:00:00';`, }, }, { stmts: []string{ ` alter table auth_session add column absolute_expiry timestamptz not null default '1970-01-01 00:00:00';`, ` alter table auth_session add column idle_expiry timestamptz not null default '1970-01-01 00:00:00';`, }, }, } ================================================ FILE: storage/sql/migrate_test.go ================================================ //go:build cgo // +build cgo package sql import ( "database/sql" "log/slog" "testing" sqlite3 "github.com/mattn/go-sqlite3" ) func TestMigrate(t *testing.T) { db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatal(err) } defer db.Close() logger := slog.New(slog.DiscardHandler) errCheck := func(err error) bool { sqlErr, ok := err.(sqlite3.Error) if !ok { return false } return sqlErr.ExtendedCode == sqlite3.ErrConstraintUnique } var sqliteMigrations []migration for _, m := range migrations { if m.flavor == nil || m.flavor == &flavorSQLite3 { sqliteMigrations = append(sqliteMigrations, m) } } c := &conn{db, &flavorSQLite3, logger, errCheck} for _, want := range []int{len(sqliteMigrations), 0} { got, err := c.migrate() if err != nil { t.Fatal(err) } if got != want { t.Errorf("expected %d migrations, got %d", want, got) } } } ================================================ FILE: storage/sql/postgres_test.go ================================================ //go:build go1.11 // +build go1.11 package sql import ( "log/slog" "os" "strconv" "testing" ) func TestPostgresTunables(t *testing.T) { host := os.Getenv(testPostgresEnv) if host == "" { t.Skipf("test environment variable %q not set, skipping", testPostgresEnv) } port := uint64(5432) if rawPort := os.Getenv("DEX_POSTGRES_PORT"); rawPort != "" { var err error port, err = strconv.ParseUint(rawPort, 10, 32) if err != nil { t.Fatalf("invalid postgres port %q: %s", rawPort, err) } } baseCfg := &Postgres{ NetworkDB: NetworkDB{ Database: getenv("DEX_POSTGRES_DATABASE", "postgres"), User: getenv("DEX_POSTGRES_USER", "postgres"), Password: getenv("DEX_POSTGRES_PASSWORD", "postgres"), Host: host, Port: uint16(port), }, SSL: SSL{ Mode: pgSSLDisable, // Postgres container doesn't support SSL. }, } t.Run("with nothing set, uses defaults", func(t *testing.T) { cfg := *baseCfg logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug})) c, err := cfg.open(logger) if err != nil { t.Fatalf("error opening connector: %s", err.Error()) } defer c.db.Close() if m := c.db.Stats().MaxOpenConnections; m != 5 { t.Errorf("expected MaxOpenConnections to have its default (5), got %d", m) } }) t.Run("with something set, uses that", func(t *testing.T) { cfg := *baseCfg cfg.MaxOpenConns = 101 logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug})) c, err := cfg.open(logger) if err != nil { t.Fatalf("error opening connector: %s", err.Error()) } defer c.db.Close() if m := c.db.Stats().MaxOpenConnections; m != 101 { t.Errorf("expected MaxOpenConnections to be set to 101, got %d", m) } }) } ================================================ FILE: storage/sql/sql.go ================================================ // Package sql provides SQL implementations of the storage interface. package sql import ( "database/sql" "log/slog" "regexp" "time" // import third party drivers _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" ) // flavor represents a specific SQL implementation, and is used to translate query strings // between different drivers. Flavors shouldn't aim to translate all possible SQL statements, // only the specific queries used by the SQL storages. type flavor struct { queryReplacers []replacer // Optional function to create and finish a transaction. executeTx func(db *sql.DB, fn func(*sql.Tx) error) error // Does the flavor support timezones? supportsTimezones bool } // A regexp with a replacement string. type replacer struct { re *regexp.Regexp with string } // Match a postgres query binds. E.g. "$1", "$12", etc. var bindRegexp = regexp.MustCompile(`\$\d+`) func matchLiteral(s string) *regexp.Regexp { return regexp.MustCompile(`\b` + regexp.QuoteMeta(s) + `\b`) } var ( // The "github.com/lib/pq" driver is the default flavor. All others are // translations of this. flavorPostgres = flavor{ // The default behavior for Postgres transactions is consistent reads, not consistent writes. // For each transaction opened, ensure it has the correct isolation level. // // See: https://www.postgresql.org/docs/9.3/static/sql-set-transaction.html // // NOTE(ericchiang): For some reason using `SET SESSION CHARACTERISTICS AS TRANSACTION` at a // session level didn't work for some edge cases. Might be something worth exploring. executeTx: func(db *sql.DB, fn func(sqlTx *sql.Tx) error) error { tx, err := db.Begin() if err != nil { return err } defer tx.Rollback() if _, err := tx.Exec(`SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;`); err != nil { return err } if err := fn(tx); err != nil { return err } return tx.Commit() }, supportsTimezones: true, } flavorSQLite3 = flavor{ queryReplacers: []replacer{ {bindRegexp, "?"}, // Translate for booleans to integers. {matchLiteral("true"), "1"}, {matchLiteral("false"), "0"}, {matchLiteral("boolean"), "integer"}, // Translate other types. {matchLiteral("bytea"), "blob"}, {matchLiteral("timestamptz"), "timestamp"}, // SQLite doesn't have a "now()" method, replace with "date('now')" {regexp.MustCompile(`\bnow\(\)`), "date('now')"}, }, } flavorMySQL = flavor{ queryReplacers: []replacer{ {bindRegexp, "?"}, // Translate types. {matchLiteral("bytea"), "blob"}, {matchLiteral("timestamptz"), "datetime(3)"}, // MySQL doesn't support indices on text fields w/o // specifying key length. Use varchar instead (767 byte // is the max key length for InnoDB with 4k pages). // For compound indexes (with two keys) even less. {matchLiteral("text"), "varchar(384)"}, // Quote keywords and reserved words used as identifiers. {regexp.MustCompile(`\b(keys|groups)\b`), "`$1`"}, // Change default timestamp to fit datetime. {regexp.MustCompile(`0001-01-01 00:00:00 UTC`), "1000-01-01 00:00:00"}, }, } ) func (f flavor) translate(query string) string { // TODO(ericchiang): Heavy cashing. for _, r := range f.queryReplacers { query = r.re.ReplaceAllString(query, r.with) } return query } // translateArgs translates query parameters that may be unique to // a specific SQL flavor. For example, standardizing "time.Time" // types to UTC for clients that don't provide timezone support. func (c *conn) translateArgs(args []interface{}) []interface{} { if c.flavor.supportsTimezones { return args } for i, arg := range args { if t, ok := arg.(time.Time); ok { args[i] = t.UTC() } } return args } // conn is the main database connection. type conn struct { db *sql.DB flavor *flavor logger *slog.Logger alreadyExistsCheck func(err error) bool } func (c *conn) Close() error { return c.db.Close() } // conn implements the same method signatures as encoding/sql.DB. func (c *conn) Exec(query string, args ...interface{}) (sql.Result, error) { query = c.flavor.translate(query) return c.db.Exec(query, c.translateArgs(args)...) } func (c *conn) Query(query string, args ...interface{}) (*sql.Rows, error) { query = c.flavor.translate(query) return c.db.Query(query, c.translateArgs(args)...) } func (c *conn) QueryRow(query string, args ...interface{}) *sql.Row { query = c.flavor.translate(query) return c.db.QueryRow(query, c.translateArgs(args)...) } // ExecTx runs a method which operates on a transaction. func (c *conn) ExecTx(fn func(tx *trans) error) error { if c.flavor.executeTx != nil { return c.flavor.executeTx(c.db, func(sqlTx *sql.Tx) error { return fn(&trans{sqlTx, c}) }) } sqlTx, err := c.db.Begin() if err != nil { return err } if err := fn(&trans{sqlTx, c}); err != nil { sqlTx.Rollback() return err } return sqlTx.Commit() } type trans struct { tx *sql.Tx c *conn } // trans implements the same method signatures as encoding/sql.Tx. func (t *trans) Exec(query string, args ...interface{}) (sql.Result, error) { query = t.c.flavor.translate(query) return t.tx.Exec(query, t.c.translateArgs(args)...) } func (t *trans) Query(query string, args ...interface{}) (*sql.Rows, error) { query = t.c.flavor.translate(query) return t.tx.Query(query, t.c.translateArgs(args)...) } func (t *trans) QueryRow(query string, args ...interface{}) *sql.Row { query = t.c.flavor.translate(query) return t.tx.QueryRow(query, t.c.translateArgs(args)...) } ================================================ FILE: storage/sql/sql_test.go ================================================ package sql import "testing" func TestTranslate(t *testing.T) { tests := []struct { testCase string flavor flavor query string exp string }{ { "sqlite3 query bind replacement", flavorSQLite3, `select foo from bar where foo.zam = $1;`, `select foo from bar where foo.zam = ?;`, }, { "sqlite3 query bind replacement at newline", flavorSQLite3, `select foo from bar where foo.zam = $1`, `select foo from bar where foo.zam = ?`, }, { "sqlite3 query true", flavorSQLite3, `select foo from bar where foo.zam = true`, `select foo from bar where foo.zam = 1`, }, { "sqlite3 query false", flavorSQLite3, `select foo from bar where foo.zam = false`, `select foo from bar where foo.zam = 0`, }, { "sqlite3 bytea", flavorSQLite3, `"connector_data" bytea not null,`, `"connector_data" blob not null,`, }, { "sqlite3 now", flavorSQLite3, `now(),`, `date('now'),`, }, } for _, tc := range tests { if got := tc.flavor.translate(tc.query); got != tc.exp { t.Errorf("%s: want=%q, got=%q", tc.testCase, tc.exp, got) } } } ================================================ FILE: storage/sql/sqlite.go ================================================ //go:build cgo // +build cgo package sql import ( "database/sql" "fmt" "log/slog" sqlite3 "github.com/mattn/go-sqlite3" "github.com/dexidp/dex/storage" ) // SQLite3 options for creating an SQL db. type SQLite3 struct { // File to File string `json:"file"` } // Open creates a new storage implementation backed by SQLite3 func (s *SQLite3) Open(logger *slog.Logger) (storage.Storage, error) { conn, err := s.open(logger) if err != nil { return nil, err } return conn, nil } func (s *SQLite3) open(logger *slog.Logger) (*conn, error) { db, err := sql.Open("sqlite3", s.File) if err != nil { return nil, err } // always allow only one connection to sqlite3, any other thread/go-routine // attempting concurrent access will have to wait db.SetMaxOpenConns(1) errCheck := func(err error) bool { sqlErr, ok := err.(sqlite3.Error) if !ok { return false } return sqlErr.ExtendedCode == sqlite3.ErrConstraintPrimaryKey } c := &conn{db, &flavorSQLite3, logger, errCheck} if _, err := c.migrate(); err != nil { return nil, fmt.Errorf("failed to perform migrations: %v", err) } return c, nil } ================================================ FILE: storage/sql/sqlite_no_cgo.go ================================================ //go:build !cgo // +build !cgo // This is a stub for the no CGO compilation (CGO_ENABLED=0) package sql import ( "fmt" "log/slog" "github.com/dexidp/dex/storage" ) type SQLite3 struct{} func (s *SQLite3) Open(logger *slog.Logger) (storage.Storage, error) { return nil, fmt.Errorf("SQLite storage is not available: binary compiled without CGO support. Recompile with CGO_ENABLED=1 or use a different storage backend.") } ================================================ FILE: storage/sql/sqlite_test.go ================================================ //go:build cgo // +build cgo package sql import ( "testing" ) func TestSQLite3(t *testing.T) { testDB(t, &SQLite3{":memory:"}, false, true) } ================================================ FILE: storage/static.go ================================================ package storage import ( "context" "errors" "log/slog" "strings" ) // Tests for this code are in the "memory" package, since this package doesn't // define a concrete storage implementation. // staticClientsStorage is a storage that only allow read-only actions on clients. // All read actions return from the list of clients stored in memory, not the // underlying type staticClientsStorage struct { Storage // A read-only set of clients. clients []Client clientsByID map[string]Client } // WithStaticClients adds a read-only set of clients to the underlying storages. func WithStaticClients(s Storage, staticClients []Client) Storage { clientsByID := make(map[string]Client, len(staticClients)) for _, client := range staticClients { clientsByID[client.ID] = client } return staticClientsStorage{s, staticClients, clientsByID} } func (s staticClientsStorage) GetClient(ctx context.Context, id string) (Client, error) { if client, ok := s.clientsByID[id]; ok { return client, nil } return s.Storage.GetClient(ctx, id) } func (s staticClientsStorage) isStatic(id string) bool { _, ok := s.clientsByID[id] return ok } func (s staticClientsStorage) ListClients(ctx context.Context) ([]Client, error) { clients, err := s.Storage.ListClients(ctx) if err != nil { return nil, err } n := 0 for _, client := range clients { // If a client in the backing storage has the same ID as a static client // prefer the static client. if !s.isStatic(client.ID) { clients[n] = client n++ } } return append(clients[:n], s.clients...), nil } func (s staticClientsStorage) CreateClient(ctx context.Context, c Client) error { if s.isStatic(c.ID) { return errors.New("static clients: read-only cannot create client") } return s.Storage.CreateClient(ctx, c) } func (s staticClientsStorage) DeleteClient(ctx context.Context, id string) error { if s.isStatic(id) { return errors.New("static clients: read-only cannot delete client") } return s.Storage.DeleteClient(ctx, id) } func (s staticClientsStorage) UpdateClient(ctx context.Context, id string, updater func(old Client) (Client, error)) error { if s.isStatic(id) { return errors.New("static clients: read-only cannot update client") } return s.Storage.UpdateClient(ctx, id, updater) } type staticPasswordsStorage struct { Storage // A read-only set of passwords. passwords []Password // A map of passwords that is indexed by lower-case email ids passwordsByEmail map[string]Password logger *slog.Logger } // WithStaticPasswords returns a storage with a read-only set of passwords. func WithStaticPasswords(s Storage, staticPasswords []Password, logger *slog.Logger) Storage { passwordsByEmail := make(map[string]Password, len(staticPasswords)) for _, p := range staticPasswords { // Enable case insensitive email comparison. lowerEmail := strings.ToLower(p.Email) if _, ok := passwordsByEmail[lowerEmail]; ok { logger.Error("attempting to create StaticPasswords with the same email id", "email", p.Email) } passwordsByEmail[lowerEmail] = p } return staticPasswordsStorage{s, staticPasswords, passwordsByEmail, logger} } func (s staticPasswordsStorage) isStatic(email string) bool { _, ok := s.passwordsByEmail[strings.ToLower(email)] return ok } func (s staticPasswordsStorage) GetPassword(ctx context.Context, email string) (Password, error) { // TODO(ericchiang): BLAH. We really need to figure out how to handle // lower cased emails better. email = strings.ToLower(email) if password, ok := s.passwordsByEmail[email]; ok { return password, nil } return s.Storage.GetPassword(ctx, email) } func (s staticPasswordsStorage) ListPasswords(ctx context.Context) ([]Password, error) { passwords, err := s.Storage.ListPasswords(ctx) if err != nil { return nil, err } n := 0 for _, password := range passwords { // If an entry has the same email as those provided in the static // values, prefer the static value. if !s.isStatic(password.Email) { passwords[n] = password n++ } } return append(passwords[:n], s.passwords...), nil } func (s staticPasswordsStorage) CreatePassword(ctx context.Context, p Password) error { if s.isStatic(p.Email) { return errors.New("static passwords: read-only cannot create password") } return s.Storage.CreatePassword(ctx, p) } func (s staticPasswordsStorage) DeletePassword(ctx context.Context, email string) error { if s.isStatic(email) { return errors.New("static passwords: read-only cannot delete password") } return s.Storage.DeletePassword(ctx, email) } func (s staticPasswordsStorage) UpdatePassword(ctx context.Context, email string, updater func(old Password) (Password, error)) error { if s.isStatic(email) { return errors.New("static passwords: read-only cannot update password") } return s.Storage.UpdatePassword(ctx, email, updater) } // staticConnectorsStorage represents a storage with read-only set of connectors. type staticConnectorsStorage struct { Storage // A read-only set of connectors. connectors []Connector connectorsByID map[string]Connector } // WithStaticConnectors returns a storage with a read-only set of Connectors. Write actions, // such as updating existing Connectors, will fail. func WithStaticConnectors(s Storage, staticConnectors []Connector) Storage { connectorsByID := make(map[string]Connector, len(staticConnectors)) for _, c := range staticConnectors { connectorsByID[c.ID] = c } return staticConnectorsStorage{s, staticConnectors, connectorsByID} } func (s staticConnectorsStorage) isStatic(id string) bool { _, ok := s.connectorsByID[id] return ok } func (s staticConnectorsStorage) GetConnector(ctx context.Context, id string) (Connector, error) { if connector, ok := s.connectorsByID[id]; ok { return connector, nil } return s.Storage.GetConnector(ctx, id) } func (s staticConnectorsStorage) ListConnectors(ctx context.Context) ([]Connector, error) { connectors, err := s.Storage.ListConnectors(ctx) if err != nil { return nil, err } n := 0 for _, connector := range connectors { // If an entry has the same id as those provided in the static // values, prefer the static value. if !s.isStatic(connector.ID) { connectors[n] = connector n++ } } return append(connectors[:n], s.connectors...), nil } func (s staticConnectorsStorage) CreateConnector(ctx context.Context, c Connector) error { if s.isStatic(c.ID) { return errors.New("static connectors: read-only cannot create connector") } return s.Storage.CreateConnector(ctx, c) } func (s staticConnectorsStorage) DeleteConnector(ctx context.Context, id string) error { if s.isStatic(id) { return errors.New("static connectors: read-only cannot delete connector") } return s.Storage.DeleteConnector(ctx, id) } func (s staticConnectorsStorage) UpdateConnector(ctx context.Context, id string, updater func(old Connector) (Connector, error)) error { if s.isStatic(id) { return errors.New("static connectors: read-only cannot update connector") } return s.Storage.UpdateConnector(ctx, id, updater) } ================================================ FILE: storage/storage.go ================================================ package storage import ( "context" "crypto" "crypto/rand" "encoding/base32" "errors" "io" "math/big" "strings" "time" "github.com/go-jose/go-jose/v4" ) var ( // ErrNotFound is the error returned by storages if a resource cannot be found. ErrNotFound = errors.New("not found") // ErrAlreadyExists is the error returned by storages if a resource ID is taken during a create. ErrAlreadyExists = errors.New("ID already exists") ) // Kubernetes only allows lower case letters for names. // // TODO(ericchiang): refactor ID creation onto the storage. var encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567") // Valid characters for user codes const validUserCharacters = "BCDFGHJKLMNPQRSTVWXZ" // NewDeviceCode returns a 32 char alphanumeric cryptographically secure string func NewDeviceCode() string { return newSecureID(32) } // NewID returns a random string which can be used as an ID for objects. func NewID() string { return newSecureID(16) } func newSecureID(len int) string { buff := make([]byte, len) // random ID. if _, err := io.ReadFull(rand.Reader, buff); err != nil { panic(err) } // Avoid the identifier to begin with number and trim padding return string(buff[0]%26+'a') + strings.TrimRight(encoding.EncodeToString(buff[1:]), "=") } // NewHMACKey returns a random key which can be used in the computation of an HMAC func NewHMACKey(h crypto.Hash) []byte { return []byte(newSecureID(h.Size())) } // GCResult returns the number of objects deleted by garbage collection. type GCResult struct { AuthRequests int64 AuthCodes int64 DeviceRequests int64 DeviceTokens int64 AuthSessions int64 } // IsEmpty returns whether the garbage collection result is empty or not. func (g *GCResult) IsEmpty() bool { return g.AuthRequests == 0 && g.AuthCodes == 0 && g.DeviceRequests == 0 && g.DeviceTokens == 0 && g.AuthSessions == 0 } // Storage is the storage interface used by the server. Implementations are // required to be able to perform atomic compare-and-swap updates and either // support timezones or standardize on UTC. type Storage interface { Close() error // TODO(ericchiang): Let the storages set the IDs of these objects. CreateAuthRequest(ctx context.Context, a AuthRequest) error CreateClient(ctx context.Context, c Client) error CreateAuthCode(ctx context.Context, c AuthCode) error CreateRefresh(ctx context.Context, r RefreshToken) error CreatePassword(ctx context.Context, p Password) error CreateOfflineSessions(ctx context.Context, s OfflineSessions) error CreateUserIdentity(ctx context.Context, u UserIdentity) error CreateAuthSession(ctx context.Context, s AuthSession) error CreateConnector(ctx context.Context, c Connector) error CreateDeviceRequest(ctx context.Context, d DeviceRequest) error CreateDeviceToken(ctx context.Context, d DeviceToken) error // TODO(ericchiang): return (T, bool, error) so we can indicate not found // requests that way instead of using ErrNotFound. GetAuthRequest(ctx context.Context, id string) (AuthRequest, error) GetAuthCode(ctx context.Context, id string) (AuthCode, error) GetClient(ctx context.Context, id string) (Client, error) GetKeys(ctx context.Context) (Keys, error) GetRefresh(ctx context.Context, id string) (RefreshToken, error) GetPassword(ctx context.Context, email string) (Password, error) GetOfflineSessions(ctx context.Context, userID string, connID string) (OfflineSessions, error) GetUserIdentity(ctx context.Context, userID, connectorID string) (UserIdentity, error) GetAuthSession(ctx context.Context, userID, connectorID string) (AuthSession, error) GetConnector(ctx context.Context, id string) (Connector, error) GetDeviceRequest(ctx context.Context, userCode string) (DeviceRequest, error) GetDeviceToken(ctx context.Context, deviceCode string) (DeviceToken, error) ListClients(ctx context.Context) ([]Client, error) ListRefreshTokens(ctx context.Context) ([]RefreshToken, error) ListPasswords(ctx context.Context) ([]Password, error) ListConnectors(ctx context.Context) ([]Connector, error) ListUserIdentities(ctx context.Context) ([]UserIdentity, error) ListAuthSessions(ctx context.Context) ([]AuthSession, error) // Delete methods MUST be atomic. DeleteAuthRequest(ctx context.Context, id string) error DeleteAuthCode(ctx context.Context, code string) error DeleteClient(ctx context.Context, id string) error DeleteRefresh(ctx context.Context, id string) error DeletePassword(ctx context.Context, email string) error DeleteOfflineSessions(ctx context.Context, userID string, connID string) error DeleteUserIdentity(ctx context.Context, userID, connectorID string) error DeleteAuthSession(ctx context.Context, userID, connectorID string) error DeleteConnector(ctx context.Context, id string) error // Update methods take a function for updating an object then performs that update within // a transaction. "updater" functions may be called multiple times by a single update call. // // Because new fields may be added to resources, updaters should only modify existing // fields on the old object rather then creating new structs. For example: // // updater := func(old storage.Client) (storage.Client, error) { // old.Secret = newSecret // return old, nil // } // if err := s.UpdateClient(clientID, updater); err != nil { // // update failed, handle error // } // UpdateClient(ctx context.Context, id string, updater func(old Client) (Client, error)) error UpdateKeys(ctx context.Context, updater func(old Keys) (Keys, error)) error UpdateAuthRequest(ctx context.Context, id string, updater func(a AuthRequest) (AuthRequest, error)) error UpdateRefreshToken(ctx context.Context, id string, updater func(r RefreshToken) (RefreshToken, error)) error UpdatePassword(ctx context.Context, email string, updater func(p Password) (Password, error)) error UpdateOfflineSessions(ctx context.Context, userID string, connID string, updater func(s OfflineSessions) (OfflineSessions, error)) error UpdateUserIdentity(ctx context.Context, userID, connectorID string, updater func(u UserIdentity) (UserIdentity, error)) error UpdateAuthSession(ctx context.Context, userID, connectorID string, updater func(s AuthSession) (AuthSession, error)) error UpdateConnector(ctx context.Context, id string, updater func(c Connector) (Connector, error)) error UpdateDeviceToken(ctx context.Context, deviceCode string, updater func(t DeviceToken) (DeviceToken, error)) error // GarbageCollect deletes all expired AuthCodes, // AuthRequests, DeviceRequests, and DeviceTokens. GarbageCollect(ctx context.Context, now time.Time) (GCResult, error) } // Client represents an OAuth2 client. // // For further reading see: // - Trusted peers: https://developers.google.com/identity/protocols/CrossClientAuth // - Public clients: https://developers.google.com/api-client-library/python/auth/installed-app type Client struct { // Client ID and secret used to identify the client. ID string `json:"id"` IDEnv string `json:"idEnv"` Secret string `json:"secret"` SecretEnv string `json:"secretEnv"` // A registered set of redirect URIs. When redirecting from dex to the client, the URI // requested to redirect to MUST match one of these values, unless the client is "public". RedirectURIs []string `json:"redirectURIs"` // TrustedPeers are a list of peers which can issue tokens on this client's behalf using // the dynamic "oauth2:server:client_id:(client_id)" scope. If a peer makes such a request, // this client's ID will appear as the ID Token's audience. // // Clients inherently trust themselves. TrustedPeers []string `json:"trustedPeers"` // Public clients must use either use a redirectURL 127.0.0.1:X or "urn:ietf:wg:oauth:2.0:oob" Public bool `json:"public"` // Name and LogoURL used when displaying this client to the end user. Name string `json:"name"` LogoURL string `json:"logoURL"` // AllowedConnectors is a list of connector IDs that the client is allowed to use for authentication. // If empty, all connectors are allowed. AllowedConnectors []string `json:"allowedConnectors"` // MFAChain is an ordered list of MFA authenticator IDs that a user must complete // during login. Empty means no MFA required. MFAChain []string `json:"mfaChain"` } // Claims represents the ID Token claims supported by the server. type Claims struct { UserID string Username string PreferredUsername string Email string EmailVerified bool Groups []string } // PKCE is a container for the data needed to perform Proof Key for Code Exchange (RFC 7636) auth flow type PKCE struct { CodeChallenge string CodeChallengeMethod string } // AuthRequest represents a OAuth2 client authorization request. It holds the state // of a single auth flow up to the point that the user authorizes the client. type AuthRequest struct { // ID used to identify the authorization request. ID string // ID of the client requesting authorization from a user. ClientID string // Values parsed from the initial request. These describe the resources the client is // requesting as well as values describing the form of the response. ResponseTypes []string Scopes []string RedirectURI string Nonce string State string // The client has indicated that the end user must be shown an approval prompt // on all requests. The server cannot cache their initial action for subsequent // attempts. ForceApprovalPrompt bool // OIDC prompt parameter. Controls authentication and consent UI behavior. // Values: "none", "login", "consent", "select_account". Prompt string // MaxAge is the OIDC max_age parameter — maximum allowable elapsed time // in seconds since the user last actively authenticated. // -1 means not specified. MaxAge int // AuthTime is when the user last actively authenticated (entered credentials). // Set during finalizeLogin (= now) or trySessionLogin (= UserIdentity.LastLogin). // Used in ID token as "auth_time" claim. AuthTime time.Time Expiry time.Time // Has the user proved their identity through a backing identity provider? // // If false, the following fields are invalid. LoggedIn bool // The identity of the end user. Generally nil until the user authenticates // with a backend. Claims Claims // The connector used to login the user and any data the connector wishes to persists. // Set when the user authenticates. ConnectorID string ConnectorData []byte // PKCE CodeChallenge and CodeChallengeMethod PKCE PKCE // HMACKey is used when generating an AuthRequest-specific HMAC HMACKey []byte // MFAValidated is set to true if the user has completed multi-factor authentication. MFAValidated bool } // AuthCode represents a code which can be exchanged for an OAuth2 token response. // // This value is created once an end user has authorized a client, the server has // redirect the end user back to the client, but the client hasn't exchanged the // code for an access_token and id_token. type AuthCode struct { // Actual string returned as the "code" value. ID string // The client this code value is valid for. When exchanging the code for a // token response, the client must use its client_secret to authenticate. ClientID string // As part of the OAuth2 spec when a client makes a token request it MUST // present the same redirect_uri as the initial redirect. This values is saved // to make this check. // // https://tools.ietf.org/html/rfc6749#section-4.1.3 RedirectURI string // If provided by the client in the initial request, the provider MUST create // a ID Token with this nonce in the JWT payload. Nonce string // Scopes authorized by the end user for the client. Scopes []string // Authentication data provided by an upstream source. ConnectorID string ConnectorData []byte Claims Claims Expiry time.Time // AuthTime is when the user last actively authenticated. // Carried over from AuthRequest to include in ID tokens. AuthTime time.Time // PKCE CodeChallenge and CodeChallengeMethod PKCE PKCE } // RefreshToken is an OAuth2 refresh token which allows a client to request new // tokens on the end user's behalf. type RefreshToken struct { ID string // A single token that's rotated every time the refresh token is refreshed. // // May be empty. Token string ObsoleteToken string CreatedAt time.Time LastUsed time.Time // Client this refresh token is valid for. ClientID string // Authentication data provided by an upstream source. ConnectorID string ConnectorData []byte Claims Claims // Scopes present in the initial request. Refresh requests may specify a set // of scopes different from the initial request when refreshing a token, // however those scopes must be encompassed by this set. Scopes []string // Nonce value supplied during the initial redirect. This is required to be part // of the claims of any future id_token generated by the client. Nonce string } // RefreshTokenRef is a reference object that contains metadata about refresh tokens. type RefreshTokenRef struct { ID string // Client the refresh token is valid for. ClientID string CreatedAt time.Time LastUsed time.Time } // MFASecret stores the enrollment state and secret for an MFA authenticator. // Note: Secret is stored without encryption. Encrypting secrets at rest is the // responsibility of the storage backend (e.g., encrypted etcd, disk encryption). type MFASecret struct { AuthenticatorID string `json:"authenticatorID"` Type string `json:"type"` Secret string `json:"secret"` Confirmed bool `json:"confirmed"` CreatedAt time.Time `json:"createdAt"` } // UserIdentity represents persistent per-user identity data. type UserIdentity struct { UserID string ConnectorID string Claims Claims Consents map[string][]string // clientID -> approved scopes MFASecrets map[string]*MFASecret // authenticatorID -> secret CreatedAt time.Time LastLogin time.Time BlockedUntil time.Time } // ClientAuthState represents authentication state for a specific client within an auth session. type ClientAuthState struct { Active bool ExpiresAt time.Time LastActivity time.Time LastTokenIssuedAt time.Time } // AuthSession represents a user's authentication session from a specific connector. // Keyed by composite (UserID, ConnectorID), similar to OfflineSessions. // The Nonce field is a random value included in the session cookie to prevent forgery. // // TODO(nabokihms): support multiple sessions in one browser by storing multiple // session references in the cookie (e.g. "ref1|ref2") so that different users // can maintain independent sessions in the same browser. type AuthSession struct { UserID string ConnectorID string Nonce string // random, included in cookie for verification ClientStates map[string]*ClientAuthState // clientID -> auth state CreatedAt time.Time LastActivity time.Time IPAddress string UserAgent string // AbsoluteExpiry is CreatedAt + AbsoluteLifetime, set once at creation. AbsoluteExpiry time.Time // IdleExpiry is LastActivity + ValidIfNotUsedFor, updated on every activity. IdleExpiry time.Time } // OfflineSessions objects are sessions pertaining to users with refresh tokens. type OfflineSessions struct { // UserID of an end user who has logged into the server. UserID string // The ID of the connector used to login the user. ConnID string // Refresh is a hash table of refresh token reference objects // indexed by the ClientID of the refresh token. Refresh map[string]*RefreshTokenRef // Authentication data provided by an upstream source. ConnectorData []byte } // Password is an email to password mapping managed by the storage. type Password struct { // Email and identifying name of the password. Emails are assumed to be valid and // determining that an end-user controls the address is left to an outside application. // // Emails are case insensitive and should be standardized by the storage. // // Storages that don't support an extended character set for IDs, such as '.' and '@' // (cough cough, kubernetes), must map this value appropriately. Email string `json:"email"` // Bcrypt encoded hash of the password. This package enforces a min cost value of 10 Hash []byte `json:"hash"` // Bcrypt encoded hash of the password set in environment variable of this name. HashFromEnv string `json:"hashFromEnv"` // Optional username to display. NOT used during login. Username string `json:"username"` // Optional full name for OIDC "name" claim. // Defaults to Username when empty. Name string `json:"name"` // Optional preferred username for OIDC "preferred_username" claim. PreferredUsername string `json:"preferredUsername"` // Optional value for OIDC "email_verified" claim. // Defaults to true for backwards compatibility when nil. EmailVerified *bool `json:"emailVerified,omitempty"` // Randomly generated user ID. This is NOT the primary ID of the Password object. UserID string `json:"userID"` // Groups assigned to the user Groups []string `json:"groups"` } // Connector is an object that contains the metadata about connectors used to login to Dex. type Connector struct { // ID that will uniquely identify the connector object. ID string `json:"id"` // The Type of the connector. E.g. 'oidc' or 'ldap' Type string `json:"type"` // The Name of the connector that is used when displaying it to the end user. Name string `json:"name"` // ResourceVersion is the static versioning used to keep track of dynamic configuration // changes to the connector object made by the API calls. ResourceVersion string `json:"resourceVersion"` // Config holds all the configuration information specific to the connector type. Since there // no generic struct we can use for this purpose, it is stored as a byte stream. // // NOTE: This is a bug. The JSON tag should be `config`. // However, fixing this requires migrating Kubernetes objects for all previously created connectors, // or making Dex reading both tags and act accordingly. Config []byte `json:"email"` // GrantTypes is a list of grant types that this connector is allowed to be used with. // If empty, all grant types are allowed. GrantTypes []string `json:"grantTypes,omitempty"` } // VerificationKey is a rotated signing key which can still be used to verify // signatures. type VerificationKey struct { PublicKey *jose.JSONWebKey `json:"publicKey"` Expiry time.Time `json:"expiry"` } // Keys hold encryption and signing keys. type Keys struct { // Key for creating and verifying signatures. These may be nil. SigningKey *jose.JSONWebKey SigningKeyPub *jose.JSONWebKey // Old signing keys which have been rotated but can still be used to validate // existing signatures. VerificationKeys []VerificationKey // The next time the signing key will rotate. // // For caching purposes, implementations MUST NOT update keys before this time. NextRotation time.Time } // NewUserCode returns a randomized 8 character user code for the device flow. // No vowels are included to prevent accidental generation of words func NewUserCode() string { code := randomString(8) return code[:4] + "-" + code[4:] } func randomString(n int) string { v := big.NewInt(int64(len(validUserCharacters))) bytes := make([]byte, n) for i := 0; i < n; i++ { c, _ := rand.Int(rand.Reader, v) bytes[i] = validUserCharacters[c.Int64()] } return string(bytes) } // DeviceRequest represents an OIDC device authorization request. It holds the state of a device request until the user // authenticates using their user code or the expiry time passes. type DeviceRequest struct { // The code the user will enter in a browser UserCode string // The unique device code for device authentication DeviceCode string // The client ID the code is for ClientID string // The Client Secret ClientSecret string // The scopes the device requests Scopes []string // The expire time Expiry time.Time } // DeviceToken is a structure which represents the actual token of an authorized device and its rotation parameters type DeviceToken struct { DeviceCode string Status string Token string Expiry time.Time LastRequestTime time.Time PollIntervalSeconds int PKCE PKCE } ================================================ FILE: web/robots.txt ================================================ User-agent: * Disallow: / ================================================ FILE: web/static/main.css ================================================ * { box-sizing: border-box; } body { margin: 0; } .dex-container { color: #333; margin: 60px auto; max-width: 480px; min-width: 320px; padding: 0 16px; text-align: center; } .dex-btn { border-radius: 8px; border: 0; cursor: pointer; font-size: 15px; padding: 0; transition: background-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease; } .dex-btn:focus-visible { outline: 2px solid #4A90D9; outline-offset: 2px; } .dex-btn:active { transform: scale(0.98); } .dex-btn:disabled { cursor: not-allowed; opacity: 0.5; } .dex-btn-icon { background-position: center; background-repeat: no-repeat; background-size: 20px; border-radius: 8px 0 0 8px; float: left; height: 40px; margin-right: 4px; width: 40px; } .dex-btn-icon--google { background-color: #FFFFFF; background-image: url(../static/img/google-icon.svg); } .dex-btn-icon--local { background-color: #84B6EF; background-image: url(../static/img/email-icon.svg); } .dex-btn-icon--gitea { background-color: #F5F5F5; background-image: url(../static/img/gitea-icon.svg); } .dex-btn-icon--github { background-color: #F5F5F5; background-image: url(../static/img/github-icon.svg); } .dex-btn-icon--gitlab { background-color: #F5F5F5; background-image: url(../static/img/gitlab-icon.svg); background-size: contain; } .dex-btn-icon--keystone { background-color: #F5F5F5; background-image: url(../static/img/keystone-icon.svg); background-size: contain; } .dex-btn-icon--oidc { background-color: #EBEBEE; background-image: url(../static/img/oidc-icon.svg); background-size: contain; } .dex-btn-icon--bitbucket-cloud { background-color: #205081; background-image: url(../static/img/bitbucket-icon.svg); } .dex-btn-icon--atlassian-crowd { background-color: #CFDCEA; background-image: url(../static/img/atlassian-crowd-icon.svg); } .dex-btn-icon--ldap { background-color: #84B6EF; background-image: url(../static/img/ldap-icon.svg); } .dex-btn-icon--saml { background-color: #84B6EF; background-image: url(../static/img/saml-icon.svg); } .dex-btn-icon--linkedin { background-image: url(../static/img/linkedin-icon.svg); background-size: contain; } .dex-btn-icon--microsoft { background-image: url(../static/img/microsoft-icon.svg); } .dex-btn-icon--mockCallback, .dex-btn-icon--mockPassword { background-color: #6c5ce7; background-image: url(../static/img/mock-icon.svg); } .dex-btn-text { font-weight: 600; line-height: 40px; padding: 6px 14px; text-align: center; } .dex-subtle-text { color: #888; font-size: 13px; } .dex-separator { color: #aaa; } .dex-list { color: #888; display: inline-block; font-size: 13px; list-style: circle; text-align: left; } .dex-error-box { background-color: #e5383b; border-radius: 6px; color: #fff; font-size: 14px; font-weight: normal; margin: 20px auto; max-width: 320px; padding: 8px 12px; } ================================================ FILE: web/templates/approval.html ================================================ {{ template "header.html" . }}

Grant Access


{{ if .Scopes }}
{{ .Client }} would like to:
    {{ range $scope := .Scopes }}
  • {{ $scope }}
  • {{ end }}
{{ else }}
{{ .Client }} has not requested any personal information
{{ end }}

{{ template "footer.html" . }} ================================================ FILE: web/templates/device.html ================================================ {{ template "header.html" . }}

Enter User Code

{{ if( .UserCode )}} {{ else }} {{ end }}
{{ if .Invalid }}
Invalid or Expired User Code
{{ end }}
{{ template "footer.html" . }} ================================================ FILE: web/templates/device_success.html ================================================ {{ template "header.html" . }}

Login Successful for {{ .ClientName }}

Return to your device to continue

{{ template "footer.html" . }} ================================================ FILE: web/templates/error.html ================================================ {{ template "header.html" . }}

{{ .ErrType }}

{{ .ErrMsg }}

{{ template "footer.html" . }} ================================================ FILE: web/templates/footer.html ================================================ ================================================ FILE: web/templates/header.html ================================================ {{ issuer }}
================================================ FILE: web/templates/login.html ================================================ {{ template "header.html" . }}

Log in to {{ issuer }}

{{ range $c := .Connectors }} {{ end }}
{{ template "footer.html" . }} ================================================ FILE: web/templates/oob.html ================================================ {{ template "header.html" . }}

Login Successful

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

{{ template "footer.html" . }} ================================================ FILE: web/templates/password.html ================================================ {{ template "header.html" . }}

Log in to Your Account

{{ if .Invalid }}
Invalid {{ .UsernamePrompt }} and password.
{{ end }} {{ if .ShowRememberMe }}
{{ end }}
{{ if .BackLink }} {{ end }}
{{ template "footer.html" . }} ================================================ FILE: web/templates/totp_verify.html ================================================ {{ template "header.html" . }}

Two-factor authentication

{{ if not (eq .QRCode "") }}

Scan the QR code below using your authenticator app, then enter the code.

QR code
{{ else }}

Enter the code from your authenticator app.

{{ .Issuer }}

{{ end }}
{{ if .Invalid }}
Invalid code. Please try again.
{{ end }}
{{ template "footer.html" . }} ================================================ FILE: web/themes/dark/styles.css ================================================ .theme-body { background-color: #131519; color: #b8bcc4; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; } .theme-navbar { background-color: #1a1d23; border-bottom: 1px solid #2a2d35; color: #b8bcc4; font-size: 13px; font-weight: 400; height: 52px; overflow: hidden; padding: 0 16px; } .theme-navbar__logo-wrap { display: inline-block; height: 100%; overflow: hidden; padding: 12px 15px; width: 300px; } .theme-navbar__logo { height: 100%; max-height: 26px; } .theme-heading { color: #dcdfe5; font-size: 20px; font-weight: 600; margin-bottom: 16px; margin-top: 0; } .theme-panel { background-color: #1a1d23; border: 1px solid #2a2d35; border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); padding: 32px; } .theme-btn-provider { background-color: #22252c; border: 1px solid #33363e; color: #b8bcc4; min-width: 260px; } .theme-btn-provider:hover { background-color: #2a2d35; border-color: #3e414a; color: #dcdfe5; } .theme-btn--primary { background-color: #3d3f47; border: none; color: #e8eaed; min-width: 200px; padding: 8px 16px; } .theme-btn--primary:hover { background-color: #4a4c55; color: #fff; } .theme-btn--success { background-color: #2d7d9a; color: #e8eaed; width: 260px; } .theme-btn--success:hover { background-color: #358fae; } .theme-form-row { display: block; margin: 16px auto; } .theme-form-input { background-color: #131519; border: 1px solid #33363e; border-radius: 8px; color: #b8bcc4; display: block; font-size: 14px; height: 40px; line-height: 1.5; margin: auto; padding: 8px 12px; transition: border-color 0.15s ease, box-shadow 0.15s ease; width: 260px; } .theme-form-input:focus, .theme-form-input:active { border-color: #5a9bb5; box-shadow: 0 0 0 3px rgba(90, 155, 181, 0.15); color: #dcdfe5; outline: none; } .theme-form-label { color: #b8bcc4; font-size: 14px; font-weight: 500; margin: 4px auto; position: relative; text-align: left; width: 260px; } .theme-link-back { margin-top: 8px; } .theme-remember-me { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; width: 260px; margin: 4px auto; } .dex-container { color: #b8bcc4; } ================================================ FILE: web/themes/light/styles.css ================================================ .theme-body { background-color: #f4f5f7; color: #1a1a1a; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; } .theme-navbar { background-color: #fff; border-bottom: 1px solid #e1e4e8; color: #1a1a1a; font-size: 13px; font-weight: 400; height: 52px; overflow: hidden; padding: 0 16px; } .theme-navbar__logo-wrap { display: inline-block; height: 100%; overflow: hidden; padding: 12px 15px; width: 300px; } .theme-navbar__logo { height: 100%; max-height: 26px; } .theme-heading { font-size: 20px; font-weight: 600; margin-bottom: 16px; margin-top: 0; } .theme-panel { background-color: #fff; border: 1px solid #e1e4e8; border-radius: 12px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06); padding: 32px; } .theme-btn-provider { background-color: #fff; border: 1px solid #d0d5dd; color: #1a1a1a; min-width: 260px; } .theme-btn-provider:hover { background-color: #f9fafb; border-color: #b0b5bd; color: #1a1a1a; } .theme-btn--primary { background-color: #1a1a1a; border: none; color: #fff; min-width: 200px; padding: 8px 16px; } .theme-btn--primary:hover { background-color: #333; color: #fff; } .theme-btn--success { background-color: #16a34a; color: #fff; width: 260px; } .theme-btn--success:hover { background-color: #15803d; } .theme-form-row { display: block; margin: 16px auto; } .theme-form-input { border-radius: 8px; border: 1px solid #d0d5dd; color: #1a1a1a; display: block; font-size: 14px; height: 40px; line-height: 1.5; margin: auto; padding: 8px 12px; transition: border-color 0.15s ease, box-shadow 0.15s ease; width: 260px; } .theme-form-input:focus, .theme-form-input:active { border-color: #4A90D9; box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.15); outline: none; } .theme-form-label { font-size: 14px; font-weight: 500; margin: 4px auto; position: relative; text-align: left; width: 260px; } .theme-link-back { margin-top: 8px; } .theme-remember-me { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; width: 260px; margin: 4px auto; } ================================================ FILE: web/web.go ================================================ package web import ( "embed" "io/fs" ) //go:embed static/* templates/* themes/* robots.txt var files embed.FS // FS returns a filesystem with the default web assets. func FS() fs.FS { return files }