snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
35
.docker/Dockerfile-alpine
Normal file
35
.docker/Dockerfile-alpine
Normal file
@@ -0,0 +1,35 @@
|
||||
FROM alpine:3.20.0
|
||||
|
||||
# Because this image supports SQLite, we create /home/ory and /home/ory/sqlite which is owned by the ory user
|
||||
# and declare /home/ory/sqlite a volume.
|
||||
#
|
||||
# To get SQLite and Docker Volumes working with this image, mount the volume where SQLite should be written to at:
|
||||
#
|
||||
# /home/ory/sqlite/some-file.
|
||||
|
||||
RUN addgroup -S ory; \
|
||||
adduser -S ory -G ory -D -u 10000 -h /home/ory -s /bin/nologin; \
|
||||
chown -R ory:ory /home/ory
|
||||
RUN apk --update upgrade && apk --no-cache --update-cache --upgrade --latest add ca-certificates
|
||||
|
||||
WORKDIR /home/ory
|
||||
|
||||
COPY kratos /usr/bin/kratos
|
||||
|
||||
# By creating the sqlite folder as the ory user, the mounted volume will be owned by ory:ory, which
|
||||
# is required for read/write of SQLite.
|
||||
RUN mkdir -p /var/lib/sqlite
|
||||
RUN chown ory:ory /var/lib/sqlite
|
||||
VOLUME /var/lib/sqlite
|
||||
|
||||
# Exposing the ory home directory to simplify passing in Kratos configuration (e.g. if the file $HOME/.kratos.yaml
|
||||
# exists, it will be automatically used as the configuration file).
|
||||
VOLUME /home/ory
|
||||
|
||||
# Declare the standard ports used by Kratos (4433 for public service endpoint, 4434 for admin service endpoint)
|
||||
EXPOSE 4433 4434
|
||||
|
||||
USER 10000
|
||||
|
||||
ENTRYPOINT ["kratos"]
|
||||
CMD ["serve"]
|
||||
46
.docker/Dockerfile-build
Normal file
46
.docker/Dockerfile-build
Normal file
@@ -0,0 +1,46 @@
|
||||
FROM golang:1.26-trixie AS builder
|
||||
|
||||
RUN apt-get update && apt-get upgrade -y &&\
|
||||
mkdir -p /var/lib/sqlite
|
||||
|
||||
WORKDIR /go/src/github.com/ory/kratos
|
||||
|
||||
COPY oryx/go.mod oryx/go.mod
|
||||
COPY oryx/go.sum oryx/go.sum
|
||||
|
||||
|
||||
COPY go.mod go.mod
|
||||
COPY go.sum go.sum
|
||||
COPY pkg/client-go/go.* pkg/client-go/
|
||||
|
||||
ENV CGO_ENABLED 0
|
||||
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG VERSION
|
||||
ARG COMMIT
|
||||
ARG BUILD_DATE
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build go build \
|
||||
-ldflags="-X 'github.com/ory/kratos/driver/config.Version=${VERSION}' -X 'github.com/ory/kratos/driver/config.Date=${BUILD_DATE}' -X 'github.com/ory/kratos/driver/config.Commit=${COMMIT}'" \
|
||||
-o /usr/bin/kratos
|
||||
|
||||
#########################
|
||||
# Kratos is built with CGO_ENABLED=0, so it needs no glibc at runtime. The static
|
||||
# base carries none, which keeps this image free of the unfixable libc6
|
||||
# advisories the base-nossl variant ships with, and matches the base the released
|
||||
# images use (.docker/Dockerfile-distroless-static).
|
||||
FROM gcr.io/distroless/static-debian12:nonroot AS runner
|
||||
|
||||
COPY --from=builder --chown=nonroot:nonroot /var/lib/sqlite /var/lib/sqlite
|
||||
COPY --from=builder --chown=nonroot:nonroot /usr/bin/kratos /usr/bin/kratos
|
||||
|
||||
VOLUME /var/lib/sqlite
|
||||
|
||||
# Declare the standard ports used by Kratos (4433 for public service endpoint, 4434 for admin service endpoint)
|
||||
EXPOSE 4433 4434
|
||||
|
||||
ENTRYPOINT ["kratos"]
|
||||
CMD ["serve"]
|
||||
18
.docker/Dockerfile-debug
Normal file
18
.docker/Dockerfile-debug
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM golang:1.26-trixie
|
||||
ENV CGO_ENABLED 0
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends inotify-tools psmisc
|
||||
RUN go install github.com/go-delve/delve/cmd/dlv@latest
|
||||
|
||||
COPY script/debug-entrypoint.sh /entrypoint.sh
|
||||
|
||||
VOLUME /dockerdev
|
||||
|
||||
WORKDIR /dockerdev
|
||||
|
||||
ENV DELVE_PORT 40000
|
||||
ENV SERVICE_NAME service
|
||||
|
||||
EXPOSE 8000 $DELVE_PORT
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
7
.docker/Dockerfile-distroless-static
Normal file
7
.docker/Dockerfile-distroless-static
Normal file
@@ -0,0 +1,7 @@
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
|
||||
COPY kratos /usr/bin/kratos
|
||||
EXPOSE 4433 4434
|
||||
|
||||
ENTRYPOINT ["kratos"]
|
||||
CMD ["serve"]
|
||||
17
.docker/docker-compose.template.dbg
Normal file
17
.docker/docker-compose.template.dbg
Normal file
@@ -0,0 +1,17 @@
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
${SERVICE_NAME}:
|
||||
build:
|
||||
dockerfile: ./.docker/Dockerfile-debug
|
||||
ports:
|
||||
- ${REMOTE_DEBUGGING_PORT}:40000
|
||||
security_opt:
|
||||
- apparmor=unconfined
|
||||
cap_add:
|
||||
- SYS_PTRACE
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ${SERVICE_ROOT}
|
||||
target: /dockerdev
|
||||
read_only: false
|
||||
16
.dockerignore
Normal file
16
.dockerignore
Normal file
@@ -0,0 +1,16 @@
|
||||
docs
|
||||
.releaser
|
||||
.github
|
||||
.circleci
|
||||
tmp
|
||||
scripts
|
||||
.idea
|
||||
.git/
|
||||
database.yaml
|
||||
contrib/quickstart
|
||||
node_modules/
|
||||
./quickstart.yml
|
||||
./quickstart-*.yml
|
||||
.bin/
|
||||
test/
|
||||
pgked.go
|
||||
11
.editorconfig
Normal file
11
.editorconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.go]
|
||||
indent_size = 4
|
||||
indent_style = tab
|
||||
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @aeneasr @ory/product-development
|
||||
8
.github/FUNDING.yml
vendored
Normal file
8
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/FUNDING.yml
|
||||
|
||||
# These are supported funding model platforms
|
||||
|
||||
# github:
|
||||
patreon: _ory
|
||||
open_collective: ory
|
||||
122
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
Normal file
122
.github/ISSUE_TEMPLATE/BUG-REPORT.yml
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/ISSUE_TEMPLATE/BUG-REPORT.yml
|
||||
|
||||
description: "Create a bug report"
|
||||
labels:
|
||||
- bug
|
||||
name: "Bug Report"
|
||||
body:
|
||||
- attributes:
|
||||
value: "Thank you for taking the time to fill out this bug report!\n"
|
||||
type: markdown
|
||||
- attributes:
|
||||
label: "Preflight checklist"
|
||||
options:
|
||||
- label:
|
||||
"I could not find a solution in the existing issues, docs, nor
|
||||
discussions."
|
||||
required: true
|
||||
- label:
|
||||
"I agree to follow this project's [Code of
|
||||
Conduct](https://github.com/ory/kratos/blob/master/CODE_OF_CONDUCT.md)."
|
||||
required: true
|
||||
- label:
|
||||
"I have read and am following this repository's [Contribution
|
||||
Guidelines](https://github.com/ory/kratos/blob/master/CONTRIBUTING.md)."
|
||||
required: true
|
||||
- label:
|
||||
"I have joined the [Ory Community Slack](https://slack.ory.com)."
|
||||
- label:
|
||||
"I am signed up to the [Ory Security Patch
|
||||
Newsletter](https://www.ory.com/l/sign-up-newsletter)."
|
||||
id: checklist
|
||||
type: checkboxes
|
||||
- attributes:
|
||||
description:
|
||||
"Enter the slug or API URL of the affected Ory Network project. Leave
|
||||
empty when you are self-hosting."
|
||||
label: "Ory Network Project"
|
||||
placeholder: "https://<your-project-slug>.projects.oryapis.com"
|
||||
id: ory-network-project
|
||||
type: input
|
||||
- attributes:
|
||||
description: "A clear and concise description of what the bug is."
|
||||
label: "Describe the bug"
|
||||
placeholder: "Tell us what you see!"
|
||||
id: describe-bug
|
||||
type: textarea
|
||||
validations:
|
||||
required: true
|
||||
- attributes:
|
||||
description: |
|
||||
Clear, formatted, and easy to follow steps to reproduce the behavior:
|
||||
placeholder: |
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Run `docker run ....`
|
||||
2. Make API Request to with `curl ...`
|
||||
3. Request fails with response: `{"some": "error"}`
|
||||
label: "Reproducing the bug"
|
||||
id: reproduce-bug
|
||||
type: textarea
|
||||
validations:
|
||||
required: true
|
||||
- attributes:
|
||||
description:
|
||||
"Please copy and paste any relevant log output. This will be
|
||||
automatically formatted into code, so no need for backticks. Please
|
||||
redact any sensitive information"
|
||||
label: "Relevant log output"
|
||||
render: shell
|
||||
placeholder: |
|
||||
log=error ....
|
||||
id: logs
|
||||
type: textarea
|
||||
- attributes:
|
||||
description:
|
||||
"Please copy and paste any relevant configuration. This will be
|
||||
automatically formatted into code, so no need for backticks. Please
|
||||
redact any sensitive information!"
|
||||
label: "Relevant configuration"
|
||||
render: yml
|
||||
placeholder: |
|
||||
server:
|
||||
admin:
|
||||
port: 1234
|
||||
id: config
|
||||
type: textarea
|
||||
- attributes:
|
||||
description: "What version of our software are you running?"
|
||||
label: Version
|
||||
id: version
|
||||
type: input
|
||||
validations:
|
||||
required: true
|
||||
- attributes:
|
||||
label: "On which operating system are you observing this issue?"
|
||||
options:
|
||||
- Ory Network
|
||||
- macOS
|
||||
- Linux
|
||||
- Windows
|
||||
- FreeBSD
|
||||
- Other
|
||||
id: operating-system
|
||||
type: dropdown
|
||||
- attributes:
|
||||
label: "In which environment are you deploying?"
|
||||
options:
|
||||
- Ory Network
|
||||
- Docker
|
||||
- "Docker Compose"
|
||||
- "Kubernetes with Helm"
|
||||
- Kubernetes
|
||||
- Binary
|
||||
- Other
|
||||
id: deployment
|
||||
type: dropdown
|
||||
- attributes:
|
||||
description: "Add any other context about the problem here."
|
||||
label: Additional Context
|
||||
id: additional
|
||||
type: textarea
|
||||
125
.github/ISSUE_TEMPLATE/DESIGN-DOC.yml
vendored
Normal file
125
.github/ISSUE_TEMPLATE/DESIGN-DOC.yml
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/ISSUE_TEMPLATE/DESIGN-DOC.yml
|
||||
|
||||
description:
|
||||
"A design document is needed for non-trivial changes to the code base."
|
||||
labels:
|
||||
- rfc
|
||||
name: "Design Document"
|
||||
body:
|
||||
- attributes:
|
||||
value: |
|
||||
Thank you for writing this design document.
|
||||
|
||||
One of the key elements of Ory's software engineering culture is the use of defining software designs through design docs. These are relatively informal documents that the primary author or authors of a software system or application create before they embark on the coding project. The design doc documents the high level implementation strategy and key design decisions with emphasis on the trade-offs that were considered during those decisions.
|
||||
|
||||
Ory is leaning heavily on [Google's design docs process](https://www.industrialempathy.com/posts/design-docs-at-google/)
|
||||
and [Golang Proposals](https://github.com/golang/proposal).
|
||||
|
||||
Writing a design doc before contributing your change ensures that your ideas are checked with
|
||||
the community and maintainers. It will save you a lot of time developing things that might need to be changed
|
||||
after code reviews, and your pull requests will be merged faster.
|
||||
type: markdown
|
||||
- attributes:
|
||||
label: "Preflight checklist"
|
||||
options:
|
||||
- label:
|
||||
"I could not find a solution in the existing issues, docs, nor
|
||||
discussions."
|
||||
required: true
|
||||
- label:
|
||||
"I agree to follow this project's [Code of
|
||||
Conduct](https://github.com/ory/kratos/blob/master/CODE_OF_CONDUCT.md)."
|
||||
required: true
|
||||
- label:
|
||||
"I have read and am following this repository's [Contribution
|
||||
Guidelines](https://github.com/ory/kratos/blob/master/CONTRIBUTING.md)."
|
||||
required: true
|
||||
- label:
|
||||
"I have joined the [Ory Community Slack](https://slack.ory.com)."
|
||||
- label:
|
||||
"I am signed up to the [Ory Security Patch
|
||||
Newsletter](https://www.ory.com/l/sign-up-newsletter)."
|
||||
id: checklist
|
||||
type: checkboxes
|
||||
- attributes:
|
||||
description:
|
||||
"Enter the slug or API URL of the affected Ory Network project. Leave
|
||||
empty when you are self-hosting."
|
||||
label: "Ory Network Project"
|
||||
placeholder: "https://<your-project-slug>.projects.oryapis.com"
|
||||
id: ory-network-project
|
||||
type: input
|
||||
- attributes:
|
||||
description: |
|
||||
This section gives the reader a very rough overview of the landscape in which the new system is being built and what is actually being built. This isn’t a requirements doc. Keep it succinct! The goal is that readers are brought up to speed but some previous knowledge can be assumed and detailed info can be linked to. This section should be entirely focused on objective background facts.
|
||||
label: "Context and scope"
|
||||
id: scope
|
||||
type: textarea
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- attributes:
|
||||
description: |
|
||||
A short list of bullet points of what the goals of the system are, and, sometimes more importantly, what non-goals are. Note, that non-goals aren’t negated goals like “The system shouldn’t crash”, but rather things that could reasonably be goals, but are explicitly chosen not to be goals. A good example would be “ACID compliance”; when designing a database, you’d certainly want to know whether that is a goal or non-goal. And if it is a non-goal you might still select a solution that provides it, if it doesn’t introduce trade-offs that prevent achieving the goals.
|
||||
label: "Goals and non-goals"
|
||||
id: goals
|
||||
type: textarea
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- attributes:
|
||||
description: |
|
||||
This section should start with an overview and then go into details.
|
||||
The design doc is the place to write down the trade-offs you made in designing your software. Focus on those trade-offs to produce a useful document with long-term value. That is, given the context (facts), goals and non-goals (requirements), the design doc is the place to suggest solutions and show why a particular solution best satisfies those goals.
|
||||
|
||||
The point of writing a document over a more formal medium is to provide the flexibility to express the problem at hand in an appropriate manner. Because of this, there is no explicit guidance on how to actually describe the design.
|
||||
label: "The design"
|
||||
id: design
|
||||
type: textarea
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- attributes:
|
||||
description: |
|
||||
If the system under design exposes an API, then sketching out that API is usually a good idea. In most cases, however, one should withstand the temptation to copy-paste formal interface or data definitions into the doc as these are often verbose, contain unnecessary detail and quickly get out of date. Instead, focus on the parts that are relevant to the design and its trade-offs.
|
||||
label: "APIs"
|
||||
id: apis
|
||||
type: textarea
|
||||
|
||||
- attributes:
|
||||
description: |
|
||||
Systems that store data should likely discuss how and in what rough form this happens. Similar to the advice on APIs, and for the same reasons, copy-pasting complete schema definitions should be avoided. Instead, focus on the parts that are relevant to the design and its trade-offs.
|
||||
label: "Data storage"
|
||||
id: persistence
|
||||
type: textarea
|
||||
|
||||
- attributes:
|
||||
description: |
|
||||
Design docs should rarely contain code, or pseudo-code except in situations where novel algorithms are described. As appropriate, link to prototypes that show the feasibility of the design.
|
||||
label: "Code and pseudo-code"
|
||||
id: pseudocode
|
||||
type: textarea
|
||||
|
||||
- attributes:
|
||||
description: |
|
||||
One of the primary factors that would influence the shape of a software design and hence the design doc, is the degree of constraint of the solution space.
|
||||
|
||||
On one end of the extreme is the “greenfield software project”, where all we know are the goals, and the solution can be whatever makes the most sense. Such a document may be wide-ranging, but it also needs to quickly define a set of rules that allow zooming in on a manageable set of solutions.
|
||||
|
||||
On the other end are systems where the possible solutions are very well defined, but it isn't at all obvious how they could even be combined to achieve the goals. This may be a legacy system that is difficult to change and wasn't designed to do what you want it to do or a library design that needs to operate within the constraints of the host programming language.
|
||||
|
||||
In this situation, you may be able to enumerate all the things you can do relatively easily, but you need to creatively put those things together to achieve the goals. There may be multiple solutions, and none of them are great, and hence such a document should focus on selecting the best way given all identified trade-offs.
|
||||
label: "Degree of constraint"
|
||||
id: constrait
|
||||
type: textarea
|
||||
|
||||
- attributes:
|
||||
description: |
|
||||
This section lists alternative designs that would have reasonably achieved similar outcomes. The focus should be on the trade-offs that each respective design makes and how those trade-offs led to the decision to select the design that is the primary topic of the document.
|
||||
|
||||
While it is fine to be succinct about a solution that ended up not being selected, this section is one of the most important ones as it shows very explicitly why the selected solution is the best given the project goals and how other solutions, that the reader may be wondering about, introduce trade-offs that are less desirable given the goals.
|
||||
|
||||
label: Alternatives considered
|
||||
id: alternatives
|
||||
type: textarea
|
||||
86
.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml
vendored
Normal file
86
.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml
|
||||
|
||||
description:
|
||||
"Suggest an idea for this project without a plan for implementation"
|
||||
labels:
|
||||
- feat
|
||||
name: "Feature Request"
|
||||
body:
|
||||
- attributes:
|
||||
value: |
|
||||
Thank you for suggesting an idea for this project!
|
||||
|
||||
If you already have a plan to implement a feature or a change, please create a [design document](https://github.com/aeneasr/gh-template-test/issues/new?assignees=&labels=rfc&template=DESIGN-DOC.yml) instead if the change is non-trivial!
|
||||
type: markdown
|
||||
- attributes:
|
||||
label: "Preflight checklist"
|
||||
options:
|
||||
- label:
|
||||
"I could not find a solution in the existing issues, docs, nor
|
||||
discussions."
|
||||
required: true
|
||||
- label:
|
||||
"I agree to follow this project's [Code of
|
||||
Conduct](https://github.com/ory/kratos/blob/master/CODE_OF_CONDUCT.md)."
|
||||
required: true
|
||||
- label:
|
||||
"I have read and am following this repository's [Contribution
|
||||
Guidelines](https://github.com/ory/kratos/blob/master/CONTRIBUTING.md)."
|
||||
required: true
|
||||
- label:
|
||||
"I have joined the [Ory Community Slack](https://slack.ory.com)."
|
||||
- label:
|
||||
"I am signed up to the [Ory Security Patch
|
||||
Newsletter](https://www.ory.com/l/sign-up-newsletter)."
|
||||
id: checklist
|
||||
type: checkboxes
|
||||
- attributes:
|
||||
description:
|
||||
"Enter the slug or API URL of the affected Ory Network project. Leave
|
||||
empty when you are self-hosting."
|
||||
label: "Ory Network Project"
|
||||
placeholder: "https://<your-project-slug>.projects.oryapis.com"
|
||||
id: ory-network-project
|
||||
type: input
|
||||
- attributes:
|
||||
description:
|
||||
"Is your feature request related to a problem? Please describe."
|
||||
label: "Describe your problem"
|
||||
placeholder:
|
||||
"A clear and concise description of what the problem is. Ex. I'm always
|
||||
frustrated when [...]"
|
||||
id: problem
|
||||
type: textarea
|
||||
validations:
|
||||
required: true
|
||||
- attributes:
|
||||
description: |
|
||||
Describe the solution you'd like
|
||||
placeholder: |
|
||||
A clear and concise description of what you want to happen.
|
||||
label: "Describe your ideal solution"
|
||||
id: solution
|
||||
type: textarea
|
||||
validations:
|
||||
required: true
|
||||
- attributes:
|
||||
description: "Describe alternatives you've considered"
|
||||
label: "Workarounds or alternatives"
|
||||
id: alternatives
|
||||
type: textarea
|
||||
validations:
|
||||
required: true
|
||||
- attributes:
|
||||
description: "What version of our software are you running?"
|
||||
label: Version
|
||||
id: version
|
||||
type: input
|
||||
validations:
|
||||
required: true
|
||||
- attributes:
|
||||
description:
|
||||
"Add any other context or screenshots about the feature request here."
|
||||
label: Additional Context
|
||||
id: additional
|
||||
type: textarea
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/ISSUE_TEMPLATE/config.yml
|
||||
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Ory Kratos Forum
|
||||
url: https://github.com/ory/kratos/discussions
|
||||
about:
|
||||
Please ask and answer questions here, show your implementations and
|
||||
discuss ideas.
|
||||
- name: Ory Chat
|
||||
url: https://www.ory.com/chat
|
||||
about:
|
||||
Hang out with other Ory community members to ask and answer questions.
|
||||
16
.github/auto_assign.yml
vendored
Normal file
16
.github/auto_assign.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/auto_assign.yml
|
||||
|
||||
# Set to true to add reviewers to pull requests
|
||||
addReviewers: true
|
||||
|
||||
# Set to true to add assignees to pull requests
|
||||
addAssignees: true
|
||||
|
||||
# A list of reviewers to be added to pull requests (GitHub user name)
|
||||
assignees:
|
||||
- ory/maintainers
|
||||
|
||||
# A number of reviewers added to the pull request
|
||||
# Set 0 to add all the reviewers (default: 0)
|
||||
numberOfReviewers: 0
|
||||
8
.github/codeql/codeql-config.yml
vendored
Normal file
8
.github/codeql/codeql-config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
name: "CodeQL config"
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
paths-ignore:
|
||||
- "/test/"
|
||||
- "/pkg/testhelpers"
|
||||
6
.github/config.yml
vendored
Normal file
6
.github/config.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/config.yml
|
||||
|
||||
todo:
|
||||
keyword: "@todo"
|
||||
label: todo
|
||||
5
.github/conventional_commits.json
vendored
Normal file
5
.github/conventional_commits.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/ory/ci/master/conventional_commit_config/dist/config.schema.json",
|
||||
"addTypes": ["improvement", "perf"],
|
||||
"addScopes": ["cli", "sql"]
|
||||
}
|
||||
67
.github/labels.json
vendored
Normal file
67
.github/labels.json
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
[
|
||||
{
|
||||
"name": "package/2fa",
|
||||
"color": "0A28FD",
|
||||
"aliases": ["module:2fa"]
|
||||
},
|
||||
{
|
||||
"name": "package/cli",
|
||||
"color": "0A28FD",
|
||||
"aliases": ["module:cli"]
|
||||
},
|
||||
{
|
||||
"name": "package/courier",
|
||||
"color": "0A28FD",
|
||||
"aliases": ["module:courier"]
|
||||
},
|
||||
{
|
||||
"name": "package/courier",
|
||||
"color": "0A28FD",
|
||||
"aliases": ["module:docs"]
|
||||
},
|
||||
{
|
||||
"name": "package/selfservice/errorx",
|
||||
"color": "0A28FD",
|
||||
"aliases": ["module:errorx"]
|
||||
},
|
||||
{
|
||||
"name": "package/identity",
|
||||
"color": "0A28FD",
|
||||
"aliases": ["module:identity"]
|
||||
},
|
||||
{
|
||||
"name": "package/persistence/sql",
|
||||
"color": "0A28FD",
|
||||
"aliases": ["module:migrations"]
|
||||
},
|
||||
{
|
||||
"name": "package/selfservice",
|
||||
"color": "0A28FD",
|
||||
"aliases": ["module:selfservice"]
|
||||
},
|
||||
{
|
||||
"name": "package/selfservice/oidc",
|
||||
"color": "0A28FD",
|
||||
"aliases": ["module:ss/oidc"]
|
||||
},
|
||||
{
|
||||
"name": "package/selfservice/password",
|
||||
"color": "0A28FD",
|
||||
"aliases": ["module:ss/password"]
|
||||
},
|
||||
{
|
||||
"name": "package/selfservice/verification",
|
||||
"color": "0A28FD",
|
||||
"aliases": ["module:verification"]
|
||||
},
|
||||
{
|
||||
"name": "package/selfservice/recovery",
|
||||
"color": "0A28FD",
|
||||
"aliases": []
|
||||
},
|
||||
{
|
||||
"name": "package/session",
|
||||
"color": "0A28FD",
|
||||
"aliases": ["module:session"]
|
||||
}
|
||||
]
|
||||
60
.github/pull_request_template.md
vendored
Normal file
60
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
<!--
|
||||
Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request.
|
||||
|
||||
This text will be included in the changelog. If applicable, include links to documentation or pieces of code.
|
||||
If your change includes breaking changes please add a code block documenting the breaking change:
|
||||
|
||||
```
|
||||
BREAKING CHANGES: This patch changes the behavior of configuration item `foo` to do bar. To keep the existing
|
||||
behavior please do baz.
|
||||
```
|
||||
-->
|
||||
|
||||
## Related issue(s)
|
||||
|
||||
<!--
|
||||
If this pull request
|
||||
|
||||
1. is a fix for a known bug, link the issue where the bug was reported in the format of `#1234`;
|
||||
2. is a fix for a previously unknown bug, explain the bug and how to reproduce it in this pull request;
|
||||
3. implements a new feature, link the issue containing the design document in the format of `#1234`;
|
||||
4. improves the documentation, no issue reference is required.
|
||||
|
||||
Pull requests introducing new features, which do not have a design document linked are more likely to be rejected and take on average 2-8 weeks longer to
|
||||
get merged.
|
||||
|
||||
You can discuss changes with maintainers either in the Github Discussions in this repository or
|
||||
join the [Ory Chat](https://www.ory.com/chat).
|
||||
-->
|
||||
|
||||
## Checklist
|
||||
|
||||
<!--
|
||||
Put an `x` in the boxes that apply. You can also fill these out after creating the PR.
|
||||
|
||||
Please be aware that pull requests must have all boxes ticked in order to be merged.
|
||||
|
||||
If you're unsure about any of them, don't hesitate to ask. We're here to help!
|
||||
-->
|
||||
|
||||
- [ ] I have read the [contributing guidelines](../blob/master/CONTRIBUTING.md).
|
||||
- [ ] I have referenced an issue containing the design document if my change
|
||||
introduces a new feature.
|
||||
- [ ] I am following the
|
||||
[contributing code guidelines](../blob/master/CONTRIBUTING.md#contributing-code).
|
||||
- [ ] I have read the [security policy](../security/policy).
|
||||
- [ ] I confirm that this pull request does not address a security
|
||||
vulnerability. If this pull request addresses a security vulnerability, I
|
||||
confirm that I got the approval (please contact
|
||||
[security@ory.com](mailto:security@ory.com)) from the maintainers to push
|
||||
the changes.
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature
|
||||
works.
|
||||
- [ ] I have added or changed [the documentation](https://github.com/ory/docs).
|
||||
|
||||
## Further Comments
|
||||
|
||||
<!--
|
||||
If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution
|
||||
you did and what alternatives you considered, etc...
|
||||
-->
|
||||
385
.github/workflows/ci.yaml
vendored
Normal file
385
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,385 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- "*"
|
||||
pull_request:
|
||||
|
||||
# Cancel in-progress runs in current workflow.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run tests and lints
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
env:
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_PASSWORD: test
|
||||
POSTGRES_USER: test
|
||||
ports:
|
||||
- 5432:5432
|
||||
mysql:
|
||||
image: mysql:9.6
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: test
|
||||
ports:
|
||||
- 3306:3306
|
||||
env:
|
||||
TEST_DATABASE_POSTGRESQL: "postgres://test:test@localhost:5432/postgres?sslmode=disable"
|
||||
TEST_DATABASE_MYSQL: "mysql://root:test@(localhost:3306)/mysql?parseTime=true&multiStatements=true"
|
||||
TEST_DATABASE_COCKROACHDB: "cockroach://root@localhost:26257/defaultdb?sslmode=disable"
|
||||
steps:
|
||||
- run: |
|
||||
docker create --name cockroach -p 26257:26257 \
|
||||
cockroachdb/cockroach:latest-v25.4 start-single-node --insecure \
|
||||
|| true
|
||||
docker start cockroach
|
||||
name: Start CockroachDB
|
||||
- run: docker pull oryd/hydra:v2.2.0
|
||||
name: Pull Hydra
|
||||
- uses: ory/ci/checkout@master
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
check-latest: true
|
||||
go-version-file: go.mod
|
||||
- run: go list -json > go.list
|
||||
- name: Run nancy
|
||||
uses: sonatype-nexus-community/nancy-github-action@v1.0.3
|
||||
with:
|
||||
nancyVersion: v1.0.42
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
name: apt-get update
|
||||
- run: npm install
|
||||
name: Install node deps
|
||||
- name: Run golangci-lint
|
||||
if: ${{ github.ref_type != 'tag' }}
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
env:
|
||||
GOGC: 100
|
||||
with:
|
||||
args: --timeout 10m0s
|
||||
version: latest
|
||||
only-new-issues: "true"
|
||||
- name: Build Kratos
|
||||
run: make install
|
||||
- name: Run go tests
|
||||
run: make test-coverage
|
||||
- name: Submit to Codecov
|
||||
run: |
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
test-e2e:
|
||||
name: Run end-to-end tests
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
env:
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_PASSWORD: test
|
||||
POSTGRES_USER: test
|
||||
ports:
|
||||
- 5432:5432
|
||||
mysql:
|
||||
image: mysql:9.6
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: test
|
||||
ports:
|
||||
- 3306:3306
|
||||
mailslurper:
|
||||
image: oryd/mailslurper:latest-smtps
|
||||
ports:
|
||||
- 4436:4436
|
||||
- 4437:4437
|
||||
- 1025:1025
|
||||
env:
|
||||
TEST_DATABASE_POSTGRESQL: "postgres://test:test@localhost:5432/postgres?sslmode=disable"
|
||||
TEST_DATABASE_MYSQL: "mysql://root:test@(localhost:3306)/mysql?parseTime=true&multiStatements=true"
|
||||
TEST_DATABASE_COCKROACHDB: "cockroach://root@localhost:26257/defaultdb?sslmode=disable"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
database: ["postgres", "sqlite"]
|
||||
# "cockroach", "mysql" TODO: fix tests and uncomment
|
||||
steps:
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24"
|
||||
- run: |
|
||||
docker create --name cockroach -p 26257:26257 \
|
||||
cockroachdb/cockroach:latest-v25.4 start-single-node --insecure
|
||||
docker start cockroach
|
||||
name: Start CockroachDB
|
||||
- uses: browser-actions/setup-chrome@latest
|
||||
name: Install Chrome
|
||||
# - uses: browser-actions/setup-firefox@latest
|
||||
# name: Install Firefox
|
||||
# - uses: browser-actions/setup-geckodriver@latest
|
||||
# name: Install Geckodriver
|
||||
# with:
|
||||
# geckodriver-version: 0.32.0
|
||||
- uses: ory/ci/checkout@master
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
name: apt-get update
|
||||
- run: |
|
||||
npm ci
|
||||
cd test/e2e; npm ci
|
||||
npm i -g expo-cli
|
||||
name: Install node deps
|
||||
- run: |
|
||||
sudo apt-get install -y moreutils gettext
|
||||
name: Install tools
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
check-latest: true
|
||||
go-version-file: go.mod
|
||||
- name: Install selfservice-ui-react-native
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ory/kratos-selfservice-ui-react-native
|
||||
path: react-native-ui
|
||||
- run: |
|
||||
cd react-native-ui
|
||||
npm install
|
||||
|
||||
- name: Install selfservice-ui-node
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ory/kratos-selfservice-ui-node
|
||||
path: node-ui
|
||||
- run: |
|
||||
cd node-ui
|
||||
npm install --legacy-peer-deps
|
||||
|
||||
- name: Install selfservice-ui-react-nextjs
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ory/kratos-selfservice-ui-react-nextjs
|
||||
path: react-ui
|
||||
- run: |
|
||||
cd react-ui
|
||||
npm ci
|
||||
|
||||
- run: |
|
||||
echo 'RN_UI_PATH='"$(realpath react-native-ui)" >> "$GITHUB_ENV"
|
||||
echo 'NODE_UI_PATH='"$(realpath node-ui)" >> "$GITHUB_ENV"
|
||||
echo 'REACT_UI_PATH='"$(realpath react-ui)" >> "$GITHUB_ENV"
|
||||
- name: "Run Cypress tests"
|
||||
run: ./test/e2e/run.sh ${{ matrix.database }}
|
||||
env:
|
||||
RN_UI_PATH: react-native-ui
|
||||
NODE_UI_PATH: node-ui
|
||||
REACT_UI_PATH: react-ui
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
- if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: cypress-${{ matrix.database }}-logs
|
||||
path: test/e2e/*.e2e.log
|
||||
|
||||
test-e2e-playwright:
|
||||
name: Run Playwright end-to-end tests
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
env:
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_PASSWORD: test
|
||||
POSTGRES_USER: test
|
||||
ports:
|
||||
- 5432:5432
|
||||
mysql:
|
||||
image: mysql:9.6
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: test
|
||||
ports:
|
||||
- 3306:3306
|
||||
mailslurper:
|
||||
image: oryd/mailslurper:latest-smtps
|
||||
ports:
|
||||
- 4436:4436
|
||||
- 4437:4437
|
||||
- 1025:1025
|
||||
env:
|
||||
TEST_DATABASE_POSTGRESQL: "postgres://test:test@localhost:5432/postgres?sslmode=disable"
|
||||
TEST_DATABASE_MYSQL: "mysql://root:test@(localhost:3306)/mysql?parseTime=true&multiStatements=true"
|
||||
TEST_DATABASE_COCKROACHDB: "cockroach://root@localhost:26257/defaultdb?sslmode=disable"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
database: ["postgres", "cockroach", "sqlite", "mysql"]
|
||||
steps:
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24"
|
||||
- run: |
|
||||
docker create --name cockroach -p 26257:26257 \
|
||||
cockroachdb/cockroach:latest-v25.4 start-single-node --insecure
|
||||
docker start cockroach
|
||||
name: Start CockroachDB
|
||||
- uses: ory/ci/checkout@master
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
name: apt-get update
|
||||
- run: |
|
||||
npm ci
|
||||
cd test/e2e; npm ci
|
||||
npx playwright install --with-deps
|
||||
npm i -g expo-cli
|
||||
name: Install node deps
|
||||
- run: |
|
||||
sudo apt-get install -y moreutils gettext
|
||||
name: Install tools
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
check-latest: true
|
||||
go-version-file: go.mod
|
||||
- run: go build .
|
||||
- name: Install selfservice-ui-react-native
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ory/kratos-selfservice-ui-react-native
|
||||
path: react-native-ui
|
||||
- run: |
|
||||
cd react-native-ui
|
||||
npm install
|
||||
|
||||
- name: Install selfservice-ui-node
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ory/kratos-selfservice-ui-node
|
||||
path: node-ui
|
||||
- run: |
|
||||
cd node-ui
|
||||
npm install --legacy-peer-deps
|
||||
|
||||
- name: Install selfservice-ui-react-nextjs
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ory/kratos-selfservice-ui-react-nextjs
|
||||
path: react-ui
|
||||
- run: |
|
||||
cd react-ui
|
||||
npm ci
|
||||
|
||||
- run: |
|
||||
echo 'RN_UI_PATH='"$(realpath react-native-ui)" >> "$GITHUB_ENV"
|
||||
echo 'NODE_UI_PATH='"$(realpath node-ui)" >> "$GITHUB_ENV"
|
||||
echo 'REACT_UI_PATH='"$(realpath react-ui)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: "Set up environment"
|
||||
run: test/e2e/run.sh --only-setup
|
||||
- name: "Run Playwright tests"
|
||||
run: |
|
||||
cd test/e2e
|
||||
npm run playwright
|
||||
env:
|
||||
DB: ${{ matrix.database }}
|
||||
RN_UI_PATH: react-native-ui
|
||||
NODE_UI_PATH: node-ui
|
||||
REACT_UI_PATH: react-ui
|
||||
- if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: playwright-${{ matrix.database }}-logs
|
||||
path: test/e2e/*.e2e.log
|
||||
- if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: playwright-test-results-${{ matrix.database }}-${{ github.sha }}
|
||||
path: |
|
||||
test/e2e/test-results/
|
||||
test/e2e/playwright-report/
|
||||
|
||||
docs-cli:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build CLI docs
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- uses: ory/ci/docs/cli-next@master
|
||||
with:
|
||||
token: ${{ secrets.ORY_BOT_PAT }}
|
||||
arg: "."
|
||||
output-dir: docs/kratos
|
||||
|
||||
release:
|
||||
name: Generate release
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
needs:
|
||||
- test
|
||||
- test-e2e
|
||||
steps:
|
||||
- uses: ory/ci/releaser@master
|
||||
with:
|
||||
token: ${{ secrets.ORY_BOT_PAT }}
|
||||
goreleaser_key: ${{ secrets.GORELEASER_KEY }}
|
||||
cosign_pwd: ${{ secrets.COSIGN_PWD }}
|
||||
docker_username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
docker_password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
newsletter-draft:
|
||||
name: Draft newsletter
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
needs:
|
||||
- release
|
||||
steps:
|
||||
- uses: ory/ci/newsletter@master
|
||||
with:
|
||||
mailchimp_list_id: f605a41b53
|
||||
mailchmip_segment_id: 6479477
|
||||
mailchimp_api_key: ${{ secrets.MAILCHIMP_API_KEY }}
|
||||
draft: "true"
|
||||
ssh_key: ${{ secrets.ORY_BOT_SSH_KEY }}
|
||||
|
||||
slack-approval-notification:
|
||||
name: Pending approval Slack notification
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
needs:
|
||||
- newsletter-draft
|
||||
steps:
|
||||
- uses: ory/ci/newsletter/slack-notify@master
|
||||
with:
|
||||
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
newsletter-send:
|
||||
name: Send newsletter
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- newsletter-draft
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
environment: production
|
||||
steps:
|
||||
- uses: ory/ci/newsletter@master
|
||||
with:
|
||||
mailchimp_list_id: f605a41b53
|
||||
mailchmip_segment_id: 6479477
|
||||
mailchimp_api_key: ${{ secrets.MAILCHIMP_API_KEY }}
|
||||
draft: "false"
|
||||
ssh_key: ${{ secrets.ORY_BOT_SSH_KEY }}
|
||||
30
.github/workflows/closed_references.yml
vendored
Normal file
30
.github/workflows/closed_references.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/closed_references.yml
|
||||
|
||||
name: Closed Reference Notifier
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issueLimit:
|
||||
description: Max. number of issues to create
|
||||
required: true
|
||||
default: "5"
|
||||
|
||||
jobs:
|
||||
find_closed_references:
|
||||
if: github.repository_owner == 'ory'
|
||||
runs-on: ubuntu-latest
|
||||
name: Find closed references
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24"
|
||||
- uses: ory/closed-reference-notifier@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issueLabels: upstream,good first issue,help wanted
|
||||
issueLimit: ${{ github.event.inputs.issueLimit || '5' }}
|
||||
68
.github/workflows/codeql-analysis.yml
vendored
Normal file
68
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: "26 21 * * 3"
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ["go", "javascript"]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
59
.github/workflows/conventional_commits.yml
vendored
Normal file
59
.github/workflows/conventional_commits.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/conventional_commits.yml
|
||||
|
||||
name: Conventional commits
|
||||
|
||||
# This GitHub CI Action enforces that pull request titles follow conventional commits.
|
||||
# More info at https://www.conventionalcommits.org.
|
||||
#
|
||||
# The Ory-wide defaults for commit titles and scopes are below.
|
||||
# Your repository can add/replace elements via a configuration file at the path below.
|
||||
# More info at https://github.com/ory/ci/blob/master/conventional_commit_config/README.md
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- edited
|
||||
- opened
|
||||
- ready_for_review
|
||||
- reopened
|
||||
# pull_request: # for debugging, uses config in local branch but supports only Pull Requests from this repo
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- id: config
|
||||
uses: ory/ci/conventional_commit_config@master
|
||||
with:
|
||||
config_path: .github/conventional_commits.json
|
||||
default_types: |
|
||||
feat
|
||||
fix
|
||||
revert
|
||||
docs
|
||||
style
|
||||
refactor
|
||||
test
|
||||
build
|
||||
autogen
|
||||
security
|
||||
ci
|
||||
chore
|
||||
default_scopes: |
|
||||
deps
|
||||
docs
|
||||
default_require_scope: false
|
||||
- uses: amannn/action-semantic-pull-request@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
types: ${{ steps.config.outputs.types }}
|
||||
scopes: ${{ steps.config.outputs.scopes }}
|
||||
requireScope: ${{ steps.config.outputs.requireScope }}
|
||||
subjectPattern: ^(?![A-Z]).+$
|
||||
subjectPatternError: |
|
||||
The subject should start with a lowercase letter, yours is uppercase:
|
||||
"{subject}"
|
||||
114
.github/workflows/cve-scan.yaml
vendored
Normal file
114
.github/workflows/cve-scan.yaml
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/server/.github/workflows/cve-scan.yaml
|
||||
|
||||
name: Docker Image Scanners
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
scanners:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
- name: Setup Env
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
# Store values in local variables
|
||||
SHA_SHORT=$(git rev-parse --short HEAD)
|
||||
REPO_NAME=${{ github.event.repository.name }}
|
||||
|
||||
IMAGE_NAME="oryd/${REPO_NAME}:${SHA_SHORT}"
|
||||
|
||||
# Output values for debugging
|
||||
echo "Values to be set:"
|
||||
echo "SHA_SHORT: ${SHA_SHORT}"
|
||||
echo "REPO_NAME: ${REPO_NAME}"
|
||||
echo "IMAGE_NAME: ${IMAGE_NAME}"
|
||||
|
||||
# Set GitHub Environment variables
|
||||
echo "SHA_SHORT=${SHA_SHORT}" >> "${GITHUB_ENV}"
|
||||
echo "IMAGE_NAME=${IMAGE_NAME}" >> "${GITHUB_ENV}"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
with:
|
||||
driver: docker
|
||||
- name: Build images
|
||||
shell: bash
|
||||
run: |
|
||||
IMAGE_TAG="${{ env.SHA_SHORT }}" make docker
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Anchore Scanner
|
||||
uses: anchore/scan-action@v7
|
||||
id: grype-scan
|
||||
with:
|
||||
image: ${{ env.IMAGE_NAME }}
|
||||
fail-build: true
|
||||
severity-cutoff: high
|
||||
add-cpes-if-none: true
|
||||
- name: Inspect action SARIF report
|
||||
shell: bash
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
echo "::group::Anchore Scan Details"
|
||||
jq '.runs[0].results' ${{ steps.grype-scan.outputs.sarif }}
|
||||
echo "::endgroup::"
|
||||
- name: Anchore upload scan SARIF report
|
||||
if: always()
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
with:
|
||||
sarif_file: ${{ steps.grype-scan.outputs.sarif }}
|
||||
- name: Kubescape scanner
|
||||
uses: kubescape/github-action@main
|
||||
id: kubescape
|
||||
with:
|
||||
image: ${{ env.IMAGE_NAME }}
|
||||
verbose: true
|
||||
format: pretty-printer
|
||||
# can't whitelist CVE yet: https://github.com/kubescape/kubescape/pull/1568
|
||||
severityThreshold: critical
|
||||
- name: Dockle Linter
|
||||
uses: erzz/dockle-action@v1
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
image: ${{ env.IMAGE_NAME }}
|
||||
exit-code: 42
|
||||
failure-threshold: high
|
||||
- name: Hadolint
|
||||
uses: hadolint/hadolint-action@v3.3.0
|
||||
id: hadolint
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
dockerfile: .docker/Dockerfile-build
|
||||
verbose: true
|
||||
format: "json"
|
||||
failure-threshold: "error"
|
||||
- name: View Hadolint results
|
||||
if: ${{ always() }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::group::Hadolint Scan Details"
|
||||
echo "${HADOLINT_RESULTS}" | jq '.'
|
||||
echo "::endgroup::"
|
||||
21
.github/workflows/format.yml
vendored
Normal file
21
.github/workflows/format.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Format
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
check-latest: true
|
||||
go-version-file: go.mod
|
||||
- run: make format
|
||||
- name: Indicate formatting issues
|
||||
run: git diff HEAD --exit-code --color
|
||||
25
.github/workflows/labels.yml
vendored
Normal file
25
.github/workflows/labels.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/labels.yml
|
||||
|
||||
name: Synchronize Issue Labels
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
milestone:
|
||||
if: github.repository_owner == 'ory'
|
||||
name: Synchronize Issue Labels
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
- name: Synchronize Issue Labels
|
||||
uses: ory/label-sync-action@v0
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
dry: false
|
||||
forced: true
|
||||
33
.github/workflows/milestone.yml
vendored
Normal file
33
.github/workflows/milestone.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/server/.github/workflows/milestone.yml
|
||||
|
||||
name: Generate and Publish Milestone Document
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
milestone:
|
||||
if: github.repository_owner == 'ory'
|
||||
name: Generate and Publish Milestone Document
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
token: ${{ secrets.TOKEN_PRIVILEGED }}
|
||||
- name: Milestone Documentation Generator
|
||||
uses: ory/milestone-action@v0
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
outputFile: docs/docs/milestones.md
|
||||
- name: Commit Milestone Documentation
|
||||
uses: EndBug/add-and-commit@v10.0.0
|
||||
with:
|
||||
message: "autogen(docs): update milestone document"
|
||||
author_name: aeneasr
|
||||
author_email: "3372410+aeneasr@users.noreply.github.com"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.TOKEN_PRIVILEGED }}
|
||||
30
.github/workflows/pm.yml
vendored
Normal file
30
.github/workflows/pm.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Synchronize with product board
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- ready_for_review
|
||||
|
||||
jobs:
|
||||
automate:
|
||||
if: github.event.pull_request.head.repo.fork == false
|
||||
name: Add issue to project
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: ory-corp/planning-automation-action@v0.2
|
||||
with:
|
||||
project: 5
|
||||
organization: ory-corp
|
||||
token: ${{ secrets.ORY_BOT_PAT }}
|
||||
todoLabel: "Needs Triage"
|
||||
statusName: Status
|
||||
prStatusValue: "Needs Triage"
|
||||
issueStatusValue: "Needs Triage"
|
||||
includeEffort: "false"
|
||||
monthlyMilestoneName: Roadmap Monthly
|
||||
quarterlyMilestoneName: Roadmap
|
||||
47
.github/workflows/stale.yml
vendored
Normal file
47
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# AUTO-GENERATED, DO NOT EDIT!
|
||||
# Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/.github/workflows/stale.yml
|
||||
|
||||
name: "Close Stale Issues"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: github.repository_owner == 'ory'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: |
|
||||
Hello contributors!
|
||||
|
||||
I am marking this issue as stale as it has not received any engagement from the community or maintainers for a year. That does not imply that the issue has no merit! If you feel strongly about this issue
|
||||
|
||||
- open a PR referencing and resolving the issue;
|
||||
- leave a comment on it and discuss ideas on how you could contribute towards resolving it;
|
||||
- leave a comment and describe in detail why this issue is critical for your use case;
|
||||
- open a new issue with updated details and a plan for resolving the issue.
|
||||
|
||||
Throughout its lifetime, Ory has received over 10.000 issues and PRs. To sustain that growth, we need to prioritize and focus on issues that are important to the community. A good indication of importance, and thus priority, is activity on a topic.
|
||||
|
||||
Unfortunately, [burnout](https://www.jeffgeerling.com/blog/2016/why-i-close-prs-oss-project-maintainer-notes) has become a [topic](https://opensource.guide/best-practices/#its-okay-to-hit-pause) of [concern](https://docs.brew.sh/Maintainers-Avoiding-Burnout) amongst open-source projects.
|
||||
|
||||
It can lead to severe personal and health issues as well as [opening](https://haacked.com/archive/2019/05/28/maintainer-burnout/) catastrophic [attack vectors](https://www.gradiant.org/en/blog/open-source-maintainer-burnout-as-an-attack-surface/).
|
||||
|
||||
The motivation for this automation is to help prioritize issues in the backlog and not ignore, reject, or belittle anyone.
|
||||
|
||||
If this issue was marked as stale erroneously you can exempt it by adding the `backlog` label, assigning someone, or setting a milestone for it.
|
||||
|
||||
Thank you for your understanding and to anyone who participated in the conversation! And as written above, please do participate in the conversation if this topic is important to you!
|
||||
|
||||
Thank you 🙏✌️
|
||||
stale-issue-label: "stale"
|
||||
exempt-issue-labels: "bug,blocking,docs,backlog"
|
||||
days-before-stale: 365
|
||||
days-before-close: 30
|
||||
exempt-milestones: true
|
||||
exempt-assignees: true
|
||||
only-pr-labels: "stale"
|
||||
68
.gitignore
vendored
Normal file
68
.gitignore
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
cover.out
|
||||
.idea/
|
||||
tmp/
|
||||
ddl/
|
||||
.DS_Store
|
||||
/kratos
|
||||
packrd/
|
||||
*-packr.go
|
||||
dist/
|
||||
node_modules
|
||||
.bin/
|
||||
test/e2e/cypress/videos
|
||||
test/e2e/cypress/screenshots
|
||||
test/e2e/.bin
|
||||
pkged.go
|
||||
coverage.*
|
||||
schema.sql
|
||||
*.sqlite
|
||||
heap_profiler/
|
||||
goroutine_dump/
|
||||
inflight_trace_dump/
|
||||
|
||||
contrib/quickstart/kratos/oidc
|
||||
|
||||
e2e/*.log
|
||||
e2e/kratos.*.yml
|
||||
e2e/proxy.json
|
||||
e2e/cypress/downloads
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
internal/httpclient/*.o
|
||||
internal/httpclient/*.a
|
||||
internal/httpclient/*.so
|
||||
|
||||
# Folders
|
||||
internal/httpclient/_obj
|
||||
internal/httpclient/_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
internal/httpclient/*.[568vq]
|
||||
internal/httpclient/[568vq].out
|
||||
|
||||
internal/httpclient/*.cgo1.go
|
||||
internal/httpclient/*.cgo2.c
|
||||
internal/httpclient/_cgo_defun.c
|
||||
internal/httpclient/_cgo_gotypes.go
|
||||
internal/httpclient/_cgo_export.*
|
||||
|
||||
internal/httpclient/_testmain.go
|
||||
|
||||
internal/httpclient/*.exe
|
||||
internal/httpclient/*.test
|
||||
internal/httpclient/*.prof
|
||||
|
||||
test/e2e/hydra-login-consent/hydra-login-consent
|
||||
|
||||
persistence/sql/migrations/sql/schema.sql
|
||||
|
||||
test/e2e/hydra-kratos-login-consent/hydra-kratos-login-consent
|
||||
|
||||
*.log
|
||||
test/e2e/proxy.json
|
||||
test/e2e/kratos.*.yml
|
||||
|
||||
# VSCode debug artifact
|
||||
__debug_bin
|
||||
.debug.sqlite.db
|
||||
.last-run.json
|
||||
39
.golangci.yml
Normal file
39
.golangci.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
version: "2"
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gosec
|
||||
- govet
|
||||
- errcheck
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- unused
|
||||
- forbidigo
|
||||
disable:
|
||||
- bodyclose # too many false negatives
|
||||
|
||||
settings:
|
||||
gosec:
|
||||
excludes:
|
||||
- G101
|
||||
- G117
|
||||
- G306
|
||||
- G704
|
||||
- G705
|
||||
forbidigo:
|
||||
forbid:
|
||||
- pattern: "http\\.DefaultClient"
|
||||
msg: "Use reg.HTTPClient(ctx) or httpx.NewResilientClient() — http.DefaultClient bypasses SSRF protection and retry logic."
|
||||
- pattern: "retryablehttp\\.NewClient\\b"
|
||||
msg: "Use httpx.NewResilientClient() — retryablehttp.NewClient() bypasses SSRF protection and registry configuration."
|
||||
exclusions:
|
||||
rules:
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "SA1019" # we do use deprecated APIs on purpose sometimes
|
||||
- linters:
|
||||
- forbidigo
|
||||
path: "pkg/(httpclient|client-go)/"
|
||||
- linters:
|
||||
- forbidigo
|
||||
path: "_test\\.go$"
|
||||
23
.goreleaser.yml
Normal file
23
.goreleaser.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
version: 2
|
||||
|
||||
includes:
|
||||
- from_url:
|
||||
url: https://raw.githubusercontent.com/ory/xgoreleaser/master/build.tmpl.yml
|
||||
|
||||
variables:
|
||||
brew_name: kratos
|
||||
brew_description: "The Ory Identity Platform (Ory Kratos)"
|
||||
buildinfo_hash: "github.com/ory/kratos/driver/config.Commit"
|
||||
buildinfo_tag: "github.com/ory/kratos/driver/config.Version"
|
||||
buildinfo_date: "github.com/ory/kratos/driver/config.Date"
|
||||
dockerfile_alpine: ".docker/Dockerfile-alpine"
|
||||
dockerfile_static: ".docker/Dockerfile-distroless-static"
|
||||
|
||||
project_name: kratos
|
||||
|
||||
after:
|
||||
hooks:
|
||||
- cmd: "bash <(curl -s https://raw.githubusercontent.com/ory/xgoreleaser/master/docs.sh)"
|
||||
env:
|
||||
- "TAG_VERSION={{ .Tag }}"
|
||||
- "DOCS_VERSION={{ .Major }}.{{ .Minor }}"
|
||||
9
.grype.yaml
Normal file
9
.grype.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
#only-fixed: true
|
||||
ignore:
|
||||
- vulnerability: GHSA-c5pj-mqfh-rvc3 # https://github.com/advisories/GHSA-c5pj-mqfh-rvc3
|
||||
- vulnerability: CVE-2015-5237
|
||||
- vulnerability: CVE-2022-30065
|
||||
- vulnerability: CVE-2023-2650
|
||||
- vulnerability: CVE-2023-4813
|
||||
- vulnerability: CVE-2023-4806
|
||||
- vulnerability: CVE-2025-0395 # no fix available
|
||||
13
.mailmap
Normal file
13
.mailmap
Normal file
@@ -0,0 +1,13 @@
|
||||
Aeneas Rekkas <aeneas@ory.sh> <arekkas@users.noreply.github.com>
|
||||
Aeneas Rekkas <aeneas@ory.sh> <3372410+aeneasr@users.noreply.github.com>
|
||||
Aeneas Rekkas <aeneas@ory.sh> <aeneasr@users.noreply.github.com>
|
||||
Aeneas Rekkas <aeneas@ory.sh> <aeneas@ory.sh>
|
||||
Ajay Kelkar <ajay.kelkar@ory.sh> <ajaykelkar@outlook.com>
|
||||
Alano Terblanche <alano@ory.sh> <18033717+Benehiko@users.noreply.github.com>
|
||||
Alano Terblanche <alano@ory.sh> <alanoterblanche@gmail.com>
|
||||
Jonas Hungershausen <jonas.hungershausen@ory.sh> <jonas.hungershausen@gmail.com>
|
||||
Matt Bonnell <matt.bonnell@icloud.com> <64976795+mbonnell-wish@users.noreply.github.com>
|
||||
Nick Ufer <nick@ufer.dev> <me@nick-ufer.de>
|
||||
Nick Ufer <nick@ufer.dev> <nick@ufer.dev>
|
||||
Patrik Neu <patrik@ory.sh> <zepatrik@users.noreply.github.com>
|
||||
Patrik Neu <patrik@ory.sh> <patrik@ory.sh>
|
||||
15
.nancy-ignore
Normal file
15
.nancy-ignore
Normal file
@@ -0,0 +1,15 @@
|
||||
# HashiCorp Consul related CVEs
|
||||
# Consul is not used but instead an indirect dependency to the pf13/viper config backend which is not actively used.
|
||||
CVE-2020-7219
|
||||
CVE-2019-12291
|
||||
CVE-2020-7955
|
||||
CVE-2020-12797
|
||||
CVE-2020-13250
|
||||
CVE-2020-13170
|
||||
# End HashiCorp Consul
|
||||
|
||||
# etcd issues - can be ignored because etcd is not used.
|
||||
CVE-2020-15114
|
||||
CVE-2020-15136
|
||||
CVE-2020-15115
|
||||
# end
|
||||
7
.orycli.yml
Normal file
7
.orycli.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
project: kratos
|
||||
|
||||
pre_release_hooks:
|
||||
- make sdk
|
||||
- ./script/render-schemas.sh
|
||||
- git config --unset user.email
|
||||
- git config --unset user.name
|
||||
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
.schema/
|
||||
.github/ISSUE_TEMPLATE
|
||||
oryx
|
||||
3
.reference-ignore
Normal file
3
.reference-ignore
Normal file
@@ -0,0 +1,3 @@
|
||||
**/node_modules
|
||||
docs
|
||||
CHANGELOG.md
|
||||
8
.reports/dep-licenses.csv
Normal file
8
.reports/dep-licenses.csv
Normal file
@@ -0,0 +1,8 @@
|
||||
"module name","licenses"
|
||||
|
||||
"github.com/arbovm/levenshtein","BSD-3-Clause"
|
||||
"github.com/ory/x","Apache-2.0"
|
||||
"github.com/stretchr/testify","MIT"
|
||||
"go.opentelemetry.io/otel/sdk","Apache-2.0"
|
||||
"golang.org/x/text","BSD-3-Clause"
|
||||
|
||||
|
3239
.schema/api.openapi.json
Normal file
3239
.schema/api.openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
3285
.schema/openapi.json
Normal file
3285
.schema/openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
7
.schema/openapi/gen.go.yml
Normal file
7
.schema/openapi/gen.go.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
disallowAdditionalPropertiesIfNotPresent: false
|
||||
packageName: client
|
||||
generateInterfaces: true
|
||||
structPrefix: true
|
||||
enumClassPrefix: true
|
||||
useOneOfDiscriminatorLookup: true
|
||||
isGoSubmodule: false
|
||||
11
.schema/openapi/gen.typescript.yml
Normal file
11
.schema/openapi/gen.typescript.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
npmName: "@ory/kratos-client"
|
||||
npmVersion: 0.0.0
|
||||
# typescriptThreePlus: true
|
||||
#npmRepository: https://github.com/ory/sdk.git
|
||||
supportsES6: true
|
||||
ensureUniqueParams: true
|
||||
modelPropertyNaming: original
|
||||
disallowAdditionalPropertiesIfNotPresent: false
|
||||
withInterfaces: false
|
||||
useSingleRequestParameter: true
|
||||
enumUnknownDefaultCase: true
|
||||
1
.schema/openapi/patches/common.yaml
Normal file
1
.schema/openapi/patches/common.yaml
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
29
.schema/openapi/patches/courier.yaml
Normal file
29
.schema/openapi/patches/courier.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
# Makes courierMessageStatus a string enum
|
||||
- op: remove
|
||||
path: /components/schemas/courierMessageStatus/format
|
||||
- op: replace
|
||||
path: /components/schemas/courierMessageStatus/type
|
||||
value: string
|
||||
- op: add
|
||||
path: /components/schemas/courierMessageStatus/enum
|
||||
value:
|
||||
- queued
|
||||
- sent
|
||||
- processing
|
||||
- abandoned
|
||||
# Makes courierMessageType a string enum
|
||||
- op: remove
|
||||
path: /components/schemas/courierMessageType/format
|
||||
- op: replace
|
||||
path: /components/schemas/courierMessageType/type
|
||||
value: string
|
||||
- op: add
|
||||
path: /components/schemas/courierMessageType/enum
|
||||
value:
|
||||
- email
|
||||
- phone
|
||||
# Fix courierMessageStatus query parameter in listMessages endpoint
|
||||
- op: replace
|
||||
path: /paths/~1admin~1courier~1messages/get/parameters/2/schema
|
||||
value:
|
||||
$ref: "#/components/schemas/courierMessageStatus"
|
||||
6
.schema/openapi/patches/generic_error.yaml
Normal file
6
.schema/openapi/patches/generic_error.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
- op: add
|
||||
path: /paths/~1sessions~1whoami/get/parameters/0/example
|
||||
value: MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj
|
||||
- op: add
|
||||
path: /paths/~1sessions~1whoami/get/parameters/1/example
|
||||
value: ory_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==
|
||||
13
.schema/openapi/patches/identity.yaml
Normal file
13
.schema/openapi/patches/identity.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
- op: remove
|
||||
path: /components/schemas/updateIdentityBody/properties/metadata_admin/type
|
||||
- op: remove
|
||||
path: /components/schemas/updateIdentityBody/properties/metadata_public/type
|
||||
- op: remove
|
||||
path: /components/schemas/createIdentityBody/properties/metadata_admin/type
|
||||
- op: remove
|
||||
path: /components/schemas/createIdentityBody/properties/metadata_public/type
|
||||
- op: remove
|
||||
path: /components/schemas/nullJsonRawMessage/type
|
||||
- op: add
|
||||
path: /components/schemas/nullJsonRawMessage/nullable
|
||||
value: true
|
||||
23
.schema/openapi/patches/meta.yaml
Normal file
23
.schema/openapi/patches/meta.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
- op: replace
|
||||
path: /info
|
||||
value:
|
||||
title: Ory Identities API
|
||||
description: |
|
||||
This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more.
|
||||
version: >-
|
||||
{{ getenv "CIRCLE_TAG" }}
|
||||
license:
|
||||
name: Apache 2.0
|
||||
contact:
|
||||
email: "office@ory.sh"
|
||||
- op: replace
|
||||
path: /tags
|
||||
value:
|
||||
- name: identity
|
||||
description: APIs for managing identities.
|
||||
- name: frontend
|
||||
description: Endpoints used by frontend applications (e.g. Single-Page-App, Native Apps, Server Apps, ...) to manage a user's own profile.
|
||||
- name: courier
|
||||
description: APIs for managing email and SMS message delivery.
|
||||
- name: metadata
|
||||
description: Server Metadata provides relevant information about the running server. Only available when self-hosting this service.
|
||||
37
.schema/openapi/patches/nulls.yaml
Normal file
37
.schema/openapi/patches/nulls.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
- op: replace
|
||||
path: "#/components/schemas/NullUUID"
|
||||
value:
|
||||
type: string
|
||||
format: uuid4
|
||||
nullable: true
|
||||
- op: replace
|
||||
path: "#/components/schemas/NullTime"
|
||||
value:
|
||||
format: date-time
|
||||
type: string
|
||||
nullable: true
|
||||
- op: replace
|
||||
path: "#/components/schemas/Time"
|
||||
value:
|
||||
format: date-time
|
||||
type: string
|
||||
- op: replace
|
||||
path: "#/components/schemas/NullString"
|
||||
value:
|
||||
type: string
|
||||
nullable: true
|
||||
- op: replace
|
||||
path: "#/components/schemas/NullBool"
|
||||
value:
|
||||
type: boolean
|
||||
nullable: true
|
||||
- op: replace
|
||||
path: "#/components/schemas/NullInt"
|
||||
value:
|
||||
type: integer
|
||||
nullable: true
|
||||
- op: replace
|
||||
path: "#/components/schemas/nullInt64"
|
||||
value:
|
||||
type: integer
|
||||
nullable: true
|
||||
141
.schema/openapi/patches/schema.yaml
Normal file
141
.schema/openapi/patches/schema.yaml
Normal file
@@ -0,0 +1,141 @@
|
||||
# Makes uiNodeAttributes polymorph
|
||||
- op: remove
|
||||
path: /components/schemas/uiNodeAttributes/type
|
||||
- op: add
|
||||
path: /components/schemas/uiNodeAttributes/discriminator
|
||||
value:
|
||||
propertyName: node_type
|
||||
mapping:
|
||||
input: "#/components/schemas/uiNodeInputAttributes"
|
||||
text: "#/components/schemas/uiNodeTextAttributes"
|
||||
img: "#/components/schemas/uiNodeImageAttributes"
|
||||
a: "#/components/schemas/uiNodeAnchorAttributes"
|
||||
script: "#/components/schemas/uiNodeScriptAttributes"
|
||||
div: "#/components/schemas/uiNodeDivisionAttributes"
|
||||
- op: add
|
||||
path: /components/schemas/uiNodeAttributes/oneOf
|
||||
value:
|
||||
- "$ref": "#/components/schemas/uiNodeInputAttributes"
|
||||
- "$ref": "#/components/schemas/uiNodeTextAttributes"
|
||||
- "$ref": "#/components/schemas/uiNodeImageAttributes"
|
||||
- "$ref": "#/components/schemas/uiNodeAnchorAttributes"
|
||||
- "$ref": "#/components/schemas/uiNodeScriptAttributes"
|
||||
- "$ref": "#/components/schemas/uiNodeDivisionAttributes"
|
||||
|
||||
- op: replace
|
||||
path: /components/schemas/uiNodeDivisionAttributes/properties/node_type/enum
|
||||
value:
|
||||
- div
|
||||
|
||||
- op: replace
|
||||
path: /components/schemas/uiNodeInputAttributes/properties/node_type/enum
|
||||
value:
|
||||
- input
|
||||
|
||||
- op: replace
|
||||
path: /components/schemas/uiNodeTextAttributes/properties/node_type/enum
|
||||
value:
|
||||
- text
|
||||
|
||||
- op: replace
|
||||
path: /components/schemas/uiNodeImageAttributes/properties/node_type/enum
|
||||
value:
|
||||
- img
|
||||
|
||||
- op: replace
|
||||
path: /components/schemas/uiNodeAnchorAttributes/properties/node_type/enum
|
||||
value:
|
||||
- a
|
||||
|
||||
- op: replace
|
||||
path: /components/schemas/uiNodeScriptAttributes/properties/node_type/enum
|
||||
value:
|
||||
- script
|
||||
|
||||
# Makes the uiNodeInputAttributes value attribute polymorph
|
||||
- op: add
|
||||
path: /components/schemas/uiNodeInputAttributes/properties/value/nullable
|
||||
value: true
|
||||
|
||||
- op: replace
|
||||
path: /components/schemas/flowError/properties/error
|
||||
value:
|
||||
type: object
|
||||
|
||||
- op: remove
|
||||
path: "#/components/schemas/identityTraits/type"
|
||||
|
||||
- op: add
|
||||
path: /components/schemas/continueWith/discriminator
|
||||
value:
|
||||
propertyName: action
|
||||
mapping:
|
||||
show_verification_ui: "#/components/schemas/continueWithVerificationUi"
|
||||
set_ory_session_token: "#/components/schemas/continueWithSetOrySessionToken"
|
||||
show_settings_ui: "#/components/schemas/continueWithSettingsUi"
|
||||
show_recovery_ui: "#/components/schemas/continueWithRecoveryUi"
|
||||
redirect_browser_to: "#/components/schemas/continueWithRedirectBrowserTo"
|
||||
show_pin_entry_ui: "#/components/schemas/continueWithDeviceAuthnPinEntryUi"
|
||||
|
||||
- op: add
|
||||
path: /components/schemas/continueWith/oneOf
|
||||
value:
|
||||
- "$ref": "#/components/schemas/continueWithVerificationUi"
|
||||
- "$ref": "#/components/schemas/continueWithSetOrySessionToken"
|
||||
- "$ref": "#/components/schemas/continueWithSettingsUi"
|
||||
- "$ref": "#/components/schemas/continueWithRecoveryUi"
|
||||
- "$ref": "#/components/schemas/continueWithRedirectBrowserTo"
|
||||
- "$ref": "#/components/schemas/continueWithDeviceAuthnPinEntryUi"
|
||||
|
||||
# DeviceAuthn: constrain UserVerification to its known values. go-swagger renders
|
||||
# the `type UserVerification string` alias as a plain string, so the enum is
|
||||
# pinned here. The valid values are pin, platform, and none.
|
||||
- op: add
|
||||
path: /components/schemas/UserVerification/enum
|
||||
value:
|
||||
- pin
|
||||
- platform
|
||||
- none
|
||||
|
||||
# DeviceAuthn: constrain KeyState and DeviceType to their known values, for the
|
||||
# same reason as UserVerification above.
|
||||
- op: add
|
||||
path: /components/schemas/KeyState/enum
|
||||
value:
|
||||
- initial
|
||||
- confirmed
|
||||
- locked
|
||||
|
||||
- op: add
|
||||
path: /components/schemas/DeviceType/enum
|
||||
value:
|
||||
- Android
|
||||
- iOS
|
||||
|
||||
# Without explicit variable names, generators derive the "iOS" identifier as
|
||||
# I_OS / IOs. The names apply verbatim in every generated language.
|
||||
- op: add
|
||||
path: /components/schemas/DeviceType/x-enum-varnames
|
||||
value:
|
||||
- ANDROID
|
||||
- IOS
|
||||
|
||||
# DeviceAuthn: the at-rest pin_secret ciphertext is server-internal and must
|
||||
# never be returned to clients. Mark it write-only so the SDK and docs flag it
|
||||
# as not-readable (Swagger 2.0 cannot express writeOnly, so this lands in the
|
||||
# OpenAPI 3.0 spec only).
|
||||
- op: add
|
||||
path: /components/schemas/PINConfig/properties/pin_secret/writeOnly
|
||||
value: true
|
||||
|
||||
# DeviceAuthn: attach a description to the `data` property. go-swagger
|
||||
# drops a description next to a bare $ref, so wrap the $ref in allOf to keep the
|
||||
# reference and carry the description.
|
||||
- op: replace
|
||||
path: /components/schemas/continueWithDeviceAuthnPinEntryUi/properties/data
|
||||
value:
|
||||
description: >-
|
||||
The one-time HPKE-sealed pin_secret material the device opens to bind the
|
||||
user's PIN after a PIN-protected DeviceAuthn enrollment or secret rotation.
|
||||
allOf:
|
||||
- "$ref": "#/components/schemas/continueWithDeviceAuthnPinEntryUiData"
|
||||
3
.schema/openapi/patches/security.yaml
Normal file
3
.schema/openapi/patches/security.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
- op: replace
|
||||
path: /components/schemas/genericError/properties/details/additionalProperties
|
||||
value: false
|
||||
187
.schema/openapi/patches/selfservice.yaml
Normal file
187
.schema/openapi/patches/selfservice.yaml
Normal file
@@ -0,0 +1,187 @@
|
||||
# Makes updateLoginFlowPayload polymorph
|
||||
#- op: remove
|
||||
# path: /components/schemas/updateLoginFlowBody/type
|
||||
#- op: add
|
||||
# path: /components/schemas/updateLoginFlowBody/oneOf
|
||||
# value:
|
||||
# - "$ref": "#/components/schemas/updateLoginFlowWithPasswordMethod"
|
||||
|
||||
# Makes updateRegistrationFlowPayload polymorph
|
||||
|
||||
# All modifications for the registration flow
|
||||
- op: remove
|
||||
path: /components/schemas/updateRegistrationFlowBody/type
|
||||
- op: add
|
||||
path: /components/schemas/updateRegistrationFlowBody/oneOf
|
||||
value:
|
||||
- "$ref": "#/components/schemas/updateRegistrationFlowWithPasswordMethod"
|
||||
- "$ref": "#/components/schemas/updateRegistrationFlowWithOidcMethod"
|
||||
- "$ref": "#/components/schemas/updateRegistrationFlowWithSamlMethod"
|
||||
- "$ref": "#/components/schemas/updateRegistrationFlowWithWebAuthnMethod"
|
||||
- "$ref": "#/components/schemas/updateRegistrationFlowWithCodeMethod"
|
||||
- "$ref": "#/components/schemas/updateRegistrationFlowWithPasskeyMethod"
|
||||
- "$ref": "#/components/schemas/updateRegistrationFlowWithProfileMethod"
|
||||
- op: add
|
||||
path: /components/schemas/updateRegistrationFlowBody/discriminator
|
||||
value:
|
||||
propertyName: method
|
||||
mapping:
|
||||
password: "#/components/schemas/updateRegistrationFlowWithPasswordMethod"
|
||||
oidc: "#/components/schemas/updateRegistrationFlowWithOidcMethod"
|
||||
saml: "#/components/schemas/updateRegistrationFlowWithSamlMethod"
|
||||
webauthn: "#/components/schemas/updateRegistrationFlowWithWebAuthnMethod"
|
||||
code: "#/components/schemas/updateRegistrationFlowWithCodeMethod"
|
||||
passkey: "#/components/schemas/updateRegistrationFlowWithPasskeyMethod"
|
||||
profile: "#/components/schemas/updateRegistrationFlowWithProfileMethod"
|
||||
- op: add
|
||||
path: /components/schemas/registrationFlowState
|
||||
value:
|
||||
title: Registration flow state (experimental)
|
||||
description: The experimental state represents the state of a registration flow. This field is EXPERIMENTAL and subject to change!
|
||||
type: string
|
||||
enum:
|
||||
- choose_method
|
||||
- sent_email
|
||||
- passed_challenge
|
||||
# end
|
||||
|
||||
# All modifications for the login flow
|
||||
- op: remove
|
||||
path: /components/schemas/updateLoginFlowBody/type
|
||||
- op: add
|
||||
path: /components/schemas/updateLoginFlowBody/oneOf
|
||||
value:
|
||||
- "$ref": "#/components/schemas/updateLoginFlowWithPasswordMethod"
|
||||
- "$ref": "#/components/schemas/updateLoginFlowWithOidcMethod"
|
||||
- "$ref": "#/components/schemas/updateLoginFlowWithSamlMethod"
|
||||
- "$ref": "#/components/schemas/updateLoginFlowWithTotpMethod"
|
||||
- "$ref": "#/components/schemas/updateLoginFlowWithWebAuthnMethod"
|
||||
- "$ref": "#/components/schemas/updateLoginFlowWithLookupSecretMethod"
|
||||
- "$ref": "#/components/schemas/updateLoginFlowWithCodeMethod"
|
||||
- "$ref": "#/components/schemas/updateLoginFlowWithPasskeyMethod"
|
||||
- "$ref": "#/components/schemas/updateLoginFlowWithIdentifierFirstMethod"
|
||||
- op: add
|
||||
path: /components/schemas/updateLoginFlowBody/discriminator
|
||||
value:
|
||||
propertyName: method
|
||||
mapping:
|
||||
password: "#/components/schemas/updateLoginFlowWithPasswordMethod"
|
||||
oidc: "#/components/schemas/updateLoginFlowWithOidcMethod"
|
||||
saml: "#/components/schemas/updateLoginFlowWithSamlMethod"
|
||||
totp: "#/components/schemas/updateLoginFlowWithTotpMethod"
|
||||
webauthn: "#/components/schemas/updateLoginFlowWithWebAuthnMethod"
|
||||
lookup_secret: "#/components/schemas/updateLoginFlowWithLookupSecretMethod"
|
||||
code: "#/components/schemas/updateLoginFlowWithCodeMethod"
|
||||
passkey: "#/components/schemas/updateLoginFlowWithPasskeyMethod"
|
||||
identifier_first: "#/components/schemas/updateLoginFlowWithIdentifierFirstMethod"
|
||||
- op: add
|
||||
path: /components/schemas/loginFlowState
|
||||
value:
|
||||
title: Login flow state (experimental)
|
||||
description: The experimental state represents the state of a login flow. This field is EXPERIMENTAL and subject to change!
|
||||
type: string
|
||||
enum:
|
||||
- choose_method
|
||||
- sent_email
|
||||
- passed_challenge
|
||||
# end
|
||||
|
||||
# All modifications for the recovery flow
|
||||
- op: remove
|
||||
path: /components/schemas/updateRecoveryFlowBody/type
|
||||
- op: add
|
||||
path: /components/schemas/updateRecoveryFlowBody/oneOf
|
||||
value:
|
||||
- "$ref": "#/components/schemas/updateRecoveryFlowWithLinkMethod"
|
||||
- "$ref": "#/components/schemas/updateRecoveryFlowWithCodeMethod"
|
||||
- op: add
|
||||
path: /components/schemas/updateRecoveryFlowBody/discriminator
|
||||
value:
|
||||
propertyName: method
|
||||
mapping:
|
||||
link: "#/components/schemas/updateRecoveryFlowWithLinkMethod"
|
||||
code: "#/components/schemas/updateRecoveryFlowWithCodeMethod"
|
||||
- op: add
|
||||
path: /components/schemas/recoveryFlowState
|
||||
type: string
|
||||
value:
|
||||
title: Recovery flow state (experimental)
|
||||
description: The experimental state represents the state of a recovery flow. This field is EXPERIMENTAL and subject to change!
|
||||
enum:
|
||||
- choose_method
|
||||
- sent_email
|
||||
- passed_challenge
|
||||
# End
|
||||
|
||||
# All modifications for the verification flow
|
||||
- op: remove
|
||||
path: /components/schemas/updateVerificationFlowBody/type
|
||||
- op: add
|
||||
path: /components/schemas/updateVerificationFlowBody/oneOf
|
||||
value:
|
||||
- "$ref": "#/components/schemas/updateVerificationFlowWithLinkMethod"
|
||||
- "$ref": "#/components/schemas/updateVerificationFlowWithCodeMethod"
|
||||
- op: add
|
||||
path: /components/schemas/updateVerificationFlowBody/discriminator
|
||||
value:
|
||||
propertyName: method
|
||||
mapping:
|
||||
link: "#/components/schemas/updateVerificationFlowWithLinkMethod"
|
||||
code: "#/components/schemas/updateVerificationFlowWithCodeMethod"
|
||||
- op: add
|
||||
path: /components/schemas/verificationFlowState
|
||||
type: string
|
||||
value:
|
||||
title: Verification flow state (experimental)
|
||||
description: The experimental state represents the state of a verification flow. This field is EXPERIMENTAL and subject to change!
|
||||
enum:
|
||||
- choose_method
|
||||
- sent_email
|
||||
- passed_challenge
|
||||
# End
|
||||
|
||||
# All modifications for the settings flow
|
||||
- op: remove
|
||||
path: /components/schemas/updateSettingsFlowBody/type
|
||||
- op: add
|
||||
path: /components/schemas/updateSettingsFlowBody/oneOf
|
||||
value:
|
||||
- "$ref": "#/components/schemas/updateSettingsFlowWithPasswordMethod"
|
||||
- "$ref": "#/components/schemas/updateSettingsFlowWithProfileMethod"
|
||||
- "$ref": "#/components/schemas/updateSettingsFlowWithOidcMethod"
|
||||
- "$ref": "#/components/schemas/updateSettingsFlowWithSamlMethod"
|
||||
- "$ref": "#/components/schemas/updateSettingsFlowWithTotpMethod"
|
||||
- "$ref": "#/components/schemas/updateSettingsFlowWithWebAuthnMethod"
|
||||
- "$ref": "#/components/schemas/updateSettingsFlowWithLookupMethod"
|
||||
- "$ref": "#/components/schemas/updateSettingsFlowWithPasskeyMethod"
|
||||
- op: add
|
||||
path: /components/schemas/updateSettingsFlowBody/discriminator
|
||||
value:
|
||||
propertyName: method
|
||||
mapping:
|
||||
password: "#/components/schemas/updateSettingsFlowWithPasswordMethod"
|
||||
profile: "#/components/schemas/updateSettingsFlowWithProfileMethod"
|
||||
oidc: "#/components/schemas/updateSettingsFlowWithOidcMethod"
|
||||
saml: "#/components/schemas/updateSettingsFlowWithSamlMethod"
|
||||
totp: "#/components/schemas/updateSettingsFlowWithTotpMethod"
|
||||
webauthn: "#/components/schemas/updateSettingsFlowWithWebAuthnMethod"
|
||||
passkey: "#/components/schemas/updateSettingsFlowWithPasskeyMethod"
|
||||
lookup_secret: "#/components/schemas/updateSettingsFlowWithLookupMethod"
|
||||
- op: add
|
||||
path: /components/schemas/settingsFlowState
|
||||
value:
|
||||
title: Settings flow state (experimental)
|
||||
description: The experimental state represents the state of a settings flow. This field is EXPERIMENTAL and subject to change!
|
||||
type: string
|
||||
enum:
|
||||
- show_form
|
||||
- success
|
||||
# end
|
||||
|
||||
# Some issues with AdditionalProperties
|
||||
- op: remove
|
||||
path: "#/components/schemas/OAuth2LoginRequest/properties/AdditionalProperties"
|
||||
- op: remove
|
||||
path: "#/components/schemas/OAuth2ConsentRequestOpenIDConnectContext/properties/AdditionalProperties"
|
||||
- op: remove
|
||||
path: "#/components/schemas/OAuth2Client/properties/AdditionalProperties"
|
||||
13
.schema/openapi/patches/session.yaml
Normal file
13
.schema/openapi/patches/session.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
- op: add
|
||||
path: /paths/~1sessions~1whoami/get/parameters/0/example
|
||||
value: MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj
|
||||
- op: add
|
||||
path: /paths/~1sessions~1whoami/get/parameters/1/example
|
||||
value: ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==
|
||||
- op: add
|
||||
path: /components/schemas/authenticatorAssuranceLevel/enum
|
||||
value:
|
||||
- aal0
|
||||
- aal1
|
||||
- aal2
|
||||
- aal3
|
||||
601
.schema/version.schema.json
Normal file
601
.schema/version.schema.json
Normal file
@@ -0,0 +1,601 @@
|
||||
{
|
||||
"$id": "https://github.com/ory/kratos/.schema/versions.config.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"oneOf": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v1.3.0"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v1.3.0/.schemastore/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v1.2.0"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v1.2.0/.schemastore/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v1.1.0"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v1.1.0/.schemastore/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v1.0.0"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v1.0.0/.schemastore/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.13.0"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.13.0/.schemastore/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.11.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.11.1/.schemastore/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.11.0"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.11.0/.schemastore/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.10.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.10.1/.schemastore/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.10.0"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.10.0/.schemastore/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.9.0-alpha.3"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.9.0-alpha.3/embedx/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.9.0-alpha.2"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.9.0-alpha.2/embedx/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.4.6-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.4.6-alpha.1/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.5.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.5.0-alpha.1/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.5.1-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.5.1-alpha.1/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.5.2-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.5.2-alpha.1/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.5.3-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.5.3-alpha.1/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.5.4-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.5.4-alpha.1/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.5.5-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.5.5-alpha.1/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.6.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.6.0-alpha.1/driver/config/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.6.0-alpha.2"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.6.0-alpha.2/driver/config/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.6.1-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.6.1-alpha.1/driver/config/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.6.2-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.6.2-alpha.1/driver/config/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.6.3-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.6.3-alpha.1/driver/config/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.7.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.7.0-alpha.1/driver/config/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.7.1-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.7.1-alpha.1/driver/config/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.7.3-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.7.3-alpha.1/driver/config/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.7.4-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.7.4-alpha.1/driver/config/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.7.5-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.7.5-alpha.1/driver/config/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.7.6-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.7.6-alpha.1/driver/config/.schema/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.8.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.8.0-alpha.1/embedx/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.8.0-alpha.2"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.8.0-alpha.2/embedx/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.8.0-alpha.3"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.8.0-alpha.3/embedx/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"const": "v0.8.2-alpha.1"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "https://raw.githubusercontent.com/ory/kratos/v0.8.2-alpha.1/embedx/config.schema.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "string",
|
||||
"maxLength": 0
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
},
|
||||
{
|
||||
"not": {
|
||||
"properties": {
|
||||
"version": {}
|
||||
},
|
||||
"required": [
|
||||
"version"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "#/oneOf/0/allOf/1"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "All Versions for Ory Kratos Configuration",
|
||||
"type": "object"
|
||||
}
|
||||
3
.schemastore/README.md
Normal file
3
.schemastore/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
The config schema is generated from the internal one at
|
||||
`embedx/config.schema.json`, so in case of changes to the config schema,
|
||||
please edit that internal schema instead.
|
||||
3190
.schemastore/config.schema.json
Normal file
3190
.schemastore/config.schema.json
Normal file
File diff suppressed because it is too large
Load Diff
34
.vscode/launch.json
vendored
Normal file
34
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
// Launch configuration to build and launch Kratos
|
||||
// It uses a barebones
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Kratos",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/main.go",
|
||||
"buildFlags": "",
|
||||
"preLaunchTask": "Kratos: setup",
|
||||
"postDebugTask": "close tasks", // stops mailhog. Needed, because VSCode does not re-use existing isBackground tasks
|
||||
"args": [
|
||||
"serve",
|
||||
"--dev",
|
||||
"--watch-courier",
|
||||
"-c=${workspaceFolder}/contrib/quickstart/kratos/email-password/kratos.yml"
|
||||
],
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"env": {
|
||||
"IDENTITY_SCHEMAS_0_URL": "file://${workspaceFolder}/contrib/quickstart/kratos/email-password/identity.schema.json",
|
||||
"DSN": "sqlite://${workspaceFolder}/.debug.sqlite.db?_fk=true",
|
||||
"COURIER_SMTP_CONNECTION_URI": "smtp://localhost:8026/?disable_starttls=true",
|
||||
"DEV_DISABLE_API_FLOW_ENFORCEMENT": "true",
|
||||
"SELFSERVICE_METHODS_PASSWORD_CONFIG_HAVEIBEENPWNED_ENABLED": "false", // disable locally, as the integration hangs requests, if internet is slow
|
||||
"TRACING_PROVIDER": "jaeger",
|
||||
"TRACING_PROVIDERS_JAEGER_SAMPLING_SERVER_URL": "http://127.0.0.1:5778/sampling",
|
||||
"TRACING_PROVIDERS_JAEGER_LOCAL_AGENT_ADDRESS": "127.0.0.1:6831"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"gopls": {
|
||||
"formatting.gofumpt": true,
|
||||
"formatting.local": "github.com/ory"
|
||||
}
|
||||
}
|
||||
58
.vscode/tasks.json
vendored
Normal file
58
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Kratos: build",
|
||||
"type": "shell",
|
||||
"command": "go",
|
||||
"args": [
|
||||
"build",
|
||||
"-tags",
|
||||
"sqlite",
|
||||
"-o",
|
||||
"${workspaceFolder}/kratos",
|
||||
"${workspaceFolder}/main.go"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Kratos debug: setup sqlite",
|
||||
"type": "shell",
|
||||
"presentation": {
|
||||
"echo": false,
|
||||
"reveal": "always"
|
||||
},
|
||||
"dependsOn": ["Kratos: build"],
|
||||
"command": "rm -f ${workspaceFolder}/.debug.sqlite.db && ${workspaceFolder}/kratos migrate sql up -e --yes",
|
||||
"options": {
|
||||
"env": {
|
||||
"DSN": "sqlite://${workspaceFolder}/.debug.sqlite.db?_fk=true"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Kratos: setup",
|
||||
"type": "shell",
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"dependsOn": ["Kratos debug: setup sqlite"],
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "close tasks",
|
||||
"command": "echo ${input:terminate}",
|
||||
"type": "shell",
|
||||
"problemMatcher": []
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "terminate",
|
||||
"type": "command",
|
||||
"command": "workbench.action.tasks.terminate",
|
||||
"args": "terminateAll"
|
||||
}
|
||||
]
|
||||
}
|
||||
198
AUTHORS
Normal file
198
AUTHORS
Normal file
@@ -0,0 +1,198 @@
|
||||
# This is the official list of Kratos authors.
|
||||
# If you don't want to be on this list, please contact Ory.
|
||||
|
||||
Adam Schepis <adam.schepis@gmail.com>
|
||||
Addison Snelling <addison@asnell.io>
|
||||
adrian5 <adrian5@users.noreply.github.com>
|
||||
Aeneas Rekkas <aeneas@ory.sh>
|
||||
aenoralanar <38026322+aenoralanar@users.noreply.github.com>
|
||||
Aidan Holland <thehappydinoa@gmail.com>
|
||||
Ajay Kelkar <ajay.kelkar@ory.sh>
|
||||
Alano Terblanche <alano@ory.sh>
|
||||
Alexander Probst <probstal.hamm@gmail.com>
|
||||
Alexandre Burgoni <burgoni.alexandre@protonmail.com>
|
||||
Alexandre Gagneux <46828169+alexGNX@users.noreply.github.com>
|
||||
Alexey Reshetnik <getupandgo11@gmail.com>
|
||||
Andreas <AndreasGB@users.noreply.github.com>
|
||||
Andreas Bucksteeg <tricky42@users.noreply.github.com>
|
||||
Andrew Banchich <13824577+andrewbanchich@users.noreply.github.com>
|
||||
Andrew Minkin <minkin.andrew@gmail.com>
|
||||
Andrew Tan <origandrew@gmail.com>
|
||||
Andrey <weivall@gmail.com>
|
||||
Andy Steele <andy@spacemeld.com>
|
||||
angryPopcorn <me@renyijiu.com>
|
||||
Anirudh Oppiliappan <x@icyphox.sh>
|
||||
Antoine Beyet <Stoakes@users.noreply.github.com>
|
||||
Anuar Ustayev <anuar.ustayev@gmail.com>
|
||||
Arne <a.luenser@gmail.com>
|
||||
Arne <arne.luenser@ory.sh>
|
||||
aspeteRakete <fdubrownik@gmail.com>
|
||||
Atreya <44151328+atreya2011@users.noreply.github.com>
|
||||
AVA Monitoring <56961522+avamonitoring@users.noreply.github.com>
|
||||
Avinash Dwarapu <d.nash.avi@gmail.com>
|
||||
b3j0f <b3j0f@users.noreply.github.com>
|
||||
Bengt Hagemeister <30391176+BengtHagemeister@users.noreply.github.com>
|
||||
Bengt Hagemeister <bengt@localticketing.de>
|
||||
Benjamin Freeman <b.t.freeman16@gmail.com>
|
||||
BIKI DAS <bikid475@gmail.com>
|
||||
Bill Monkman <bmonkman@gmail.com>
|
||||
Bipin Paul Bedi <bipinpaulbedi@gmail.com>
|
||||
Brahm Lower <bplower@gmail.com>
|
||||
Brahm Lower <brahm@censys.io>
|
||||
BrentChesny <bchesny@fb.com>
|
||||
CaptainStandby <18215579+CaptainStandby@users.noreply.github.com>
|
||||
Carlos Chida <carlos.chida@gmail.com>
|
||||
chlasch <72044019+chlasch@users.noreply.github.com>
|
||||
cwei-wish <cwei@wish.com>
|
||||
Cλctysman <git@cactys.dev>
|
||||
Daniel Hobbs <daniel.b.hobbs@gmail.com>
|
||||
David ALEXANDRE <9482408+david972@users.noreply.github.com>
|
||||
David Cheung <davidcheung@live.ca>
|
||||
David Laban <alsuren@gmail.com>
|
||||
David Laban <david.laban@red-badger.com>
|
||||
David San <davidsanfr@gmail.com>
|
||||
David van der Sluijs <david@pexlab.net>
|
||||
debrutal <duennes@gmail.com>
|
||||
Dejan Filipovic <dejan.filipovic@gmail.com>
|
||||
Dejan Filipovic <dejan@gensdeconfiance.com>
|
||||
Denis Palnitsky <palnitsky@gmail.com>
|
||||
Dibyajyoti Behera <dibyajyotibehera@gmail.com>
|
||||
Dimitrij Drus <dadrus@gmx.de>
|
||||
Dimitrij Drus <dimitrij.drus@innoq.com>
|
||||
Eifoen <35534229+Eifoen@users.noreply.github.com>
|
||||
emrah <emrah.com@gmail.com>
|
||||
emtammaru <emtammaru@gmail.com>
|
||||
Eric B <ericbellotti@gmail.com>
|
||||
ernax78 <65963237+ernax78@users.noreply.github.com>
|
||||
Erol Keskin <erolkeskin.dev@gmail.com>
|
||||
Erol Keskin <erolmon.kskn@gmail.com>
|
||||
Fabian Meyer <3982806+meyfa@users.noreply.github.com>
|
||||
Felix Beuke <felix.j.beuke@gmail.com>
|
||||
Ferdi <ferdina.kusumah@gmail.com>
|
||||
Florian Kramer <flo.kramer@outlook.com>
|
||||
Florian Nagel <nagelflorian@users.noreply.github.com>
|
||||
Florian Wiech <florian.wiech@gmail.com>
|
||||
frederikhors <41120635+frederikhors@users.noreply.github.com>
|
||||
Gajewski Dmitriy <dmit8815@gmail.com>
|
||||
Geoff Lawler <geoff.lawler@gmail.com>
|
||||
Giacomo Mazzamuto <g.mazzamuto@gmail.com>
|
||||
Gibheer <gibheer+github@zero-knowledge.org>
|
||||
goughjo02 <goughjo02@gmail.com>
|
||||
Grant Zvolský <GrantZvolsky@gmail.com>
|
||||
Harsimran Singh Maan <maan.harry@gmail.com>
|
||||
Henning Perl <henning.perl@gmail.com>
|
||||
Jacob Lehr <jacoblehr@gmail.com>
|
||||
Jakob Høgenes <1014990+jakhog@users.noreply.github.com>
|
||||
Jakob Sinclair <sinclair.jakob@mailbox.org>
|
||||
Jakub Błaszczyk <daemonsthere@gmail.com>
|
||||
James D. Nurmi <github@qwe.cc>
|
||||
James D. Nurmi <jdnurmi@qwe.cc>
|
||||
Jason Ertel <jertel@users.noreply.github.com>
|
||||
JeffreyThijs <jeffrey.thijs@hotmail.com>
|
||||
Jelle Besseling <jelle@pingiun.com>
|
||||
Jhonatan Hulse <jhonatan.hulse@gmail.com>
|
||||
JiggyDown <84430646+JiggyDown@users.noreply.github.com>
|
||||
jingkai <contact@jingk.ai>
|
||||
jld3103 <jld3103@users.noreply.github.com>
|
||||
Joe Krill <joekrill@users.noreply.github.com>
|
||||
Johan Forssell <johanforssell@users.noreply.github.com>
|
||||
John Binstead <67282336+Bin-fluence@users.noreply.github.com>
|
||||
John <jfcurran@users.noreply.github.com>
|
||||
Jonas Hungershausen <jonas.hungershausen@ory.sh>
|
||||
Jonathon Sheffield <samsmug@gmail.com>
|
||||
Jordan <jdisaacs@hotmail.com>
|
||||
Jordan May <jwmay2012@gmail.com>
|
||||
Juri <1773075+2mol@users.noreply.github.com>
|
||||
Kevin Goslar <kevin.goslar@gmail.com>
|
||||
Kim Neunert <kneunert@gmail.com>
|
||||
Klaus Herrmann <106238709+kmherrmann@users.noreply.github.com>
|
||||
koenmtb1 <koenmtb1@users.noreply.github.com>
|
||||
Koen van Marrewijk <koenmtb1@users.noreply.github.com>
|
||||
kszafran <k.p.szafranski@gmail.com>
|
||||
Kun Chong <kun@refs.cn>
|
||||
Landon Pattison <67596936+LandonPattison@users.noreply.github.com>
|
||||
Lan Phan <quoclan@gmail.com>
|
||||
Li Ming <lmcl90@hotmail.com>
|
||||
Lorenzo Baldassarri <lorenzobaldassarri16@gmail.com>
|
||||
Łukasz Harasimowicz <dev@harnash.eu>
|
||||
Łukasz Szcześniak <viters@student.agh.edu.pl>
|
||||
Márk Bartos <Jackneill@users.noreply.github.com>
|
||||
Mart Aarma <mart.aarma@nortal.com>
|
||||
Martin Eigenbrodt <martin.eigenbrodt@googlemail.com>
|
||||
Martin Eigenbrodt <martin.eigenbrodt@innoq.com>
|
||||
Martin <martin.boehm@1mbsoftware.net>
|
||||
Mathias Polligkeit <13847569+woylie@users.noreply.github.com>
|
||||
Matt Bonnell <matt.bonnell@icloud.com>
|
||||
Matthieu Jacquot <matthieu.jacquot.tech@protonmail.ch>
|
||||
Matúš Múčka <zippersk.mm@gmail.com>
|
||||
Maurice Freitag <maurice.freitag@hotmail.de>
|
||||
Mike Milano <coder1@gmail.com>
|
||||
Mike <perryao@users.noreply.github.com>
|
||||
Mikołaj Meller <52668809+mmeller-wikia@users.noreply.github.com>
|
||||
Miłosz <12242002+mszekiel@users.noreply.github.com>
|
||||
MiniDigger | Martin <admin@benndorf.dev>
|
||||
Mitar <mitar.github@tnode.com>
|
||||
Mitsuo Heijo <25817501+johejo@users.noreply.github.com>
|
||||
Nanik <nanikjava@gmail.com>
|
||||
Natanael Oliva <natanael.og@gmail.com>
|
||||
Neil Rutherford <374275+nrutherford@users.noreply.github.com>
|
||||
Nichlas Lendal <62498670+pr1ze@users.noreply.github.com>
|
||||
Nicholas Bush <nickdbush@hotmail.co.uk>
|
||||
Nick Campbell <nickcampbell18@gmail.com>
|
||||
Nick Malcev <41836660+nmlc@users.noreply.github.com>
|
||||
Nick Ufer <nick@ufer.dev>
|
||||
Nikita Puzankov <humb1t@yandex.ru>
|
||||
nipsufn <17983323+nipsufn@users.noreply.github.com>
|
||||
Ole Petersen <56505957+peteole@users.noreply.github.com>
|
||||
oliverpool <3864879+oliverpool@users.noreply.github.com>
|
||||
Omar A. Hachach <contact@omarhachach.com>
|
||||
Patrik Neu <patrik@ory.sh>
|
||||
Paweł Hemperek <pawel+github@hemperek.pl>
|
||||
Piotr Mścichowski <piotr.mscichowski@gmail.com>
|
||||
Przemysław Czaus <przemyslaw@czaus.pl>
|
||||
Quentin Gliech <quentingliech@gmail.com>
|
||||
qvamatel <qvamatel@freemail.hu>
|
||||
Radek Gruchalski <radekg@users.noreply.github.com>
|
||||
RamiBerm <54766858+RamiBerm@users.noreply.github.com>
|
||||
Rauno Viskus <Rauno56@users.noreply.github.com>
|
||||
reshetnik-alexey <oleksii.reshetnik@gmail.com>
|
||||
Rodrigo Queiro <overdrigzed@gmail.com>
|
||||
rvo <ryanvanoss@users.noreply.github.com>
|
||||
sawadashota <shota@sslife.tech>
|
||||
sawadashota <xiootas@gmail.com>
|
||||
Seb <git@erhardt.net>
|
||||
seremenko-wish <60801091+seremenko-wish@users.noreply.github.com>
|
||||
Sergey Plaunov <splaunov@users.noreply.github.com>
|
||||
sherbang <brian@sherbang.com>
|
||||
shivam <86538330+shivam-909@users.noreply.github.com>
|
||||
Simon Lipp <sloonz@gmail.com>
|
||||
Simon-Pierre Gingras <892367+spg@users.noreply.github.com>
|
||||
Steffen Heidel <kontakt@sthh.eu>
|
||||
TannerGabriel <40315960+TannerGabriel@users.noreply.github.com>
|
||||
Theodor Brandt <teddy@brandts.se>
|
||||
theotherian <ian.g.simpson@gmail.com>
|
||||
Thomas Aidan Curran <2030403+tacurran@users.noreply.github.com>
|
||||
Thomas Guillet <guillet.thomas@gmail.com>
|
||||
Thomas Ruiz <contact@thomasruiz.eu>
|
||||
Tomasz Tomalak <12939493+t-tomalak@users.noreply.github.com>
|
||||
Tom Fenech <tomjwfenech@gmail.com>
|
||||
Toon van Strijp <toonvanstrijp@gmail.com>
|
||||
Tsirkin Evgeny <tsirkin@gmail.com>
|
||||
ttimonen <toni.timonen@iki.fi>
|
||||
Tyler Battle <tyler@battle.ca>
|
||||
VeenaInd <104088519+VeenaInd@users.noreply.github.com>
|
||||
Victor Duarte <zvictor@users.noreply.github.com>
|
||||
Vincent <vincent@ory.sh>
|
||||
vinckr <vincent@ory.sh>
|
||||
Viz <viz@linux.com>
|
||||
WeiQi <17816875364@163.com>
|
||||
wezzle <wesley@black-sky.nl>
|
||||
Yagiz Nizipli <yagiz@nizipli.com>
|
||||
yon <38630464+yonbh@users.noreply.github.com>
|
||||
Yorick Holkamp <github@nextpulse.nl>
|
||||
you1996 <45292366+you1996@users.noreply.github.com>
|
||||
Yuvraj <10830562+evalsocket@users.noreply.github.com>
|
||||
Zageron <hello@adambryant.ca>
|
||||
Zhiming Guo <guo.zhming@gmail.com>
|
||||
znerol <lo+github@znerol.ch>
|
||||
好风 <gwindfree@gmail.com>
|
||||
145
CODE_OF_CONDUCT.md
Normal file
145
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,145 @@
|
||||
<!-- AUTO-GENERATED, DO NOT EDIT! -->
|
||||
<!-- Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/CODE_OF_CONDUCT.md -->
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Open Source Community Support
|
||||
|
||||
Ory Open source software is collaborative and based on contributions by
|
||||
developers in the Ory community. There is no obligation from Ory to help with
|
||||
individual problems. If Ory open source software is used in production in a
|
||||
for-profit company or enterprise environment, we mandate a paid support contract
|
||||
where Ory is obligated under their service level agreements (SLAs) to offer a
|
||||
defined level of availability and responsibility. For more information about
|
||||
paid support please contact us at sales@ory.com.
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[office@ory.com](mailto:office@ory.com). All complaints will be reviewed and
|
||||
investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder][mozilla coc].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][faq]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[mozilla coc]: https://github.com/mozilla/diversity
|
||||
[faq]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
293
CONTRIBUTING.md
Normal file
293
CONTRIBUTING.md
Normal file
@@ -0,0 +1,293 @@
|
||||
<!-- AUTO-GENERATED, DO NOT EDIT! -->
|
||||
<!-- Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/CONTRIBUTING.md -->
|
||||
|
||||
# Contribute to Ory Kratos<!-- omit in toc -->
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [FAQ](#faq)
|
||||
- [How can I contribute?](#how-can-i-contribute)
|
||||
- [Communication](#communication)
|
||||
- [Contribute examples or community projects](#contribute-examples-or-community-projects)
|
||||
- [Contribute code](#contribute-code)
|
||||
- [Contribute documentation](#contribute-documentation)
|
||||
- [Disclosing vulnerabilities](#disclosing-vulnerabilities)
|
||||
- [Code style](#code-style)
|
||||
- [Working with forks](#working-with-forks)
|
||||
- [Conduct](#conduct)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Introduction
|
||||
|
||||
_Please note_: We take Ory Kratos's security and our users' trust very
|
||||
seriously. If you believe you have found a security issue in Ory Kratos, please
|
||||
disclose it by contacting us at security@ory.com.
|
||||
|
||||
There are many ways in which you can contribute. The goal of this document is to
|
||||
provide a high-level overview of how you can get involved in Ory.
|
||||
|
||||
As a potential contributor, your changes and ideas are welcome at any hour of
|
||||
the day or night, on weekdays, weekends, and holidays. Please do not ever
|
||||
hesitate to ask a question or send a pull request.
|
||||
|
||||
If you are unsure, just ask or submit the issue or pull request anyways. You
|
||||
won't be yelled at for giving it your best effort. The worst that can happen is
|
||||
that you'll be politely asked to change something. We appreciate any sort of
|
||||
contributions and don't want a wall of rules to get in the way of that.
|
||||
|
||||
That said, if you want to ensure that a pull request is likely to be merged,
|
||||
talk to us! You can find out our thoughts and ensure that your contribution
|
||||
won't clash with Ory Kratos's direction. A great way to do this is via
|
||||
[Ory Kratos Discussions](https://github.com/ory/kratos/discussions) or the
|
||||
[Ory Chat](https://www.ory.com/chat).
|
||||
|
||||
## FAQ
|
||||
|
||||
- I am new to the community. Where can I find the
|
||||
[Ory Community Code of Conduct?](https://github.com/ory/kratos/blob/master/CODE_OF_CONDUCT.md)
|
||||
|
||||
- I have a question. Where can I get
|
||||
[answers to questions regarding Ory Kratos?](#communication)
|
||||
|
||||
- I would like to contribute but I am not sure how. Are there
|
||||
[easy ways to contribute?](#how-can-i-contribute)
|
||||
[Or good first issues?](https://github.com/search?l=&o=desc&q=label%3A%22help+wanted%22+label%3A%22good+first+issue%22+is%3Aopen+user%3Aory+user%3Aory-corp&s=updated&type=Issues)
|
||||
|
||||
- I want to talk to other Ory Kratos users.
|
||||
[How can I become a part of the community?](#communication)
|
||||
|
||||
- I would like to know what I am agreeing to when I contribute to Ory Kratos.
|
||||
Does Ory have
|
||||
[a Contributors License Agreement?](https://cla-assistant.io/ory/kratos)
|
||||
|
||||
- I would like updates about new versions of Ory Kratos.
|
||||
[How are new releases announced?](https://www.ory.com/l/sign-up-newsletter)
|
||||
|
||||
## How can I contribute?
|
||||
|
||||
If you want to start to contribute code right away, take a look at the
|
||||
[list of good first issues](https://github.com/ory/kratos/labels/good%20first%20issue).
|
||||
|
||||
There are many other ways you can contribute. Here are a few things you can do
|
||||
to help out:
|
||||
|
||||
- **Give us a star.** It may not seem like much, but it really makes a
|
||||
difference. This is something that everyone can do to help out Ory Kratos.
|
||||
Github stars help the project gain visibility and stand out.
|
||||
|
||||
- **Join the community.** Sometimes helping people can be as easy as listening
|
||||
to their problems and offering a different perspective. Join our Slack, have a
|
||||
look at discussions in the forum and take part in community events. More info
|
||||
on this in [Communication](#communication).
|
||||
|
||||
- **Answer discussions.** At all times, there are several unanswered discussions
|
||||
on GitHub. You can see an
|
||||
[overview here](https://github.com/discussions?discussions_q=is%3Aunanswered+org%3Aory+sort%3Aupdated-desc).
|
||||
If you think you know an answer or can provide some information that might
|
||||
help, please share it! Bonus: You get GitHub achievements for answered
|
||||
discussions.
|
||||
|
||||
- **Help with open issues.** We have a lot of open issues for Ory Kratos and
|
||||
some of them may lack necessary information, some are duplicates of older
|
||||
issues. You can help out by guiding people through the process of filling out
|
||||
the issue template, asking for clarifying information or pointing them to
|
||||
existing issues that match their description of the problem.
|
||||
|
||||
- **Review documentation changes.** Most documentation just needs a review for
|
||||
proper spelling and grammar. If you think a document can be improved in any
|
||||
way, feel free to hit the `edit` button at the top of the page. More info on
|
||||
contributing to the documentation [here](#contribute-documentation).
|
||||
|
||||
- **Help with tests.** Pull requests may lack proper tests or test plans. These
|
||||
are needed for the change to be implemented safely.
|
||||
|
||||
## Communication
|
||||
|
||||
We use [Slack](https://www.ory.com/chat). You are welcome to drop in and ask
|
||||
questions, discuss bugs and feature requests, talk to other users of Ory, etc.
|
||||
|
||||
Check out [Ory Kratos Discussions](https://github.com/ory/kratos/discussions).
|
||||
This is a great place for in-depth discussions and lots of code examples, logs
|
||||
and similar data.
|
||||
|
||||
You can also join our community calls if you want to speak to the Ory team
|
||||
directly or ask some questions. You can find more info and participate in
|
||||
[Slack](https://www.ory.com/chat) in the #community-call channel.
|
||||
|
||||
If you want to receive regular notifications about updates to Ory Kratos,
|
||||
consider joining the mailing list. We will _only_ send you vital information on
|
||||
the projects that you are interested in.
|
||||
|
||||
Also, [follow us on Twitter](https://twitter.com/orycorp).
|
||||
|
||||
## Contribute examples or community projects
|
||||
|
||||
One of the most impactful ways to contribute is by adding code examples or other
|
||||
Ory-related code. You can find an overview of community code in the
|
||||
[awesome-ory](https://github.com/ory/awesome-ory) repository.
|
||||
|
||||
_If you would like to contribute a new example, we would love to hear from you!_
|
||||
|
||||
Please [open a pull request at awesome-ory](https://github.com/ory/awesome-ory/)
|
||||
to add your example or Ory-related project to the awesome-ory README.
|
||||
|
||||
## Contribute code
|
||||
|
||||
**All code contributions require prior discussion and agreement with maintainers
|
||||
before opening a pull request.**
|
||||
|
||||
This applies to bug fixes, new features, and refactoring. Before writing code:
|
||||
|
||||
1. Open a [Discussion](https://github.com/ory/kratos/discussions/new/choose) or
|
||||
[Issue](https://github.com/ory/kratos/issues/new/choose) describing the
|
||||
problem and your proposed solution.
|
||||
2. Wait for maintainer feedback and explicit agreement on the implementation
|
||||
approach.
|
||||
3. Only then begin writing code.
|
||||
|
||||
Pull requests without prior discussion may be closed without review. This policy
|
||||
exists to protect your time and maintainer time.
|
||||
|
||||
"Drive-by" PRs, even well-intentioned ones, often conflict with the project
|
||||
roadmap, duplicate ongoing work, or introduce architectural inconsistencies. A
|
||||
quick conversation can help avoid these problems.
|
||||
|
||||
All contributions are made via pull requests. To make a pull request, you will
|
||||
need a GitHub account; if you are unclear on this process, see GitHub's
|
||||
documentation on [forking](https://help.github.com/articles/fork-a-repo) and
|
||||
[pull requests](https://help.github.com/articles/using-pull-requests). Pull
|
||||
requests should be targeted at the `master` branch. Before creating a pull
|
||||
request, go through this checklist:
|
||||
|
||||
1. Create a feature branch off of `master` so that changes do not get mixed up.
|
||||
1. [Rebase](http://git-scm.com/book/en/Git-Branching-Rebasing) your local
|
||||
changes against the `master` branch.
|
||||
1. Run the full project test suite with the `go test ./...` (or
|
||||
equivalent) command and confirm that it passes.
|
||||
1. Run `make format`
|
||||
1. Add a descriptive prefix to commits. This ensures a uniform commit history
|
||||
and helps structure the changelog. Please refer to this
|
||||
[Convential Commits configuration](https://github.com/ory/kratos/blob/master/.github/workflows/conventional_commits.yml)
|
||||
for the list of accepted prefixes. You can read more about the Conventional
|
||||
Commit specification
|
||||
[at their site](https://www.conventionalcommits.org/en/v1.0.0/).
|
||||
|
||||
If a pull request is not ready to be reviewed yet
|
||||
[it should be marked as a "Draft"](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request).
|
||||
|
||||
Before your contributions can be reviewed you need to sign our
|
||||
[Contributor License Agreement](https://cla-assistant.io/ory/kratos).
|
||||
|
||||
This agreement defines the terms under which your code is contributed to Ory.
|
||||
More specifically it declares that you have the right to, and actually do, grant
|
||||
us the rights to use your contribution. You can see the Apache 2.0 license under
|
||||
which our projects are published
|
||||
[here](https://github.com/ory/meta/blob/master/LICENSE).
|
||||
|
||||
When pull requests fail the automated testing stages (for example unit or E2E
|
||||
tests), authors are expected to update their pull requests to address the
|
||||
failures until the tests pass.
|
||||
|
||||
Pull requests eligible for review
|
||||
|
||||
1. follow the repository's code formatting conventions;
|
||||
2. include tests that prove that the change works as intended and does not add
|
||||
regressions;
|
||||
3. document the changes in the code and/or the project's documentation;
|
||||
4. pass the CI pipeline;
|
||||
5. have signed our
|
||||
[Contributor License Agreement](https://cla-assistant.io/ory/kratos);
|
||||
6. include a proper git commit message following the
|
||||
[Conventional Commit Specification](https://www.conventionalcommits.org/en/v1.0.0/).
|
||||
|
||||
If all of these items are checked, the pull request is ready to be reviewed and
|
||||
you should change the status to "Ready for review" and
|
||||
[request review from a maintainer](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review).
|
||||
|
||||
Reviewers will approve the pull request once they are satisfied with the patch.
|
||||
|
||||
### AI-assisted contributions
|
||||
|
||||
AI can be a valuable aid for writing code, documentation, and tests. However, to
|
||||
maintain the quality and reliability of Ory Kratos, please follow these
|
||||
guidelines:
|
||||
|
||||
- When submitting a pull request or issue that involved AI assistance, mention
|
||||
the tools you used and the extent of their involvement. This helps reviewers
|
||||
understand the context of your contribution.
|
||||
|
||||
- Pull requests created with AI assistance should address an existing, accepted
|
||||
issue. If you have an idea for a new feature or improvement, please open a
|
||||
discussion or issue first to ensure alignment with the project's direction
|
||||
before investing time in implementation.
|
||||
|
||||
- You must use AI responsibly when writing code. All contributions must be
|
||||
tested and verified before submission. Unreviewed AI-generated code will not
|
||||
be accepted, and repeated submissions of this nature may result in restricted
|
||||
contribution privileges.
|
||||
|
||||
- When using AI to help draft issues, discussions, or documentation, review and
|
||||
edit the output before submitting. AI tends to be verbose. Trim unnecessary
|
||||
content and ensure your submission is clear and focused.
|
||||
|
||||
Contributors must use AI responsibly. These guidelines exist to ensure that
|
||||
every contribution meets the high standards our community expects, while still
|
||||
embracing the productivity benefits that AI tools can provide.
|
||||
|
||||
## Contribute documentation
|
||||
|
||||
Please provide documentation when changing, removing, or adding features. All
|
||||
Ory Documentation resides in the
|
||||
[Ory documentation repository](https://github.com/ory/docs/). For further
|
||||
instructions please head over to the Ory Documentation
|
||||
[README.md](https://github.com/ory/docs/blob/master/README.md).
|
||||
|
||||
## Disclosing vulnerabilities
|
||||
|
||||
Please disclose vulnerabilities exclusively to
|
||||
[security@ory.com](mailto:security@ory.com). Do not use GitHub issues.
|
||||
|
||||
## Code style
|
||||
|
||||
Please run `make format` to format all source code following the Ory standard.
|
||||
|
||||
### Working with forks
|
||||
|
||||
```bash
|
||||
# First you clone the original repository
|
||||
git clone git@github.com:ory/ory/kratos.git
|
||||
|
||||
# Next you add a git remote that is your fork:
|
||||
git remote add fork git@github.com:<YOUR-GITHUB-USERNAME-HERE>/ory/kratos.git
|
||||
|
||||
# Next you fetch the latest changes from origin for master:
|
||||
git fetch origin
|
||||
git checkout master
|
||||
git pull --rebase
|
||||
|
||||
# Next you create a new feature branch off of master:
|
||||
git checkout my-feature-branch
|
||||
|
||||
# Now you do your work and commit your changes:
|
||||
git add -A
|
||||
git commit -a -m "fix: this is the subject line" -m "This is the body line. Closes #123"
|
||||
|
||||
# And the last step is pushing this to your fork
|
||||
git push -u fork my-feature-branch
|
||||
```
|
||||
|
||||
Now go to the project's GitHub Pull Request page and click "New pull request"
|
||||
|
||||
## Conduct
|
||||
|
||||
Whether you are a regular contributor or a newcomer, we care about making this
|
||||
community a safe place for you and we've got your back.
|
||||
|
||||
[Ory Community Code of Conduct](https://github.com/ory/kratos/blob/master/CODE_OF_CONDUCT.md)
|
||||
|
||||
We welcome discussion about creating a welcoming, safe, and productive
|
||||
environment for the community. If you have any questions, feedback, or concerns
|
||||
[please let us know](https://www.ory.com/chat).
|
||||
215
DEVELOP.md
Normal file
215
DEVELOP.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# Development
|
||||
|
||||
This document explains how to develop Ory Kratos, run tests, and work with the tooling around it.
|
||||
|
||||
## Upgrading and changelog
|
||||
|
||||
Check [releases tab](https://github.com/ory/kratos/releases) for updates and changelogs when using the open source license.
|
||||
|
||||
## Command line documentation
|
||||
|
||||
To see available commands and flags, run:
|
||||
|
||||
```bash
|
||||
kratos -h
|
||||
# or
|
||||
kratos help
|
||||
```
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
We encourage all contributions. Before opening a pull request, read the
|
||||
[contribution guidelines](./CONTRIBUTING.md).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You need Go 1.16+ and, for the test suites:
|
||||
|
||||
* Docker and Docker Compose
|
||||
* `make`
|
||||
* Node.js and npm
|
||||
|
||||
You can develop Ory Kratos on Windows, but most guides assume a Unix shell such as `bash` or `zsh`.
|
||||
|
||||
## Install from source
|
||||
|
||||
To install Kratos from source:
|
||||
|
||||
```make
|
||||
make install
|
||||
```
|
||||
|
||||
## Formatting code
|
||||
|
||||
Format all code using:
|
||||
|
||||
```make
|
||||
make format
|
||||
```
|
||||
|
||||
The continuous integration pipeline checks code formatting.
|
||||
|
||||
## Running tests
|
||||
|
||||
There are three types of tests:
|
||||
|
||||
* Short tests that do not require a SQL database
|
||||
* Regular tests that require PostgreSQL, MySQL, and CockroachDB
|
||||
* End to end tests that use real databases and a test browser
|
||||
|
||||
### Short tests
|
||||
|
||||
Short tests run quickly and use SQLite.
|
||||
|
||||
Run all short tests:
|
||||
|
||||
```bash
|
||||
go test -short ./...
|
||||
```
|
||||
|
||||
Run short tests in a specific module:
|
||||
|
||||
```bash
|
||||
cd client
|
||||
go test -short .
|
||||
```
|
||||
|
||||
### Regular tests
|
||||
|
||||
Regular tests require a database setup.
|
||||
|
||||
The test suite can start databases using
|
||||
[ory/dockertest](https://github.com/ory/dockertest). In practice, it is usually
|
||||
easier and faster to use the Makefile targets.
|
||||
|
||||
Run the full test suite:
|
||||
|
||||
```make
|
||||
make test
|
||||
```
|
||||
|
||||
> Note: `make test` recreates the databases every time. This can be slow if you
|
||||
> are iterating frequently on a specific test.
|
||||
|
||||
If you want to reuse databases across test runs, initialize them once:
|
||||
|
||||
```bash
|
||||
make test-resetdb
|
||||
export TEST_DATABASE_MYSQL='mysql://root:secret@(127.0.0.1:3444)/mysql?parseTime=true'
|
||||
export TEST_DATABASE_POSTGRESQL='postgres://postgres:secret@127.0.0.1:3445/kratos?sslmode=disable'
|
||||
export TEST_DATABASE_COCKROACHDB='cockroach://root@127.0.0.1:3446/defaultdb?sslmode=disable'
|
||||
```
|
||||
|
||||
Then you can run Go tests directly as often as needed:
|
||||
|
||||
```bash
|
||||
go test ./...
|
||||
|
||||
# or in a module:
|
||||
cd client
|
||||
go test .
|
||||
```
|
||||
|
||||
### Updating test fixtures
|
||||
|
||||
Some tests use snapshot fixtures.
|
||||
|
||||
Update snapshots for short tests:
|
||||
|
||||
```bash
|
||||
make test-update-snapshots
|
||||
```
|
||||
|
||||
Update all snapshots:
|
||||
|
||||
```bash
|
||||
UPDATE_SNAPSHOTS=true go test -p 4 ./...
|
||||
```
|
||||
|
||||
You can run this from the repository root or from subdirectories.
|
||||
|
||||
### End-to-end tests
|
||||
|
||||
End to end tests are implemented with [Cypress](https://www.cypress.io).
|
||||
|
||||
> On ARM based Macs you may need to install Rosetta 2 to run Cypress.
|
||||
> See the Cypress documentation:
|
||||
> [https://www.cypress.io/blog/2021/01/20/running-cypress-on-the-apple-m1-silicon-arm-architecture-using-rosetta-2/](https://www.cypress.io/blog/2021/01/20/running-cypress-on-the-apple-m1-silicon-arm-architecture-using-rosetta-2/)
|
||||
|
||||
To install Rosetta 2:
|
||||
|
||||
```bash
|
||||
softwareupdate --install-rosetta --agree-to-license
|
||||
```
|
||||
|
||||
Run e2e tests in development mode:
|
||||
|
||||
```bash
|
||||
./test/e2e/run.sh --dev sqlite
|
||||
```
|
||||
|
||||
Run all e2e tests with databases:
|
||||
|
||||
```make
|
||||
make test-e2e
|
||||
```
|
||||
|
||||
For more options:
|
||||
|
||||
```bash
|
||||
./test/e2e/run.sh
|
||||
```
|
||||
|
||||
#### Run a single test
|
||||
|
||||
Add `.only` to the test you want to run, for example:
|
||||
|
||||
```ts
|
||||
it.only('invalid remote recovery email template', () => {
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
#### Run a subset of tests
|
||||
|
||||
To run a subset of e2e tests:
|
||||
|
||||
1. Edit `cypress.json` in `test/e2e/`.
|
||||
|
||||
2. Add the `testFiles` option and point it to the specs you want, for example:
|
||||
|
||||
```json
|
||||
"testFiles": ["profiles/network/*"]
|
||||
```
|
||||
|
||||
3. Start the tests again using the run script or Makefile.
|
||||
|
||||
## Build Docker image
|
||||
|
||||
To build a development Docker image:
|
||||
|
||||
```make
|
||||
make docker
|
||||
```
|
||||
|
||||
## Preview API documentation
|
||||
|
||||
To work on and preview the generated API documentation:
|
||||
|
||||
1. Update the SDK including the OpenAPI specification:
|
||||
|
||||
```make
|
||||
make sdk
|
||||
```
|
||||
|
||||
2. Run the preview server for API documentation:
|
||||
|
||||
```make
|
||||
make docs/api
|
||||
```
|
||||
|
||||
3. Run the preview server for Swagger documentation:
|
||||
|
||||
```make
|
||||
make docs/swagger
|
||||
```
|
||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
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.
|
||||
214
Makefile
Normal file
214
Makefile
Normal file
@@ -0,0 +1,214 @@
|
||||
SHELL=/usr/bin/env bash -o pipefail
|
||||
|
||||
# EXECUTABLES = docker-compose docker node npm go
|
||||
# K := $(foreach exec,$(EXECUTABLES),\
|
||||
# $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))
|
||||
|
||||
export PATH := .bin:${PATH}
|
||||
export PWD := $(shell pwd)
|
||||
export BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
export VCS_REF := $(shell git rev-parse HEAD)
|
||||
export QUICKSTART_OPTIONS ?=
|
||||
export IMAGE_TAG := $(if $(IMAGE_TAG),$(IMAGE_TAG),latest)
|
||||
|
||||
.bin/clidoc:
|
||||
echo "deprecated usage, use docs/cli instead"
|
||||
go build -o .bin/clidoc ./cmd/clidoc/.
|
||||
|
||||
.PHONY: docs/cli
|
||||
docs/cli:
|
||||
go run ./cmd/clidoc/. .
|
||||
|
||||
.PHONY: docs/api
|
||||
docs/api:
|
||||
npx @redocly/openapi-cli preview-docs spec/api.json
|
||||
|
||||
.PHONY: docs/swagger
|
||||
docs/swagger:
|
||||
npx @redocly/openapi-cli preview-docs spec/swagger.json
|
||||
|
||||
.bin/golangci-lint: Makefile
|
||||
curl --retry 7 --retry-connrefused -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -d -b .bin v2.10.1
|
||||
|
||||
.bin/hydra: Makefile
|
||||
bash <(curl --retry 7 --retry-connrefused https://raw.githubusercontent.com/ory/meta/master/install.sh) -d -b .bin hydra v2.2.0
|
||||
|
||||
.bin/ory: Makefile
|
||||
curl --retry 7 --retry-connrefused https://raw.githubusercontent.com/ory/meta/master/install.sh | bash -s -- -b .bin ory v0.2.2
|
||||
touch -a -m .bin/ory
|
||||
|
||||
.bin/buf: Makefile
|
||||
curl -sSL \
|
||||
"https://github.com/bufbuild/buf/releases/download/v1.39.0/buf-$(shell uname -s)-$(shell uname -m).tar.gz" | \
|
||||
tar -xvzf - -C ".bin/" --strip-components=2 buf/bin/buf buf/bin/protoc-gen-buf-breaking buf/bin/protoc-gen-buf-lint
|
||||
touch -a -m .bin/buf
|
||||
|
||||
.PHONY: lint
|
||||
lint: .bin/golangci-lint .bin/buf
|
||||
.bin/golangci-lint run -v --timeout 10m ./...
|
||||
.bin/buf lint
|
||||
|
||||
.PHONY: mocks
|
||||
mocks:
|
||||
go tool mockgen -mock_names Manager=MockLoginExecutorDependencies -package pkg -destination pkg/hook_login_executor_dependencies.go github.com/ory/kratos/selfservice loginExecutorDependencies
|
||||
|
||||
.PHONY: proto
|
||||
proto: gen/oidc/v1/state.pb.go
|
||||
|
||||
gen/oidc/v1/state.pb.go: proto/oidc/v1/state.proto buf.yaml buf.gen.yaml .bin/buf
|
||||
.bin/buf generate
|
||||
go tool goimports -w gen/
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
go install .
|
||||
|
||||
.PHONY: test-resetdb
|
||||
test-resetdb:
|
||||
script/testenv.sh
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
docker pull oryd/hydra:v2.2.0
|
||||
go test -p 1 -count=1 -failfast $(if $(TAGS),-tags $(TAGS)) ./...
|
||||
|
||||
test-short:
|
||||
go test -count=1 -failfast -short $(if $(TAGS),-tags $(TAGS)) ./...
|
||||
|
||||
.PHONY: test-coverage
|
||||
test-coverage:
|
||||
go test -coverprofile=coverage.out -failfast -timeout=20m ./...
|
||||
|
||||
.PHONY: test-coverage-next
|
||||
test-coverage-next:
|
||||
go test -short -failfast -timeout=20m -cover ./... --args test.gocoverdir="$$PWD/coverage"
|
||||
go tool covdata percent -i=coverage
|
||||
go tool covdata textfmt -i=./coverage -o coverage.new.out
|
||||
|
||||
# Generates the SDK
|
||||
.PHONY: sdk
|
||||
sdk: .bin/ory node_modules
|
||||
../../.bin/swagger generate spec -m -o spec/swagger.json \
|
||||
-c github.com/ory/kratos \
|
||||
-c github.com/ory/x/healthx \
|
||||
-c github.com/ory/x/crdbx \
|
||||
-c github.com/ory/x/openapix \
|
||||
-c github.com/ory/x/region
|
||||
ory dev swagger sanitize ./spec/swagger.json
|
||||
../../.bin/swagger validate ./spec/swagger.json
|
||||
CIRCLE_PROJECT_USERNAME=ory CIRCLE_PROJECT_REPONAME=kratos \
|
||||
ory dev openapi migrate \
|
||||
--health-path-tags metadata \
|
||||
-p https://raw.githubusercontent.com/ory/x/master/healthx/openapi/patch.yaml \
|
||||
-p file://.schema/openapi/patches/meta.yaml \
|
||||
-p file://.schema/openapi/patches/schema.yaml \
|
||||
-p file://.schema/openapi/patches/selfservice.yaml \
|
||||
-p file://.schema/openapi/patches/security.yaml \
|
||||
-p file://.schema/openapi/patches/session.yaml \
|
||||
-p file://.schema/openapi/patches/identity.yaml \
|
||||
-p file://.schema/openapi/patches/courier.yaml \
|
||||
-p file://.schema/openapi/patches/generic_error.yaml \
|
||||
-p file://.schema/openapi/patches/nulls.yaml \
|
||||
-p file://.schema/openapi/patches/common.yaml \
|
||||
spec/swagger.json spec/api.json
|
||||
|
||||
rm -rf pkg/httpclient
|
||||
mkdir -p pkg/httpclient/
|
||||
npm run openapi-generator-cli -- generate -i "spec/api.json" \
|
||||
-g go \
|
||||
-o "pkg/httpclient" \
|
||||
--git-user-id ory \
|
||||
--git-repo-id client-go \
|
||||
--git-host github.com \
|
||||
--api-name-suffix "API" \
|
||||
-c .schema/openapi/gen.go.yml
|
||||
|
||||
(cd pkg/httpclient; rm -rf go.mod go.sum test api docs)
|
||||
|
||||
rm -rf pkg/client-go
|
||||
mkdir -p pkg/client-go/
|
||||
npm run openapi-generator-cli -- generate -i "spec/api.json" \
|
||||
-g go \
|
||||
-o "pkg/client-go" \
|
||||
--git-user-id ory \
|
||||
--git-repo-id client-go \
|
||||
--git-host github.com \
|
||||
--api-name-suffix "API" \
|
||||
-c .schema/openapi/gen.go.yml
|
||||
|
||||
(cd pkg/client-go; go mod edit -module github.com/ory/client-go go.mod; rm -rf test api docs; go mod tidy)
|
||||
|
||||
make format
|
||||
|
||||
.PHONY: quickstart
|
||||
quickstart:
|
||||
docker pull oryd/kratos:latest
|
||||
docker pull oryd/kratos-selfservice-ui-node:latest
|
||||
docker-compose -f quickstart.yml -f quickstart-standalone.yml up --build --force-recreate
|
||||
|
||||
.PHONY: quickstart-dev
|
||||
quickstart-dev:
|
||||
docker build -f .docker/Dockerfile-build -t oryd/kratos:latest .
|
||||
docker-compose -f quickstart.yml -f quickstart-standalone.yml -f quickstart-latest.yml $(QUICKSTART_OPTIONS) up --build --force-recreate
|
||||
|
||||
authors: # updates the AUTHORS file
|
||||
curl --retry 7 --retry-connrefused https://raw.githubusercontent.com/ory/ci/master/authors/authors.sh | env PRODUCT="Ory Kratos" bash
|
||||
|
||||
PRETTIER_VERSION=$(shell cat package.json | jq -r '.devDependencies["prettier"] // .dependencies["prettier"]')
|
||||
|
||||
# Formats the code
|
||||
.PHONY: format
|
||||
format: .bin/ory .bin/buf
|
||||
.bin/ory dev headers copyright --exclude=gen --exclude=pkg/httpclient --exclude=pkg/client-go --exclude test/e2e/proxy/node_modules --exclude test/e2e/node_modules --exclude node_modules --exclude=oryx
|
||||
go tool goimports -w -local github.com/ory .
|
||||
@echo "Prettier Version: $(PRETTIER_VERSION)"
|
||||
npx --package=prettier@$(PRETTIER_VERSION) prettier 'test/e2e/**/*{.ts,.js}' --write
|
||||
npx --package=prettier@$(PRETTIER_VERSION) prettier '.github' --write
|
||||
.bin/buf format --write
|
||||
|
||||
# Build local docker image
|
||||
.PHONY: docker
|
||||
docker:
|
||||
DOCKER_BUILDKIT=1 DOCKER_CONTENT_TRUST=1 docker build -f .docker/Dockerfile-build --build-arg=COMMIT=$(VCS_REF) --build-arg=BUILD_DATE=$(BUILD_DATE) -t oryd/kratos:${IMAGE_TAG} .
|
||||
|
||||
.PHONY: test-e2e
|
||||
test-e2e: node_modules test-resetdb kratos-config-e2e
|
||||
source script/test-envs.sh
|
||||
test/e2e/run.sh sqlite
|
||||
test/e2e/run.sh postgres
|
||||
test/e2e/run.sh cockroach
|
||||
test/e2e/run.sh mysql
|
||||
|
||||
.PHONY: test-e2e-playwright
|
||||
test-e2e-playwright: node_modules test-resetdb kratos-config-e2e
|
||||
source script/test-envs.sh
|
||||
test/e2e/run.sh --only-setup
|
||||
(cd test/e2e; DB=memory npm run playwright)
|
||||
|
||||
.PHONY: test-refresh
|
||||
test-refresh:
|
||||
UPDATE_SNAPSHOTS=true go test -tags refresh -short ./...
|
||||
|
||||
.PHONY: pre-release
|
||||
pre-release:
|
||||
go tool yq '.services.kratos.image = "oryd/kratos:'$$DOCKER_TAG'"' -i quickstart.yml
|
||||
go tool yq '.services.kratos-migrate.image = "oryd/kratos:'$$DOCKER_TAG'"' -i quickstart.yml
|
||||
go tool yq '.services.kratos-selfservice-ui-node.image = "oryd/kratos-selfservice-ui-node:'$$DOCKER_TAG'"' -i quickstart.yml
|
||||
|
||||
.PHONY: post-release
|
||||
post-release:
|
||||
echo "nothing to do"
|
||||
|
||||
licenses: .bin/licenses node_modules # checks open-source licenses
|
||||
.bin/licenses
|
||||
|
||||
.bin/licenses: Makefile
|
||||
curl --retry 7 --retry-connrefused https://raw.githubusercontent.com/ory/ci/master/licenses/install | sh
|
||||
|
||||
node_modules: package-lock.json
|
||||
npm ci
|
||||
touch node_modules
|
||||
|
||||
.PHONY: kratos-config-e2e
|
||||
kratos-config-e2e:
|
||||
sh ./test/e2e/render-kratos-config.sh
|
||||
517
README.md
Normal file
517
README.md
Normal file
@@ -0,0 +1,517 @@
|
||||
<h1 align="center">
|
||||
<img src="https://raw.githubusercontent.com/ory/meta/master/static/banners/kratos.svg" alt="Ory Kratos - Cloud native identity and user management">
|
||||
</h1>
|
||||
|
||||
<h4 align="center">
|
||||
<a href="https://www.ory.com/chat">Chat</a> ·
|
||||
<a href="https://github.com/ory/kratos/discussions">Discussions</a> ·
|
||||
<a href="https://www.ory.com/l/sign-up-newsletter">Newsletter</a> ·
|
||||
<a href="https://www.ory.com/docs/">Docs</a> ·
|
||||
<a href="https://console.ory.sh/">Try Ory Network</a> ·
|
||||
<a href="https://www.ory.com/jobs/">Jobs</a>
|
||||
</h4>
|
||||
|
||||
Ory Kratos is an API first identity and user management system for cloud native
|
||||
applications. It centralizes login, registration, recovery, verification, and
|
||||
profile management flows so your services consume them instead of reimplementing
|
||||
them.
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
**Table of contents**
|
||||
|
||||
- [What is Ory Kratos?](#what-is-ory-kratos)
|
||||
- [Why Ory Kratos](#why-ory-kratos)
|
||||
- [Migrating from Auth0, Okta, and similar providers](#migrating-from-auth0-okta-and-similar-providers)
|
||||
- [Deployment options](#deployment-options)
|
||||
- [Use Ory Kratos on the Ory Network](#use-ory-kratos-on-the-ory-network)
|
||||
- [Self-host Ory Kratos](#self-host-ory-kratos)
|
||||
- [Quickstart](#quickstart)
|
||||
- [Who is using it?](#who-is-using-it)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## What is Ory Kratos?
|
||||
|
||||
Ory Kratos is an API first identity and user management system that follows
|
||||
[cloud architecture best practices](https://www.ory.com/docs/ecosystem/software-architecture-philosophy).
|
||||
It focuses on core identity workflows that almost every application needs:
|
||||
|
||||
- Self service login and registration
|
||||
- Account verification and recovery
|
||||
- Multi factor authentication
|
||||
- Profile and account management
|
||||
- Identity schemas and traits
|
||||
- Admin APIs for lifecycle management
|
||||
|
||||
We recommend starting with the
|
||||
[Ory Kratos introduction docs](https://www.ory.com/kratos/docs/) to learn more
|
||||
about its architecture, feature set, and how it compares to other systems.
|
||||
|
||||
### Why Ory Kratos
|
||||
|
||||
Ory Kratos is designed to:
|
||||
|
||||
- Remove identity logic from your application code and expose it over HTTP APIs
|
||||
- Work well with any UI framework through browser based and native app flows
|
||||
- Scale to large numbers of identities and devices
|
||||
- Integrate with the rest of the Ory stack for OAuth2, OpenID Connect, and
|
||||
access control
|
||||
- Fit into modern cloud native environments such as Kubernetes and managed
|
||||
platforms
|
||||
|
||||
## Migrating from Auth0, Okta, and similar providers
|
||||
|
||||
If you are migrating from Auth0, Okta, or another identity provider that uses
|
||||
OAuth2 / OpenID Connect based login, consider using **Ory Hydra + Ory Kratos**
|
||||
together:
|
||||
|
||||
- **Ory Hydra** acts as the OAuth2 and OpenID Connect provider and can replace
|
||||
most authorization server and token issuing capabilities of your existing IdP.
|
||||
- **Ory Kratos** provides identity, credentials, and user-facing flows (login,
|
||||
registration, recovery, verification, profile management).
|
||||
|
||||
This combination is often a drop-in replacement for OAuth2 and OpenID Connect
|
||||
capabilities at the protocol level. In practice, you update client configuration
|
||||
and endpoints to point to Hydra, migrate identities into Kratos, and keep your
|
||||
applications speaking the same OAuth2 / OIDC protocols they already use.
|
||||
|
||||
## Deployment options
|
||||
|
||||
You can run Ory Kratos in two main ways:
|
||||
|
||||
- As a managed service on the Ory Network
|
||||
- As a self hosted service under your own control, with or without the Ory
|
||||
Enterprise License
|
||||
|
||||
### Use Ory Kratos on the Ory Network
|
||||
|
||||
The [Ory Network](https://www.ory.com/cloud) is the fastest way to use Ory
|
||||
services in production. **Ory Identities** is powered by the open source Ory
|
||||
Kratos server and is API compatible.
|
||||
|
||||
The Ory Network provides:
|
||||
|
||||
- Identity and credential management that scales to billions of users and
|
||||
devices
|
||||
- Registration, login, and account management flows for passkeys, biometrics,
|
||||
social login, SSO, and multi factor authentication
|
||||
- Prebuilt login, registration, and account management pages and components
|
||||
- OAuth2 and OpenID Connect for single sign on, API access, and machine to
|
||||
machine authorization
|
||||
- Low latency permission checks based on the Zanzibar model with the Ory
|
||||
Permission Language
|
||||
- GDPR friendly storage with data locality and compliance in mind
|
||||
- Web based Ory Console and Ory CLI for administration and operations
|
||||
- Cloud native APIs compatible with the open source servers
|
||||
- Fair, usage based [pricing](https://www.ory.com/pricing)
|
||||
|
||||
Sign up for a
|
||||
[free developer account](https://console.ory.sh/registration?utm_source=github&utm_medium=banner&utm_campaign=kratos-readme)
|
||||
to get started.
|
||||
|
||||
### Self-host Ory Kratos
|
||||
|
||||
You can run Ory Kratos yourself for full control over infrastructure,
|
||||
deployment, and customization.
|
||||
|
||||
The [install guide](https://www.ory.com/kratos/docs/install) explains how to:
|
||||
|
||||
- Install Kratos on Linux, macOS, Windows, and Docker
|
||||
- Configure databases such as PostgreSQL, MySQL, and CockroachDB
|
||||
- Deploy to Kubernetes and other orchestration systems
|
||||
- Build Kratos from source
|
||||
|
||||
This guide uses the open source distribution to get you started without license
|
||||
requirements. It is a great fit for individuals, researchers, hackers, and
|
||||
companies that want to experiment, prototype, or run unimportant workloads
|
||||
without SLAs. You get the full core engine, and you are free to inspect, extend,
|
||||
and build it from source.
|
||||
|
||||
If you run Kratos as part of a business-critical system, for example login and
|
||||
account recovery for all your users, you should use a commercial agreement to
|
||||
reduce operational and security risk. The **Ory Enterprise License (OEL)**
|
||||
layers on top of self-hosted Kratos and provides:
|
||||
|
||||
- Additional enterprise features that are not available in the open source
|
||||
version such as SCIM, SAML, organization login ("SSO"), CAPTCHAs and more
|
||||
- Regular security releases, including CVE patches, with service level
|
||||
agreements
|
||||
- Support for advanced scaling, multi-tenancy, and complex deployments
|
||||
- Premium support options with SLAs, direct access to engineers, and onboarding
|
||||
help
|
||||
- Access to a private Docker registry with frequent and vetted, up-to-date
|
||||
enterprise builds
|
||||
|
||||
For guaranteed CVE fixes, current enterprise builds, advanced features, and
|
||||
support in production, you need a valid
|
||||
[Ory Enterprise License](https://www.ory.com/ory-enterprise-license) and access
|
||||
to the Ory Enterprise Docker registry. To learn more,
|
||||
[contact the Ory team](https://www.ory.com/contact/).
|
||||
|
||||
## Quickstart
|
||||
|
||||
Install the [Ory CLI](https://www.ory.com/docs/guides/cli/installation) and
|
||||
create a new project to try Ory Identities.
|
||||
|
||||
```bash
|
||||
# Install the Ory CLI if you do not have it yet:
|
||||
bash <(curl https://raw.githubusercontent.com/ory/meta/master/install.sh) -b . ory
|
||||
sudo mv ./ory /usr/local/bin/
|
||||
|
||||
# Sign in or sign up
|
||||
ory auth
|
||||
|
||||
# Create a new project
|
||||
ory create project --create-workspace "Ory Open Source" --name "GitHub Quickstart" --use-project
|
||||
ory open ax login
|
||||
```
|
||||
|
||||
### Who is using it?
|
||||
|
||||
<!--BEGIN ADOPTERS-->
|
||||
|
||||
The Ory community stands on the shoulders of individuals, companies, and
|
||||
maintainers. The Ory team thanks everyone involved - from submitting bug reports
|
||||
and feature requests, to contributing patches and documentation. The Ory
|
||||
community counts more than 50.000 members and is growing. The Ory stack protects
|
||||
7.000.000.000+ API requests every day across thousands of companies. None of
|
||||
this would have been possible without each and everyone of you!
|
||||
|
||||
The following list represents companies that have accompanied us along the way
|
||||
and that have made outstanding contributions to our ecosystem. _If you think
|
||||
that your company deserves a spot here, reach out to
|
||||
<a href="mailto:office@ory.com">office@ory.com</a> now_!
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Logo</th>
|
||||
<th>Website</th>
|
||||
<th>Case Study</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>OpenAI</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/openai.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/openai.svg" alt="OpenAI">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://openai.com/">openai.com</a></td>
|
||||
<td><a href="https://www.ory.com/case-studies/openai">OpenAI Case Study</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fandom</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/fandom.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/fandom.svg" alt="Fandom">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.fandom.com/">fandom.com</a></td>
|
||||
<td><a href="https://www.ory.com/case-studies/fandom">Fandom Case Study</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Lumin</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/lumin.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/lumin.svg" alt="Lumin">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.luminpdf.com/">luminpdf.com</a></td>
|
||||
<td><a href="https://www.ory.com/case-studies/lumin">Lumin Case Study</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sencrop</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/sencrop.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/sencrop.svg" alt="Sencrop">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://sencrop.com/">sencrop.com</a></td>
|
||||
<td><a href="https://www.ory.com/case-studies/sencrop">Sencrop Case Study</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>OSINT Industries</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/osint.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/osint.svg" alt="OSINT Industries">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.osint.industries/">osint.industries</a></td>
|
||||
<td><a href="https://www.ory.com/case-studies/osint">OSINT Industries Case Study</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HGV</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/hgv.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/hgv.svg" alt="HGV">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.hgv.it/">hgv.it</a></td>
|
||||
<td><a href="https://www.ory.com/case-studies/hgv">HGV Case Study</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Maxroll</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/maxroll.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/maxroll.svg" alt="Maxroll">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://maxroll.gg/">maxroll.gg</a></td>
|
||||
<td><a href="https://www.ory.com/case-studies/maxroll">Maxroll Case Study</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Zezam</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/zezam.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/zezam.svg" alt="Zezam">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.zezam.io/">zezam.io</a></td>
|
||||
<td><a href="https://www.ory.com/case-studies/zezam">Zezam Case Study</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>T.RowePrice</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/troweprice.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/troweprice.svg" alt="T.RowePrice">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.troweprice.com/">troweprice.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mistral</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/mistral.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/mistral.svg" alt="Mistral">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.mistral.ai/">mistral.ai</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Axel Springer</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/axelspringer.svg" />
|
||||
<img height="22px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/axelspringer.svg" alt="Axel Springer">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.axelspringer.com/">axelspringer.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hemnet</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/hemnet.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/hemnet.svg" alt="Hemnet">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.hemnet.se/">hemnet.se</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cisco</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/cisco.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/cisco.svg" alt="Cisco">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.cisco.com/">cisco.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Presidencia de la República Dominicana</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/republica-dominicana.svg" />
|
||||
<img height="42px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/republica-dominicana.svg" alt="Presidencia de la República Dominicana">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.presidencia.gob.do/">presidencia.gob.do</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Moonpig</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/moonpig.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/moonpig.svg" alt="Moonpig">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.moonpig.com/">moonpig.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Booster</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/booster.svg" />
|
||||
<img height="18px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/booster.svg" alt="Booster">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.choosebooster.com/">choosebooster.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Zaptec</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/zaptec.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/zaptec.svg" alt="Zaptec">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.zaptec.com/">zaptec.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Klarna</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/klarna.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/klarna.svg" alt="Klarna">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.klarna.com/">klarna.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Raspberry PI Foundation</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/raspi.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/raspi.svg" alt="Raspberry PI Foundation">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.raspberrypi.org/">raspberrypi.org</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tulip</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/tulip.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/tulip.svg" alt="Tulip Retail">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://tulip.com/">tulip.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hootsuite</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/hootsuite.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/hootsuite.svg" alt="Hootsuite">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://hootsuite.com/">hootsuite.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Segment</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/segment.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/segment.svg" alt="Segment">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://segment.com/">segment.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Arduino</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/arduino.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/arduino.svg" alt="Arduino">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.arduino.cc/">arduino.cc</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sainsbury's</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/sainsburys.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/sainsburys.svg" alt="Sainsbury's">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.sainsburys.co.uk/">sainsburys.co.uk</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Contraste</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/contraste.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/contraste.svg" alt="Contraste">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://www.contraste.com/en">contraste.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>inMusic</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/inmusic.svg" />
|
||||
<img height="24px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/inmusic.svg" alt="InMusic">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://inmusicbrands.com/">inmusicbrands.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Buhta</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/buhta.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/buhta.svg" alt="Buhta">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://buhta.com/">buhta.com</a></td>
|
||||
</tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Amplitude</td>
|
||||
<td align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/amplitude.svg" />
|
||||
<img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/amplitude.svg" alt="amplitude.com">
|
||||
</picture>
|
||||
</td>
|
||||
<td><a href="https://amplitude.com/">amplitude.com</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://tier4.jp/en/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/tieriv.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/tieriv.svg" alt="TIER IV"></picture></a></td>
|
||||
<td align="center"><a href="https://kyma-project.io"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/kyma.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/kyma.svg" alt="Kyma Project"></picture></a></td>
|
||||
<td align="center"><a href="https://serlo.org/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/serlo.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/serlo.svg" alt="Serlo"></picture></a></td>
|
||||
<td align="center"><a href="https://padis.io/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/padis.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/padis.svg" alt="Padis"></picture></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://cloudbear.eu/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/cloudbear.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/cloudbear.svg" alt="Cloudbear"></picture></a></td>
|
||||
<td align="center"><a href="https://securityonionsolutions.com/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/securityonion.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/securityonion.svg" alt="Security Onion Solutions"></picture></a></td>
|
||||
<td align="center"><a href="https://factlylabs.com/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/factly.svg" /><img height="24px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/factly.svg" alt="Factly"></picture></a></td>
|
||||
<td align="center"><a href="https://cashdeck.com.au/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/allmyfunds.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/allmyfunds.svg" alt="All My Funds"></picture></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://nortal.com/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/nortal.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/nortal.svg" alt="Nortal"></picture></a></td>
|
||||
<td align="center"><a href="https://www.ordermygear.com/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/ordermygear.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/ordermygear.svg" alt="OrderMyGear"></picture></a></td>
|
||||
<td align="center"><a href="https://r2devops.io/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/r2devops.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/r2devops.svg" alt="R2Devops"></picture></a></td>
|
||||
<td align="center"><a href="https://www.paralus.io/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/paralus.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/paralus.svg" alt="Paralus"></picture></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://dyrector.io/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/dyrector_io.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/dyrector_io.svg" alt="dyrector.io"></picture></a></td>
|
||||
<td align="center"><a href="https://pinniped.dev/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/pinniped.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/pinniped.svg" alt="pinniped.dev"></picture></a></td>
|
||||
<td align="center"><a href="https://pvotal.tech/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ory/meta/master/static/adopters/light/pvotal.svg" /><img height="32px" src="https://raw.githubusercontent.com/ory/meta/master/static/adopters/dark/pvotal.svg" alt="pvotal.tech"></picture></a></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Many thanks to all individual contributors
|
||||
|
||||
<a href="https://opencollective.com/ory" target="_blank"><img src="https://opencollective.com/ory/contributors.svg?width=890&limit=714&button=false" /></a>
|
||||
|
||||
<!--END ADOPTERS-->
|
||||
56
SECURITY.md
Normal file
56
SECURITY.md
Normal file
@@ -0,0 +1,56 @@
|
||||
<!-- AUTO-GENERATED, DO NOT EDIT! -->
|
||||
<!-- Please edit the original at https://github.com/ory/meta/blob/master/templates/repository/common/SECURITY.md -->
|
||||
|
||||
# Ory Security Policy
|
||||
|
||||
This policy outlines Ory's security commitments and practices for users across
|
||||
different licensing and deployment models.
|
||||
|
||||
To learn more about Ory's security service level agreements (SLAs) and
|
||||
processes, please [contact us](https://www.ory.com/contact/).
|
||||
|
||||
## Ory Network Users
|
||||
|
||||
- **Security SLA:** Ory addresses vulnerabilities in the Ory Network according
|
||||
to the following guidelines:
|
||||
- Critical: Typically addressed within 14 days.
|
||||
- High: Typically addressed within 30 days.
|
||||
- Medium: Typically addressed within 90 days.
|
||||
- Low: Typically addressed within 180 days.
|
||||
- Informational: Addressed as necessary.
|
||||
These timelines are targets and may vary based on specific circumstances.
|
||||
- **Release Schedule:** Updates are deployed to the Ory Network as
|
||||
vulnerabilities are resolved.
|
||||
- **Version Support:** The Ory Network always runs the latest version, ensuring
|
||||
up-to-date security fixes.
|
||||
|
||||
## Ory Enterprise License Customers
|
||||
|
||||
- **Security SLA:** Ory addresses vulnerabilities based on their severity:
|
||||
- Critical: Typically addressed within 14 days.
|
||||
- High: Typically addressed within 30 days.
|
||||
- Medium: Typically addressed within 90 days.
|
||||
- Low: Typically addressed within 180 days.
|
||||
- Informational: Addressed as necessary.
|
||||
These timelines are targets and may vary based on specific circumstances.
|
||||
- **Release Schedule:** Updates are made available as vulnerabilities are
|
||||
resolved. Ory works closely with enterprise customers to ensure timely updates
|
||||
that align with their operational needs.
|
||||
- **Version Support:** Ory may provide security support for multiple versions,
|
||||
depending on the terms of the enterprise agreement.
|
||||
|
||||
## Apache 2.0 License Users
|
||||
|
||||
- **Security SLA:** Ory does not provide a formal SLA for security issues under
|
||||
the Apache 2.0 License.
|
||||
- **Release Schedule:** Releases prioritize new functionality and include fixes
|
||||
for known security vulnerabilities at the time of release. While major
|
||||
releases typically occur one to two times per year, Ory does not guarantee a
|
||||
fixed release schedule.
|
||||
- **Version Support:** Security patches are only provided for the latest release
|
||||
version.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
For details on how to report security vulnerabilities, visit our
|
||||
[security policy documentation](https://www.ory.com/docs/ecosystem/security).
|
||||
12
buf.gen.yaml
Normal file
12
buf.gen.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
version: v2
|
||||
managed:
|
||||
enabled: true
|
||||
override:
|
||||
- file_option: go_package_prefix
|
||||
value: github.com/ory/kratos
|
||||
plugins:
|
||||
- remote: buf.build/protocolbuffers/go
|
||||
out: gen
|
||||
opt: paths=source_relative
|
||||
inputs:
|
||||
- directory: proto
|
||||
9
buf.yaml
Normal file
9
buf.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
version: v2
|
||||
modules:
|
||||
- path: proto
|
||||
lint:
|
||||
use:
|
||||
- DEFAULT
|
||||
breaking:
|
||||
use:
|
||||
- FILE
|
||||
84
cipher/aes.go
Normal file
84
cipher/aes.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cipher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/aes"
|
||||
stdcipher "crypto/cipher"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
)
|
||||
|
||||
type AES struct {
|
||||
c SecretsProvider
|
||||
}
|
||||
|
||||
func NewCryptAES(c SecretsProvider) *AES {
|
||||
return &AES{c: c}
|
||||
}
|
||||
|
||||
// aesGCM returns an AES-256-GCM AEAD that generates a random nonce internally
|
||||
// and prepends it to the ciphertext (nonce | ciphertext | tag).
|
||||
func aesGCM(key *[32]byte) (stdcipher.AEAD, error) {
|
||||
block, err := aes.NewCipher(key[:])
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
gcm, err := stdcipher.NewGCMWithRandomNonce(block)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
return gcm, nil
|
||||
}
|
||||
|
||||
// Encrypt returns the AES-256-GCM encryption of plaintext.
|
||||
func (a *AES) Encrypt(ctx context.Context, message []byte) (string, error) {
|
||||
if len(message) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(a.c.SecretsCipher(ctx)) == 0 {
|
||||
return "", errors.WithStack(herodot.ErrMisconfiguration().WithReason("Unable to encrypt message because no cipher secrets were configured."))
|
||||
}
|
||||
|
||||
gcm, err := aesGCM(&a.c.SecretsCipher(ctx)[0])
|
||||
if err != nil {
|
||||
return "", errors.WithStack(herodot.ErrForbidden().WithWrap(err))
|
||||
}
|
||||
|
||||
return hex.EncodeToString(gcm.Seal(nil, nil, message, nil)), nil
|
||||
}
|
||||
|
||||
// Decrypt returns the decrypted AES data.
|
||||
func (a *AES) Decrypt(ctx context.Context, ciphertext string) ([]byte, error) {
|
||||
if len(ciphertext) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
secrets := a.c.SecretsCipher(ctx)
|
||||
if len(secrets) == 0 {
|
||||
return nil, errors.WithStack(herodot.ErrMisconfiguration().WithReason("Unable to decipher the encrypted message because no AES secrets were configured."))
|
||||
}
|
||||
|
||||
decode, err := hex.DecodeString(ciphertext)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(herodot.ErrBadRequest().WithWrap(err))
|
||||
}
|
||||
|
||||
for i := range secrets {
|
||||
gcm, err := aesGCM(&secrets[i])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if plaintext, err := gcm.Open(nil, nil, decode, nil); err == nil {
|
||||
return plaintext, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.WithStack(herodot.ErrForbidden().WithReason("Unable to decipher the encrypted message."))
|
||||
}
|
||||
94
cipher/chacha20.go
Normal file
94
cipher/chacha20.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cipher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
)
|
||||
|
||||
type XChaCha20Poly1305 struct {
|
||||
c SecretsProvider
|
||||
}
|
||||
|
||||
func NewCryptChaCha20(c SecretsProvider) *XChaCha20Poly1305 {
|
||||
return &XChaCha20Poly1305{c: c}
|
||||
}
|
||||
|
||||
// Encrypt returns a ChaCha encryption of plaintext
|
||||
func (c *XChaCha20Poly1305) Encrypt(ctx context.Context, message []byte) (string, error) {
|
||||
if len(message) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(c.c.SecretsCipher(ctx)) == 0 {
|
||||
return "", errors.WithStack(herodot.ErrMisconfiguration().WithReason("Unable to encrypt message because no cipher secrets were configured."))
|
||||
}
|
||||
|
||||
aead, err := chacha20poly1305.NewX(c.c.SecretsCipher(ctx)[0][:])
|
||||
if err != nil {
|
||||
return "", herodot.ErrInternalServerError().WithWrap(err).WithReason("Unable to generate key")
|
||||
}
|
||||
|
||||
// Make sure the size calculation does not overflow.
|
||||
if len(message) > math.MaxInt-aead.NonceSize()-aead.Overhead() {
|
||||
return "", errors.WithStack(herodot.ErrInternalServerError().WithReason("plaintext too large"))
|
||||
}
|
||||
|
||||
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(message)+aead.Overhead())
|
||||
_, err = io.ReadFull(rand.Reader, nonce)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(herodot.ErrInternalServerError().WithWrap(err).WithReason("Unable to generate nonce"))
|
||||
}
|
||||
|
||||
encryptedMsg := aead.Seal(nonce, nonce, message, nil)
|
||||
return hex.EncodeToString(encryptedMsg), nil
|
||||
}
|
||||
|
||||
// Decrypt decrypts data using 256 bit key
|
||||
func (c *XChaCha20Poly1305) Decrypt(ctx context.Context, ciphertext string) ([]byte, error) {
|
||||
if len(ciphertext) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
secrets := c.c.SecretsCipher(ctx)
|
||||
if len(secrets) == 0 {
|
||||
return nil, errors.WithStack(herodot.ErrMisconfiguration().WithReason("Unable to decipher the encrypted message because no cipher secrets were configured."))
|
||||
}
|
||||
|
||||
rawCiphertext, err := hex.DecodeString(ciphertext)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(herodot.ErrBadRequest().WithWrap(err).WithReason("Unable to decode hex encrypted string"))
|
||||
}
|
||||
|
||||
for i := range secrets {
|
||||
aead, err := chacha20poly1305.NewX(secrets[i][:])
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(herodot.ErrInternalServerError().WithWrap(err).WithReason("Unable to instantiate chacha20"))
|
||||
}
|
||||
|
||||
// Guard the DECODED length, not the hex string length: a hex string of
|
||||
// 24+ chars passes a hex-length check while decoding to fewer than
|
||||
// NonceSize bytes, and the slice below would then panic.
|
||||
if len(rawCiphertext) < aead.NonceSize() {
|
||||
return nil, errors.WithStack(herodot.ErrBadRequest().WithReason("cipher text too short"))
|
||||
}
|
||||
|
||||
nonce, ciphertext := rawCiphertext[:aead.NonceSize()], rawCiphertext[aead.NonceSize():]
|
||||
plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
|
||||
if err == nil {
|
||||
return plaintext, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.WithStack(herodot.ErrForbidden().WithReason("Unable to decrypt string"))
|
||||
}
|
||||
29
cipher/cipher.go
Normal file
29
cipher/cipher.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cipher
|
||||
|
||||
import "context"
|
||||
|
||||
// Cipher provides methods for encrypt and decrypt string
|
||||
type Cipher interface {
|
||||
// Encrypt encrypts the (binary) message and returns a hex-encoded binary ciphertext
|
||||
// or an error if the encryption failed.
|
||||
//
|
||||
// If the message is empty, the ciphertext is also empty and no error is returned.
|
||||
Encrypt(ctx context.Context, message []byte) (string, error)
|
||||
|
||||
// Decrypt takes a hex-encoded binary ciphertext and decrypts it or returns an error if the decryption
|
||||
// failed.
|
||||
//
|
||||
// If the ciphertext is empty a nil byte slice is returned.
|
||||
Decrypt(ctx context.Context, encrypted string) ([]byte, error)
|
||||
}
|
||||
|
||||
type Provider interface {
|
||||
Cipher(ctx context.Context) Cipher
|
||||
}
|
||||
|
||||
type SecretsProvider interface {
|
||||
SecretsCipher(ctx context.Context) [][32]byte
|
||||
}
|
||||
136
cipher/cipher_test.go
Normal file
136
cipher/cipher_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cipher_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
"github.com/ory/kratos/cipher"
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/kratos/pkg"
|
||||
"github.com/ory/x/configx"
|
||||
"github.com/ory/x/contextx"
|
||||
)
|
||||
|
||||
var goodSecret = []string{"secret-thirty-two-character-long"}
|
||||
|
||||
func TestCipher(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValue(config.ViperKeySecretsDefault, goodSecret))
|
||||
|
||||
ciphers := []cipher.Cipher{
|
||||
cipher.NewCryptAES(reg.Config()),
|
||||
cipher.NewCryptChaCha20(reg.Config()),
|
||||
}
|
||||
|
||||
for _, c := range ciphers {
|
||||
t.Run(fmt.Sprintf("cipher=%T", c), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("case=all_work", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testAllWork(ctx, t, c)
|
||||
})
|
||||
|
||||
t.Run("case=encryption_failed", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := contextx.WithConfigValue(ctx, config.ViperKeySecretsCipher, []string{""})
|
||||
|
||||
// secret have to be set
|
||||
_, err := c.Encrypt(ctx, []byte("not-empty"))
|
||||
require.Error(t, err)
|
||||
var hErr *herodot.DefaultError
|
||||
require.ErrorAs(t, err, &hErr)
|
||||
assert.Equal(t, "Unable to encrypt message because no cipher secrets were configured.", hErr.Reason())
|
||||
|
||||
ctx = contextx.WithConfigValue(ctx, config.ViperKeySecretsCipher, []string{"bad-length"})
|
||||
|
||||
// bad secret length
|
||||
_, err = c.Encrypt(ctx, []byte("not-empty"))
|
||||
require.ErrorAs(t, err, &hErr)
|
||||
assert.Equal(t, "Unable to encrypt message because no cipher secrets were configured.", hErr.Reason())
|
||||
})
|
||||
|
||||
t.Run("case=decryption_failed", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := c.Decrypt(ctx, hex.EncodeToString([]byte("bad-data")))
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = c.Decrypt(ctx, "not-empty")
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = c.Decrypt(contextx.WithConfigValue(ctx, config.ViperKeySecretsCipher, []string{""}), "not-empty")
|
||||
require.Error(t, err)
|
||||
|
||||
// Valid hex that decodes to fewer bytes than the AEAD nonce must be
|
||||
// a clean error, not a panic: a corrupt or truncated stored
|
||||
// ciphertext must never crash the caller. 16 bytes (32 hex chars)
|
||||
// clears the hex-length check but is shorter than XChaCha20's
|
||||
// 24-byte nonce.
|
||||
_, err = c.Decrypt(ctx, hex.EncodeToString([]byte("sixteen-bytes!!!")))
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
c := cipher.NewNoop()
|
||||
t.Run(fmt.Sprintf("cipher=%T", c), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testAllWork(ctx, t, c)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAESBackwardsCompatibility(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Ciphertext produced by the previous implementation
|
||||
// (github.com/gtank/cryptopasta: caller-generated GCM nonce). The new
|
||||
// stdlib NewGCMWithRandomNonce implementation must keep decrypting it.
|
||||
// Key (32 bytes): the literal config secret below.
|
||||
// Plaintext: "fixture: encrypted with caller-supplied nonce".
|
||||
const legacyCiphertext = "74756223e46170ef0579f207af41b7ed435d649ae94f22bd57934398e9861870e58feba1e949d28f18dfdda6395eabf4d2d31fdeb2b07c4c428e9829509470f6d8e23c17708f1368b6"
|
||||
|
||||
ctx := t.Context()
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValue(config.ViperKeySecretsCipher, []string{"change this password to a secret"}))
|
||||
c := cipher.NewCryptAES(reg.Config())
|
||||
|
||||
plain, err := c.Decrypt(ctx, legacyCiphertext)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "fixture: encrypted with caller-supplied nonce", string(plain))
|
||||
|
||||
// Round-trip with the current implementation.
|
||||
out, err := c.Encrypt(ctx, []byte("fixture: encrypted with caller-supplied nonce"))
|
||||
require.NoError(t, err)
|
||||
plain2, err := c.Decrypt(ctx, out)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "fixture: encrypted with caller-supplied nonce", string(plain2))
|
||||
}
|
||||
|
||||
func testAllWork(ctx context.Context, t *testing.T, c cipher.Cipher) {
|
||||
message := "my secret message!"
|
||||
|
||||
encryptedSecret, err := c.Encrypt(ctx, []byte(message))
|
||||
require.NoError(t, err)
|
||||
|
||||
decryptedSecret, err := c.Decrypt(ctx, encryptedSecret)
|
||||
require.NoError(t, err, "encrypted", encryptedSecret)
|
||||
assert.Equal(t, message, string(decryptedSecret))
|
||||
|
||||
// data to encrypt return blank result
|
||||
_, err = c.Encrypt(ctx, []byte(""))
|
||||
require.NoError(t, err)
|
||||
|
||||
// empty encrypted data return blank
|
||||
_, err = c.Decrypt(ctx, "")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
26
cipher/noop.go
Normal file
26
cipher/noop.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cipher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// Noop is default cipher implementation witch does not do encryption
|
||||
type Noop struct{}
|
||||
|
||||
func NewNoop() *Noop {
|
||||
return &Noop{}
|
||||
}
|
||||
|
||||
// Encrypt encode message to hex
|
||||
func (*Noop) Encrypt(_ context.Context, message []byte) (string, error) {
|
||||
return hex.EncodeToString(message), nil
|
||||
}
|
||||
|
||||
// Decrypt decode the hex message
|
||||
func (*Noop) Decrypt(_ context.Context, ciphertext string) ([]byte, error) {
|
||||
return hex.DecodeString(ciphertext)
|
||||
}
|
||||
67
cmd/cleanup/handler.go
Normal file
67
cmd/cleanup/handler.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cleanup
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/x/contextx"
|
||||
|
||||
"github.com/ory/x/configx"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/driver"
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/x/flagx"
|
||||
)
|
||||
|
||||
type CleanupHandler struct{}
|
||||
|
||||
func NewCleanupHandler() *CleanupHandler {
|
||||
return &CleanupHandler{}
|
||||
}
|
||||
|
||||
func (h *CleanupHandler) CleanupSQL(cmd *cobra.Command, args []string) error {
|
||||
opts := []configx.OptionModifier{
|
||||
configx.WithFlags(cmd.Flags()),
|
||||
configx.SkipValidation(),
|
||||
}
|
||||
|
||||
if !flagx.MustGetBool(cmd, "read-from-env") {
|
||||
if len(args) != 1 {
|
||||
return errors.New(`expected to get the DSN as an argument, or the "read-from-env" flag`)
|
||||
}
|
||||
opts = append(opts, configx.WithValue(config.ViperKeyDSN, args[0]))
|
||||
}
|
||||
|
||||
d, err := driver.NewWithoutInit(
|
||||
cmd.Context(),
|
||||
cmd.ErrOrStderr(),
|
||||
driver.WithConfigOptions(opts...),
|
||||
)
|
||||
if len(d.Config().DSN(cmd.Context())) == 0 {
|
||||
return errors.New(`required config value "dsn" was not set`)
|
||||
} else if err != nil {
|
||||
return errors.Wrap(err, "An error occurred initializing cleanup")
|
||||
}
|
||||
|
||||
err = d.Init(cmd.Context(), &contextx.Default{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "An error occurred initializing cleanup")
|
||||
}
|
||||
|
||||
keepLast := flagx.MustGetDuration(cmd, "keep-last")
|
||||
|
||||
err = d.Persister().CleanupDatabase(
|
||||
cmd.Context(),
|
||||
d.Config().DatabaseCleanupSleepTables(cmd.Context()),
|
||||
keepLast,
|
||||
d.Config().DatabaseCleanupBatchSize(cmd.Context()))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "An error occurred while cleaning up expired data")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
25
cmd/cleanup/root.go
Normal file
25
cmd/cleanup/root.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cleanup
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/x/configx"
|
||||
)
|
||||
|
||||
func NewCleanupCmd() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "cleanup",
|
||||
Short: "Various cleanup helpers",
|
||||
}
|
||||
configx.RegisterFlags(c.PersistentFlags())
|
||||
return c
|
||||
}
|
||||
|
||||
func RegisterCommandRecursive(parent *cobra.Command) {
|
||||
c := NewCleanupCmd()
|
||||
parent.AddCommand(c)
|
||||
c.AddCommand(NewCleanupSQLCmd())
|
||||
}
|
||||
49
cmd/cleanup/sql.go
Normal file
49
cmd/cleanup/sql.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cleanup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ory/x/cmdx"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/driver/config"
|
||||
|
||||
"github.com/ory/x/configx"
|
||||
)
|
||||
|
||||
// cleanupSqlCmd represents the sql command
|
||||
func NewCleanupSQLCmd() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "sql <database-url>",
|
||||
Short: "Cleanup sql database from expired flows and sessions",
|
||||
Long: `Run this command as frequently as you need.
|
||||
It is recommended to run this command close to the SQL instance (e.g. same subnet) instead of over the public internet.
|
||||
This decreases risk of failure and decreases time required.
|
||||
You can read in the database URL using the -e flag, for example:
|
||||
export DSN=...
|
||||
kratos cleanup sql -e
|
||||
### WARNING ###
|
||||
Before running this command on an existing database, create a back up!
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := NewCleanupHandler().CleanupSQL(cmd, args)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), err)
|
||||
return cmdx.FailSilently(cmd)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
configx.RegisterFlags(c.PersistentFlags())
|
||||
c.Flags().BoolP("read-from-env", "e", true, "If set, reads the database connection string from the environment variable DSN or config file key dsn.")
|
||||
c.Flags().Duration(config.ViperKeyDatabaseCleanupSleepTables, time.Minute, "How long to wait between each table cleanup")
|
||||
c.Flags().IntP(config.ViperKeyDatabaseCleanupBatchSize, "b", 100, "Set the number of records to be cleaned per run")
|
||||
c.Flags().Duration("keep-last", 0, "Don't remove records younger than")
|
||||
return c
|
||||
}
|
||||
27
cmd/cleanup/sql_test.go
Normal file
27
cmd/cleanup/sql_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cleanup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_ExecuteCleanupFailedDSN(t *testing.T) {
|
||||
cmd := NewCleanupSQLCmd()
|
||||
b := bytes.NewBufferString("")
|
||||
cmd.SetOut(b)
|
||||
cmd.SetArgs([]string{"--read-from-env=false"})
|
||||
_ = cmd.Execute()
|
||||
out, err := io.ReadAll(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(out), "expected to get the DSN as an argument") {
|
||||
t.Fatalf("expected \"%s\" got \"%s\"", "expected to get the DSN as an argument", string(out))
|
||||
}
|
||||
_ = cmd.Execute()
|
||||
}
|
||||
82
cmd/cliclient/client.go
Normal file
82
cmd/cliclient/client.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cliclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
kratos "github.com/ory/kratos/pkg/httpclient"
|
||||
"github.com/ory/x/httpx"
|
||||
)
|
||||
|
||||
const (
|
||||
envKeyEndpoint = "KRATOS_ADMIN_URL"
|
||||
FlagEndpoint = "endpoint"
|
||||
)
|
||||
|
||||
type ContextKey int
|
||||
|
||||
const (
|
||||
ClientContextKey ContextKey = iota + 1
|
||||
)
|
||||
|
||||
type ClientContext struct {
|
||||
Endpoint string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
func NewClient(cmd *cobra.Command) (*kratos.APIClient, error) {
|
||||
if f, ok := cmd.Context().Value(ClientContextKey).(func(cmd *cobra.Command) (*ClientContext, error)); ok {
|
||||
cc, err := f(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := kratos.NewConfiguration()
|
||||
conf.HTTPClient = cc.HTTPClient
|
||||
conf.Servers = kratos.ServerConfigurations{{URL: cc.Endpoint}}
|
||||
return kratos.NewAPIClient(conf), nil
|
||||
} else if f != nil {
|
||||
return nil, errors.Errorf("ClientContextKey was expected to be *client.OryKratos but it contained an invalid type %T ", f)
|
||||
}
|
||||
|
||||
endpoint, err := cmd.Flags().GetString(FlagEndpoint)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if endpoint == "" {
|
||||
endpoint = os.Getenv(envKeyEndpoint)
|
||||
}
|
||||
|
||||
if endpoint == "" {
|
||||
return nil, errors.Errorf("you have to set the remote endpoint, try --help for details")
|
||||
}
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, `could not parse the endpoint URL "%s"`, endpoint)
|
||||
}
|
||||
|
||||
conf := kratos.NewConfiguration()
|
||||
conf.HTTPClient = httpx.NewResilientClient(
|
||||
httpx.ResilientClientWithConnectionTimeout(10 * time.Second),
|
||||
).StandardClient()
|
||||
conf.Servers = kratos.ServerConfigurations{{URL: u.String()}}
|
||||
return kratos.NewAPIClient(conf), nil
|
||||
}
|
||||
|
||||
func RegisterClientFlags(flags *pflag.FlagSet) {
|
||||
flags.StringP(FlagEndpoint, FlagEndpoint[:1], "", fmt.Sprintf("The URL of Ory Kratos' Admin API. Alternatively set using the %s environmental variable.", envKeyEndpoint))
|
||||
}
|
||||
431
cmd/clidoc/main.go
Normal file
431
cmd/clidoc/main.go
Normal file
@@ -0,0 +1,431 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/kratos/cmd"
|
||||
"github.com/ory/kratos/text"
|
||||
|
||||
"github.com/ory/x/clidoc"
|
||||
"github.com/ory/x/clock"
|
||||
)
|
||||
|
||||
var (
|
||||
aSecondAgo = time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC).Add(-time.Second)
|
||||
inAMinute = time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC).Add(time.Minute)
|
||||
|
||||
// Fixed clocks make the rendered "expired N minutes ago" / "within the next
|
||||
// N minutes" example messages deterministic for doc generation. They are
|
||||
// anchored so the durations come out as exactly one minute.
|
||||
docExpiredClock = clock.NewMock(aSecondAgo.Add(time.Minute))
|
||||
docUntilClock = clock.NewMock(inAMinute.Add(-time.Minute))
|
||||
)
|
||||
|
||||
var messages map[string]*text.Message
|
||||
|
||||
func init() {
|
||||
messages = map[string]*text.Message{
|
||||
"NewInfoNodeLabelVerifyOTP": text.NewInfoNodeLabelVerifyOTP(),
|
||||
"NewInfoNodeLabelVerificationCode": text.NewInfoNodeLabelVerificationCode(),
|
||||
"NewInfoNodeLabelRecoveryCode": text.NewInfoNodeLabelRecoveryCode(),
|
||||
"NewInfoNodeInputPassword": text.NewInfoNodeInputPassword(),
|
||||
"NewInfoNodeInputPhoneNumber": text.NewInfoNodeInputPhoneNumber(),
|
||||
"NewInfoNodeLabelGenerated": text.NewInfoNodeLabelGenerated("{title}", "{name}"),
|
||||
"NewInfoNodeLabelSave": text.NewInfoNodeLabelSave(),
|
||||
"NewInfoNodeLabelSubmit": text.NewInfoNodeLabelSubmit(),
|
||||
"NewInfoNodeLabelID": text.NewInfoNodeLabelID(),
|
||||
"NewErrorValidationSettingsFlowExpired": text.NewErrorValidationSettingsFlowExpired(docExpiredClock, aSecondAgo),
|
||||
"NewInfoSelfServiceSettingsTOTPQRCode": text.NewInfoSelfServiceSettingsTOTPQRCode(),
|
||||
"NewInfoSelfServiceSettingsTOTPSecret": text.NewInfoSelfServiceSettingsTOTPSecret("{secret}"),
|
||||
"NewInfoSelfServiceSettingsTOTPSecretLabel": text.NewInfoSelfServiceSettingsTOTPSecretLabel(),
|
||||
"NewInfoSelfServiceSettingsUpdateSuccess": text.NewInfoSelfServiceSettingsUpdateSuccess(),
|
||||
"NewInfoSelfServiceSettingsUpdateUnlinkTOTP": text.NewInfoSelfServiceSettingsUpdateUnlinkTOTP(),
|
||||
"NewInfoSelfServiceSettingsRevealLookup": text.NewInfoSelfServiceSettingsRevealLookup(),
|
||||
"NewInfoSelfServiceSettingsRegenerateLookup": text.NewInfoSelfServiceSettingsRegenerateLookup(),
|
||||
"NewInfoSelfServiceSettingsDisableLookup": text.NewInfoSelfServiceSettingsDisableLookup(),
|
||||
"NewInfoSelfServiceSettingsLookupConfirm": text.NewInfoSelfServiceSettingsLookupConfirm(),
|
||||
"NewInfoSelfServiceSettingsManagedByOrganization": text.NewInfoSelfServiceSettingsManagedByOrganization(),
|
||||
"NewInfoSelfServiceSettingsLookupSecretList": text.NewInfoSelfServiceSettingsLookupSecretList([]string{"{secrets_list}"}, []interface{}{
|
||||
text.NewInfoSelfServiceSettingsLookupSecret("{secret}"),
|
||||
text.NewInfoSelfServiceSettingsLookupSecretUsed(aSecondAgo),
|
||||
}),
|
||||
"NewInfoSelfServiceSettingsLookupSecret": text.NewInfoSelfServiceSettingsLookupSecret("{secret}"),
|
||||
"NewInfoSelfServiceSettingsLookupSecretUsed": text.NewInfoSelfServiceSettingsLookupSecretUsed(aSecondAgo),
|
||||
"NewInfoSelfServiceSettingsLookupSecretsLabel": text.NewInfoSelfServiceSettingsLookupSecretsLabel(),
|
||||
"NewInfoSelfServiceSettingsUpdateLinkOIDC": text.NewInfoSelfServiceSettingsUpdateLinkOIDC("{provider}"),
|
||||
"NewInfoSelfServiceSettingsUpdateUnlinkOIDC": text.NewInfoSelfServiceSettingsUpdateUnlinkOIDC("{provider}"),
|
||||
"NewInfoSelfServiceRegisterWebAuthnDisplayName": text.NewInfoSelfServiceRegisterWebAuthnDisplayName(),
|
||||
"NewInfoSelfServiceRemoveWebAuthn": text.NewInfoSelfServiceRemoveWebAuthn("{display_name}", aSecondAgo),
|
||||
"NewInfoSelfServiceRemovePasskey": text.NewInfoSelfServiceRemovePasskey("{display_name}", aSecondAgo),
|
||||
"NewErrorValidationVerificationFlowExpired": text.NewErrorValidationVerificationFlowExpired(docExpiredClock, aSecondAgo),
|
||||
"NewInfoSelfServiceVerificationSuccessful": text.NewInfoSelfServiceVerificationSuccessful(),
|
||||
"NewVerificationEmailSent": text.NewVerificationEmailSent(),
|
||||
"NewVerificationEmailWithCodeSent": text.NewVerificationEmailWithCodeSent(),
|
||||
"NewVerificationPhoneWithCodeSent": text.NewVerificationPhoneWithCodeSent(),
|
||||
"NewInfoSelfServiceVerificationPhoneSuccessful": text.NewInfoSelfServiceVerificationPhoneSuccessful(),
|
||||
"NewErrorValidationVerificationTokenInvalidOrAlreadyUsed": text.NewErrorValidationVerificationTokenInvalidOrAlreadyUsed(),
|
||||
"NewErrorValidationVerificationRetrySuccess": text.NewErrorValidationVerificationRetrySuccess(),
|
||||
"NewErrorValidationVerificationStateFailure": text.NewErrorValidationVerificationStateFailure(),
|
||||
"NewErrorValidationVerificationCodeInvalidOrAlreadyUsed": text.NewErrorValidationVerificationCodeInvalidOrAlreadyUsed(),
|
||||
"NewErrorSystemGeneric": text.NewErrorSystemGeneric("{reason}"),
|
||||
"NewErrorSystemNoAuthenticationMethodsAvailable": text.NewErrorSystemNoAuthenticationMethodsAvailable(),
|
||||
"NewErrorSystemOrganizationNoSSOProvidersAvailable": text.NewErrorSystemOrganizationNoSSOProvidersAvailable(),
|
||||
"NewValidationErrorGeneric": text.NewValidationErrorGeneric("{reason}"),
|
||||
"NewValidationErrorRequired": text.NewValidationErrorRequired("{property}"),
|
||||
"NewErrorValidationMinLength": text.NewErrorValidationMinLength(5, 3),
|
||||
"NewErrorValidationMaxLength": text.NewErrorValidationMaxLength(5, 6),
|
||||
"NewErrorValidationInvalidFormat": text.NewErrorValidationInvalidFormat("{pattern}"),
|
||||
"NewErrorValidationMinimum": text.NewErrorValidationMinimum(5, 3),
|
||||
"NewErrorValidationExclusiveMinimum": text.NewErrorValidationExclusiveMinimum(5, 5),
|
||||
"NewErrorValidationMaximum": text.NewErrorValidationMaximum(5, 6),
|
||||
"NewErrorValidationExclusiveMaximum": text.NewErrorValidationExclusiveMaximum(5, 5),
|
||||
"NewErrorValidationMultipleOf": text.NewErrorValidationMultipleOf(7, 3),
|
||||
"NewErrorValidationMaxItems": text.NewErrorValidationMaxItems(3, 4),
|
||||
"NewErrorValidationMinItems": text.NewErrorValidationMinItems(3, 2),
|
||||
"NewErrorValidationUniqueItems": text.NewErrorValidationUniqueItems(0, 2),
|
||||
"NewErrorValidationWrongType": text.NewErrorValidationWrongType([]string{"{allowed_types_list}"}, "{actual_type}"),
|
||||
"NewErrorValidationConst": text.NewErrorValidationConst("{expected}"),
|
||||
"NewErrorValidationConstGeneric": text.NewErrorValidationConstGeneric(),
|
||||
"NewErrorValidationPasswordPolicyViolationGeneric": text.NewErrorValidationPasswordPolicyViolationGeneric("{reason}"),
|
||||
"NewErrorValidationPasswordIdentifierTooSimilar": text.NewErrorValidationPasswordIdentifierTooSimilar(),
|
||||
"NewErrorValidationPasswordMinLength": text.NewErrorValidationPasswordMinLength(6, 5),
|
||||
"NewErrorValidationPasswordMaxLength": text.NewErrorValidationPasswordMaxLength(72, 80),
|
||||
"NewErrorValidationPasswordTooManyBreaches": text.NewErrorValidationPasswordTooManyBreaches(101),
|
||||
"NewErrorValidationPasswordNewSameAsOld": text.NewErrorValidationPasswordNewSameAsOld(),
|
||||
"NewErrorValidationInvalidCredentials": text.NewErrorValidationInvalidCredentials(),
|
||||
"NewErrorValidationDuplicateCredentials": text.NewErrorValidationDuplicateCredentials(),
|
||||
"NewErrorValidationDuplicateCredentialsWithHints": text.NewErrorValidationDuplicateCredentialsWithHints([]string{"{available_credential_types_list}"}, []string{"{available_oidc_providers_list}"}, "{credential_identifier_hint}"),
|
||||
"NewErrorValidationDuplicateCredentialsOnOIDCLink": text.NewErrorValidationDuplicateCredentialsOnOIDCLink(),
|
||||
"NewErrorValidationTOTPVerifierWrong": text.NewErrorValidationTOTPVerifierWrong(),
|
||||
"NewErrorValidationWebAuthnVerifierWrong": text.NewErrorValidationWebAuthnVerifierWrong(),
|
||||
"NewErrorValidationDeviceAuthnVerifierWrong": text.NewErrorValidationDeviceAuthnVerifierWrong(),
|
||||
"NewErrorValidationDeviceAuthnRelaxedAttestationNoLongerValid": text.NewErrorValidationDeviceAuthnRelaxedAttestationNoLongerValid(),
|
||||
"NewErrorValidationDeviceAuthnKeyReenrollmentRequired": text.NewErrorValidationDeviceAuthnKeyReenrollmentRequired(),
|
||||
"NewErrorValidationLookupAlreadyUsed": text.NewErrorValidationLookupAlreadyUsed(),
|
||||
"NewErrorValidationLookupInvalid": text.NewErrorValidationLookupInvalid(),
|
||||
"NewErrorValidationIdentifierMissing": text.NewErrorValidationIdentifierMissing(),
|
||||
"NewErrorValidationAddressNotVerified": text.NewErrorValidationAddressNotVerified(),
|
||||
"NewErrorValidationNoTOTPDevice": text.NewErrorValidationNoTOTPDevice(),
|
||||
"NewErrorValidationNoLookup": text.NewErrorValidationNoLookup(),
|
||||
"NewErrorValidationNoWebAuthnDevice": text.NewErrorValidationNoWebAuthnDevice(),
|
||||
"NewInfoLoginReAuth": text.NewInfoLoginReAuth(),
|
||||
"NewInfoLoginMFA": text.NewInfoLoginMFA(),
|
||||
"NewInfoLoginTOTPLabel": text.NewInfoLoginTOTPLabel(),
|
||||
"NewInfoLoginLookupLabel": text.NewInfoLoginLookupLabel(),
|
||||
"NewInfoLogin": text.NewInfoLogin(),
|
||||
"NewInfoLoginAndLink": text.NewInfoLoginAndLink(),
|
||||
"NewInfoLoginLinkMessage": text.NewInfoLoginLinkMessage("{duplicateIdentifier}", "{provider}", "{newLoginUrl}", []string{"{available_credential_types_list}"}, []string{"{available_oidc_providers_list}"}),
|
||||
"NewInfoLoginTOTP": text.NewInfoLoginTOTP(),
|
||||
"NewInfoLoginLookup": text.NewInfoLoginLookup(),
|
||||
"NewInfoLoginVerify": text.NewInfoLoginVerify(),
|
||||
"NewInfoLoginWith": text.NewInfoLoginWith("{provider}", "{providerID}"),
|
||||
"NewInfoLoginWithAndLink": text.NewInfoLoginWithAndLink("{provider}"),
|
||||
"NewErrorValidationNoDeviceAuthnDevice": text.NewErrorValidationNoDeviceAuthnDevice(),
|
||||
"NewErrorValidationLoginFlowExpired": text.NewErrorValidationLoginFlowExpired(docExpiredClock, aSecondAgo),
|
||||
"NewErrorValidationLoginNoStrategyFound": text.NewErrorValidationLoginNoStrategyFound(),
|
||||
"NewErrorValidationRegistrationNoStrategyFound": text.NewErrorValidationRegistrationNoStrategyFound(),
|
||||
"NewErrorValidationSettingsNoStrategyFound": text.NewErrorValidationSettingsNoStrategyFound(),
|
||||
"NewErrorValidationRecoveryNoStrategyFound": text.NewErrorValidationRecoveryNoStrategyFound(),
|
||||
"NewErrorValidationVerificationNoStrategyFound": text.NewErrorValidationVerificationNoStrategyFound(),
|
||||
"NewInfoSelfServiceLoginWebAuthn": text.NewInfoSelfServiceLoginWebAuthn(),
|
||||
"NewInfoSelfServiceLoginDeviceAuthn": text.NewInfoSelfServiceLoginDeviceAuthn(),
|
||||
"NewInfoRegistration": text.NewInfoRegistration(),
|
||||
"NewInfoRegistrationWith": text.NewInfoRegistrationWith("{provider}", "{providerID}"),
|
||||
"NewInfoRegistrationContinue": text.NewInfoRegistrationContinue(),
|
||||
"NewInfoRegistrationBack": text.NewInfoRegistrationBack(),
|
||||
"NewInfoSelfServiceChooseCredentials": text.NewInfoSelfServiceChooseCredentials(),
|
||||
"NewErrorValidationRegistrationFlowExpired": text.NewErrorValidationRegistrationFlowExpired(docExpiredClock, aSecondAgo),
|
||||
"NewErrorValidationRecoveryFlowExpired": text.NewErrorValidationRecoveryFlowExpired(docExpiredClock, aSecondAgo),
|
||||
"NewRecoverySuccessful": text.NewRecoverySuccessful(docUntilClock, inAMinute),
|
||||
"NewRecoveryEmailSent": text.NewRecoveryEmailSent(),
|
||||
"NewRecoveryEmailWithCodeSent": text.NewRecoveryEmailWithCodeSent(),
|
||||
"NewRecoveryCodeRecoverySelectAddressSent": text.NewRecoveryCodeRecoverySelectAddressSent("{masked_address}"),
|
||||
"NewRecoveryAskAnyRecoveryAddress": text.NewRecoveryAskAnyRecoveryAddress(),
|
||||
"NewRecoveryAskForFullAddress": text.NewRecoveryAskForFullAddress(),
|
||||
"NewRecoveryAskToChooseAddress": text.NewRecoveryAskToChooseAddress(),
|
||||
"NewRecoveryBack": text.NewRecoveryBack(),
|
||||
"NewErrorValidationRecoveryTokenInvalidOrAlreadyUsed": text.NewErrorValidationRecoveryTokenInvalidOrAlreadyUsed(),
|
||||
"NewErrorValidationRecoveryCodeInvalidOrAlreadyUsed": text.NewErrorValidationRecoveryCodeInvalidOrAlreadyUsed(),
|
||||
"NewErrorValidationRecoveryRetrySuccess": text.NewErrorValidationRecoveryRetrySuccess(),
|
||||
"NewErrorValidationRecoveryStateFailure": text.NewErrorValidationRecoveryStateFailure(),
|
||||
"NewInfoNodeInputEmail": text.NewInfoNodeInputEmail(),
|
||||
"NewInfoNodeInputEmailOrPhone": text.NewInfoNodeInputEmailOrPhone(),
|
||||
"NewInfoNodeResendOTP": text.NewInfoNodeResendOTP(),
|
||||
"NewInfoNodeLoginAndLinkCredential": text.NewInfoNodeLoginAndLinkCredential(),
|
||||
"NewInfoNodeLabelContinue": text.NewInfoNodeLabelContinue(),
|
||||
"NewInfoSelfServiceSettingsRegisterWebAuthn": text.NewInfoSelfServiceSettingsRegisterWebAuthn(),
|
||||
"NewInfoSelfServiceSettingsRegisterPasskey": text.NewInfoSelfServiceSettingsRegisterPasskey(),
|
||||
"NewInfoLoginWebAuthnPasswordless": text.NewInfoLoginWebAuthnPasswordless(),
|
||||
"NewInfoSelfServiceRegistrationRegisterWebAuthn": text.NewInfoSelfServiceRegistrationRegisterWebAuthn(),
|
||||
"NewInfoSelfServiceContinueLoginWebAuthn": text.NewInfoSelfServiceContinueLoginWebAuthn(),
|
||||
"NewInfoSelfServiceLoginPasskey": text.NewInfoSelfServiceLoginPasskey(),
|
||||
"NewInfoSelfServiceRegistrationRegisterPasskey": text.NewInfoSelfServiceRegistrationRegisterPasskey(),
|
||||
"NewInfoSelfServiceLoginContinue": text.NewInfoSelfServiceLoginContinue(),
|
||||
"NewErrorValidationSuchNoWebAuthnUser": text.NewErrorValidationSuchNoWebAuthnUser(),
|
||||
"NewRegistrationEmailWithCodeSent": text.NewRegistrationEmailWithCodeSent(),
|
||||
"NewLoginCodeSent": text.NewLoginCodeSent(),
|
||||
"NewLoginCodeSentForAuthenticatedUser": text.NewLoginCodeSentForAuthenticatedUser(),
|
||||
"NewErrorValidationRegistrationCodeInvalidOrAlreadyUsed": text.NewErrorValidationRegistrationCodeInvalidOrAlreadyUsed(),
|
||||
"NewErrorValidationLoginCodeInvalidOrAlreadyUsed": text.NewErrorValidationLoginCodeInvalidOrAlreadyUsed(),
|
||||
"NewErrorValidationNoCodeUser": text.NewErrorValidationNoCodeUser(),
|
||||
"NewInfoNodeLabelRegistrationCode": text.NewInfoNodeLabelRegistrationCode(),
|
||||
"NewInfoNodeLabelLoginCode": text.NewInfoNodeLabelLoginCode(),
|
||||
"NewErrorValidationLoginRetrySuccessful": text.NewErrorValidationLoginRetrySuccessful(),
|
||||
"NewErrorValidationTraitsMismatch": text.NewErrorValidationTraitsMismatch(),
|
||||
"NewInfoSelfServiceLoginCode": text.NewInfoSelfServiceLoginCode(),
|
||||
"NewErrorValidationRegistrationRetrySuccessful": text.NewErrorValidationRegistrationRetrySuccessful(),
|
||||
"NewInfoSelfServiceRegistrationRegisterCode": text.NewInfoSelfServiceRegistrationRegisterCode(),
|
||||
"NewErrorValidationLoginLinkedCredentialsDoNotMatch": text.NewErrorValidationLoginLinkedCredentialsDoNotMatch(),
|
||||
"NewErrorValidationAddressUnknown": text.NewErrorValidationAddressUnknown(),
|
||||
"NewInfoSelfServiceLoginCodeMFA": text.NewInfoSelfServiceLoginCodeMFA(),
|
||||
"NewInfoLoginPassword": text.NewInfoLoginPassword(),
|
||||
"NewErrorValidationAccountNotFound": text.NewErrorValidationAccountNotFound(),
|
||||
"NewInfoSelfServiceLoginAAL2CodeAddress": text.NewInfoSelfServiceLoginAAL2CodeAddress("{channel}", "{address}"),
|
||||
"NewErrorCaptchaFailed": text.NewErrorCaptchaFailed(),
|
||||
"NewCaptchaContainerMessage": text.NewCaptchaContainerMessage(),
|
||||
"NewErrorValidationEmail": text.NewErrorValidationEmail("{value}"),
|
||||
"NewErrorValidationPhone": text.NewErrorValidationPhone("{value}"),
|
||||
"NewErrorValidationIdentityDisabled": text.NewErrorValidationIdentityDisabled(),
|
||||
"NewErrorValidationSettingsTooManyAddressChanges": text.NewErrorValidationSettingsTooManyAddressChanges(),
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if os.Args[1] == "elements" {
|
||||
path, err := filepath.Abs(os.Args[2])
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unable to determine absolute path for elements generation: %+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := generateElements(path); err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unable to generate locales for elements: %+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := clidoc.Generate(cmd.NewRootCmd(nil, nil), []string{filepath.Join(os.Args[2], "cli")}); err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unable to generate CLI docs: %+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := validateAllMessages(filepath.Join(os.Args[1], "text")); err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unable to validate messages: %+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sortedMessages := sortMessages()
|
||||
for i := 1; i < len(sortedMessages); i++ {
|
||||
if sortedMessages[i].ID == sortedMessages[i-1].ID {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Message ID %d is used more than once: %q %q\n", sortedMessages[i].ID, sortedMessages[i].Text, sortedMessages[i-1].Text)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if err := writeMessages(filepath.Join(os.Args[2], "concepts/ui-messages.md"), sortedMessages); err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unable to generate message table: %+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := writeMessagesJson(filepath.Join(os.Args[2], "concepts/messages.json"), sortedMessages); err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unable to generate messages.json: %+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("All files have been generated and updated.")
|
||||
}
|
||||
|
||||
func codeEncode(in interface{}) string {
|
||||
out, err := json.MarshalIndent(in, "", " ")
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unable to encode to JSON: %+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func sortMessages() []*text.Message {
|
||||
var toSort []*text.Message
|
||||
for _, m := range messages {
|
||||
toSort = append(toSort, m)
|
||||
}
|
||||
|
||||
sort.Slice(toSort, func(i, j int) bool {
|
||||
if toSort[i].ID == toSort[j].ID {
|
||||
return toSort[i].Text < toSort[j].Text
|
||||
}
|
||||
return toSort[i].ID < toSort[j].ID
|
||||
})
|
||||
|
||||
return toSort
|
||||
}
|
||||
|
||||
func writeMessages(path string, sortedMessages []*text.Message) error {
|
||||
//nolint:gosec
|
||||
content, err := os.ReadFile(path) // #nosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var w bytes.Buffer
|
||||
for _, m := range sortedMessages {
|
||||
fmt.Fprintf(&w, `###### %s (%d)
|
||||
|
||||
%s
|
||||
|
||||
`, m.Text, m.ID, "```json\n"+codeEncode(m)+"\n```")
|
||||
}
|
||||
|
||||
r := regexp.MustCompile(`(?s)<!-- START MESSAGE TABLE -->(.*?)<!-- END MESSAGE TABLE -->`)
|
||||
result := r.ReplaceAllString(string(content), "<!-- START MESSAGE TABLE -->\n"+w.String()+"\n<!-- END MESSAGE TABLE -->")
|
||||
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) // #nosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := f.WriteString(result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeMessagesJson(path string, sortedMessages []*text.Message) error {
|
||||
result := codeEncode(sortedMessages)
|
||||
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644) // #nosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := f.WriteString(result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAllMessages(path string) error {
|
||||
type message struct {
|
||||
ID, Name string
|
||||
}
|
||||
|
||||
usedIDs := make([]message, 0, len(messages))
|
||||
set := token.NewFileSet()
|
||||
packs, err := parser.ParseDir(set, path, nil, 0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to parse text directory")
|
||||
}
|
||||
info := &types.Info{
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
var pack *ast.Package
|
||||
for _, p := range packs {
|
||||
if p.Name == "text" {
|
||||
pack = p
|
||||
break
|
||||
}
|
||||
}
|
||||
allFiles := make([]*ast.File, 0)
|
||||
for fn, f := range pack.Files {
|
||||
if strings.HasSuffix(fn, "/id.go") {
|
||||
allFiles = append(allFiles, f)
|
||||
}
|
||||
}
|
||||
_, err = (&types.Config{Importer: importer.Default()}).Check("text", set, allFiles, info)
|
||||
if err != nil {
|
||||
return err // type error
|
||||
}
|
||||
|
||||
for _, f := range pack.Files {
|
||||
for _, d := range f.Decls {
|
||||
switch decl := d.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if name := decl.Name.String(); decl.Name.IsExported() && strings.HasPrefix(name, "New") {
|
||||
if _, ok := messages[name]; !ok {
|
||||
return errors.Errorf("expected to find message %s in the list for the documentation generation but could not", name)
|
||||
}
|
||||
}
|
||||
case *ast.GenDecl:
|
||||
if decl.Tok == token.CONST {
|
||||
for _, spec := range decl.Specs {
|
||||
value := spec.(*ast.ValueSpec) // safe because decl.Tok is token.CONST
|
||||
if t, ok := value.Type.(*ast.Ident); ok {
|
||||
if t.Name == "ID" {
|
||||
for _, name := range value.Names {
|
||||
c := info.ObjectOf(name)
|
||||
if c == nil {
|
||||
return errors.Errorf("expected to find const %s in text/id.go", name.Name)
|
||||
}
|
||||
usedIDs = append(usedIDs, message{
|
||||
ID: c.(*types.Const).Val().ExactString(),
|
||||
Name: name.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(usedIDs, func(i, j int) bool {
|
||||
return usedIDs[i].ID < usedIDs[j].ID
|
||||
})
|
||||
for i := 1; i < len(usedIDs); i++ {
|
||||
if usedIDs[i].ID == usedIDs[i-1].ID {
|
||||
return errors.Errorf("message ID %s is used more than once: %s %s", usedIDs[i].ID, usedIDs[i].Name, usedIDs[i-1].Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateElements(messageFilePath string) error {
|
||||
// If the file at messageFilePath does not exist, create it
|
||||
f, err := os.OpenFile(messageFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) // #nosec
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to open or create message file at %s", messageFilePath)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return errors.Wrapf(err, "unable to close message file at %s", messageFilePath)
|
||||
}
|
||||
|
||||
sortedMessages := sortMessages()
|
||||
if err := writeMessagesJson(messageFilePath, sortedMessages); err != nil {
|
||||
return errors.Wrapf(err, "unable to write messages json to %s", messageFilePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
14
cmd/clidoc/main_test.go
Normal file
14
cmd/clidoc/main_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMessages(t *testing.T) {
|
||||
require.NoError(t, validateAllMessages("../../text"))
|
||||
}
|
||||
27
cmd/courier/root.go
Normal file
27
cmd/courier/root.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package courier
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/driver"
|
||||
"github.com/ory/x/configx"
|
||||
)
|
||||
|
||||
// NewCourierCmd creates a new courier command
|
||||
func NewCourierCmd() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "courier",
|
||||
Short: "Commands related to the Ory Kratos message courier",
|
||||
}
|
||||
configx.RegisterFlags(c.PersistentFlags())
|
||||
return c
|
||||
}
|
||||
|
||||
func RegisterCommandRecursive(parent *cobra.Command, dOpts []driver.RegistryOption) {
|
||||
c := NewCourierCmd()
|
||||
parent.AddCommand(c)
|
||||
c.AddCommand(NewWatchCmd(dOpts))
|
||||
}
|
||||
108
cmd/courier/watch.go
Normal file
108
cmd/courier/watch.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package courier
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/urfave/negroni"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/ory/graceful"
|
||||
"github.com/ory/kratos/driver"
|
||||
"github.com/ory/x/configx"
|
||||
"github.com/ory/x/prometheusx"
|
||||
"github.com/ory/x/reqlog"
|
||||
)
|
||||
|
||||
func NewWatchCmd(dOpts []driver.RegistryOption) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "watch",
|
||||
Short: "Starts the Ory Kratos message courier",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
r, err := driver.New(cmd.Context(), cmd.ErrOrStderr(), append(dOpts, driver.WithConfigOptions(configx.WithFlags(cmd.Flags())))...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return StartCourier(cmd.Context(), r)
|
||||
},
|
||||
}
|
||||
c.PersistentFlags().Int("expose-metrics-port", 0, "The port to expose the metrics endpoint on (not exposed by default)")
|
||||
return c
|
||||
}
|
||||
|
||||
func StartCourier(ctx context.Context, r driver.Registry) error {
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
if port := r.Config().CourierExposeMetricsPort(ctx); port != 0 {
|
||||
eg.Go(func() error {
|
||||
return ServeMetrics(ctx, r, port)
|
||||
})
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
return Watch(ctx, r)
|
||||
})
|
||||
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func ServeMetrics(ctx context.Context, r driver.Registry, port int) error {
|
||||
cfg := r.Config().ServeAdmin(ctx)
|
||||
l := r.Logger()
|
||||
|
||||
recovery := negroni.NewRecovery()
|
||||
recovery.Logger = l
|
||||
|
||||
n := negroni.New(
|
||||
recovery,
|
||||
reqlog.NewMiddlewareFromLogger(l, "admin#"+cfg.BaseURL.String()),
|
||||
)
|
||||
|
||||
router := http.NewServeMux()
|
||||
router.Handle(prometheusx.MetricsPrometheusPath, prometheusx.Handler())
|
||||
n.UseHandler(router)
|
||||
|
||||
var handler http.Handler = n
|
||||
|
||||
//#nosec G112 -- the correct settings are set by graceful.WithDefaults
|
||||
server := graceful.WithDefaults(&http.Server{
|
||||
Addr: configx.GetAddress(cfg.Host, port),
|
||||
Handler: handler,
|
||||
})
|
||||
|
||||
l.WithField("addr", server.Addr).Info("Starting the metrics httpd")
|
||||
if err := graceful.GracefulContext(ctx, server.ListenAndServe, server.Shutdown); err != nil {
|
||||
l.Errorln("Failed to gracefully shutdown metrics httpd")
|
||||
return err
|
||||
}
|
||||
l.Println("Metrics httpd was shutdown gracefully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func Watch(ctx context.Context, r driver.Registry) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
r.Logger().Println("Courier worker started.")
|
||||
if err := graceful.Graceful(func() error {
|
||||
c, err := r.Courier(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Work(ctx)
|
||||
}, func(_ context.Context) error {
|
||||
cancel()
|
||||
return nil
|
||||
}); err != nil {
|
||||
r.Logger().WithError(err).Error("Failed to run courier worker.")
|
||||
return err
|
||||
}
|
||||
|
||||
r.Logger().Println("Courier worker was shutdown gracefully.")
|
||||
return nil
|
||||
}
|
||||
47
cmd/courier/watch_test.go
Normal file
47
cmd/courier/watch_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package courier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/phayes/freeport"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ory/kratos/pkg"
|
||||
"github.com/ory/x/configx"
|
||||
)
|
||||
|
||||
func TestStartCourier(t *testing.T) {
|
||||
t.Run("case=without metrics", func(t *testing.T) {
|
||||
_, r := pkg.NewFastRegistryWithMocks(t)
|
||||
go func() { _ = StartCourier(t.Context(), r) }()
|
||||
require.Equal(t, r.Config().CourierExposeMetricsPort(t.Context()), 0)
|
||||
})
|
||||
|
||||
t.Run("case=with metrics", func(t *testing.T) {
|
||||
port, err := freeport.GetFreePort()
|
||||
require.NoError(t, err)
|
||||
_, r := pkg.NewFastRegistryWithMocks(t, configx.WithValue("expose-metrics-port", port))
|
||||
go func() { _ = StartCourier(t.Context(), r) }()
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
t.Cleanup(transport.CloseIdleConnections)
|
||||
client := &http.Client{Transport: transport}
|
||||
url := fmt.Sprintf("http://127.0.0.1:%d/metrics/prometheus", port)
|
||||
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
||||
res, err := client.Get(url)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
assert.NoError(t, res.Body.Close())
|
||||
}()
|
||||
assert.Equal(t, 200, res.StatusCode)
|
||||
}, 10*time.Second, 10*time.Millisecond)
|
||||
})
|
||||
}
|
||||
371
cmd/daemon/serve.go
Normal file
371
cmd/daemon/serve.go
Normal file
@@ -0,0 +1,371 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/cors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/urfave/negroni"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/ory/analytics-go/v5"
|
||||
"github.com/ory/graceful"
|
||||
"github.com/ory/kratos/cmd/courier"
|
||||
"github.com/ory/kratos/driver"
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/kratos/identity"
|
||||
"github.com/ory/kratos/schema"
|
||||
"github.com/ory/kratos/selfservice/errorx"
|
||||
"github.com/ory/kratos/selfservice/flow/login"
|
||||
"github.com/ory/kratos/selfservice/flow/logout"
|
||||
"github.com/ory/kratos/selfservice/flow/recovery"
|
||||
"github.com/ory/kratos/selfservice/flow/registration"
|
||||
"github.com/ory/kratos/selfservice/flow/settings"
|
||||
"github.com/ory/kratos/selfservice/flow/verification"
|
||||
"github.com/ory/kratos/selfservice/strategy/link"
|
||||
"github.com/ory/kratos/selfservice/strategy/oidc"
|
||||
"github.com/ory/kratos/session"
|
||||
"github.com/ory/kratos/x"
|
||||
"github.com/ory/kratos/x/nosurfx"
|
||||
"github.com/ory/x/healthx"
|
||||
"github.com/ory/x/httprouterx"
|
||||
"github.com/ory/x/metricsx"
|
||||
"github.com/ory/x/networkx"
|
||||
"github.com/ory/x/otelx"
|
||||
"github.com/ory/x/otelx/semconv"
|
||||
"github.com/ory/x/popx"
|
||||
"github.com/ory/x/prometheusx"
|
||||
"github.com/ory/x/reqlog"
|
||||
"github.com/ory/x/urlx"
|
||||
)
|
||||
|
||||
func init() {
|
||||
graceful.DefaultShutdownTimeout = 120 * time.Second
|
||||
}
|
||||
|
||||
var httpMetrics = prometheusx.NewHTTPMetrics("kratos", prometheusx.HTTPPrefix, config.Version, config.Commit, config.Date)
|
||||
|
||||
func servePublic(ctx context.Context, r *driver.RegistryDefault, cmd *cobra.Command) (func() error, error) {
|
||||
cfg := r.Config().ServePublic(ctx)
|
||||
l := r.Logger()
|
||||
|
||||
rec := negroni.NewRecovery()
|
||||
rec.Logger = l
|
||||
|
||||
router := httprouterx.NewRouterPublic()
|
||||
n := negroni.New(
|
||||
rec,
|
||||
httpMetrics,
|
||||
)
|
||||
|
||||
for _, mw := range r.HTTPMiddlewares() {
|
||||
n.Use(mw)
|
||||
}
|
||||
|
||||
publicLogger := reqlog.NewMiddlewareFromLogger(l, "public#"+cfg.BaseURL.String())
|
||||
|
||||
if cfg.RequestLog.DisableHealth {
|
||||
publicLogger.ExcludePaths(healthx.AliveCheckPath, healthx.ReadyCheckPath)
|
||||
}
|
||||
|
||||
n.UseFunc(semconv.Middleware)
|
||||
n.Use(publicLogger)
|
||||
n.Use(x.HTTPLoaderContextMiddleware(r))
|
||||
n.UseFunc(httprouterx.NoCacheNegroni)
|
||||
n.Use(sqa(ctx, cmd, r))
|
||||
|
||||
csrf := nosurfx.NewCSRFHandler(router, r)
|
||||
|
||||
// we need to always load the CORS middleware even if it is disabled, to allow hot-enabling CORS
|
||||
n.UseFunc(func(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
|
||||
cfg, enabled := r.Config().CORSPublic(req.Context())
|
||||
if !enabled {
|
||||
next(w, req)
|
||||
return
|
||||
}
|
||||
cors.New(cfg).ServeHTTP(w, req, next)
|
||||
})
|
||||
|
||||
r.WithCSRFHandler(csrf)
|
||||
n.UseHandler(r.CSRFHandler())
|
||||
|
||||
// Disable CSRF for these endpoints
|
||||
csrf.DisablePath(healthx.AliveCheckPath)
|
||||
csrf.DisablePath(healthx.ReadyCheckPath)
|
||||
csrf.DisablePath(healthx.VersionPath)
|
||||
csrf.DisablePath(prometheusx.MetricsPrometheusPath)
|
||||
|
||||
r.RegisterPublicRoutes(ctx, router)
|
||||
|
||||
var handler http.Handler = n
|
||||
if tracer := r.Tracer(ctx); tracer.IsLoaded() {
|
||||
handler = otelx.NewMiddleware(handler, "servePublic",
|
||||
otelhttp.WithTracerProvider(tracer.Provider()),
|
||||
)
|
||||
}
|
||||
|
||||
handler = http.MaxBytesHandler(handler, 5*1024*1024 /* 5 MB */) // Important: this must be the outermost handler or our tracing breaks
|
||||
|
||||
certFunc, err := cfg.TLS.GetCertFunc(ctx, l, "public")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//#nosec G112 -- the correct settings are set by graceful.WithDefaults
|
||||
server := graceful.WithDefaults(&http.Server{
|
||||
Handler: handler,
|
||||
TLSConfig: &tls.Config{GetCertificate: certFunc, MinVersion: tls.VersionTLS12},
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 60 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
})
|
||||
addr := cfg.GetAddress()
|
||||
|
||||
return func() error {
|
||||
l.WithField("addr", addr).Info("Starting the public httpd")
|
||||
if err := graceful.GracefulContext(ctx, func() error {
|
||||
listener, err := networkx.MakeListener(addr, &cfg.Socket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if certFunc == nil {
|
||||
return server.Serve(listener)
|
||||
}
|
||||
return server.ServeTLS(listener, "", "")
|
||||
}, server.Shutdown); err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
l.Errorf("Failed to gracefully shutdown public httpd: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.Println("Public httpd was shutdown gracefully")
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func serveAdmin(ctx context.Context, r *driver.RegistryDefault, cmd *cobra.Command) (func() error, error) {
|
||||
cfg := r.Config().ServeAdmin(ctx)
|
||||
l := r.Logger()
|
||||
|
||||
rec := negroni.NewRecovery()
|
||||
rec.Logger = l
|
||||
|
||||
router := httprouterx.NewRouterAdminWithPrefix()
|
||||
n := negroni.New(
|
||||
rec,
|
||||
httpMetrics,
|
||||
)
|
||||
|
||||
for _, mw := range r.HTTPMiddlewares() {
|
||||
n.Use(mw)
|
||||
}
|
||||
|
||||
adminLogger := reqlog.NewMiddlewareFromLogger(l, "admin#"+cfg.BaseURL.String())
|
||||
|
||||
if cfg.RequestLog.DisableHealth {
|
||||
adminLogger.ExcludePaths(
|
||||
httprouterx.AdminPrefix+healthx.AliveCheckPath,
|
||||
httprouterx.AdminPrefix+healthx.ReadyCheckPath,
|
||||
httprouterx.AdminPrefix+prometheusx.MetricsPrometheusPath,
|
||||
)
|
||||
}
|
||||
n.UseFunc(semconv.Middleware)
|
||||
n.Use(adminLogger)
|
||||
n.UseFunc(httprouterx.AddAdminPrefixIfNotPresentNegroni)
|
||||
n.UseFunc(httprouterx.NoCacheNegroni)
|
||||
n.Use(x.HTTPLoaderContextMiddleware(r))
|
||||
n.Use(sqa(ctx, cmd, r))
|
||||
|
||||
r.RegisterAdminRoutes(ctx, router)
|
||||
|
||||
n.UseHandler(router)
|
||||
|
||||
var handler http.Handler = n
|
||||
if tracer := r.Tracer(ctx); tracer.IsLoaded() {
|
||||
handler = otelx.NewMiddleware(handler, "serveAdmin",
|
||||
otelhttp.WithTracerProvider(tracer.Provider()),
|
||||
)
|
||||
}
|
||||
|
||||
handler = http.MaxBytesHandler(handler, 5*1024*1024 /* 5 MB */) // Important: this must be the outermost handler or our tracing breaks
|
||||
|
||||
certFunc, err := cfg.TLS.GetCertFunc(ctx, l, "admin")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//#nosec G112 -- the correct settings are set by graceful.WithDefaults
|
||||
server := graceful.WithDefaults(&http.Server{
|
||||
Handler: handler,
|
||||
TLSConfig: &tls.Config{GetCertificate: certFunc, MinVersion: tls.VersionTLS12},
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 120 * time.Second,
|
||||
IdleTimeout: 600 * time.Second,
|
||||
})
|
||||
|
||||
addr := cfg.GetAddress()
|
||||
|
||||
return func() error {
|
||||
l.WithField("addr", addr).Info("Starting the admin httpd")
|
||||
if err := graceful.GracefulContext(ctx, func() error {
|
||||
listener, err := networkx.MakeListener(addr, &cfg.Socket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if certFunc == nil {
|
||||
return server.Serve(listener)
|
||||
}
|
||||
return server.ServeTLS(listener, "", "")
|
||||
}, server.Shutdown); err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
l.Errorf("Failed to gracefully shutdown admin httpd: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.Println("Admin httpd was shutdown gracefully")
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sqa(ctx context.Context, cmd *cobra.Command, d driver.Registry) *metricsx.Service {
|
||||
urls := []string{
|
||||
d.Config().ServePublic(ctx).BaseURL.Host,
|
||||
d.Config().ServeAdmin(ctx).BaseURL.Host,
|
||||
d.Config().SelfServiceFlowLoginUI(ctx).Host,
|
||||
d.Config().SelfServiceFlowSettingsUI(ctx).Host,
|
||||
d.Config().SelfServiceFlowErrorURL(ctx).Host,
|
||||
d.Config().SelfServiceFlowRegistrationUI(ctx).Host,
|
||||
d.Config().SelfServiceFlowRecoveryUI(ctx).Host,
|
||||
d.Config().ServePublic(ctx).Host,
|
||||
d.Config().ServeAdmin(ctx).Host,
|
||||
}
|
||||
if c, y := d.Config().CORSPublic(ctx); y {
|
||||
urls = append(urls, c.AllowedOrigins...)
|
||||
}
|
||||
host := urlx.ExtractPublicAddress(urls...)
|
||||
|
||||
// Creates only ones
|
||||
// instance
|
||||
return metricsx.New(
|
||||
cmd,
|
||||
d.Logger(),
|
||||
d.Config().GetProvider(ctx),
|
||||
&metricsx.Options{
|
||||
Service: "kratos",
|
||||
DeploymentId: metricsx.Hash(d.Persister().NetworkID(ctx).String()),
|
||||
DBDialect: d.Persister().GetConnection(ctx).Dialect.Name(),
|
||||
IsDevelopment: d.Config().IsInsecureDevMode(ctx),
|
||||
WriteKey: "qQlI6q8Q4WvkzTjKQSor4sHYOikHIvvi",
|
||||
WhitelistedPaths: []string{
|
||||
"/",
|
||||
healthx.AliveCheckPath,
|
||||
healthx.ReadyCheckPath,
|
||||
healthx.VersionPath,
|
||||
|
||||
oidc.RouteBase,
|
||||
|
||||
login.RouteInitBrowserFlow,
|
||||
login.RouteInitAPIFlow,
|
||||
login.RouteGetFlow,
|
||||
login.RouteSubmitFlow,
|
||||
|
||||
logout.RouteInitBrowserFlow,
|
||||
logout.RouteSubmitFlow,
|
||||
logout.RouteAPIFlow,
|
||||
|
||||
registration.RouteInitBrowserFlow,
|
||||
registration.RouteInitAPIFlow,
|
||||
registration.RouteGetFlow,
|
||||
registration.RouteSubmitFlow,
|
||||
|
||||
session.RouteWhoami,
|
||||
|
||||
httprouterx.AdminPrefix + "/" + schema.SchemasPath,
|
||||
httprouterx.AdminPrefix + identity.RouteCollection,
|
||||
|
||||
settings.RouteInitBrowserFlow,
|
||||
settings.RouteInitAPIFlow,
|
||||
settings.RouteGetFlow,
|
||||
settings.RouteSubmitFlow,
|
||||
|
||||
verification.RouteInitAPIFlow,
|
||||
verification.RouteInitBrowserFlow,
|
||||
verification.RouteGetFlow,
|
||||
verification.RouteSubmitFlow,
|
||||
|
||||
recovery.RouteInitAPIFlow,
|
||||
recovery.RouteInitBrowserFlow,
|
||||
recovery.RouteGetFlow,
|
||||
recovery.RouteSubmitFlow,
|
||||
|
||||
link.RouteAdminCreateRecoveryLink,
|
||||
|
||||
errorx.RouteGet,
|
||||
prometheusx.MetricsPrometheusPath,
|
||||
},
|
||||
BuildVersion: config.Version,
|
||||
BuildHash: config.Commit,
|
||||
BuildTime: config.Date,
|
||||
Config: &analytics.Config{
|
||||
Endpoint: "https://sqa.ory.sh",
|
||||
GzipCompressionLevel: 6,
|
||||
BatchMaxSize: 500 * 1000,
|
||||
BatchSize: 1000,
|
||||
Interval: time.Hour * 6,
|
||||
},
|
||||
Hostname: host,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func courierTask(ctx context.Context, d driver.Registry) func() error {
|
||||
return func() error {
|
||||
if d.Config().IsBackgroundCourierEnabled(ctx) {
|
||||
return courier.Watch(ctx, d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func ServeAll(d *driver.RegistryDefault) func(cmd *cobra.Command, args []string) error {
|
||||
return func(cmd *cobra.Command, _ []string) error {
|
||||
ctx := cmd.Context()
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
cmd.SetContext(ctx)
|
||||
|
||||
if err := popx.VerifyDialect(ctx, d.Persister().GetConnection(ctx)); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// construct all tasks upfront to avoid race conditions
|
||||
publicSrv, err := servePublic(ctx, d, cmd)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
adminSrv, err := serveAdmin(ctx, d, cmd)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
tasks := []func() error{
|
||||
publicSrv,
|
||||
adminSrv,
|
||||
courierTask(ctx, d),
|
||||
}
|
||||
for _, task := range tasks {
|
||||
g.Go(task)
|
||||
}
|
||||
return g.Wait()
|
||||
}
|
||||
}
|
||||
76
cmd/daemon/serve_test.go
Normal file
76
cmd/daemon/serve_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright © 2026 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/phayes/freeport"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/ory/x/configx"
|
||||
|
||||
"github.com/ory/kratos/pkg"
|
||||
)
|
||||
|
||||
func TestMetricsRouterPaths(t *testing.T) {
|
||||
// TODO refactor this test to be parallelizable once we rewrite the server setup to be properly testable
|
||||
ports, err := freeport.GetFreePorts(2)
|
||||
require.NoError(t, err)
|
||||
adminPort, publicPort := ports[0], ports[1]
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Flags().Bool("sqa-opt-out", true, "")
|
||||
_, reg := pkg.NewFastRegistryWithMocks(t, configx.WithValues(map[string]any{
|
||||
"serve.admin.port": adminPort,
|
||||
"serve.public.port": publicPort,
|
||||
}))
|
||||
|
||||
startAdmin, err := serveAdmin(t.Context(), reg, cmd)
|
||||
require.NoError(t, err)
|
||||
startPublic, err := servePublic(t.Context(), reg, cmd)
|
||||
require.NoError(t, err)
|
||||
eg, _ := errgroup.WithContext(t.Context())
|
||||
eg.Go(startAdmin)
|
||||
eg.Go(startPublic)
|
||||
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
t.Cleanup(transport.CloseIdleConnections)
|
||||
client := &http.Client{Transport: transport}
|
||||
|
||||
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
||||
resp, err := client.Get(fmt.Sprintf("http://127.0.0.1:%d/health/ready", publicPort))
|
||||
require.NoError(t, err)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
assert.Equalf(t, http.StatusOK, resp.StatusCode, "%s", body)
|
||||
}, 2*time.Second, 10*time.Millisecond)
|
||||
|
||||
// Make some requests that should be recorded in the metrics
|
||||
req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://127.0.0.1:%d/sessions/session-id", publicPort), nil)
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||||
resp, err = client.Get(fmt.Sprintf("http://127.0.0.1:%d/admin/identities/8e5f29ec-db29-4e56-8517-75f7d4e2f1bc/sessions", adminPort))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
assert.EventuallyWithT(t, func(t *assert.CollectT) {
|
||||
res, err := client.Get(fmt.Sprintf("http://127.0.0.1:%d/admin/metrics/prometheus", adminPort))
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, http.StatusOK, res.StatusCode)
|
||||
respBody, err := io.ReadAll(res.Body)
|
||||
body := string(respBody)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, body, `endpoint="/sessions/{param}"`)
|
||||
assert.Contains(t, body, `endpoint="/admin/identities/{param}/sessions"`)
|
||||
}, 5*time.Second, 100*time.Millisecond)
|
||||
}
|
||||
332
cmd/hashers/argon2/calibrate.go
Normal file
332
cmd/hashers/argon2/calibrate.go
Normal file
@@ -0,0 +1,332 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package argon2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ory/x/configx"
|
||||
|
||||
"github.com/ory/x/cmdx"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/inhies/go-bytesize"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/kratos/hash"
|
||||
)
|
||||
|
||||
const (
|
||||
FlagStartMemory = "start-memory"
|
||||
FlagMaxMemory = "max-memory"
|
||||
FlagAdjustMemory = "adjust-memory-by"
|
||||
FlagStartIterations = "start-iterations"
|
||||
FlagMaxConcurrent = "max-concurrent"
|
||||
|
||||
FlagRuns = "probe-runs"
|
||||
)
|
||||
|
||||
type (
|
||||
colorWriter struct {
|
||||
c *color.Color
|
||||
w io.Writer
|
||||
}
|
||||
loadResult struct {
|
||||
r *resultTable
|
||||
v *config.Argon2
|
||||
}
|
||||
loadResults []*loadResult
|
||||
)
|
||||
|
||||
var _ cmdx.Table = loadResults{}
|
||||
|
||||
func (l loadResults) Header() []string {
|
||||
return append((&resultTable{}).Header(), "MEMOry PARAM", "ITERATIONS PARAM")
|
||||
}
|
||||
|
||||
func (l loadResults) Table() [][]string {
|
||||
t := make([][]string, len(l))
|
||||
|
||||
for i, r := range l {
|
||||
t[i] = append(r.r.Columns(), fmt.Sprintf("%d", r.v.Memory), fmt.Sprintf("%d", r.v.Iterations))
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (l loadResults) Interface() interface{} {
|
||||
return l
|
||||
}
|
||||
|
||||
func (l loadResults) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (c *colorWriter) Write(o []byte) (int, error) {
|
||||
return c.c.Fprintf(c.w, "%s", o)
|
||||
}
|
||||
|
||||
func newCalibrateCmd() *cobra.Command {
|
||||
var (
|
||||
maxMemory, adjustMemory bytesize.ByteSize = 0, 512 * bytesize.MB
|
||||
runs int
|
||||
)
|
||||
|
||||
flagConfig := &argon2Config{
|
||||
localConfig: config.Argon2{},
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "calibrate <requests-per-minute>",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "Computes Optimal Argon2 Parameters",
|
||||
Long: `This command helps you calibrate the configuration parameters for Argon2. Password hashing is a trade-off between security, resource consumption, and user experience. Resource consumption should not be too high and the login should not take too long.
|
||||
|
||||
We recommend that the login process takes between half a second and one second for password hashing, giving a good balance between security and user experience.
|
||||
|
||||
Please note that the values depend on the machine you run the hashing on. If you have RAM constraints, please set the memory dedicated to Ory Kratos to avoid out of memory panics.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
progressPrinter := cmdx.NewLoudErrPrinter(cmd)
|
||||
resultPrinter := cmdx.NewLoudPrinter(cmd, &colorWriter{c: color.New(color.FgGreen), w: cmd.ErrOrStderr()})
|
||||
|
||||
conf, err := configProvider(cmd, flagConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// always take start flags, or their default
|
||||
conf.localConfig.Memory = flagConfig.localConfig.Memory
|
||||
conf.localConfig.Iterations = flagConfig.localConfig.Iterations
|
||||
|
||||
desiredDuration := conf.localConfig.ExpectedDuration
|
||||
|
||||
reqPerMin, err := strconv.ParseInt(args[0], 0, 0)
|
||||
if err != nil {
|
||||
// we want the error and usage string printed so just return
|
||||
return err
|
||||
}
|
||||
|
||||
hasher := hash.NewHasherArgon2(conf)
|
||||
|
||||
var currentDuration time.Duration
|
||||
|
||||
_, _ = progressPrinter.Printf("Increasing memory to get over %s:\n", desiredDuration)
|
||||
|
||||
for {
|
||||
if maxMemory != 0 && conf.localConfig.Memory > maxMemory {
|
||||
// don't further increase memory
|
||||
_, _ = progressPrinter.Println(" ouch, hit the memory limit there")
|
||||
conf.localConfig.Memory = maxMemory
|
||||
break
|
||||
}
|
||||
|
||||
currentDuration, err = probe(cmd, hasher, runs, progressPrinter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = progressPrinter.Printf(" took %s with %s of memory\n", currentDuration, conf.localConfig.Memory)
|
||||
|
||||
if currentDuration > desiredDuration {
|
||||
if conf.localConfig.Memory <= adjustMemory {
|
||||
// adjusting the memory would now result in <= 0B
|
||||
adjustMemory = adjustMemory >> 1
|
||||
}
|
||||
conf.localConfig.Memory -= adjustMemory
|
||||
break
|
||||
}
|
||||
|
||||
// adjust config
|
||||
conf.localConfig.Memory += adjustMemory
|
||||
}
|
||||
|
||||
_, _ = progressPrinter.Printf("Decreasing memory to get under %s:\n", desiredDuration)
|
||||
|
||||
for {
|
||||
currentDuration, err = probe(cmd, hasher, runs, progressPrinter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = progressPrinter.Printf(" took %s with %s of memory\n", currentDuration, conf.localConfig.Memory)
|
||||
|
||||
if currentDuration < desiredDuration {
|
||||
break
|
||||
}
|
||||
|
||||
for conf.localConfig.Memory <= adjustMemory {
|
||||
// adjusting the memory would now result in <= 0B
|
||||
adjustMemory = adjustMemory >> 1
|
||||
}
|
||||
|
||||
// adjust config
|
||||
conf.localConfig.Memory -= adjustMemory
|
||||
}
|
||||
|
||||
_, _ = resultPrinter.Printf("Settled on %s of memory.\n", conf.localConfig.Memory)
|
||||
_, _ = progressPrinter.Printf("Increasing iterations to get over %s:\n", desiredDuration)
|
||||
|
||||
for {
|
||||
currentDuration, err = probe(cmd, hasher, runs, progressPrinter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = progressPrinter.Printf(" took %s with %d iterations\n", currentDuration, conf.localConfig.Iterations)
|
||||
|
||||
if currentDuration > desiredDuration {
|
||||
if conf.localConfig.Iterations > 1 {
|
||||
conf.localConfig.Iterations -= 1
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// adjust config
|
||||
conf.localConfig.Iterations += 1
|
||||
}
|
||||
|
||||
_, _ = progressPrinter.Printf("Decreasing iterations to get under %s:\n", desiredDuration)
|
||||
|
||||
for {
|
||||
currentDuration, err = probe(cmd, hasher, runs, progressPrinter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = progressPrinter.Printf(" took %s with %d iterations\n", currentDuration, conf.localConfig.Iterations)
|
||||
|
||||
// break also when iterations is 1; this catches the case where 1 was only slightly under the desired time and took longer a bit longer on another run
|
||||
if currentDuration < desiredDuration || conf.localConfig.Iterations == 1 {
|
||||
break
|
||||
}
|
||||
|
||||
// adjust config
|
||||
conf.localConfig.Iterations -= 1
|
||||
}
|
||||
_, _ = resultPrinter.Printf("Settled on %d iterations.\n", conf.localConfig.Iterations)
|
||||
|
||||
results := make(loadResults, 5)
|
||||
for i := 0; i < len(results); i++ {
|
||||
_, _ = progressPrinter.Printf("\nRunning load test to simulate %d login requests per minute.\n", reqPerMin)
|
||||
|
||||
res, err := runLoadTest(cmd, conf, int(reqPerMin))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cv, _ := conf.HasherArgon2()
|
||||
ccv := *cv
|
||||
results[i] = &loadResult{r: res, v: &ccv}
|
||||
|
||||
_, _ = progressPrinter.Println("The load test result is:")
|
||||
_, _ = progressPrinter.Println()
|
||||
cmdx.PrintRow(cmd, res)
|
||||
|
||||
switch {
|
||||
// too fast
|
||||
case res.MedianTime < conf.localConfig.ExpectedDuration:
|
||||
_, _ = progressPrinter.Printf("The median was %s under the minimal duration of %s, going to increase the hash cost.\n", conf.localConfig.ExpectedDuration-res.MedianTime, conf.localConfig.ExpectedDuration)
|
||||
|
||||
// try to increase memory first
|
||||
if res.MaxMem+64*bytesize.MB < maxMemory {
|
||||
// only small amounts of memory are sensible as we already benchmarked a single, non-concurrent request
|
||||
conf.localConfig.Memory += 64 * bytesize.MB
|
||||
_, _ = progressPrinter.Printf("Increasing memory to %s\n", conf.localConfig.Memory)
|
||||
} else {
|
||||
// increasing memory is not allowed by maxMemory, therefore increase CPU load
|
||||
conf.localConfig.Iterations++
|
||||
_, _ = progressPrinter.Printf("Increasing iterations to %d\n", conf.localConfig.Iterations)
|
||||
}
|
||||
// too much memory
|
||||
case res.MaxMem > conf.localConfig.DedicatedMemory:
|
||||
_, _ = progressPrinter.Printf("The required memory was %s more than the maximum allowed of %s.\n", res.MaxMem-maxMemory, conf.localConfig.DedicatedMemory)
|
||||
|
||||
//nolint:gosec // disable G115
|
||||
conf.localConfig.Memory -= (res.MaxMem - conf.localConfig.DedicatedMemory) / bytesize.ByteSize(reqPerMin)
|
||||
_, _ = progressPrinter.Printf("Decreasing memory to %s\n", conf.localConfig.Memory)
|
||||
// too slow
|
||||
case res.MaxTime > conf.localConfig.ExpectedDeviation+conf.localConfig.ExpectedDuration:
|
||||
_, _ = progressPrinter.Printf("The longest request took %s longer than the longest acceptable time of %s, going to decrease the hash cost.\n", res.MaxTime-conf.localConfig.ExpectedDeviation+conf.localConfig.ExpectedDuration, conf.localConfig.ExpectedDeviation+conf.localConfig.ExpectedDuration)
|
||||
|
||||
// try to decrease iterations first
|
||||
if conf.localConfig.Iterations > 1 {
|
||||
conf.localConfig.Iterations--
|
||||
_, _ = progressPrinter.Printf("Decreasing iterations to %d\n", conf.localConfig.Iterations)
|
||||
} else {
|
||||
// decreasing iterations is not possible anymore, decreasing memory
|
||||
// only small amounts of memory are sensible as we already benchmarked a single, non-concurrent request
|
||||
conf.localConfig.Memory -= 64 * bytesize.MB
|
||||
_, _ = progressPrinter.Printf("Decreasing memory to %s\n", conf.localConfig.Memory)
|
||||
}
|
||||
// too high deviation
|
||||
case res.StdDev > conf.localConfig.ExpectedDeviation:
|
||||
_, _ = progressPrinter.Printf("The deviation was %s more than the expected deviation of %s.\n", res.StdDev-conf.localConfig.ExpectedDeviation, conf.localConfig.ExpectedDeviation)
|
||||
|
||||
// try to decrease iterations first
|
||||
if conf.localConfig.Iterations > 1 {
|
||||
conf.localConfig.Iterations--
|
||||
_, _ = progressPrinter.Printf("Decreasing iterations to %d\n", conf.localConfig.Iterations)
|
||||
} else {
|
||||
// decreasing iterations is not possible anymore, decreasing memory
|
||||
// only small amounts of memory are sensible as we already benchmarked a single, non-concurrent request
|
||||
conf.localConfig.Memory -= 64 * bytesize.MB
|
||||
_, _ = progressPrinter.Printf("Decreasing memory to %s\n", conf.localConfig.Memory)
|
||||
}
|
||||
// all values seem reasonable
|
||||
default:
|
||||
_, _ = progressPrinter.Println("These values look good to me.")
|
||||
_, _ = progressPrinter.Println()
|
||||
cmdx.PrintRow(cmd, conf)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Could not automatically determine good parameters. Have a look at all the measurements taken and select acceptable values yourself. Have a look in the docs for more information: https://www.ory.com/kratos/docs/debug/performance-out-of-memory-password-hashing-argon2")
|
||||
cmdx.PrintTable(cmd, results)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.IntVarP(&runs, FlagRuns, "r", 2, "Runs per probe, median of all runs is taken as the result.")
|
||||
|
||||
flags.VarP(&flagConfig.localConfig.Memory, FlagStartMemory, "m", "Amount of memory to start probing at.")
|
||||
flags.Var(&maxMemory, FlagMaxMemory, "Maximum memory allowed (0 means no limit).")
|
||||
flags.Var(&adjustMemory, FlagAdjustMemory, "Amount by which the memory is adjusted in every step while probing.")
|
||||
|
||||
flags.Uint32VarP(&flagConfig.localConfig.Iterations, FlagStartIterations, "i", 1, "Number of iterations to start probing at.")
|
||||
|
||||
flags.Uint8(FlagMaxConcurrent, 16, "Maximum number of concurrent hashing operations.")
|
||||
|
||||
registerArgon2ConstantConfigFlags(flags, flagConfig)
|
||||
cmdx.RegisterFormatFlags(flags)
|
||||
configx.RegisterFlags(flags)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func probe(cmd *cobra.Command, hasher hash.Hasher, runs int, progressPrinter *cmdx.ConditionalPrinter) (time.Duration, error) {
|
||||
// force GC at the start of the experiment
|
||||
runtime.GC()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
var mid time.Time
|
||||
for i := 0; i < runs; i++ {
|
||||
mid = time.Now()
|
||||
_, err := hasher.Generate(cmd.Context(), []byte("password"))
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not generate a hash: %s\n", err)
|
||||
return 0, cmdx.FailSilently(cmd)
|
||||
}
|
||||
|
||||
_, _ = progressPrinter.Printf(" took %s in try %d\n", time.Since(mid), i)
|
||||
}
|
||||
return time.Duration(int64(time.Since(start)) / int64(runs)), nil
|
||||
}
|
||||
83
cmd/hashers/argon2/hash.go
Normal file
83
cmd/hashers/argon2/hash.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package argon2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/hash"
|
||||
"github.com/ory/x/cmdx"
|
||||
"github.com/ory/x/configx"
|
||||
"github.com/ory/x/flagx"
|
||||
)
|
||||
|
||||
const (
|
||||
FlagParallel = "parallel"
|
||||
)
|
||||
|
||||
func newHashCmd() *cobra.Command {
|
||||
flagConfig := &argon2Config{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "hash <password1> [<password2> ...]",
|
||||
Short: "Hash a list of passwords for benchmarking the hashing parameters",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
conf, err := configProvider(cmd, flagConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flagConfig.ctx = cmd.Context()
|
||||
|
||||
hasher := hash.NewHasherArgon2(conf)
|
||||
hashes := make([][]byte, len(args))
|
||||
errs := make(chan error, len(args))
|
||||
|
||||
start := time.Now()
|
||||
|
||||
for i, pw := range args {
|
||||
go func(i int, pw string) {
|
||||
start := time.Now()
|
||||
h, err := hasher.Generate(cmd.Context(), []byte(pw))
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "password %d: %s\n", i, time.Since(start))
|
||||
|
||||
hashes[i] = h
|
||||
errs <- err
|
||||
}(i, pw)
|
||||
|
||||
if !flagx.MustGetBool(cmd, FlagParallel) {
|
||||
if err := <-errs; err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not generate hash: %s\n", err.Error())
|
||||
return cmdx.FailSilently(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if flagx.MustGetBool(cmd, FlagParallel) {
|
||||
for i := 0; i < len(args); i++ {
|
||||
if err := <-errs; err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not generate hash: %s\n", err.Error())
|
||||
return cmdx.FailSilently(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "total: %s\n", time.Since(start))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.Bool(FlagParallel, false, "Run all hashing operations in parallel.")
|
||||
|
||||
registerArgon2ConfigFlags(flags, flagConfig)
|
||||
configx.RegisterFlags(flags)
|
||||
|
||||
return cmd
|
||||
}
|
||||
269
cmd/hashers/argon2/loadtest.go
Normal file
269
cmd/hashers/argon2/loadtest.go
Normal file
@@ -0,0 +1,269 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package argon2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ory/x/flagx"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/inhies/go-bytesize"
|
||||
"github.com/montanaflynn/stats"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/ory/kratos/hash"
|
||||
"github.com/ory/x/cmdx"
|
||||
"github.com/ory/x/configx"
|
||||
)
|
||||
|
||||
type resultTable struct {
|
||||
TotalTime time.Duration `json:"total_time"`
|
||||
MedianTime time.Duration `json:"median_request_time"`
|
||||
StdDev time.Duration `json:"std_deviation"`
|
||||
MinTime time.Duration `json:"min_request_time"`
|
||||
MaxTime time.Duration `json:"max_request_time"`
|
||||
MaxMem bytesize.ByteSize `json:"mem_used"`
|
||||
}
|
||||
|
||||
var (
|
||||
ErrSampleTimeExceeded = fmt.Errorf("the sample time was exceeded")
|
||||
ErrMemoryConsumptionExceeded = fmt.Errorf("the memory consumption was exceeded")
|
||||
|
||||
_ cmdx.TableRow = &resultTable{}
|
||||
)
|
||||
|
||||
func (r *resultTable) Header() []string {
|
||||
return []string{"TOTAL SAMPLE TIME", "MEDIAN REQUEST TIME", "STANDARD DEVIATION", "MIN REQUEST TIME", "MAX REQUEST TIME", "MEMOry USED"}
|
||||
}
|
||||
|
||||
func (r *resultTable) Columns() []string {
|
||||
return []string{
|
||||
r.TotalTime.String(),
|
||||
r.MedianTime.String(),
|
||||
r.StdDev.String(),
|
||||
r.MinTime.String(),
|
||||
r.MaxTime.String(),
|
||||
r.MaxMem.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *resultTable) Interface() interface{} {
|
||||
return r
|
||||
}
|
||||
|
||||
func newLoadTestCmd() *cobra.Command {
|
||||
flagConf := &argon2Config{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "load-test <authentication-requests-per-minute>",
|
||||
Short: "Simulate the password hashing with a number of concurrent requests/minute.",
|
||||
Long: "Simulates a number of concurrent authentication requests per minute. Gives statistical data about the measured performance and resource consumption. Can be used to tune and test the hashing parameters for peak demand situations.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
perMinute, err := strconv.ParseInt(args[0], 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flagConf.ctx = cmd.Context()
|
||||
|
||||
conf, err := configProvider(cmd, flagConf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !flagx.MustGetBool(cmd, cmdx.FlagQuiet) {
|
||||
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "The hashing configuration used is:")
|
||||
cmdx.PrintRow(cmd, conf)
|
||||
}
|
||||
|
||||
res, err := runLoadTest(cmd, conf, int(perMinute))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdx.PrintRow(cmd, res)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
registerArgon2ConfigFlags(cmd.Flags(), flagConf)
|
||||
configx.RegisterFlags(cmd.Flags())
|
||||
cmdx.RegisterFormatFlags(cmd.Flags())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runLoadTest(cmd *cobra.Command, conf *argon2Config, reqPerMin int) (*resultTable, error) {
|
||||
// force GC at the start of the experiment
|
||||
runtime.GC()
|
||||
|
||||
sampleTime := time.Minute / 3
|
||||
reqNum := reqPerMin / int(time.Minute/sampleTime)
|
||||
|
||||
progressPrinter := cmdx.NewLoudErrPrinter(cmd)
|
||||
_, _ = progressPrinter.Printf("It takes about %s to collect all necessary data, please be patient.\n", sampleTime)
|
||||
|
||||
ctx, cancel := context.WithCancel(cmd.Context())
|
||||
hasher := hash.NewHasherArgon2(conf)
|
||||
allDone := make(chan struct{})
|
||||
startAll := time.Now()
|
||||
var cancelReason error
|
||||
|
||||
var memStats []uint64
|
||||
go func() {
|
||||
clock := time.NewTicker(time.Second)
|
||||
defer func() {
|
||||
clock.Stop()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-cmd.Context().Done():
|
||||
return
|
||||
case <-allDone:
|
||||
return
|
||||
case <-clock.C:
|
||||
// cancel if the allowed time is exceeded by 110%
|
||||
if time.Since(startAll) > ((sampleTime+conf.localConfig.ExpectedDuration+conf.localConfig.ExpectedDeviation)/100)*110 {
|
||||
cancelReason = ErrSampleTimeExceeded
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
ms := runtime.MemStats{}
|
||||
runtime.ReadMemStats(&ms)
|
||||
|
||||
// cancel if memory is exceeded by 110%
|
||||
if ms.HeapAlloc > (uint64(conf.localConfig.DedicatedMemory)/100)*110 {
|
||||
cancelReason = ErrMemoryConsumptionExceeded
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
memStats = append(memStats, ms.HeapAlloc)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// don't read std_in when quiet
|
||||
if flagx.MustGetBool(cmd, cmdx.FlagQuiet) {
|
||||
return
|
||||
}
|
||||
|
||||
input := make([]byte, 1)
|
||||
for {
|
||||
n, err := cmd.InOrStdin().Read(input)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != 0 {
|
||||
_, _ = color.New(color.FgRed).Fprintln(cmd.ErrOrStderr(), "I SAID BE PATIENT!!!")
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-allDone:
|
||||
return
|
||||
case <-cmd.Context().Done():
|
||||
return
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
calcTimes := make([]time.Duration, reqNum)
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
|
||||
for i := 0; i < reqNum; i++ {
|
||||
eg.Go(func(i int) func() error {
|
||||
return func() error {
|
||||
// wait randomly before starting, max. sample time
|
||||
//#nosec G404 -- just a timeout to collect statistical data
|
||||
t := time.Duration(rand.Intn(int(sampleTime)))
|
||||
timer := time.NewTimer(t)
|
||||
defer timer.Stop()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-timer.C:
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
done := make(chan struct{})
|
||||
var err error
|
||||
|
||||
go func() {
|
||||
_, err = hasher.Generate(ctx, []byte("password"))
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-done:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
calcTimes[i] = time.Since(start)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}(i))
|
||||
}
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Error during hashing: %+v\n", err)
|
||||
return nil, cmdx.FailSilently(cmd)
|
||||
}
|
||||
switch cancelReason {
|
||||
case ErrSampleTimeExceeded:
|
||||
memUsed, err2 := stats.LoadRawData(memStats).Max()
|
||||
if err2 != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Unexpected maths error: %+v\nRaw Data: %+v\n", cancelReason, memStats)
|
||||
}
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "The hashing load test took too long. This indicates that you don't have enough resources to handle %d login requests per minute with the desired minimal time of %s. The memory used was %s. Either dedicate more CPU/memory, or decrease the hashing cost (memory and iterations parameters).\n", reqPerMin, conf.localConfig.ExpectedDuration, bytesize.ByteSize(memUsed))
|
||||
return nil, cmdx.FailSilently(cmd)
|
||||
case ErrMemoryConsumptionExceeded:
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "The hashing load test exceeded the memory limit of %s. This indicates that you don't have enough resources to handle %d login requests per minute with the desired minimal time of %s. Either dedicate more memory, or decrease the hashing cost (memory and iterations parameters).\n", conf.localConfig.DedicatedMemory, reqPerMin, conf.localConfig.ExpectedDuration)
|
||||
return nil, cmdx.FailSilently(cmd)
|
||||
}
|
||||
|
||||
totalTime := time.Since(startAll)
|
||||
close(allDone)
|
||||
|
||||
calcData := stats.LoadRawData(calcTimes)
|
||||
|
||||
duration := func(f func() (float64, error)) time.Duration {
|
||||
v, err := f()
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Unexpected maths error: %+v\nRaw Data: %+v\n", err, calcTimes)
|
||||
}
|
||||
return time.Duration(int64(v))
|
||||
}
|
||||
|
||||
memUsed, err := stats.LoadRawData(memStats).Max()
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Unexpected maths error: %+v\nRaw Data: %+v\n", err, memStats)
|
||||
}
|
||||
|
||||
return &resultTable{
|
||||
TotalTime: totalTime,
|
||||
MedianTime: duration(calcData.Mean),
|
||||
StdDev: duration(calcData.StandardDeviation),
|
||||
MinTime: duration(calcData.Min),
|
||||
MaxTime: duration(calcData.Max),
|
||||
MaxMem: bytesize.ByteSize(memUsed),
|
||||
}, nil
|
||||
}
|
||||
177
cmd/hashers/argon2/root.go
Normal file
177
cmd/hashers/argon2/root.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package argon2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/ory/x/contextx"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/x/cmdx"
|
||||
"github.com/ory/x/configx"
|
||||
"github.com/ory/x/logrusx"
|
||||
)
|
||||
|
||||
const (
|
||||
FlagIterations = "iterations"
|
||||
FlagParallelism = "parallelism"
|
||||
FlagSaltLength = "salt-length"
|
||||
FlagKeyLength = "key-length"
|
||||
FlagMemory = "memory"
|
||||
FlagDedicatedMemory = "dedicated-memory"
|
||||
FlagMinimalDuration = "min-duration"
|
||||
FlagExpectedDeviation = "expected-deviation"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "argon2",
|
||||
}
|
||||
|
||||
func RegisterCommandRecursive(parent *cobra.Command) {
|
||||
parent.AddCommand(rootCmd)
|
||||
|
||||
rootCmd.AddCommand(newCalibrateCmd(), newHashCmd(), newLoadTestCmd())
|
||||
}
|
||||
|
||||
func registerArgon2ConstantConfigFlags(flags *pflag.FlagSet, c *argon2Config) {
|
||||
// set default value first
|
||||
c.localConfig.DedicatedMemory = config.Argon2DefaultDedicatedMemory
|
||||
flags.Var(&c.localConfig.DedicatedMemory, FlagDedicatedMemory, "Amount of memory dedicated for password hashing. Kratos will try to not consume more memory.")
|
||||
flags.DurationVar(&c.localConfig.ExpectedDuration, FlagMinimalDuration, config.Argon2DefaultDuration, "Minimal duration a hashing operation (~login request) takes.")
|
||||
flags.DurationVar(&c.localConfig.ExpectedDeviation, FlagExpectedDeviation, config.Argon2DefaultDeviation, "Expected deviation of the time a hashing operation (~login request) takes.")
|
||||
|
||||
flags.Uint8Var(&c.localConfig.Parallelism, FlagParallelism, config.Argon2DefaultParallelism, "Number of threads to use.")
|
||||
|
||||
flags.Uint32Var(&c.localConfig.SaltLength, FlagSaltLength, config.Argon2DefaultSaltLength, "Length of the salt in bytes.")
|
||||
flags.Uint32Var(&c.localConfig.KeyLength, FlagKeyLength, config.Argon2DefaultKeyLength, "Length of the key in bytes.")
|
||||
}
|
||||
|
||||
func registerArgon2ConfigFlags(flags *pflag.FlagSet, c *argon2Config) {
|
||||
flags.Uint32Var(&c.localConfig.Iterations, FlagIterations, 1, "Number of iterations to start probing at.")
|
||||
|
||||
// set default value first
|
||||
c.localConfig.Memory = config.Argon2DefaultMemory
|
||||
flags.Var(&c.localConfig.Memory, FlagMemory, "Memory to use.")
|
||||
|
||||
registerArgon2ConstantConfigFlags(flags, c)
|
||||
}
|
||||
|
||||
func configProvider(cmd *cobra.Command, flagConf *argon2Config) (*argon2Config, error) {
|
||||
l := logrusx.New("Ory Kratos", config.Version)
|
||||
conf := &argon2Config{}
|
||||
var err error
|
||||
conf.config, err = config.New(
|
||||
cmd.Context(),
|
||||
l,
|
||||
cmd.ErrOrStderr(),
|
||||
&contextx.Default{},
|
||||
configx.WithFlags(cmd.Flags()),
|
||||
configx.SkipValidation(),
|
||||
configx.WithContext(cmd.Context()),
|
||||
configx.WithImmutables("hashers"),
|
||||
)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Unable to initialize the config provider: %s\n", err)
|
||||
return nil, cmdx.FailSilently(cmd)
|
||||
}
|
||||
conf.localConfig = *conf.config.HasherArgon2(cmd.Context())
|
||||
|
||||
if cmd.Flags().Changed(FlagIterations) {
|
||||
conf.localConfig.Iterations = flagConf.localConfig.Iterations
|
||||
}
|
||||
if cmd.Flags().Changed(FlagParallelism) {
|
||||
conf.localConfig.Parallelism = flagConf.localConfig.Parallelism
|
||||
}
|
||||
if cmd.Flags().Changed(FlagMemory) {
|
||||
conf.localConfig.Memory = flagConf.localConfig.Memory
|
||||
}
|
||||
if cmd.Flags().Changed(FlagDedicatedMemory) {
|
||||
conf.localConfig.DedicatedMemory = flagConf.localConfig.DedicatedMemory
|
||||
}
|
||||
if cmd.Flags().Changed(FlagKeyLength) {
|
||||
conf.localConfig.KeyLength = flagConf.localConfig.KeyLength
|
||||
}
|
||||
if cmd.Flags().Changed(FlagSaltLength) {
|
||||
conf.localConfig.SaltLength = flagConf.localConfig.SaltLength
|
||||
}
|
||||
if cmd.Flags().Changed(FlagExpectedDeviation) {
|
||||
conf.localConfig.ExpectedDeviation = flagConf.localConfig.ExpectedDeviation
|
||||
}
|
||||
if cmd.Flags().Changed(FlagMinimalDuration) {
|
||||
conf.localConfig.ExpectedDuration = flagConf.localConfig.ExpectedDuration
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
type (
|
||||
argon2Config struct {
|
||||
localConfig config.Argon2
|
||||
config *config.Config
|
||||
ctx context.Context
|
||||
}
|
||||
)
|
||||
|
||||
var _ cmdx.TableRow = (*argon2Config)(nil)
|
||||
|
||||
func (c *argon2Config) Header() []string {
|
||||
var header []string
|
||||
|
||||
t := reflect.TypeOf(c.localConfig)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
header = append(header, strings.ReplaceAll(strings.ToUpper(t.Field(i).Tag.Get("json")), "_", " "))
|
||||
}
|
||||
|
||||
return header
|
||||
}
|
||||
|
||||
func (c *argon2Config) Columns() []string {
|
||||
conf, _ := c.HasherArgon2()
|
||||
return []string{
|
||||
conf.Memory.String(),
|
||||
fmt.Sprintf("%d", conf.Iterations),
|
||||
fmt.Sprintf("%d", conf.Parallelism),
|
||||
fmt.Sprintf("%d", conf.SaltLength),
|
||||
fmt.Sprintf("%d", conf.KeyLength),
|
||||
conf.ExpectedDuration.String(),
|
||||
conf.ExpectedDeviation.String(),
|
||||
conf.DedicatedMemory.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *argon2Config) Interface() interface{} {
|
||||
i, _ := c.HasherArgon2()
|
||||
return i
|
||||
}
|
||||
|
||||
func (c *argon2Config) Config() *config.Config {
|
||||
ac, _ := c.HasherArgon2()
|
||||
for k, v := range map[string]interface{}{
|
||||
config.ViperKeyHasherArgon2ConfigIterations: ac.Iterations,
|
||||
config.ViperKeyHasherArgon2ConfigMemory: ac.Memory,
|
||||
config.ViperKeyHasherArgon2ConfigParallelism: ac.Parallelism,
|
||||
config.ViperKeyHasherArgon2ConfigDedicatedMemory: ac.DedicatedMemory,
|
||||
config.ViperKeyHasherArgon2ConfigKeyLength: ac.KeyLength,
|
||||
config.ViperKeyHasherArgon2ConfigSaltLength: ac.SaltLength,
|
||||
config.ViperKeyHasherArgon2ConfigExpectedDuration: ac.ExpectedDuration,
|
||||
config.ViperKeyHasherArgon2ConfigExpectedDeviation: ac.ExpectedDeviation,
|
||||
} {
|
||||
_ = c.config.Set(c.ctx, k, v)
|
||||
}
|
||||
return c.config
|
||||
}
|
||||
|
||||
func (c *argon2Config) HasherArgon2() (*config.Argon2, error) {
|
||||
if c.localConfig.Memory == 0 {
|
||||
c.localConfig.Memory = config.Argon2DefaultMemory
|
||||
}
|
||||
return &c.localConfig, nil
|
||||
}
|
||||
25
cmd/hashers/root.go
Normal file
25
cmd/hashers/root.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package hashers
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/cmd/hashers/argon2"
|
||||
)
|
||||
|
||||
func NewRootCmd() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "hashers",
|
||||
Short: "This command contains helpers around hashing",
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func RegisterCommandRecursive(parent *cobra.Command) {
|
||||
rootCmd := NewRootCmd()
|
||||
parent.AddCommand(rootCmd)
|
||||
|
||||
argon2.RegisterCommandRecursive(rootCmd)
|
||||
}
|
||||
90
cmd/identities/definitions.go
Normal file
90
cmd/identities/definitions.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identities
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
kratos "github.com/ory/kratos/pkg/httpclient"
|
||||
|
||||
"github.com/ory/x/cmdx"
|
||||
)
|
||||
|
||||
type (
|
||||
outputIdentity kratos.Identity
|
||||
outputIdentityCollection struct {
|
||||
Identities []kratos.Identity `json:"identities"`
|
||||
NextPageToken string `json:"next_page_token"`
|
||||
includePageToken bool
|
||||
}
|
||||
)
|
||||
|
||||
func (outputIdentity) Header() []string {
|
||||
return []string{"ID", "EXTERNAL ID", "VERIFIED ADDRESSES", "RECOVERY ADDRESSES", "SCHEMA ID", "SCHEMA URL"}
|
||||
}
|
||||
|
||||
func (i outputIdentity) Columns() []string {
|
||||
data := [6]string{
|
||||
i.Id,
|
||||
cmdx.None,
|
||||
cmdx.None,
|
||||
cmdx.None,
|
||||
cmdx.None,
|
||||
cmdx.None,
|
||||
}
|
||||
|
||||
if i.ExternalId != nil && len(*i.ExternalId) > 0 {
|
||||
data[1] = *i.ExternalId
|
||||
}
|
||||
|
||||
addresses := make([]string, 0, len(i.VerifiableAddresses))
|
||||
for _, a := range i.VerifiableAddresses {
|
||||
if len(a.Value) > 0 {
|
||||
addresses = append(addresses, a.Value)
|
||||
}
|
||||
}
|
||||
data[2] = strings.Join(addresses, ", ")
|
||||
|
||||
addresses = addresses[:0]
|
||||
for _, a := range i.RecoveryAddresses {
|
||||
if len(a.Value) > 0 {
|
||||
addresses = append(addresses, a.Value)
|
||||
}
|
||||
}
|
||||
data[3] = strings.Join(addresses, ", ")
|
||||
data[4] = i.SchemaId
|
||||
data[5] = i.SchemaUrl
|
||||
|
||||
return data[:]
|
||||
}
|
||||
|
||||
func (i outputIdentity) Interface() interface{} {
|
||||
return i
|
||||
}
|
||||
|
||||
func (outputIdentityCollection) Header() []string {
|
||||
return outputIdentity{}.Header()
|
||||
}
|
||||
|
||||
func (c outputIdentityCollection) Table() [][]string {
|
||||
rows := make([][]string, len(c.Identities))
|
||||
for i, ident := range c.Identities {
|
||||
rows[i] = outputIdentity(ident).Columns()
|
||||
}
|
||||
return append(rows,
|
||||
[]string{""},
|
||||
[]string{"NEXT PAGE TOKEN", c.NextPageToken},
|
||||
)
|
||||
}
|
||||
|
||||
func (c outputIdentityCollection) Interface() interface{} {
|
||||
if c.includePageToken {
|
||||
return c
|
||||
}
|
||||
return c.Identities
|
||||
}
|
||||
|
||||
func (c *outputIdentityCollection) Len() int {
|
||||
return len(c.Identities)
|
||||
}
|
||||
72
cmd/identities/delete.go
Normal file
72
cmd/identities/delete.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identities
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/cmd/cliclient"
|
||||
"github.com/ory/kratos/pkg/clihelpers"
|
||||
"github.com/ory/x/cmdx"
|
||||
)
|
||||
|
||||
func NewDeleteCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete resources",
|
||||
}
|
||||
cmd.AddCommand(NewDeleteIdentityCmd())
|
||||
cliclient.RegisterClientFlags(cmd.PersistentFlags())
|
||||
cmdx.RegisterFormatFlags(cmd.PersistentFlags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewDeleteIdentityCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "identity id-0 [id-1] [id-2] [id-n]",
|
||||
Short: "Delete one or more identities by their ID(s)",
|
||||
Long: fmt.Sprintf(`This command deletes one or more identities by ID. To delete an identity by some selector, e.g. the recovery email address, use the list command in combination with jq.
|
||||
|
||||
%s`, clihelpers.WarningJQIsComplicated),
|
||||
Example: `To delete the identity with the recovery email address "foo@bar.com", run:
|
||||
|
||||
{{ .CommandPath }} $({{ .Root.Name }} list identities --format json | jq -r 'map(select(.recovery_addresses[].value == "foo@bar.com")) | .[].id')`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := cliclient.NewClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
deleted = make([]cmdx.OutputIder, 0, len(args))
|
||||
failed = make(map[string]error)
|
||||
)
|
||||
|
||||
for _, a := range args {
|
||||
_, err := c.IdentityAPI.DeleteIdentity(cmd.Context(), a).Execute()
|
||||
if err != nil {
|
||||
failed[a] = cmdx.PrintOpenAPIError(cmd, err)
|
||||
continue
|
||||
}
|
||||
deleted = append(deleted, cmdx.OutputIder(a))
|
||||
}
|
||||
|
||||
if len(deleted) == 1 {
|
||||
cmdx.PrintRow(cmd, &deleted[0])
|
||||
} else if len(deleted) > 1 {
|
||||
cmdx.PrintTable(cmd, &cmdx.OutputIderCollection{Items: deleted})
|
||||
}
|
||||
|
||||
cmdx.PrintErrors(cmd, failed)
|
||||
if len(failed) != 0 {
|
||||
return cmdx.FailSilently(cmd)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
61
cmd/identities/delete_test.go
Normal file
61
cmd/identities/delete_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identities_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/ory/kratos/cmd/identities"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/kratos/identity"
|
||||
"github.com/ory/kratos/x"
|
||||
"github.com/ory/x/sqlcon"
|
||||
)
|
||||
|
||||
func TestDeleteCmd(t *testing.T) {
|
||||
reg, cmd := setup(t, identities.NewDeleteIdentityCmd)
|
||||
|
||||
t.Run("case=deletes successfully", func(t *testing.T) {
|
||||
// create identity to delete
|
||||
i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID)
|
||||
require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i))
|
||||
|
||||
stdOut := cmd.ExecNoErr(t, i.ID.String())
|
||||
|
||||
// expect ID and no error
|
||||
assert.Equal(t, i.ID.String(), gjson.Parse(stdOut).String())
|
||||
|
||||
// expect identity to be deleted
|
||||
_, err := reg.Persister().GetIdentity(context.Background(), i.ID, identity.ExpandNothing)
|
||||
assert.True(t, errors.Is(err, sqlcon.ErrNoRows()))
|
||||
})
|
||||
|
||||
t.Run("case=deletes three identities", func(t *testing.T) {
|
||||
is, ids := makeIdentities(t, reg, 3)
|
||||
|
||||
stdOut := cmd.ExecNoErr(t, ids...)
|
||||
|
||||
assert.Equal(t, `["`+strings.Join(ids, "\",\"")+"\"]\n", stdOut)
|
||||
|
||||
for _, i := range is {
|
||||
_, err := reg.Persister().GetIdentity(context.Background(), i.ID, identity.ExpandNothing)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("case=fails with unknown ID", func(t *testing.T) {
|
||||
stdErr := cmd.ExecExpectedErr(t, x.NewUUID().String())
|
||||
|
||||
assert.Contains(t, stdErr, "Unable to locate the resource", stdErr)
|
||||
})
|
||||
}
|
||||
116
cmd/identities/get.go
Normal file
116
cmd/identities/get.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identities
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
kratos "github.com/ory/kratos/pkg/httpclient"
|
||||
"github.com/ory/x/cmdx"
|
||||
"github.com/ory/x/stringsx"
|
||||
|
||||
"github.com/ory/kratos/pkg/clihelpers"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/cmd/cliclient"
|
||||
)
|
||||
|
||||
const (
|
||||
FlagIncludeCreds = "include-credentials"
|
||||
FlagExternalID = "external-id"
|
||||
)
|
||||
|
||||
func NewGetCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Get resources",
|
||||
}
|
||||
cmd.AddCommand(NewGetIdentityCmd())
|
||||
cliclient.RegisterClientFlags(cmd.PersistentFlags())
|
||||
cmdx.RegisterFormatFlags(cmd.PersistentFlags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewGetIdentityCmd() *cobra.Command {
|
||||
var (
|
||||
includeCreds []string
|
||||
byExternalID bool
|
||||
)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "identity [id-1] [id-2] [id-n]",
|
||||
Short: "Get one or more identities by their ID(s)",
|
||||
Long: fmt.Sprintf(`This command gets all the details about an identity. To get an identity by some selector, e.g. the recovery email address, use the list command in combination with jq.
|
||||
|
||||
Use the --%s flag to look up identities by their external ID instead of their Ory-internal ID.
|
||||
|
||||
%s`, FlagExternalID, clihelpers.WarningJQIsComplicated),
|
||||
Example: `To get the identities with the recovery email address at the domain "ory.sh", run:
|
||||
|
||||
{{ .CommandPath }} $({{ .Root.Name }} ls identities --format json | jq -r 'map(select(.recovery_addresses[].value | endswith("@ory.sh"))) | .[].id')
|
||||
|
||||
To get an identity by its external ID, run:
|
||||
|
||||
{{ .CommandPath }} --external-id customer-12345`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := cliclient.NewClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// we check includeCreds argument is valid
|
||||
for _, opt := range includeCreds {
|
||||
e := stringsx.SwitchExact(opt)
|
||||
if !e.AddCase("oidc") {
|
||||
cmd.PrintErrln(`You have to put a valid value of credentials type to be included, try --help for details.`)
|
||||
return cmdx.FailSilently(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
identities := make([]kratos.Identity, 0, len(args))
|
||||
failed := make(map[string]error)
|
||||
for _, id := range args {
|
||||
var identity *kratos.Identity
|
||||
if byExternalID {
|
||||
identity, _, err = c.IdentityAPI.
|
||||
GetIdentityByExternalID(cmd.Context(), id).
|
||||
IncludeCredential(includeCreds).
|
||||
Execute()
|
||||
} else {
|
||||
identity, _, err = c.IdentityAPI.
|
||||
GetIdentity(cmd.Context(), id).
|
||||
IncludeCredential(includeCreds).
|
||||
Execute()
|
||||
}
|
||||
|
||||
if clihelpers.SDKError(err) != nil {
|
||||
failed[id] = cmdx.PrintOpenAPIError(cmd, err)
|
||||
continue
|
||||
}
|
||||
|
||||
identities = append(identities, *identity)
|
||||
}
|
||||
|
||||
if len(identities) == 1 {
|
||||
cmdx.PrintRow(cmd, (*outputIdentity)(&identities[0]))
|
||||
} else if len(identities) > 1 {
|
||||
cmdx.PrintTable(cmd, &outputIdentityCollection{Identities: identities, includePageToken: false})
|
||||
}
|
||||
cmdx.PrintErrors(cmd, failed)
|
||||
|
||||
if len(failed) != 0 {
|
||||
return cmdx.FailSilently(cmd)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
// include credential flag to add third party tokens in returned data
|
||||
flags.StringArrayVarP(&includeCreds, FlagIncludeCreds, "i", []string{}, `Include third party tokens (only "oidc" supported) `)
|
||||
flags.BoolVar(&byExternalID, FlagExternalID, false, "Treat the provided ID(s) as external IDs and look up the identities by their external ID instead of their Ory-internal ID.")
|
||||
return cmd
|
||||
}
|
||||
132
cmd/identities/get_test.go
Normal file
132
cmd/identities/get_test.go
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identities_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/ory/kratos/cmd/identities"
|
||||
"github.com/ory/x/assertx"
|
||||
"github.com/ory/x/sqlxx"
|
||||
|
||||
"github.com/ory/kratos/x"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/kratos/identity"
|
||||
)
|
||||
|
||||
func TestGetCmd(t *testing.T) {
|
||||
reg, cmd := setup(t, identities.NewGetIdentityCmd)
|
||||
|
||||
t.Run("case=gets a single identity", func(t *testing.T) {
|
||||
i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID)
|
||||
i.MetadataPublic = []byte(`"public"`)
|
||||
i.MetadataAdmin = []byte(`"admin"`)
|
||||
require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i))
|
||||
|
||||
stdOut := cmd.ExecNoErr(t, i.ID.String())
|
||||
|
||||
ij, err := json.Marshal(identity.WithCredentialsNoConfigAndAdminMetadataInJSON(*i))
|
||||
require.NoError(t, err)
|
||||
|
||||
assertx.EqualAsJSONExcept(t, json.RawMessage(ij), json.RawMessage(stdOut), []string{"created_at", "updated_at", "AdditionalProperties"})
|
||||
})
|
||||
|
||||
t.Run("case=gets three identities", func(t *testing.T) {
|
||||
is, ids := makeIdentities(t, reg, 3)
|
||||
|
||||
stdOut := cmd.ExecNoErr(t, ids...)
|
||||
|
||||
isj, err := json.Marshal(is)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertx.EqualAsJSONExcept(t, json.RawMessage(isj), json.RawMessage(stdOut), []string{"created_at", "updated_at", "AdditionalProperties"})
|
||||
})
|
||||
|
||||
t.Run("case=fails with unknown ID", func(t *testing.T) {
|
||||
stdErr := cmd.ExecExpectedErr(t, x.NewUUID().String())
|
||||
|
||||
assert.Contains(t, stdErr, "Unable to locate the resource", stdErr)
|
||||
})
|
||||
|
||||
t.Run("case=gets a single identity by its external ID", func(t *testing.T) {
|
||||
externalID := "customer-" + x.NewUUID().String()
|
||||
i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID)
|
||||
i.ExternalID = sqlxx.NullString(externalID)
|
||||
require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i))
|
||||
|
||||
stdOut := cmd.ExecNoErr(t, "--"+identities.FlagExternalID, externalID)
|
||||
|
||||
assert.Equal(t, i.ID.String(), gjson.Get(stdOut, "id").String())
|
||||
assert.Equal(t, externalID, gjson.Get(stdOut, "external_id").String())
|
||||
})
|
||||
|
||||
t.Run("case=fails with unknown external ID", func(t *testing.T) {
|
||||
stdErr := cmd.ExecExpectedErr(t, "--"+identities.FlagExternalID, "does-not-exist")
|
||||
|
||||
assert.Contains(t, stdErr, "Unable to locate the resource", stdErr)
|
||||
})
|
||||
|
||||
t.Run("case=gets a single identity with oidc credentials", func(t *testing.T) {
|
||||
applyCredentials := func(identifier, accessToken, refreshToken, idToken string, encrypt bool) identity.Credentials {
|
||||
toJson := func(c identity.CredentialsOIDC) []byte {
|
||||
out, err := json.Marshal(&c)
|
||||
require.NoError(t, err)
|
||||
return out
|
||||
}
|
||||
transform := func(token string) string {
|
||||
if encrypt {
|
||||
s, err := reg.Cipher(context.Background()).Encrypt(context.Background(), []byte(token))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}
|
||||
return token
|
||||
}
|
||||
return identity.Credentials{
|
||||
Type: identity.CredentialsTypeOIDC,
|
||||
Identifiers: []string{"bar:" + identifier},
|
||||
Config: toJson(identity.CredentialsOIDC{Providers: []identity.CredentialsOIDCProvider{
|
||||
{
|
||||
Subject: "foo",
|
||||
Provider: "bar",
|
||||
InitialAccessToken: transform(accessToken + "0"),
|
||||
InitialRefreshToken: transform(refreshToken + "0"),
|
||||
InitialIDToken: transform(idToken + "0"),
|
||||
Organization: "foo-org-id",
|
||||
},
|
||||
{
|
||||
Subject: "baz",
|
||||
Provider: "zab",
|
||||
InitialAccessToken: transform(accessToken + "1"),
|
||||
InitialRefreshToken: transform(refreshToken + "1"),
|
||||
InitialIDToken: transform(idToken + "1"),
|
||||
Organization: "bar-org-id",
|
||||
},
|
||||
}}),
|
||||
}
|
||||
}
|
||||
i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID)
|
||||
i.MetadataPublic = []byte(`"public"`)
|
||||
i.MetadataAdmin = []byte(`"admin"`)
|
||||
i.SetCredentials(identity.CredentialsTypeOIDC, applyCredentials("uniqueIdentifier", "accessBar", "refreshBar", "idBar", true))
|
||||
// duplicate identity with decrypted tokens
|
||||
di := i.CopyWithoutCredentials()
|
||||
di.SetCredentials(identity.CredentialsTypeOIDC, applyCredentials("uniqueIdentifier", "accessBar", "refreshBar", "idBar", false))
|
||||
|
||||
require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i))
|
||||
|
||||
stdOut := cmd.ExecNoErr(t, "--"+identities.FlagIncludeCreds, "oidc", i.ID.String())
|
||||
ij, err := json.Marshal(identity.WithCredentialsAndAdminMetadataInJSON(*di))
|
||||
require.NoError(t, err)
|
||||
|
||||
ii := []string{"id", "schema_url", "state_changed_at", "created_at", "updated_at", "credentials.oidc.created_at", "credentials.oidc.updated_at", "credentials.oidc.version", "AdditionalProperties"}
|
||||
assertx.EqualAsJSONExcept(t, json.RawMessage(ij), json.RawMessage(stdOut), ii)
|
||||
})
|
||||
}
|
||||
53
cmd/identities/helpers.go
Normal file
53
cmd/identities/helpers.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identities
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/ory/x/cmdx"
|
||||
)
|
||||
|
||||
func parseIdentities(raw []byte) (rawIdentities []string) {
|
||||
res := gjson.ParseBytes(raw)
|
||||
if !res.IsArray() {
|
||||
return []string{res.Raw}
|
||||
}
|
||||
res.ForEach(func(_, v gjson.Result) bool {
|
||||
rawIdentities = append(rawIdentities, v.Raw)
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func readIdentities(cmd *cobra.Command, args []string) (map[string]string, error) {
|
||||
rawIdentities := make(map[string]string)
|
||||
if len(args) == 0 {
|
||||
fc, err := io.ReadAll(cmd.InOrStdin())
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "STD_IN: Could not read: %s\n", err)
|
||||
return nil, cmdx.FailSilently(cmd)
|
||||
}
|
||||
for i, id := range parseIdentities(fc) {
|
||||
rawIdentities[fmt.Sprintf("STD_IN[%d]", i)] = id
|
||||
}
|
||||
return rawIdentities, nil
|
||||
}
|
||||
for _, fn := range args {
|
||||
fc, err := os.ReadFile(fn) // #nosec G304 -- file is supplied by user
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s: Could not open identity file: %s\n", fn, err)
|
||||
return nil, cmdx.FailSilently(cmd)
|
||||
}
|
||||
for i, id := range parseIdentities(fc) {
|
||||
rawIdentities[fmt.Sprintf("%s[%d]", fn, i)] = id
|
||||
}
|
||||
}
|
||||
return rawIdentities, nil
|
||||
}
|
||||
49
cmd/identities/helpers_test.go
Normal file
49
cmd/identities/helpers_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identities_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/ory/x/cmdx"
|
||||
|
||||
"github.com/ory/kratos/identity"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ory/kratos/cmd/cliclient"
|
||||
"github.com/ory/kratos/driver"
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/kratos/pkg"
|
||||
"github.com/ory/kratos/pkg/testhelpers"
|
||||
)
|
||||
|
||||
func setup(t *testing.T, newCmd func() *cobra.Command) (*driver.RegistryDefault, *cmdx.CommandExecuter) {
|
||||
conf, reg := pkg.NewFastRegistryWithMocks(t)
|
||||
_, admin := testhelpers.NewKratosServerWithCSRF(t, reg)
|
||||
testhelpers.SetDefaultIdentitySchema(conf, "file://./stubs/identity.schema.json")
|
||||
// setup command
|
||||
return reg, &cmdx.CommandExecuter{
|
||||
New: func() *cobra.Command {
|
||||
cmd := newCmd()
|
||||
cliclient.RegisterClientFlags(cmd.Flags())
|
||||
cmdx.RegisterFormatFlags(cmd.Flags())
|
||||
return cmd
|
||||
},
|
||||
PersistentArgs: []string{"--" + cliclient.FlagEndpoint, admin.URL, "--" + cmdx.FlagFormat, string(cmdx.FormatJSON)},
|
||||
}
|
||||
}
|
||||
|
||||
func makeIdentities(t *testing.T, reg driver.Registry, n int) (is []*identity.Identity, ids []string) {
|
||||
for j := 0; j < n; j++ {
|
||||
i := identity.NewIdentity(config.DefaultIdentityTraitsSchemaID)
|
||||
i.MetadataPublic = []byte(`{"foo":"bar"}`)
|
||||
require.NoError(t, reg.Persister().CreateIdentity(context.Background(), i))
|
||||
is = append(is, i)
|
||||
ids = append(ids, i.ID.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
98
cmd/identities/import.go
Normal file
98
cmd/identities/import.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package identities
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
kratos "github.com/ory/kratos/pkg/httpclient"
|
||||
|
||||
"github.com/ory/x/cmdx"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/ory/kratos/cmd/cliclient"
|
||||
)
|
||||
|
||||
func NewImportCmd() *cobra.Command {
|
||||
var cmd = &cobra.Command{
|
||||
Use: "import",
|
||||
Short: "Import resources",
|
||||
}
|
||||
cmd.AddCommand(NewImportIdentitiesCmd())
|
||||
cliclient.RegisterClientFlags(cmd.PersistentFlags())
|
||||
cmdx.RegisterFormatFlags(cmd.PersistentFlags())
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewImportIdentitiesCmd represents the import command
|
||||
func NewImportIdentitiesCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "identities file-1.json [file-2.json] [file-3.json] [file-n.json]",
|
||||
Short: "Import one or more identities from files or STD_IN",
|
||||
Example: `Create an example identity:
|
||||
|
||||
cat > ./file.json <<EOF
|
||||
{
|
||||
"schema_id": "default",
|
||||
"traits": {
|
||||
"email": "foo@example.com"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
{{ .CommandPath }} file.json
|
||||
|
||||
Alternatively:
|
||||
|
||||
cat file.json | {{ .CommandPath }}`,
|
||||
Long: `Import identities from files or STD_IN.
|
||||
|
||||
Files can contain only a single or an array of identities. The validity of files can be tested beforehand using "... identities validate".`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c, err := cliclient.NewClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imported := make([]kratos.Identity, 0, len(args))
|
||||
failed := make(map[string]error)
|
||||
|
||||
is, err := readIdentities(cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for src, i := range is {
|
||||
var params kratos.CreateIdentityBody
|
||||
err = json.Unmarshal([]byte(i), ¶ms)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "STD_IN: Could not parse identity")
|
||||
return cmdx.FailSilently(cmd)
|
||||
}
|
||||
|
||||
ident, _, err := c.IdentityAPI.CreateIdentity(cmd.Context()).CreateIdentityBody(params).Execute()
|
||||
if err != nil {
|
||||
failed[src] = cmdx.PrintOpenAPIError(cmd, err)
|
||||
} else {
|
||||
imported = append(imported, *ident)
|
||||
}
|
||||
}
|
||||
|
||||
if len(imported) == 1 {
|
||||
cmdx.PrintRow(cmd, (*outputIdentity)(&imported[0]))
|
||||
} else {
|
||||
cmdx.PrintTable(cmd, &outputIdentityCollection{Identities: imported})
|
||||
}
|
||||
cmdx.PrintErrors(cmd, failed)
|
||||
|
||||
if len(failed) != 0 {
|
||||
return cmdx.FailSilently(cmd)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user